您现在的位置是:首页 > 文章详情

全网第一篇SIP协议之GB28181注册(JAVA版本)

日期:2018-11-22点击:442

鉴于网上大部分关于SIP注册服务器编写都是C/C++/python,故开此贴,JAVA实现也贴出分享

GB28181定义了了 基于SIP架构的 视频监控互联规范,而对于多数私有协议实现的监控系统如果想接入SIP架构,就要借助网关,GB28181 规范了实现 SIP 监控域与非SIP 监控域互联。

以下是我在实际使用过程中总结的一些问题:
1. 当客户端第一次接入时,客户端将持续向Server端发送REGISTER消息,直到Server端回复"200 OK"后结束;
2. GB28181的注册流程牵扯用户认证,所以相对比较复杂,不过这也是安防通讯安全方面的一个亮点;
它的注册流程如下图:

用抓包工具看,如下图所示


注册流程:
1. 客户端向服务器无限期发送Register消息:
这里客户端期初发送的Register消息为最简单的消息

2.当服务器接收到消息后,回送一个 401 消息“Unauthorized”,并在消息包头添加如下字段:

如下所示,这就是客户端接到401-Unauthorized之后再次发来的REGISTER消息,并且还附带了Auth字段, 而第一次REGISTER消息是没有这个字段的

完整的401回复如下(通过抓包工具Wireshark抓到的):

Via: SIP/2.0/UDP 172.24.20.109:5060;rport=5060;received=172.24.20.109;branch=z9hG4bK352707374 From: <sip:34020000001320000002@172.24.20.109:5060>;tag=2109371333 To: <sip:34020000001320000002@172.24.20.109:5060>;tag=888 Call-ID: 545122524@172.24.20.109 CSeq: 1 REGISTER WWW-Authenticate: Digest realm="3402000000",nonce="1677f194104d46aea6c9f8aebe507017" Content-Length: 0

第二次REGISTER,也就是附带了Auth字段的报文:

ìKC8¯)à¯Eóßz@@×Ǭm¬ÄÄßyÁREGISTER sip:34020000002000000001@172.24.20.26:5060 SIP/2.0 Via: SIP/2.0/UDP 172.24.20.109:5060;rport;branch=z9hG4bK742316145 Route: <sip:34020000001320000002@172.24.20.26:5060;lr> From: <sip:34020000001320000002@172.24.20.109:5060>;tag=2109371333 To: <sip:34020000001320000002@172.24.20.109:5060> Call-ID: 545122524@172.24.20.109 CSeq: 2 REGISTER Contact: <sip:34020000001320000002@172.24.20.109:5060> Authorization: Digest username="34020000001320000002", realm="3402000000", nonce="1677f194104d46aea6c9f8aebe507017", uri="sip:34020000002000000001@172.24.20.26:5060", response="dca920f418cecae456bc1566c5ac7da5", algorithm=MD5 Max-Forwards: 70 User-Agent: SIP UAS V2.1.2.438058 Expires: 3600 Content-Length: 0 

 

验证算法如下:
HA1=MD5(username:realm:passwd) #username和realm在字段“Authorization”中可以找到,passwd这个是由客户端和服务器协商得到的,一般情况下UAC端存一个UAS也知道的密码就行了
HA2=MD5(Method:Uri) #Method一般有INVITE, ACK, OPTIONS, BYE, CANCEL, REGISTER;Uri可以在字段“Authorization”找到
response = MD5(HA1:nonce:HA2)

算法来源:http://tools.ietf.org/html/rfc2069  [Page 6]

关键认证算法的JAVA实现(注意冒号是必须要的):

public static void main(String[] args) throws Exception { String ha1 = md5("34020000001320000002" + ":" + "3402000000" + ":" + "admin123", ""); //HA1=MD5(username:realm:passwd) String ha2 = md5("REGISTER" + ":" + "sip:34020000002000000001@172.24.20.26:5060", ""); //HA2=MD5(Method:Uri) String response = ha1 + ":" + "326d59f91b6e448fa461fcacd9161abe" + ":" + ha2; System.out.println("MD5加密后的字符串为:encodeStr="+md5(response, "")); }

 

 

MD5工具类

import org.apache.commons.codec.digest.DigestUtils; public class MD5Utils { /** * MD5方法 * * @param text 明文 * @param key 密钥 * @return 密文 * @throws Exception */ public static String md5(String text, String key) { //加密后的字符串 String encodeStr=DigestUtils.md5Hex(text + key); return encodeStr; } /** * MD5验证方法 * * @param text 明文 * @param key 密钥 * @param md5 密文 * @return true/false * @throws Exception */ public static boolean verify(String text, String key, String md5) throws Exception { //根据传入的密钥进行验证 String md5Text = md5(text, key); if(md5Text.equalsIgnoreCase(md5)) { System.out.println("MD5验证通过"); return true; } return false; } } 

最后运行流程如下:

注册服务器核心代码:

import java.text.ParseException; import java.util.TooManyListenersException; import javax.sip.InvalidArgumentException; import javax.sip.ObjectInUseException; import javax.sip.PeerUnavailableException; import javax.sip.SipException; import javax.sip.TransportNotSupportedException; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SIPMain { protected Logger logger = LoggerFactory.getLogger(SIPMain.class); public void run() { //用户名,IP地址,端口 try { int port = 5060; SipLayer sipLayer = new SipLayer("admin" , "172.24.20.26" , port); //本地 //SipLayer sipLayer = new SipLayer("admin","xx.xx.xx.xx",port); //阿里云上的IP VECS01532 sipLayer.setMessageProcessor(new MessageProcessorImpl()); System.out.println("服务启动完毕, 已经在"+port+"端口监听消息\n\n"); } catch (PeerUnavailableException e) { e.printStackTrace(); logger.error(ExceptionUtils.getFullStackTrace(e)); } catch (TransportNotSupportedException e) { e.printStackTrace(); logger.error(ExceptionUtils.getFullStackTrace(e)); } catch (ObjectInUseException e) { e.printStackTrace(); logger.error(ExceptionUtils.getFullStackTrace(e)); } catch (InvalidArgumentException e) { e.printStackTrace(); logger.error(ExceptionUtils.getFullStackTrace(e)); } catch (TooManyListenersException e) { e.printStackTrace(); logger.error(ExceptionUtils.getFullStackTrace(e)); } } /** * 这个方法暂时用不上,目前系统没有需要主动发送消息给SIP终端设备的业务场景 * @throws InvalidArgumentException * @throws TooManyListenersException * @throws ParseException * @throws SipException */ public void sendMsg() throws InvalidArgumentException, TooManyListenersException, ParseException, SipException{ SipLayer sipLayer = new SipLayer("admin","127.0.0.1",5060); sipLayer.sendMessage(sipLayer.getUsername(), sipLayer.getHost(), "test message"); } } 

 

SipLayer.java代码:

import java.text.ParseException; import java.util.ArrayList; import java.util.Properties; import java.util.TooManyListenersException; import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.ObjectInUseException; import javax.sip.PeerUnavailableException; import javax.sip.RequestEvent; import javax.sip.ResponseEvent; import javax.sip.SipException; import javax.sip.SipFactory; import javax.sip.SipListener; import javax.sip.SipProvider; import javax.sip.SipStack; import javax.sip.TimeoutEvent; import javax.sip.TransactionTerminatedEvent; import javax.sip.TransportNotSupportedException; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.header.CSeqHeader; import javax.sip.header.CallIdHeader; import javax.sip.header.ContactHeader; import javax.sip.header.ContentTypeHeader; import javax.sip.header.FromHeader; import javax.sip.header.HeaderFactory; import javax.sip.header.MaxForwardsHeader; import javax.sip.header.ToHeader; import javax.sip.header.ViaHeader; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; public class SipLayer implements SipListener { private MessageProcessor messageProcessor; private String username; private SipStack sipStack; private SipFactory sipFactory; private AddressFactory addressFactory; private HeaderFactory headerFactory; private MessageFactory messageFactory; private SipProvider sipProvider; /** Here we initialize the SIP stack. */ @SuppressWarnings("deprecation") public SipLayer(String username, String ip, int port) throws PeerUnavailableException, TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException { setUsername(username); sipFactory = SipFactory.getInstance(); sipFactory.setPathName("gov.nist"); Properties properties = new Properties(); properties.setProperty("javax.sip.STACK_NAME", "cameraReg"); properties.setProperty("javax.sip.IP_ADDRESS", ip); /** * sip_server_log.log 和 sip_debug_log.log * public static final int TRACE_NONE = 0; public static final int TRACE_MESSAGES = 16; public static final int TRACE_EXCEPTION = 17; public static final int TRACE_DEBUG = 32; */ properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16"); properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log"); properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log"); sipStack = sipFactory.createSipStack(properties); headerFactory = sipFactory.createHeaderFactory(); addressFactory = sipFactory.createAddressFactory(); messageFactory = sipFactory.createMessageFactory(); ListeningPoint tcp = sipStack.createListeningPoint(port, "tcp"); ListeningPoint udp = sipStack.createListeningPoint(port, "udp"); sipProvider = sipStack.createSipProvider(tcp); sipProvider.addSipListener(this); sipProvider = sipStack.createSipProvider(udp); sipProvider.addSipListener(this); } /** * This method uses the SIP stack to send a message. 第一个参数:用户名 第二个参数:IP地址 * 第三个参数:消息内容 */ public void sendMessage(String username, String address, String message) throws ParseException, InvalidArgumentException, SipException { SipURI from = addressFactory.createSipURI(getUsername(), getHost() + ":" + getPort()); Address fromNameAddress = addressFactory.createAddress(from); fromNameAddress.setDisplayName(getUsername()); FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, "cameraReg1.0"); SipURI toAddress = addressFactory.createSipURI(username, address); Address toNameAddress = addressFactory.createAddress(toAddress); toNameAddress.setDisplayName(username); ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null); SipURI requestURI = addressFactory.createSipURI(username, address); requestURI.setTransportParam("udp"); ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ViaHeader viaHeader = headerFactory.createViaHeader(getHost(), getPort(), "udp", "branch1"); viaHeaders.add(viaHeader); CallIdHeader callIdHeader = sipProvider.getNewCallId(); @SuppressWarnings("deprecation") CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1, Request.MESSAGE); MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70); Request request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards); SipURI contactURI = addressFactory.createSipURI(getUsername(), getHost()); contactURI.setPort(getPort()); Address contactAddress = addressFactory.createAddress(contactURI); contactAddress.setDisplayName(getUsername()); ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress); request.addHeader(contactHeader); ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("text", "plain"); request.setContent(message, contentTypeHeader); sipProvider.sendRequest(request); } /** This method is called by the SIP stack when a response arrives. */ public void processResponse(ResponseEvent evt) { Response response = evt.getResponse(); int status = response.getStatusCode(); if ((status >= 200) && (status < 300)) { // Success! messageProcessor.processInfo("--Sent"); return; } messageProcessor.processError("Previous message not sent: " + status); } /** * SIP服务端接收消息的方法 * Content 里面是GBK编码 * This method is called by the SIP stack when a new request arrives. */ public void processRequest(RequestEvent evt) { Request req = evt.getRequest(); messageProcessor.processMessage(req,messageFactory,sipProvider); } /** * This method is called by the SIP stack when there's no answer to a * message. Note that this is treated differently from an error message. */ public void processTimeout(TimeoutEvent evt) { messageProcessor.processError("Previous message not sent: " + "timeout"); } /** * This method is called by the SIP stack when there's an asynchronous * message transmission error. */ public void processIOException(IOExceptionEvent evt) { messageProcessor.processError("Previous message not sent: " + "I/O Exception"); } /** * This method is called by the SIP stack when a dialog (session) ends. */ public void processDialogTerminated(DialogTerminatedEvent evt) { } /** * This method is called by the SIP stack when a transaction ends. */ public void processTransactionTerminated(TransactionTerminatedEvent evt) { } @SuppressWarnings("deprecation") public String getHost() { String host = sipStack.getIPAddress(); return host; } @SuppressWarnings("deprecation") public int getPort() { int port = sipProvider.getListeningPoint().getPort(); return port; } public String getUsername() { return username; } public void setUsername(String newUsername) { username = newUsername; } public MessageProcessor getMessageProcessor() { return messageProcessor; } public void setMessageProcessor(MessageProcessor newMessageProcessor) { messageProcessor = newMessageProcessor; } } 

 

MessageProcessor接口:

import javax.sip.SipProvider; import javax.sip.message.MessageFactory; import javax.sip.message.Request; /** * 消息处理回调函数接口 */ public interface MessageProcessor { /** * 接收IPCamera发来的SIP协议消息的时候产生的回调函数 */ public void processMessage(Request req,MessageFactory messageFactory, SipProvider sipProvider); public void processError(String errorMessage); public void processInfo(String infoMessage); }

MessageProcessor实现类这里不给出,因为里面包含了很多本公司SIP注册业务的具体细节

需要提示的一点是,需要安装一个反编译工具去阅读Request源码里面的属性和方法 ,以获取SIP报文里面的内容

比如获取我想获取sender和method字段

FromHeader fromHeader = (FromHeader) req.getHeader("From"); String sender = fromHeader.getAddress().toString(); String method = req.getMethod();

再比如我想获取Contact字段

Contact contact = (Contact) req.getHeader("Contact");

类似于这样,关键是去通过eclipse点进去看一下这个Request的源码

最后,给出JAVA SIP协议的支持包MAVEN POM依赖:

<!-- SPI协议相关的包 --> <dependency> <groupId>javax.sip</groupId> <artifactId>jain-sip-api</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.sip</groupId> <artifactId>jain-sip-ri</artifactId> <version>1.2</version> </dependency>

 

原文链接:https://my.oschina.net/u/2338224/blog/2907136
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章