严禁用于任何商业用途。
msn: [email protected]
来源: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:[email protected]>;tag=a73kszlfl To: Bob <sips:[email protected]> Call-ID: [email protected] CSeq: 1 REGISTER Contact: <sips:[email protected]> 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:[email protected]>;tag=a73kszlfl To: Bob <sips:[email protected]>;tag=1410948204 Call-ID: [email protected] 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:[email protected]>;tag=ja743ks76zlflH To: Bob <sips:[email protected]> Call-ID: [email protected] CSeq: 2 REGISTER Contact: <sips:[email protected]> 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:[email protected]>;tag=ja743ks76zlflH To: Bob <sips:[email protected]>;tag=37GkEhwl6 Call-ID: [email protected] CSeq: 2 REGISTER Contact: <sips:[email protected]>;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:[email protected] SIP/2.0 Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bf9 Max-Forwards: 70 From: Alice <sip:[email protected]>;tag=9fxced76sl To: Bob <sip:[email protected]> Call-ID: [email protected] CSeq: 1 INVITE Contact: <sip:[email protected];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:[email protected]>;tag=9fxced76sl To: Bob <sip:[email protected]>;tag=8321234356 Call-ID: [email protected] CSeq: 1 INVITE Contact: <sip:[email protected];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:[email protected]>;tag=9fxced76sl To: Bob <sip:[email protected]>;tag=8321234356 Call-ID: [email protected] CSeq: 1 INVITE Contact: <sip:[email protected];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:[email protected] SIP/2.0 Via: SIP/2.0/TCP client.atlanta.example.com:5060;branch=z9hG4bK74bd5 Max-Forwards: 70 From: Alice <sip:[email protected]>;tag=9fxced76sl To: Bob <sip:[email protected]>;tag=8321234356 Call-ID: [email protected] 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:[email protected] SIP/2.0 Via: SIP/2.0/TCP client.biloxi.example.com:5060;branch=z9hG4bKnashds7 Max-Forwards: 70 From: Bob <sip:[email protected]>;tag=8321234356 To: Alice <sip:[email protected]>;tag=9fxced76sl Call-ID: [email protected] 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:[email protected]>;tag=8321234356 To: Alice <sip:[email protected]>;tag=9fxced76sl Call-ID: [email protected] 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次。