LInux下的SIP协议跟踪

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,

严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源: http://yfydz.cublog.cn

1. 前言

SIP(Session Initiation Protocol)在RFC3261中定义的用于建立会话的文本协议,多用于VoIP等多
媒体应用中,其格式和HTTP类似,先有SIP头定义,然后是具体的数据。

目前linux2.6内核中已经正式将SIP跟踪和NAT处理纳入,说明该模块应该经过足够测试证明可用了。
以下Linux内核代码版本为2.6.19.2。

2. SIP基本信息格式

SIP协议本身只定义应用层数据,对于传输层协议是TCP还是UDP没有限制,只是定义了SIP服务端口是
5060。
以下使用RFC3665中提供的SIP应用实例来描述SIP过程,从中可知道对于NAT设备来说需要修改哪些内
容信息。
2.1 登记过程
    Bob                        SIP Server
     |                               |
     |          REGISTER F1          |
     |------------------------------>|
     |      401 Unauthorized F2      |
     |<------------------------------|
     |          REGISTER F3          |
     |------------------------------>|
     |            200 OK F4          |
     |<------------------------------|
     |                               |
   Message Details
   F1 REGISTER Bob -> SIP Server
   REGISTER sips:ss2.biloxi.example.com SIP/2.0
   Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7
   Max-Forwards: 70
   From: Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl
   To: Bob <sips:bob@biloxi.example.com>
   Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
   CSeq: 1 REGISTER
   Contact: <sips:bob@client.biloxi.example.com>
   Content-Length: 0
 
   F2 401 Unauthorized SIP Server -> Bob
   SIP/2.0 401 Unauthorized
   Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7
    ;received=192.0.2.201
   From: Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl
   To: Bob <sips:bob@biloxi.example.com>;tag=1410948204
   Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
   CSeq: 1 REGISTER
   WWW-Authenticate: Digest realm="atlanta.example.com", qop="auth",
    nonce="ea9c8e88df84f1cec4341ae6cbe5a359",
    opaque="", stale=FALSE, algorithm=MD5
   Content-Length: 0

   F3 REGISTER Bob -> SIP Server
   REGISTER sips:ss2.biloxi.example.com SIP/2.0
   Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashd92
   Max-Forwards: 70
   From: Bob <sips:bob@biloxi.example.com>;tag=ja743ks76zlflH
   To: Bob <sips:bob@biloxi.example.com>
   Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
   CSeq: 2 REGISTER
   Contact: <sips:bob@client.biloxi.example.com>
   Authorization: Digest username="bob", realm="atlanta.example.com"
    nonce="ea9c8e88df84f1cec4341ae6cbe5a359", opaque="",
    uri="sips:ss2.biloxi.example.com",
    response="dfe56131d1958046689d83306477ecc"
   Content-Length: 0

   F4 200 OK SIP Server -> Bob
   SIP/2.0 200 OK
   Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashd92
    ;received=192.0.2.201
   From: Bob <sips:bob@biloxi.example.com>;tag=ja743ks76zlflH
   To: Bob <sips:bob@biloxi.example.com>;tag=37GkEhwl6
   Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
   CSeq: 2 REGISTER
   Contact: <sips:bob@client.biloxi.example.com>;expires=3600
   Content-Length: 0
由此可见,在“Via:”、“From:”、“To:”、“Call-ID:”、“Contact:”等字段中都有地址表示
的ID,对于大部分机器是没有域名的,只能由IP地址表示,因此NAT设备要能修改这些字段中的值。

2.2 SIP通信传输数据

SIP数据传输时使用SDP(Session Description Protocol, RFC4566)协议来描述数据通道信息:

   Alice                     Bob
     |                        |
     |       INVITE F1        |
     |----------------------->|
     |    180 Ringing F2      |
     |<-----------------------|
     |                        |
     |       200 OK F3        |
     |<-----------------------|
     |         ACK F4         |
     |----------------------->|
     |   Both Way RTP Media   |
     |<======================>|
     |                        |
     |         BYE F5         |
     |<-----------------------|
     |       200 OK F6        |
     |----------------------->|
     |                        |
   F1 INVITE Alice -> Bob
   INVITE sip:bob@biloxi.example.com SIP/2.0
   Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
   Max-Forwards: 70
   From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
   To: Bob <sip:bob@biloxi.example.com>
   Call-ID: 3848276298220188511@atlanta.example.com
   CSeq: 1 INVITE
   Contact: <sip:alice@client.atlanta.example.com;transport=tcp>
   Content-Type: application/sdp
   Content-Length: 151
   v=0
   o=alice 2890844526 2890844526 IN IP4 client.atlanta.example.com
   s=-
   c=IN IP4 192.0.2.101
   t=0 0
   m=audio 49172 RTP/AVP 0
   a=rtpmap:0 PCMU/8000

   F2 180 Ringing Bob -> Alice
   SIP/2.0 180 Ringing
   Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
    ;received=192.0.2.101
   From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
   To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
   Call-ID: 3848276298220188511@atlanta.example.com
   CSeq: 1 INVITE
   Contact: <sip:bob@client.biloxi.example.com;transport=tcp>
   Content-Length: 0

   F3 200 OK Bob -> Alice
   SIP/2.0 200 OK
   Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9
    ;received=192.0.2.101
   From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
   To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
   Call-ID: 3848276298220188511@atlanta.example.com
   CSeq: 1 INVITE
   Contact: <sip:bob@client.biloxi.example.com;transport=tcp>
   Content-Type: application/sdp
   Content-Length: 147
   v=0
   o=bob 2890844527 2890844527 IN IP4 client.biloxi.example.com
   s=-
   c=IN IP4 192.0.2.201
   t=0 0
   m=audio 3456 RTP/AVP 0
   a=rtpmap:0 PCMU/8000
   F4 ACK Alice -> Bob
   ACK sip:bob@client.biloxi.example.com SIP/2.0
   Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bd5
   Max-Forwards: 70
   From: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
   To: Bob <sip:bob@biloxi.example.com>;tag=8321234356
   Call-ID: 3848276298220188511@atlanta.example.com
   CSeq: 1 ACK
   Content-Length: 0
   /* RTP streams are established between Alice and Bob */
   /* Bob Hangs Up with Alice. Note that the CSeq is NOT 2, since
      Alice and Bob maintain their own independent CSeq counts.
      (The INVITE was request 1 generated by Alice, and the BYE is
      request 1 generated by Bob) */

   F5 BYE Bob -> Alice
   BYE sip:alice@client.atlanta.example.com SIP/2.0
   Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
   Max-Forwards: 70
   From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
   To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
   Call-ID: 3848276298220188511@atlanta.example.com
   CSeq: 1 BYE
   Content-Length: 0

   F6 200 OK Alice -> Bob
   SIP/2.0 200 OK
   Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7
    ;received=192.0.2.201
   From: Bob <sip:bob@biloxi.example.com>;tag=8321234356
   To: Alice <sip:alice@atlanta.example.com>;tag=9fxced76sl
   Call-ID: 3848276298220188511@atlanta.example.com
   CSeq: 1 BYE
   Content-Length: 0

可见,在SDP定义数据中,“o=”、“c=”中有地址信息,“m=”中有媒体通信用的端口信息,这些
都需要NAT设备修改,如果修改后SDP数据长度发生变化,则应该修改SIP头中的“Content-Length:”
字段的值。

3. SIP跟踪

SIP跟踪处理文件为net/ipv4/netfilter/ip_conntrack.sip.c, 头文件为
include/linux/netfilter_ipv4/ip_conntrack_sip.h.

3.1 初始化

static int __init init(void)
{
 int i, ret;
 char *tmpname;
 if (ports_c == 0)
  ports[ports_c++] = SIP_PORT;
 for (i = 0; i < ports_c; i++) {
// 以下定义SIP的ip_conntrack_helper结构参数
  /* Create helper structure */
  memset(&sip[i], 0, sizeof(struct ip_conntrack_helper));
// 只处理使用UDP协议的SIP
// 使用UDP协议简化很多处理,如TCP序列号跟踪等
  sip[i].tuple.dst.protonum = IPPROTO_UDP;
// 跟踪端口,缺省5060
  sip[i].tuple.src.u.udp.port = htons(ports[i]);
// tuple掩码
  sip[i].mask.src.u.udp.port = htons(0xFFFF);
  sip[i].mask.dst.protonum = 0xFF;
// 最大的并发子连接数为2个
  sip[i].max_expected = 2;
// 3分钟的子连接超时
  sip[i].timeout = 3 * 60; /* 3 minutes */
  sip[i].me = THIS_MODULE;
// 跟踪帮助函数
  sip[i].help = sip_help;
// helper的名字
  tmpname = &sip_names[i][0];
  if (ports[i] == SIP_PORT)
   sprintf(tmpname, "sip");
  else
   sprintf(tmpname, "sip-%d", i);
  sip[i].name = tmpname;
  DEBUGP("port #%d: %d/n", i, ports[i]);
// 登记跟踪函数
  ret = ip_conntrack_helper_register(&sip[i]);
  if (ret) {
   printk("ERROR registering helper for port %d/n",
    ports[i]);
   fini();
   return ret;
  }
 }
 return 0;
}

3.2 sip_help

static int sip_help(struct sk_buff **pskb,
      struct ip_conntrack *ct,
      enum ip_conntrack_info ctinfo)
{
 unsigned int dataoff, datalen;
 const char *dptr;
 int ret = NF_ACCEPT;
 int matchoff, matchlen;
 __be32 ipaddr;
 u_int16_t port;
 /* No Data ? */
// dataoff为ip头加UDP头长度
 dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr);
 if (dataoff >= (*pskb)->len) {
// dataoff大于等于整个IP包数据长度, 没应用数据
  DEBUGP("skb->len = %u/n", (*pskb)->len);
  return NF_ACCEPT;
        }
// 更新一下该连接的超时, 用的是sip专门定义的超时值而不是标准的UDP超时(30秒)
// 缺省3600秒
 ip_ct_refresh(ct, *pskb, sip_timeout * HZ);
// 如果这个包是非线性的,不处理,只处理线性包
// 其实可以用skb_make_writable处理一下即可继续解析
 if (!skb_is_nonlinear(*pskb))
// 从应用层数据开始解析
  dptr = (*pskb)->data + dataoff;
 else {
  DEBUGP("Copy of skbuff not supported yet./n");
  goto out;
 }
 if (ip_nat_sip_hook) {
// 如果定义了SIP NAT, 修改SIP头中信息
  if (!ip_nat_sip_hook(pskb, ctinfo, ct, &dptr)) {
   ret = NF_DROP;
   goto out;
  }
 }
 /* After this point NAT, could have mangled skb, so
    we need to recalculate payload lenght. */
// 数据长度
 datalen = (*pskb)->len - dataoff;
// 以下重点检查和修改SDP部分的信息
// 合法的最小长度检查
 if (datalen < (sizeof("SIP/2.0 200") - 1))
  goto out;
 /* RTP info only in some SDP pkts */
// SDP定义的RTP信息只在处理发起方的INVITE包和相应方的200信息
// 其他的都不处理
 if (memcmp(dptr, "INVITE", sizeof("INVITE") - 1) != 0 &&
     memcmp(dptr, "SIP/2.0 200", sizeof("SIP/2.0 200") - 1) != 0) {
  goto out;
 }
 /* Get ip and port address from SDP packet. */
// 查找SDP中的"c="信息, matchoff为地址相对起点的偏移
 if (ct_sip_get_info(dptr, datalen, &matchoff, &matchlen,
                     &ct_sip_hdrs[POS_CONNECTION]) > 0) {
  /* We'll drop only if there are parse problems. */
// 解析"c="中的地址信息, 失败则丢包
  if (parse_ipaddr(dptr + matchoff, NULL, &ipaddr,
                   dptr + datalen) < 0) {
   ret = NF_DROP;
   goto out;
  }
// 查找SDP中的"m="信息, matchoff为端口相对起点的偏移
  if (ct_sip_get_info(dptr, datalen, &matchoff, &matchlen,
                      &ct_sip_hdrs[POS_MEDIA]) > 0) {
// 获取端口
   port = simple_strtoul(dptr + matchoff, NULL, 10);
// 端口不可能是特权端口,只能是普通端口
   if (port < 1024) {
    ret = NF_DROP;
    goto out;
   }
// 建立期待的子连接信息
   ret = set_expected_rtp(pskb, ct, ctinfo,
            ipaddr, port, dptr);
  }
 }
out:
 return ret;
}

3.3 ct_sip_get_info

该函数在sip数据中查找指定的模式,获取模式的偏移和长度
/* Returns 0 if not found, -1 error parsing. */
// dptr为缓冲区起点, dlen为缓冲区总长
// matchoff和matchlen作为成功时的返回值, 记录查找模式的偏移是长度信息
// hnfo为要查找的模式信息指针
int ct_sip_get_info(const char *dptr, size_t dlen,
      unsigned int *matchoff,
      unsigned int *matchlen,
      struct sip_header_nfo *hnfo)
{
 const char *limit, *aux, *k = dptr;
 int shift = 0;
// 查找结束点
 limit = dptr + (dlen - hnfo->lnlen);
 while (dptr <= limit) {
// 线性查找两个模式: lname和sname, lname是全称, sname是缩写名称, 两种都合法
// 注意用的是大小写敏感的strncmp, 似乎用strnicmp更好一些
  if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) &&
      (strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) {
   dptr++;
   continue;
  }
// 找到模式
// 在当前行中查找ln_str标志信息,如"UDP", "sip:"等, 这就是个普通字符串查找函数
  aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen,
                      ct_sip_lnlen(dptr, limit));
  if (!aux) {
// 没有标志, 出错
   DEBUGP("'%s' not found in '%s'./n", hnfo->ln_str,
          hnfo->lname);
   return -1;
  }
// aux跳过标志长度
  aux += hnfo->ln_strlen;
// 计算匹配的模式长度, shift是从aux到实际模式地址的偏移
  *matchlen = hnfo->match_len(aux, limit, &shift);
// 如果为匹配长度为0出错
  if (!*matchlen)
   return -1;
// 模式相对数据头的偏移, 跳过了标志本身
  *matchoff = (aux - k) + shift;
  DEBUGP("%s match succeeded! - len: %u/n", hnfo->lname,
         *matchlen);
  return 1;
 }
 DEBUGP("%s header not found./n", hnfo->lname);
 return 0;
}

可查找的数据模式定义如下:
// 用于查找SIP头中的“Via:”、“Contact:”、“Content-Length:”
// SDP头中的“m=”、“v=”、“o=”、“c=”等
struct sip_header_nfo ct_sip_hdrs[] = {
 {  /* Via header */
  .lname  = "Via:",
  .lnlen  = sizeof("Via:") - 1,
  .sname  = "/r/nv:",
  .snlen  = sizeof("/r/nv:") - 1, /* rfc3261 "/r/n" */
  .ln_str  = "UDP ",
  .ln_strlen = sizeof("UDP ") - 1,
  .match_len = epaddr_len,
 },
 {  /* Contact header */
  .lname  = "Contact:",
  .lnlen  = sizeof("Contact:") - 1,
  .sname  = "/r/nm:",
  .snlen  = sizeof("/r/nm:") - 1,
  .ln_str  = "sip:",
  .ln_strlen = sizeof("sip:") - 1,
  .match_len = skp_epaddr_len
 },
 {  /* Content length header */
  .lname  = "Content-Length:",
  .lnlen  = sizeof("Content-Length:") - 1,
  .sname  = "/r/nl:",
  .snlen  = sizeof("/r/nl:") - 1,
  .ln_str  = ":",
  .ln_strlen = sizeof(":") - 1,
  .match_len = skp_digits_len
 },
 { /* SDP media info */
  .lname  = "/nm=",
  .lnlen  = sizeof("/nm=") - 1,
  .sname  = "/rm=",
  .snlen  = sizeof("/rm=") - 1,
  .ln_str  = "audio ",
  .ln_strlen = sizeof("audio ") - 1,
  .match_len = digits_len
 },
 {  /* SDP owner address*/
  .lname  = "/no=",
  .lnlen  = sizeof("/no=") - 1,
  .sname  = "/ro=",
  .snlen  = sizeof("/ro=") - 1,
  .ln_str  = "IN IP4 ",
  .ln_strlen = sizeof("IN IP4 ") - 1,
  .match_len = epaddr_len
 },
 {  /* SDP connection info */
  .lname  = "/nc=",
  .lnlen  = sizeof("/nc=") - 1,
  .sname  = "/rc=",
  .snlen  = sizeof("/rc=") - 1,
  .ln_str  = "IN IP4 ",
  .ln_strlen = sizeof("IN IP4 ") - 1,
  .match_len = epaddr_len
 },
 {  /* Requests headers */
  .lname  = "sip:",
  .lnlen  = sizeof("sip:") - 1,
  .sname  = "sip:",
  .snlen  = sizeof("sip:") - 1, /* yes, i know.. ;) */
  .ln_str  = "@",
  .ln_strlen = sizeof("@") - 1,
  .match_len = epaddr_len
 },
 {  /* SDP version header */
  .lname  = "/nv=",
  .lnlen  = sizeof("/nv=") - 1,
  .sname  = "/rv=",
  .snlen  = sizeof("/rv=") - 1,
  .ln_str  = "=",
  .ln_strlen = sizeof("=") - 1,
  .match_len = digits_len
 }
};

3.4 建立期待连接信息

static int set_expected_rtp(struct sk_buff **pskb,
       struct ip_conntrack *ct,
       enum ip_conntrack_info ctinfo,
       __be32 ipaddr, u_int16_t port,
       const char *dptr)
{
 struct ip_conntrack_expect *exp;
 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 int ret;
// 分配expect连接空间
 exp = ip_conntrack_expect_alloc(ct);
 if (exp == NULL)
  return NF_DROP;
// 期待连接的端口地址信息, 解析出的地址端口用于目的方
 exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip;
 exp->tuple.src.u.udp.port = 0;
 exp->tuple.dst.ip = ipaddr;
 exp->tuple.dst.u.udp.port = htons(port);
 exp->tuple.dst.protonum = IPPROTO_UDP;
// 掩码部分地址
 exp->mask.src.ip = htonl(0xFFFFFFFF);
 exp->mask.src.u.udp.port = 0;
 exp->mask.dst.ip = htonl(0xFFFFFFFF);
 exp->mask.dst.u.udp.port = htons(0xFFFF);
 exp->mask.dst.protonum = 0xFF;
 exp->expectfn = NULL;
 exp->flags = 0;
 if (ip_nat_sdp_hook)
// 如果定义了SDP的NAT处理,修改SDP数据中的信息,并建立期待连接
  ret = ip_nat_sdp_hook(pskb, ctinfo, exp, dptr);
 else {
// 建立期待连接
  if (ip_conntrack_expect_related(exp) != 0)
   ret = NF_DROP;
  else
   ret = NF_ACCEPT;
 }
 ip_conntrack_expect_put(exp);
 return ret;
}

4. SIP的NAT处理
nat处理函数为net/ipv4/netfilter/ip_nat_sip.c, 包括两个函数, 分别处理SIP和SDP数据

4.1 ip_nat_sip (ip_nat_sip_hook)
// 只修改SIP头中的数据, 不建立期待连接
// 函数返回0表示失败, 非0表示成功
// 最多的情况下需要修改两个字段的信息
static unsigned int ip_nat_sip(struct sk_buff **pskb,
          enum ip_conntrack_info ctinfo,
          struct ip_conntrack *ct,
          const char **dptr)
{
 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 char buffer[sizeof("nnn.nnn.nnn.nnn:nnnnn")];
 unsigned int bufflen, dataoff;
 __be32 ip;
 __be16 port;
// 重新计算应用层数据的起点
 dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr);
// 找到NAT转换后的地址和端口数据, 也就是相反方向的目的地址端口
 ip   = ct->tuplehash[!dir].tuple.dst.ip;
 port = ct->tuplehash[!dir].tuple.dst.u.udp.port;
 bufflen = sprintf(buffer, "%u.%u.%u.%u:%u", NIPQUAD(ip), ntohs(port));
 /* short packet ? */
// 异常短包, 返回
// 不过应该提前点操作, 这样就不用计算ip, port和bufflen了
 if (((*pskb)->len - dataoff) < (sizeof("SIP/2.0") - 1))
  return 0;
 /* Basic rules: requests and responses. */
 if (memcmp(*dptr, "SIP/2.0", sizeof("SIP/2.0") - 1) == 0) {
// 数据以"SIP/2.0"开头, 是SIP回应数据
  const char *aux;
// 此处为什么不用dir判断呢?
  if ((ctinfo) < IP_CT_IS_REPLY) {
// 正方向数据, 发起方->响应方,修改"Contact: "字段中的地址端口数据, 用buffer中的数据替代
   mangle_sip_packet(pskb, ctinfo, ct, dptr,
                     (*pskb)->len - dataoff,
                     buffer, bufflen,
                     &ct_sip_hdrs[POS_CONTACT]);
   return 1;
  }
// 反方向数据, 响应方->发起方
// 修改"Via: "字段中的地址端口数据, 用buffer中的数据替代
// 返回0表示失败
  if (!mangle_sip_packet(pskb, ctinfo, ct, dptr,
           (*pskb)->len - dataoff,
                         buffer, bufflen, &ct_sip_hdrs[POS_VIA]))
   return 0;
  /* This search should ignore case, but later.. */
// 查找"CSeq:"字符串的位置
  aux = ct_sip_search("CSeq:", *dptr, sizeof("CSeq:") - 1,
                      (*pskb)->len - dataoff);
  if (!aux)
   return 0;
// 如果在"CSeq:"字段行中没有"REGISTER",
  if (!ct_sip_search("REGISTER", aux, sizeof("REGISTER"),
      ct_sip_lnlen(aux, *dptr + (*pskb)->len - dataoff)))
   return 1;
// 修改"Contact: "字段中的地址端口数据, 用buffer中的数据替代
  return mangle_sip_packet(pskb, ctinfo, ct, dptr,
      (*pskb)->len - dataoff,
                           buffer, bufflen,
      &ct_sip_hdrs[POS_CONTACT]);
 }
// 运行到这里说明数据不是以"SIP/2.0"开头的, 是SIP请求方数据
// 此处为什么不用dir判断呢?
 if ((ctinfo) < IP_CT_IS_REPLY) {
// 正方向数据, 发起方->响应方,修改"Via: "字段中的地址端口数据, 用buffer中的数据替代
  if (!mangle_sip_packet(pskb, ctinfo, ct, dptr,
           (*pskb)->len - dataoff,
                         buffer, bufflen, &ct_sip_hdrs[POS_VIA]))
   return 0;
  /* Mangle Contact if exists only. - watch udp_nat_mangle()! */
// 修改"Contact: "字段中的地址端口数据, 用buffer中的数据替代
  mangle_sip_packet(pskb, ctinfo, ct, dptr, (*pskb)->len - dataoff,
                    buffer, bufflen, &ct_sip_hdrs[POS_CONTACT]);
  return 1;
 }
// 修改的是反方向数据
 /* This mangle requests headers. */
// 修改"sip:"中的数据
 return mangle_sip_packet(pskb, ctinfo, ct, dptr,
                          ct_sip_lnlen(*dptr,
                  *dptr + (*pskb)->len - dataoff),
                          buffer, bufflen, &ct_sip_hdrs[POS_REQ_HEADER]);
}

// 修改SIP字段
// 查找由hnfo定义的数据, 然后用buffer中的数据替代
static unsigned int mangle_sip_packet(struct sk_buff **pskb,
          enum ip_conntrack_info ctinfo,
          struct ip_conntrack *ct,
          const char **dptr, size_t dlen,
          char *buffer, int bufflen,
          struct sip_header_nfo *hnfo)
{
 unsigned int matchlen, matchoff;
// 查找hnfo定义的数据, 获取偏移地址matchoff和长度matchlen
 if (ct_sip_get_info(*dptr, dlen, &matchoff, &matchlen, hnfo) <= 0)
  return 0;
// 修改数据内容, 用buffer内容替代matchoff出的数据
 if (!ip_nat_mangle_udp_packet(pskb, ct, ctinfo,
                               matchoff, matchlen, buffer, bufflen))
  return 0;
 /* We need to reload this. Thanks Patrick. */
// 重新定位应用层数据起始地址
 *dptr = (*pskb)->data + (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr);
 return 1;
}

4.2 ip_nat_sdp(ip_nat_sdp_hook)

修改SDP数据, 并根据SDP中的参数建立期待子连接参数,函数返回NF_ACCEPT或NF_DROP
/* So, this packet has hit the connection tracking matching code.
   Mangle it, and change the expectation to match the new version. */
static unsigned int ip_nat_sdp(struct sk_buff **pskb,
          enum ip_conntrack_info ctinfo,
          struct ip_conntrack_expect *exp,
          const char *dptr)
{
 struct ip_conntrack *ct = exp->master;
 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 __be32 newip;
 u_int16_t port;
 DEBUGP("ip_nat_sdp():/n");
 /* Connection will come from reply */
// NAT修改后的地址
 newip = ct->tuplehash[!dir].tuple.dst.ip;
// 期待连接的参数
 exp->tuple.dst.ip = newip;
 exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port;
 exp->dir = !dir;
 /* When you see the packet, we need to NAT it the same as the
    this one. */
// 期待处理函数, 用于建立子连接的nat信息
 exp->expectfn = ip_nat_follow_master;
 /* Try to get same port: if not, try to change it. */
// 查找一个可用的空闲端口代替原来的端口值
 for (port = ntohs(exp->saved_proto.udp.port); port != 0; port++) {
  exp->tuple.dst.u.udp.port = htons(port);
  if (ip_conntrack_expect_related(exp) == 0)
   break;
 }
// 没可用端口了, 丢包
 if (port == 0)
  return NF_DROP;
// 修改SDP数据
 if (!mangle_sdp(pskb, ctinfo, ct, newip, port, dptr)) {
  ip_conntrack_unexpect_related(exp);
  return NF_DROP;
 }
 return NF_ACCEPT;
}

// 修改SDP数据
static unsigned int mangle_sdp(struct sk_buff **pskb,
          enum ip_conntrack_info ctinfo,
          struct ip_conntrack *ct,
// 新地址,端口值
          __be32 newip, u_int16_t port,
          const char *dptr)
{
 char buffer[sizeof("nnn.nnn.nnn.nnn")];
 unsigned int dataoff, bufflen;
 dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr);
 /* Mangle owner and contact info. */
// 新地址字符串
 bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip));
// 修改"o="字段中的地址
 if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff,
                        buffer, bufflen, &ct_sip_hdrs[POS_OWNER]))
  return 0;
// 修改"c="字段中的地址信息
 if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff,
                        buffer, bufflen, &ct_sip_hdrs[POS_CONNECTION]))
  return 0;
 /* Mangle media port. */
// 新端口字符串
 bufflen = sprintf(buffer, "%u", port);
// 修改"m="字段中的端口信息
 if (!mangle_sip_packet(pskb, ctinfo, ct, &dptr, (*pskb)->len - dataoff,
                        buffer, bufflen, &ct_sip_hdrs[POS_MEDIA]))
  return 0;
// 最后修改"Content-Length: "字段中的内容长度值, 因为修改了上述SDP数据后长度
// 可能会发生变化
 return mangle_content_len(pskb, ctinfo, ct, dptr);
}

// 修改"Content-Length: "字段中的内容长度值
static int mangle_content_len(struct sk_buff **pskb,
         enum ip_conntrack_info ctinfo,
         struct ip_conntrack *ct,
         const char *dptr)
{
 unsigned int dataoff, matchoff, matchlen;
 char buffer[sizeof("65536")];
 int bufflen;
 dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr);
 /* Get actual SDP lenght */
// 找"v="字符串位置,这是SDP数据的起始点
 if (ct_sip_get_info(dptr, (*pskb)->len - dataoff, &matchoff,
                     &matchlen, &ct_sip_hdrs[POS_SDP_HEADER]) > 0) {
  /* since ct_sip_get_info() give us a pointer passing 'v='
     we need to add 2 bytes in this count. */
// 目前SDP数据的真实长度, 最后+2是因为要加回"v="这两个字符长度
  int c_len = (*pskb)->len - dataoff - matchoff + 2;
  /* Now, update SDP lenght */
// 找"Content-Length: "字段位置,获取数据长度
  if (ct_sip_get_info(dptr, (*pskb)->len - dataoff, &matchoff,
                      &matchlen, &ct_sip_hdrs[POS_CONTENT]) > 0) {
// 新长度字符串
   bufflen = sprintf(buffer, "%u", c_len);
// 更新长度数据
   return ip_nat_mangle_udp_packet(pskb, ct, ctinfo,
       matchoff, matchlen,
       buffer, bufflen);
  }
 }
 return 0;
}
 
5. 结论

Linux内核中的SIP跟踪和NAT模块基本上比较完善地解决了SIP处理, 不过只是针对UDP协议的, 如果
是TCP实现的SIP则无效。 在编程中,定义了struct sip_header_nfo结构来描述要查找的各种类型数
据的信息,实现对象化编程,而且使程序更简洁。不过查找数据应该要支持大小写无关方式查找数据
,扫描数据次数也比较多,最多会扫描4次。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值