鉴于网上大部分关于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. */
-
-
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();
-
-
-
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) {
-
}
-
-
-
public String getHost() {
-
String host = sipStack.getIPAddress();
-
return host;
-
}
-
-
-
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