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_helpstatic 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次。