linux进行端口跟踪,LInux上的SIP协议跟踪

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 ;tag=a73kszlfl

To: Bob

Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com

CSeq: 1 REGISTER

Contact:

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 ;tag=a73kszlfl

To: Bob ;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 ;tag=ja743ks76zlflH

To: Bob

Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com

CSeq: 2 REGISTER

Contact:

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 ;tag=ja743ks76zlflH

To: Bob ;tag=37GkEhwl6

Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com

CSeq: 2 REGISTER

Contact: ;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 ;tag=9fxced76sl

To: Bob

Call-ID: 3848276298220188511@atlanta.example.com

CSeq: 1 INVITE

Contact:

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 ;tag=9fxced76sl

To: Bob ;tag=8321234356

Call-ID: 3848276298220188511@atlanta.example.com

CSeq: 1 INVITE

Contact:

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 ;tag=9fxced76sl

To: Bob ;tag=8321234356

Call-ID: 3848276298220188511@atlanta.example.com

CSeq: 1 INVITE

Contact:

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 ;tag=9fxced76sl

To: Bob ;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 ;tag=8321234356

To: Alice ;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 ;tag=8321234356

To: Alice ;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
    评论
嵌入式Linux可以通过使用相应的软件包或库来呼叫SIP,实现语音通信功能。SIP(会话初始协议)是一种用于建立、修改和终止多媒体会话的网络协议。在嵌入式Linux中呼叫SIP,通常需要以下步骤: 1. 安装SIP软件包或库:嵌入式Linux常用的SIP软件包有PJSIP、Linphone等。这些软件包提供了SIP协议的实现和相关的功能接口,可以用于嵌入式系统中进行语音通信。 2. 配置SIP相关参数:在Linux系统中,需要根据具体的网络环境和通信需求进行SIP配置。配置参数包括SIP服务器地址、端口号、认证信息等,这些参数可以通过配置文件或程序接口来设置。 3. 编程实现SIP功能:在嵌入式Linux中,可以通过编程的方式实现SIP呼叫功能。根据所选用的SIP软件包或库,可以使用相应的API来完成SIP协议的建立、修改等操作。编程接口提供了一系列函数用于实现SIP会话的建立、终止等操作。 4. 实时音频处理:在语音通信过程中,嵌入式Linux需要实时处理音频数据的传输和编解码。可以利用现有的音频处理库来实现音频数据的采集、压缩、解压缩、播放等功能。一些常用的音频处理库有ALSA、PulseAudio等。 总体而言,嵌入式Linux通过安装相应的SIP软件包或库,并进行配置和编程实现,可以实现通过SIP协议进行语音通信的功能。这使得嵌入式系统可以作为一个电话终端,进行实时语音通话。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值