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

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

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

350588ca489f75fc2f87903f9c2979087e6.jpg

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

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

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

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

6b9fe2703c05dd937cfe67d0fb4c78723ee.jpg

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

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

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

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

 

验证算法如下:
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实现(注意冒号是必须要的):

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

 

 

MD5工具类

  1.  
    import org.apache.commons.codec.digest.DigestUtils;
  2.  
     
  3.  
    public class MD5Utils {
  4.  
    /**
  5.  
    * MD5方法
  6.  
    *
  7.  
    * @param text 明文
  8.  
    * @param key 密钥
  9.  
    * @return 密文
  10.  
    * @throws Exception
  11.  
    */
  12.  
    public static String md5(String text, String key) {
  13.  
    //加密后的字符串
  14.  
    String encodeStr=DigestUtils.md5Hex(text + key);
  15.  
    return encodeStr;
  16.  
    }
  17.  
     
  18.  
    /**
  19.  
    * MD5验证方法
  20.  
    *
  21.  
    * @param text 明文
  22.  
    * @param key 密钥
  23.  
    * @param md5 密文
  24.  
    * @return true/false
  25.  
    * @throws Exception
  26.  
    */
  27.  
    public static boolean verify(String text, String key, String md5) throws Exception {
  28.  
    //根据传入的密钥进行验证
  29.  
    String md5Text = md5(text, key);
  30.  
    if(md5Text.equalsIgnoreCase(md5)) {
  31.  
    System.out.println(“MD5验证通过”);
  32.  
    return true;
  33.  
    }
  34.  
    return false;
  35.  
    }
  36.  
     
  37.  
     
  38.  
    }

最后运行流程如下:

3208847a0f9bf45cffd1eb5a3c204ef06b0.jpg

注册服务器核心代码:

  1.  
    import java.text.ParseException;
  2.  
    import java.util.TooManyListenersException;
  3.  
     
  4.  
    import javax.sip.InvalidArgumentException;
  5.  
    import javax.sip.ObjectInUseException;
  6.  
    import javax.sip.PeerUnavailableException;
  7.  
    import javax.sip.SipException;
  8.  
    import javax.sip.TransportNotSupportedException;
  9.  
     
  10.  
    import org.apache.commons.lang.exception.ExceptionUtils;
  11.  
    import org.slf4j.Logger;
  12.  
    import org.slf4j.LoggerFactory;
  13.  
     
  14.  
    public class SIPMain {
  15.  
     
  16.  
    protected Logger logger = LoggerFactory.getLogger(SIPMain.class);
  17.  
     
  18.  
    public void run() {
  19.  
    //用户名,IP地址,端口
  20.  
    try {
  21.  
    int port = 5060;
  22.  
    SipLayer sipLayer = new SipLayer(“admin” , “172.24.20.26” , port); //本地
  23.  
    //SipLayer sipLayer = new SipLayer(“admin”,”xx.xx.xx.xx”,port); //阿里云上的IP VECS01532
  24.  
    sipLayer.setMessageProcessor(new MessageProcessorImpl());
  25.  
    System.out.println(“服务启动完毕, 已经在”+port+“端口监听消息\n\n”);
  26.  
    } catch (PeerUnavailableException e) {
  27.  
    e.printStackTrace();
  28.  
    logger.error(ExceptionUtils.getFullStackTrace(e));
  29.  
    } catch (TransportNotSupportedException e) {
  30.  
    e.printStackTrace();
  31.  
    logger.error(ExceptionUtils.getFullStackTrace(e));
  32.  
    } catch (ObjectInUseException e) {
  33.  
    e.printStackTrace();
  34.  
    logger.error(ExceptionUtils.getFullStackTrace(e));
  35.  
    } catch (InvalidArgumentException e) {
  36.  
    e.printStackTrace();
  37.  
    logger.error(ExceptionUtils.getFullStackTrace(e));
  38.  
    } catch (TooManyListenersException e) {
  39.  
    e.printStackTrace();
  40.  
    logger.error(ExceptionUtils.getFullStackTrace(e));
  41.  
    }
  42.  
    }
  43.  
     
  44.  
     
  45.  
    /**
  46.  
    * 这个方法暂时用不上,目前系统没有需要主动发送消息给SIP终端设备的业务场景
  47.  
    * @throws InvalidArgumentException
  48.  
    * @throws TooManyListenersException
  49.  
    * @throws ParseException
  50.  
    * @throws SipException
  51.  
    */
  52.  
    public void sendMsg() throws InvalidArgumentException, TooManyListenersException, ParseException, SipException{
  53.  
    SipLayer sipLayer = new SipLayer(“admin”,“127.0.0.1”,5060);
  54.  
    sipLayer.sendMessage(sipLayer.getUsername(), sipLayer.getHost(), “test message”);
  55.  
    }
  56.  
     
  57.  
    }

 

SipLayer.java代码:

  1.  
    import java.text.ParseException;
  2.  
    import java.util.ArrayList;
  3.  
    import java.util.Properties;
  4.  
    import java.util.TooManyListenersException;
  5.  
     
  6.  
    import javax.sip.DialogTerminatedEvent;
  7.  
    import javax.sip.IOExceptionEvent;
  8.  
    import javax.sip.InvalidArgumentException;
  9.  
    import javax.sip.ListeningPoint;
  10.  
    import javax.sip.ObjectInUseException;
  11.  
    import javax.sip.PeerUnavailableException;
  12.  
    import javax.sip.RequestEvent;
  13.  
    import javax.sip.ResponseEvent;
  14.  
    import javax.sip.SipException;
  15.  
    import javax.sip.SipFactory;
  16.  
    import javax.sip.SipListener;
  17.  
    import javax.sip.SipProvider;
  18.  
    import javax.sip.SipStack;
  19.  
    import javax.sip.TimeoutEvent;
  20.  
    import javax.sip.TransactionTerminatedEvent;
  21.  
    import javax.sip.TransportNotSupportedException;
  22.  
    import javax.sip.address.Address;
  23.  
    import javax.sip.address.AddressFactory;
  24.  
    import javax.sip.address.SipURI;
  25.  
    import javax.sip.header.CSeqHeader;
  26.  
    import javax.sip.header.CallIdHeader;
  27.  
    import javax.sip.header.ContactHeader;
  28.  
    import javax.sip.header.ContentTypeHeader;
  29.  
    import javax.sip.header.FromHeader;
  30.  
    import javax.sip.header.HeaderFactory;
  31.  
    import javax.sip.header.MaxForwardsHeader;
  32.  
    import javax.sip.header.ToHeader;
  33.  
    import javax.sip.header.ViaHeader;
  34.  
    import javax.sip.message.MessageFactory;
  35.  
    import javax.sip.message.Request;
  36.  
    import javax.sip.message.Response;
  37.  
     
  38.  
     
  39.  
    public class SipLayer implements SipListener {
  40.  
     
  41.  
    private MessageProcessor messageProcessor;
  42.  
     
  43.  
    private String username;
  44.  
     
  45.  
    private SipStack sipStack;
  46.  
     
  47.  
    private SipFactory sipFactory;
  48.  
     
  49.  
    private AddressFactory addressFactory;
  50.  
     
  51.  
    private HeaderFactory headerFactory;
  52.  
     
  53.  
    private MessageFactory messageFactory;
  54.  
     
  55.  
    private SipProvider sipProvider;
  56.  
     
  57.  
    /** Here we initialize the SIP stack. */
  58.  
    @SuppressWarnings(“deprecation”)
  59.  
    public SipLayer(String username, String ip, int port) throws PeerUnavailableException,
  60.  
    TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException {
  61.  
    setUsername(username);
  62.  
    sipFactory = SipFactory.getInstance();
  63.  
    sipFactory.setPathName(“gov.nist”);
  64.  
    Properties properties = new Properties();
  65.  
    properties.setProperty(“javax.sip.STACK_NAME”, “cameraReg”);
  66.  
    properties.setProperty(“javax.sip.IP_ADDRESS”, ip);
  67.  
     
  68.  
    /**
  69.  
    * sip_server_log.log 和 sip_debug_log.log
  70.  
    * public static final int TRACE_NONE = 0;
  71.  
    public static final int TRACE_MESSAGES = 16;
  72.  
    public static final int TRACE_EXCEPTION = 17;
  73.  
    public static final int TRACE_DEBUG = 32;
  74.  
    */
  75.  
    properties.setProperty(“gov.nist.javax.sip.TRACE_LEVEL”, “16”);
  76.  
    properties.setProperty(“gov.nist.javax.sip.SERVER_LOG”, “sip_server_log”);
  77.  
    properties.setProperty(“gov.nist.javax.sip.DEBUG_LOG”, “sip_debug_log”);
  78.  
     
  79.  
    sipStack = sipFactory.createSipStack(properties);
  80.  
    headerFactory = sipFactory.createHeaderFactory();
  81.  
    addressFactory = sipFactory.createAddressFactory();
  82.  
    messageFactory = sipFactory.createMessageFactory();
  83.  
     
  84.  
    ListeningPoint tcp = sipStack.createListeningPoint(port, “tcp”);
  85.  
    ListeningPoint udp = sipStack.createListeningPoint(port, “udp”);
  86.  
     
  87.  
    sipProvider = sipStack.createSipProvider(tcp);
  88.  
    sipProvider.addSipListener(this);
  89.  
    sipProvider = sipStack.createSipProvider(udp);
  90.  
    sipProvider.addSipListener(this);
  91.  
    }
  92.  
     
  93.  
    /**
  94.  
    * This method uses the SIP stack to send a message. 第一个参数:用户名 第二个参数:IP地址
  95.  
    * 第三个参数:消息内容
  96.  
    */
  97.  
    public void sendMessage(String username, String address, String message)
  98.  
    throws ParseException, InvalidArgumentException, SipException {
  99.  
     
  100.  
    SipURI from = addressFactory.createSipURI(getUsername(), getHost() + “:” + getPort());
  101.  
    Address fromNameAddress = addressFactory.createAddress(from);
  102.  
    fromNameAddress.setDisplayName(getUsername());
  103.  
    FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, “cameraReg1.0”);
  104.  
     
  105.  
    SipURI toAddress = addressFactory.createSipURI(username, address);
  106.  
    Address toNameAddress = addressFactory.createAddress(toAddress);
  107.  
    toNameAddress.setDisplayName(username);
  108.  
    ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null);
  109.  
     
  110.  
    SipURI requestURI = addressFactory.createSipURI(username, address);
  111.  
    requestURI.setTransportParam(“udp”);
  112.  
     
  113.  
    ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
  114.  
    ViaHeader viaHeader = headerFactory.createViaHeader(getHost(), getPort(), “udp”, “branch1”);
  115.  
    viaHeaders.add(viaHeader);
  116.  
     
  117.  
    CallIdHeader callIdHeader = sipProvider.getNewCallId();
  118.  
     
  119.  
    @SuppressWarnings(“deprecation”)
  120.  
    CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1, Request.MESSAGE);
  121.  
     
  122.  
    MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);
  123.  
     
  124.  
    Request request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader,
  125.  
    fromHeader, toHeader, viaHeaders, maxForwards);
  126.  
     
  127.  
    SipURI contactURI = addressFactory.createSipURI(getUsername(), getHost());
  128.  
    contactURI.setPort(getPort());
  129.  
    Address contactAddress = addressFactory.createAddress(contactURI);
  130.  
    contactAddress.setDisplayName(getUsername());
  131.  
    ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
  132.  
    request.addHeader(contactHeader);
  133.  
     
  134.  
    ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader(“text”, “plain”);
  135.  
    request.setContent(message, contentTypeHeader);
  136.  
     
  137.  
    sipProvider.sendRequest(request);
  138.  
    }
  139.  
     
  140.  
    /** This method is called by the SIP stack when a response arrives. */
  141.  
    public void processResponse(ResponseEvent evt) {
  142.  
    Response response = evt.getResponse();
  143.  
    int status = response.getStatusCode();
  144.  
     
  145.  
    if ((status >= 200) && (status < 300)) { // Success!
  146.  
    messageProcessor.processInfo(“–Sent”);
  147.  
    return;
  148.  
    }
  149.  
     
  150.  
    messageProcessor.processError(“Previous message not sent: ” + status);
  151.  
    }
  152.  
     
  153.  
    /**
  154.  
    * SIP服务端接收消息的方法
  155.  
    * Content 里面是GBK编码
  156.  
    * This method is called by the SIP stack when a new request arrives.
  157.  
    */
  158.  
    public void processRequest(RequestEvent evt) {
  159.  
    Request req = evt.getRequest();
  160.  
    messageProcessor.processMessage(req,messageFactory,sipProvider);
  161.  
    }
  162.  
     
  163.  
     
  164.  
    /**
  165.  
    * This method is called by the SIP stack when there’s no answer to a
  166.  
    * message. Note that this is treated differently from an error message.
  167.  
    */
  168.  
    public void processTimeout(TimeoutEvent evt) {
  169.  
    messageProcessor.processError(“Previous message not sent: ” + “timeout”);
  170.  
    }
  171.  
     
  172.  
    /**
  173.  
    * This method is called by the SIP stack when there’s an asynchronous
  174.  
    * message transmission error.
  175.  
    */
  176.  
    public void processIOException(IOExceptionEvent evt) {
  177.  
    messageProcessor.processError(“Previous message not sent: ” + “I/O Exception”);
  178.  
    }
  179.  
     
  180.  
    /**
  181.  
    * This method is called by the SIP stack when a dialog (session) ends.
  182.  
    */
  183.  
    public void processDialogTerminated(DialogTerminatedEvent evt) {
  184.  
    }
  185.  
     
  186.  
    /**
  187.  
    * This method is called by the SIP stack when a transaction ends.
  188.  
    */
  189.  
    public void processTransactionTerminated(TransactionTerminatedEvent evt) {
  190.  
    }
  191.  
     
  192.  
    @SuppressWarnings(“deprecation”)
  193.  
    public String getHost() {
  194.  
    String host = sipStack.getIPAddress();
  195.  
    return host;
  196.  
    }
  197.  
     
  198.  
    @SuppressWarnings(“deprecation”)
  199.  
    public int getPort() {
  200.  
    int port = sipProvider.getListeningPoint().getPort();
  201.  
    return port;
  202.  
    }
  203.  
     
  204.  
    public String getUsername() {
  205.  
    return username;
  206.  
    }
  207.  
     
  208.  
    public void setUsername(String newUsername) {
  209.  
    username = newUsername;
  210.  
    }
  211.  
     
  212.  
    public MessageProcessor getMessageProcessor() {
  213.  
    return messageProcessor;
  214.  
    }
  215.  
     
  216.  
    public void setMessageProcessor(MessageProcessor newMessageProcessor) {
  217.  
    messageProcessor = newMessageProcessor;
  218.  
    }
  219.  
     
  220.  
    }

 

MessageProcessor接口:

  1.  
    import javax.sip.SipProvider;
  2.  
    import javax.sip.message.MessageFactory;
  3.  
    import javax.sip.message.Request;
  4.  
     
  5.  
    /**
  6.  
    * 消息处理回调函数接口
  7.  
    */
  8.  
    public interface MessageProcessor {
  9.  
     
  10.  
    /**
  11.  
    * 接收IPCamera发来的SIP协议消息的时候产生的回调函数
  12.  
    */
  13.  
    public void processMessage(Request req,MessageFactory messageFactory, SipProvider sipProvider);
  14.  
     
  15.  
    public void processError(String errorMessage);
  16.  
     
  17.  
    public void processInfo(String infoMessage);
  18.  
     
  19.  
    }

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

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

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

  1.  
    FromHeader fromHeader = (FromHeader) req.getHeader(“From”);
  2.  
    String sender = fromHeader.getAddress().toString();
  3.  
    String method = req.getMethod();

再比如我想获取Contact字段

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

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

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

  1.  
    <!– SPI协议相关的包 –>
  2.  
    <dependency>
  3.  
    <groupId>javax.sip</groupId>
  4.  
    <artifactId>jain-sip-api</artifactId>
  5.  
    <version>1.2</version>
  6.  
    </dependency>
  7.  
     
  8.  
    <dependency>
  9.  
    <groupId>javax.sip</groupId>
  10.  
    <artifactId>jain-sip-ri</artifactId>
  11.  
    <version>1.2</version>
  12.  
    </dependency>

 

转载于:https://my.oschina.net/u/2338224/blog/2907136