以连接跟踪为入口,只分析SIP ALG主要的逻辑实现(以LAN侧为client分析),不重要的函数略过,重要的函数在函数头里写分析

需要的预备知识:
1,了解网络协议族IPv4/IPv6,了解传输层协议udp/tcp,了解应用层协议SIP。
2,了解内核加载模块的机制,注册/proc/xxx, fileoperations等机制
3,了解内核网络模块hook机制,不同网络包的走向和hook点的优先级
4,了解连接跟踪,nat的基本概念

约定:a函数调b函数和c函数,b函数调用了d函数,d函数深入分析,简写作:

a()
--->b()
------->d(){dosomething();}
--->c()

用于分析的代码基于linux kernel 3.4

重要的结构体及其作用:

/*** 连接跟踪结构体,每一个连接只会有一个* 重要成员tuplehash[IP_CT_DIR_MAX]* 和ext,ext里通常包函有nat扩展,help扩展和timeout扩展* ct->ext 在添加nat, helper, timeout, 等 extend 的时候会初始化或更新,* ct->ext->offset[NF_CT_EXT_NAT] 指向 nf_conn_nat 数据的偏移地址,由nf_nat_init初始化,nf_nat_setup_info()添加* ct->ext->offset[NF_CT_EXT_HELPER] 指向 nf_conn_help数据的偏移地址,由nf_conntrack_helper_init初始化,nf_ct_helper_ext_add()添加* ct->ext->offset[NF_CT_EXT_TIMEOUT] 指向 nf_conn_timeout数据的偏移地址,由nf_conntrack_timeout_init初始化,nf_ct_timeout_ext_add()添加**/
struct nf_conn {...struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];...nf_ct_ext *ext;...
};/*** 五元组,每个传输层数据包都可以提取出一个五元组* 以ipv4,udp协议为例,对于一个转发包:* orignal方向:*      src:192.168.1.10:1234, dst:192.168.2.10:5678* 那么,reply方向:*      src:192.168.2.10:5678, dst:192.168.1.10:1234*  * 对于一个snat包(假设WAN口地址为202.n.n.n):* orignal方向:*      src:192.168.1.10:1234, dst:8.8.8.8:5678* snat后:*      src:202.n.n.n:nnnn, dst:8.8.8.8:5678* 那么,reply方向:*      src:8.8.8.8:5678, dst:202.n.n.n:nnnn* 对于每一个连接,内核都会用一个nf_conn结构体去跟踪,* ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple记录原始方向* ct->tuplehash[IP_CT_DIR_REPLY].tuple记录这条连接的回应方向*/
struct nf_conntrack_tuple
{src,{l3num;//网络层协议号,大多数情况下是AF_INET(ipv4)或AF_INET6(ipv6)u3;//源地址,可以是ipv4或ipv6地址u;//源端口号}dst{protonum;//传输层协议号,IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP等dir;//方向original, 或replyu3;//目的地址,可以是ipv4或ipv6地址u;//目的端口号}
}/*** help结构体,是 ct->ext 的一个扩展* 一个连接的ext可以有多种扩展: help, nat, timeout等* help只是ext的一种,基于连接。* 而helper是基于协议的,当一个连接的tuple匹配上helper的tuple,* help与helper 就会关联起来*/
struct nf_conn_help
{struct nf_conntrack_helper __rcu *helper;//在tuple匹配上后,对应的helper后会放入这里union nf_conntrack_help help;struct hlist_head expectations;u8 expecting[NF_CT_MAX_EXPECT_CLASSES];
}/*** helper结构体,例如 ftp, sip 等 helper* 当一个数据包的tuple匹配上helper的 tuple就会执行help函数指针指向的函数* 例如nf_conntrack_sip.c 里向内核注册了sip协议的helper,helper.tuple.src.u.udp.port ==5060* 这里src port为5060,但是在给一个数据包添加helper的时候,是以IP_CT_DIR_REPLY方向来匹配的,* 即,sip包的helper是如果期待的返回端口为5060就给这个连接添加一个sip helper.*/
struct nf_conntrack_helper
{struct nf_conntrack_expect_policy expect_policy;//连接期望策略struct nf_conntrack_tuple tuple;//五元组(*help)()//函数指针,匹配上五元组即会在ipv4/6_confirm时被调用
}//net/ipv4/netfilter/nf_conntrack_l3proto_ipv4.c
struct ipv4_conntrack_ops[]//连接跟踪"数据处理"模块向内核注册的结构体。ipv6也类似
{{.hook  = ipv4_conntrack_in,//数据包勾子函数 数据 连接跟踪 入口.owner  = THIS_MODULE,.pf  = NFPROTO_IPV4,.hooknum = NF_INET_PRE_ROUTING,//pre-routing.priority = NF_IP_PRI_CONNTRACK,//-200 conntrack,高于iptable的各个表},{.hook  = ipv4_conntrack_local,// 数据 conntrack 入口.owner  = THIS_MODULE,.pf  = NFPROTO_IPV4,.hooknum = NF_INET_LOCAL_OUT,//本机发出的包也要 conntrack.priority = NF_IP_PRI_CONNTRACK,},...
}//像这样的结构体还有很多,但是它们都会调用到 nf_conntrack_in()函数

连接跟踪模块 功能初始化的 (系统起动时初始化)主要过程:

nf_conntrack_standalone_init()//nf_conntrack_net_ops//模块入口
--->nf_conntrack_net_init()
------->nf_conntrack_init()
----------->nf_conntrack_init_init_net()
--------------->nf_conntrack_proto_init()
--------------->nf_conntrack_helper_init()//初始化helper的功能
----------->nf_conntrack_init_net()
--------------->nf_conntrack_expect_init()//初始化expect的功能
--------------->nf_conntrack_timeout_init()//初始化timeout的功能
//nat也依赖于conntrack, nat功能在哪里初始化呢?在nf_nat_standalone.c里面

下面分析helper功能的初始化:

//net/netfilter/nf_conntrack_helper.c /*** 初始化一个 hashtable, 申请内存用于存放各种helper* 将helper_extend加入nf_ct_ext_types[NF_CT_EXT_HELPER]* 而 helper_extend 则指示了 nf_conn_help 需要的空间*/
int nf_conntrack_helper_init(void)
{nf_ct_helper_hash = nf_ct_alloc_hashtable(); nf_ct_extend_register(&helper_extend); //注册extend{nf_ct_ext_types[NF_CT_EXT_HELPER] = helper_extend}
}static struct nf_ct_ext_type helper_extend __read_mostly = {.len = sizeof(struct nf_conn_help),.align = __alignof__(struct nf_conn_help),//用于指示以这个结构体为宽度申请ct->ext的内存.id = NF_CT_EXT_HELPER,
};/** 注册helper, ftp/sip/snmp/tftp都会使用这个注册函数,*  注册时计算tuple的hash值,放入hashTable nf_ct_helper_hash[h]*  nf_conntrack_ftp.c注册 nf_conntrack_helper ftp[][]*  nf_conntrack_sip.c注册 nf_conntrack_helper sip[][]*/
int nf_conntrack_helper_register(struct nf_conntrack_helper *me)
{unsigned int h = helper_hash(&me->tuple); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]);
}

下面分析数据流:

数据流调用关系:
ipv4_conntrack_in()或ipv4_conntrack_local()或__ipv6_conntrack_in()
--->nf_conntrack_in(){l3proto = __nf_ct_l3proto_find(pf);//pf为PF_INET或PF_INET6,返回网络(IP)层的处理工具l3proto->get_l4proto(skb, ...);//使用网络(IP)层处理工具解析网络层,实际调用的是ipv4_get_l4proto,或ipv6_get_l4protol4proto = __nf_ct_l4proto_find(pf, protonum);//IP层解析后,传输层协议号(protonum)已知,代入此函数后返回相应的传输协议处理工具ct = resolve_normal_ct(skb,l3proto,l4proto, ... );//下面单独分析timeouts = l4proto->get_timeouts(net);l4proto->packet(ct, skb, dataoff, ctinfo, pf, hooknum, timeouts);//传输层处理}resolve_normal_ct()
{nf_ct_get_tuple(skb, &tuple, ...);//从数据中分析出tuple,方向设为IP_CT_DIR_ORIGINALhash = hash_conntrack_raw(&tuple, zone);/**这里尝试去查找是否有匹配的tuple存在,这里original和reply方向都会查找。* 如果找不到会新建一个ct。* 存入发生在:ipv4_confirm()->nf_conntrack_confirm()->__nf_conntrack_confirm()->__nf_conntrack_hash_insert()里* 下面的函数会返回insert时的hash, 注意hash不是一个数值,* 而是一个nf_conntrack_tuple_hash结构体,带了方向*/h = __nf_conntrack_find_get(net, zone, &tuple, hash);if (!h) {h = init_conntrack();//下面单独分析}ct = nf_ct_tuplehash_to_ctrack(h);//根据h的地址偏移量,由ct->tuplehash[h->tuple.dst.dir]反向推出ct的地址...//此段主要标记连接的状态,略...
}init_conntrack()
{//填一个repl_tuple, 与tuple地址相反,端口号相反,方向为 IP_CT_DIR_REPLYnf_ct_invert_tuple(&repl_tuple, tuple, l3proto, l4proto);ct = __nf_conntrack_alloc();{ct = kmem_cache_alloc();ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *orig;ct->tuplehash[IP_CT_DIR_REPLY].tuple = *repl;setup_timer(&ct->timeout, death_by_timeout, (unsigned long)ct);}timeouts = l4proto->get_timeouts(net);//传输层协议相关的timeouts集合l4proto->new(ct, skb, dataoff, timeouts);//传输层处理开始...//查找是否有LAN侧的包期望着这个包//注意期望的匹配方法只用了协议族,协议类型,端口,没有用到IP地址//这就是说,不需要IP匹配exp = nf_ct_find_expectation(net, zone, tuple);if (exp){ct->master = exp->master;help = nf_ct_helper_ext_add(ct, GFP_ATOMIC);rcu_assign_pointer(help->helper, exp->helper);//返回的包也要指定helper}else{__nf_ct_try_assign_helper(ct, tmpl, GFP_ATOMIC);{help = nfct_help(ct); //如果找到这个数据包的目的地址在内核有helper注册过,就给这个数据包添加helper//例如,如果这个包的目的地址是5060,那这个ct就会被加上SIP helper.helper = __nf_ct_helper_find(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);help = nf_ct_helper_ext_add(ct, flags);//添加EXT_HELPERrcu_assign_pointer(help->helper, helper);}}if (exp) { if (exp->expectfn) //函数指针,对于SIP ALG而言,指向ip_nat_sip_expected()//如果匹配上exp, 则说明这个包是LAN侧期望的包,函数设置DNAT(将返回包的目的地址和端口还原回LAN侧的IP和端口)exp->expectfn(ct, exp);nf_ct_expect_put(exp); }return &ct->tuplehash[IP_CT_DIR_ORIGINAL]; }

以上是数据包到达时的处理,主要包括:
1,如果是新包,设置helper, (helper处理时会加上期望).
2,如果是包已经被期望,执行期望处理函数。

下面是数据在confirm时的处理:

/** 关于连接期望, 以SIP为例子,有如下过程,* 在ipv4_confirm()/ipv6_confirm()被调用时,会执行在helper的help()函数,SIP 的help函数即:sip_help_tcp/udp()* ipv4_confirm()是钩子函数,注册于 NF_INET_POST_ROUTING,和 NF_INET_LOCAL_IN,* 而help函数在初始包到达时(这里是register包)里会申请expect,当然rtp也会在相应的包里申请expect.*/
sip_help_tcp/udp()->process_sip_msg()
{process_sip_request(){process_register_request();//下面单独分析...}process_sip_response()//略if (ret == NF_ACCEPT && ct->status & IPS_NAT_MASK)//下一个函数有分析,数据走到此处已经nat过了{nf_nat_sip(skb, dataoff, dptr, datalen);//函数指针,指向ip_nat_sip,下面单独分析}
}/** SIP ALG不光可以用于LAN侧挂客户端,也可以是LAN侧挂服务器(需要配合port mapping支持),*  这里为了方便,我以LAN侧挂客户端为例子。*/
process_register_request()
{exp = nf_ct_expect_alloc(ct);saddr = &ct->tuplehash[!dir].tuple.src.u3;//期待返回包的源地址,通常即 SIP outbound proxy 的地址nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct), saddr, &daddr, proto, NULL, &port);{exp->class = class;exp->tuple.src.l3num = family;//IPv4 or IPv6exp->tuple.dst.protonum = proto;//UDP or TCPmemcpy(&exp->tuple.src.u3, saddr, len);//SIP outbound proxy 的地址exp->tuple.src.u.all = 0;memcpy(&exp->tuple.dst.u3, daddr, len);//这个地址是从 SIP_HDR_CONTACT字段中解析出来的,即LAN侧源地址.exp->tuple.dst.u.all = *dst;//这个是地址从 SIP_HDR_CONTACT字段中解析出来的,即LAN侧源端口.}exp->timeout.expires = sip_timeout * HZ;exp->helper = nfct_help(ct)->helper;exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE;/*下面的nf_nat_sip_expect是函数指针,ip_nat_sip_expect是真正执行的函数* 函数注册于nf_nat_sip.c* 执行条件是ct已经做过SNAT或DNAT,以SNAT为例,需要在PostRouting时换掉tuple[reply]* 而SNAT的优先级是NF_IP_PRI_NAT_SRC=100,高于ipv4_confirm()的优先级 NF_IP_PRI_CONNTRACK_CONFIRM=MAX* 所以下面的函数在执行时ct->status & IPS_NAT_MASK为true.*/if (nf_nat_sip_expect && ct->status & IPS_NAT_MASK){nf_nat_sip_expect()->ip_nat_sip_expect(){//以下都以udp写的代码,但udp和tcp实际上在tuple里是一个地址,所以tcp也支持newip = ct->tuplehash[!dir].tuple.dst.u3.ip;//因为已经做过SNAT所以这里是WAN IP.port = ntohs(exp->tuple.dst.u.udp.port);//端口还是用的LAN侧端口,但注意还没有最终确定。exp->saved_ip = exp->tuple.dst.u3.ip;//将LAN侧源IP保留下来exp->tuple.dst.u3.ip = newip;exp->saved_proto.udp.port = exp->tuple.dst.u.udp.port;//将LAN侧的源端口保留下来exp->dir = !dir;//期待返回包exp->expectfn = ip_nat_sip_expected;for (; port != 0; port++) {//WAN port很可能已经被占用了,比如LAN侧有两个客户端来自两个不同的IP,//都用5060去注册,但是WAN IP只有一个5060端口,所以这里不停的尝试,//找到可用的端口为止exp->tuple.dst.u.udp.port = htons(port);ret = nf_ct_expect_related(exp);//加入net的期望列表,开始倒计时}if (exp->tuple.dst.u3.ip != exp->saved_ip ||exp->tuple.dst.u.udp.port != exp->saved_proto.udp.port) {//如果期待的包ip或port有变化,修改将要发出的包的源ip和端口,//这样返回的包才会匹配上期望mangle_packet(skb, dataoff, dptr, datalen, xxx);}}}else{nf_ct_expect_related(exp);//没允许nat sip, 或没开nat,异常情况}nf_ct_expect_put(exp);
}ip_nat_sip()
{//改包,将LAN侧地址和端口改为nat后的地址和端口,略
}

总结:
1, SIP ALG不光可以用于LAN侧client,也可用于LAN侧做SIP Server.
2, 连接跟踪在helper里设置期望,在helper里改包,期望函数注册后的处理函数只是做DNAT。
3,

内核代码之SIP ALG分析相关推荐

  1. Linux内核分析:完成一个简单的时间片轮转多道程序内核代码

    PS.贺邦   原创作品转载请注明出处  <Linux内核分析>MOOC课程    http://mooc.study.163.com/course/USTC-1000029000 1.m ...

  2. 输入子系统代码内核代码分析

    本篇博客里将会对输入子系统进行比较深入的分析,第一部分将基于内核2.6.2版本,第二部分将基于内核的3.14版本,同时我会为两个版本都提供一个示例代码 我会尽可能深入的分析代码,如果你看完了这篇博客, ...

  3. Linux内核分析2:一个简单的时间片轮转多道程序内核代码分析

    Lab2:一个简单的时间片轮转多道程序内核代码 席金玉   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 ...

  4. linux实验:基于mykernel的一个简单的时间片轮转多道程序内核代码分析

    学号后三位:288 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 1.mykernel mykernel是由中科大孟宁老师建立的用于开发 ...

  5. 请善用工具审核您的内核代码:)

    在写内核代码时.代码风格(coding style)是一个非常重要的部分,否则内核代码将变的混乱不堪. 那么什么样的代码算美丽的代码?什么样的代码符合c99这种标准?此外,程序写完之后,有什么工具可以 ...

  6. 制造内核崩溃并使用crash分析内核崩溃产生的vmcore文件

    制造内核崩溃并使用crash分析内核崩溃产生的vmcore文件 1,安装kernel-debuginfo$(uname -r).rpm和kernel-debuginfo-common-$(uname ...

  7. linux内核计算代码时间,完成一个简单的时间片轮转多道程序内核代码

    <Linux 内核分析>实验二:How Does a Operating System Work? 1.函数调用堆栈和中断 在上一节实验中我们已经明白了,存储程序计算机的运行原理,就是通过 ...

  8. 鸿蒙轻内核M核源码分析:中断Hwi

    摘要:本文带领大家一起剖析了鸿蒙轻内核的中断模块的源代码,掌握中断相关的概念,中断初始化操作,中断创建.删除,开关中断操作等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列五 中断Hwi&g ...

  9. 鸿蒙轻内核M核源码分析:数据结构之任务排序链表

    摘要:鸿蒙轻内核的任务排序链表,用于任务延迟到期/超时唤醒等业务场景,是一个非常重要.非常基础的数据结构. 本文会继续给读者介绍鸿蒙轻内核源码中重要的数据结构:任务排序链表TaskSortLinkAt ...

  10. 鸿蒙轻内核M核源码分析:数据结构之任务就绪队列

    摘要:本文会给读者介绍鸿蒙轻内核M核源码中重要的数据结构,任务基于优先级的就绪队列Priority Queue. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列三 数据结构-任务就绪队列> ...

最新文章

  1. 基于场景建模的自动化配置
  2. 洛谷-小鱼的游泳时间-洛谷的第一个任务
  3. 最新!中国天气网api接口调用,key获取方式,数据请求秘钥获取,城市id获取方法
  4. Question for the 3D printing lattice?
  5. shell入门(二)——面试题实例
  6. C++使用boost::bind 订阅消息中的返回函数传入多个参数
  7. 两个实际任务掌握图像分类【Keras】(转)
  8. “ 鸡尾酒会问题”(cocktail party problem)
  9. 图像分割法-snake
  10. cdrx7拼版工具在哪里_CorelDRAW X7标签怎么排版?
  11. Oracle将CLOB字段类型转为Varchar2类型
  12. An error occurred.Faithfully yours, nginx
  13. layui table 渲染动态列及列数据
  14. 公网IP/内网IP:
  15. 微信商家收款码怎么申请
  16. c++后台开发项目_5900万!腾讯云中标安徽宿州wecity智慧园区EPC项目(含智慧路灯)...
  17. 输入一段字符,统计一段字符串中大小写字母的个数
  18. 每日英语:The World-Changing Margaret Thatcher
  19. 2022年全球市场紧急出口装置总体规模、主要生产商、主要地区、产品和应用细分研究报告
  20. 深入解析HotSpot

热门文章

  1. 推荐个echarts网站
  2. Plant Ecology Journal Club, 2018
  3. 杭州电子科技大学ACM1020 JAVA
  4. 马斯克在推特说特斯拉股价太高导致大跌 会被罚吗
  5. 免费公共DNS服务器大全
  6. note4-WEB源码拓展
  7. java格林威治时间转换_JAVA 格式化格林威治时间(Wed Aug 01 00:00:00 CST 2012)格式转换...
  8. 计算机硬盘能影响速度吗,BitLocker对电脑硬盘性能的影响
  9. Python爬虫获取电影链接(续)
  10. webservice 缺少根元素_草莓种植,这2种元素至关重要,直接影响草莓的产量和品质...