Linux网络地址转换分析 地址转换用来改变源/目的端口,是netfilter的一部分,也是通过hook点上注册相应的结构来工作.
Nat注册的hook点和conntrack相同,只是优先级不同,数据包进入netfilter之后先经过conntrack,再经过nat.
而在数据包离开netfilter之前先经过nat,再经过conntrack. 在ip_conntrack结构中有为nat定义的一个nat结构,为什么把这个结构放在ip_conntrack里呢。
简单的说,对于非初始化连接的数据包,即后续的数据包,一旦确定它属于某个连接,则可以直接利用连接状态里的nat信息来进行地址转换;
而对于初始数据包,必须在nat表里查找相应的规则,确定了地址转换的内容后,将这些信息放到连接跟踪结构的nat参量里面,供后续的数据包使用. struct ip_conntrack {
......
#ifdef CONFIG_IP_NF_NAT_NEEDED
struct {
struct ip_nat_info info;
union ip_conntrack_nat_help help;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
int masq_index;
#endif
} nat;
#endif /* CONFIG_IP_NF_NAT_NEEDED */
......
};
struct ip_nat_info
{
struct list_head bysource;
struct ip_nat_seq seq[IP_CT_DIR_MAX];
}; 下面我们来看初始化函数.
static int __init ip_nat_standalone_init(void) //net/ipv4/netfilter/ip_nat_standalone.c
{
int ret = ; need_conntrack(); //空函数
#ifdef CONFIG_XFRM //IPSEC相关,我们忽略
BUG_ON(ip_nat_decode_session != NULL);
ip_nat_decode_session = nat_decode_session;
#endif
//初始化nat规则
ret = ip_nat_rule_init();
if (ret < ) {
printk("ip_nat_init: can't setup rules.\n");
goto cleanup_decode_session;
}
//注册hook函数
ret = nf_register_hooks(ip_nat_ops, ARRAY_SIZE(ip_nat_ops));
if (ret < ) {
printk("ip_nat_init: can't register hooks.\n");
goto cleanup_rule_init;
}
return ret;
......
}
规则初始化
static struct ipt_target ipt_snat_reg = {
.name = "SNAT",
.target = ipt_snat_target,
.targetsize = sizeof(struct ip_nat_multi_range_compat),
.table = "nat",
.hooks = << NF_IP_POST_ROUTING,
.checkentry = ipt_snat_checkentry,
}; static struct ipt_target ipt_dnat_reg = {
.name = "DNAT",
.target = ipt_dnat_target,
.targetsize = sizeof(struct ip_nat_multi_range_compat),
.table = "nat",
.hooks = ( << NF_IP_PRE_ROUTING) | ( << NF_IP_LOCAL_OUT),
.checkentry = ipt_dnat_checkentry,
};
int __init ip_nat_rule_init(void)
{
int ret;
//注册nat表和参照模板(第二个参数),初始化表中字段(与iptable有关)
ret = ipt_register_table(&nat_table, &nat_initial_table.repl); //参看Linux Netfilter实现机制和扩展技术
if (ret != )
return ret; //把这个结构连接到一个结构中的struct list_head target连表中
ret = ipt_register_target(&ipt_snat_reg);
if (ret != )
goto unregister_table; ret = ipt_register_target(&ipt_dnat_reg);
if (ret != )
goto unregister_snat; return ret;
......
}
在看另一个文件的初始化
static int __init ip_nat_init(void) //net/ipv4/netfilter/ip_nat_core.c
{
size_t i;
ip_nat_htable_size = ip_conntrack_htable_size; //nat的hash表大小和conntrack的hash表相同 bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size); //初始化了一个叫bysource的全局链表指针
if (!bysource)
return -ENOMEM; write_lock_bh(&ip_nat_lock);
for (i = ; i < MAX_IP_NAT_PROTO; i++)
ip_nat_protos[i] = &ip_nat_unknown_protocol; //注册一些内建的协议,是用来维护nat模块中用到的协议结构ip_nat_protocol的全局链表.
ip_nat_protos[IPPROTO_TCP] = &ip_nat_protocol_tcp;
ip_nat_protos[IPPROTO_UDP] = &ip_nat_protocol_udp;
ip_nat_protos[IPPROTO_ICMP] = &ip_nat_protocol_icmp;
write_unlock_bh(&ip_nat_lock); for (i = ; i < ip_nat_htable_size; i++) { //初始化链表
INIT_LIST_HEAD(&bysource[i]);
}
//初始化一个ip_conntrack_destroyed函数,ip_nat_cleanup_conntrack(struct ip_conntrack *conn) 的作用是在bysource链表中删除conn对应的节点.
ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;
//加上这个标志后nat将跳过这个伪造的conntrack
ip_conntrack_untracked.status |= IPS_NAT_DONE_MASK;
return ;
}
我们还是假定今后遇到的包全部是tcp协议的. 看下面协议实现部分.
下面我们还是一个一个来看这些hook函数.
static struct nf_hook_ops ip_nat_ops[] = {
/* Before packet filtering, change destination */
{
.hook = ip_nat_in,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING,
.priority = NF_IP_PRI_NAT_DST,
},
/* After packet filtering, change source */
{
.hook = ip_nat_out,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_NAT_SRC,
},
/* After conntrack, adjust sequence number */
{
.hook = ip_nat_adjust,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_NAT_SEQ_ADJUST,
},
/* Before packet filtering, change destination */
{
.hook = ip_nat_local_fn,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_OUT,
.priority = NF_IP_PRI_NAT_DST,
},
/* After packet filtering, change source */
{
.hook = ip_nat_fn,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SRC,
},
/* After conntrack, adjust sequence number */
{
.hook = ip_nat_adjust,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_NAT_SEQ_ADJUST,
},
};
NF_IP_PRE_ROUTING,在报文作路由以前执行;
NF_IP_FORWARD,在报文转向另一个NIC以前执行;
NF_IP_POST_ROUTING,在报文流出以前执行;
NF_IP_LOCAL_IN,在流入本地的报文作路由以后执行;
NF_IP_LOCAL_OUT,在本地报文做流出路由前执行; NF_ACCEPT :继续正常的报文处理;
NF_DROP :将报文丢弃;
NF_STOLEN :由钩子函数处理了该报文,不要再继续传送;
NF_QUEUE :将报文入队,通常交由用户程序处理;
NF_REPEAT :再次调用该钩子函数。
NF_STOP :停止检测,不再进行下一个Hook函数 static unsigned int ip_nat_in(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
unsigned int ret;
u_int32_t daddr = (*pskb)->nh.iph->daddr; ret = ip_nat_fn(hooknum, pskb, in, out, okfn);
if (ret != NF_DROP && ret != NF_STOLEN && daddr != (*pskb)->nh.iph->daddr) { //目的地址已经改变
dst_release((*pskb)->dst); //丢弃原来的路由信息
(*pskb)->dst = NULL;
}
return ret;
}
//主要的通用函数
static unsigned int ip_nat_fn(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
struct ip_nat_info *info; //#define HOOK2MANIP(hooknum) ((hooknum) != NF_IP_POST_ROUTING && (hooknum) != NF_IP_LOCAL_IN)
//根据所在的hook点判断转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换
enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
//取得数据包的连接状态
ct = ip_conntrack_get(*pskb, &ctinfo);
//数据包没有被conntrack
if (ct == &ip_conntrack_untracked)
return NF_ACCEPT; //这个函数有两种不同的行为,取决于传给它输入包还是输出包.对于输入包,它使传输层硬件校验和无效.对于输出包,它计算传输层校验和
if ((*pskb)->ip_summed == CHECKSUM_HW)
if (skb_checksum_help(*pskb, (out == NULL)))
return NF_DROP;
//如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃
if (!ct) {
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
struct icmphdr _hdr, *hp;
hp = skb_header_pointer(*pskb, (*pskb)->nh.iph->ihl*, sizeof(_hdr), &_hdr);
if (hp != NULL && hp->type == ICMP_REDIRECT)
return NF_DROP;
}
return NF_ACCEPT;
}
switch (ctinfo) { //判断连接状态,调用相应的处理函数
case IP_CT_RELATED:
case IP_CT_RELATED+IP_CT_IS_REPLY:
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
if (!ip_nat_icmp_reply_translation(pskb, ct, maniptype, CTINFO2DIR(ctinfo)))
return NF_DROP;
else
return NF_ACCEPT;
}
case IP_CT_NEW: //初始连接的数据包
info = &ct->nat.info;
//测试ct->status中的位判断是否已经初始化conntrack中nat部分
if (!ip_nat_initialized(ct, maniptype)) {
unsigned int ret;
if (unlikely(is_confirmed(ct)))
ret = alloc_null_binding_confirmed(ct, info, hooknum);
else if (hooknum == NF_IP_LOCAL_IN)
//这是在没有找到转换规则的时候就做一个空转换,例如如果我们是希望在数据包外出的时候修改源IP,
//那么在prerouting的时候就找不到规则,这时候就会发生空转换的动作,其实这个作用一是为了保持流程的统一,
//即不管有没有规则都要调用ip_nat_setup_info(),第二个作用是这样调用了ip_nat_setup_info以后,
//会做一些NAT的辅助工作,也就是说基本信息的记录和转换信息由不同的模块来负责
ret = alloc_null_binding(ct, info, hooknum);
else //包状态为NEW,并且没有做过NAT转化的包才会通过ip_nat_rule_find()查找并生成NAT规则信息
ret = ip_nat_rule_find(pskb, hooknum, in, out, ct, info);
if (ret != NF_ACCEPT) {
return ret;
}
} else
DEBUGP("Already setup manip %s for ct %p\n", maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST", ct);
break;
default:
//看见syn+ack后ctinfo 应该是IP_CT_ESTABLISHED+IP_CT_IS_REPLY 第二次握手
//看见ack 后 ctinfo 应该是IP_CT_ESTABLISHED 第三次握手
IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
info = &ct->nat.info;
}
IP_NF_ASSERT(info);
//修改数据包内容
return ip_nat_packet(ct, ctinfo, hooknum, pskb);
}
int ip_nat_rule_find(struct sk_buff **pskb, unsigned int hooknum, const struct net_device *in, const struct net_device *out,
struct ip_conntrack *ct, struct ip_nat_info *info)
{
int ret;
//通过hooknum在iptable表得到检查点对应的默认的chain表(chain是在某个检查点上所引用规则的集合,规则由ipt_entry表示)
//ipt_do_table查找表中的所有ipt_entry,如果match全都匹配,则调用target函数
//此时的target函数就是在nat初始化时注册的ipt_snat_target和ipt_dnat_target
//例如添加iptables -t nat -A PREROUTING -p TCP -i eth0 -d 10.0.0.1 --dport 80 -j DNAT --to-destination 192.168.0.1
//其中用到了nat 表和 DNAT,看上面的初始化函数
ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL); if (ret == NF_ACCEPT) {
if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum)))
ret = alloc_null_binding(ct, info, hooknum);
}
return ret;
}
//我们看一下这个注册的snat_target
static unsigned int ipt_snat_target(struct sk_buff **pskb, const struct net_device *in, const struct net_device *out,
unsigned int hooknum, const struct ipt_target *target, const void *targinfo, void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
const struct ip_nat_multi_range_compat *mr = targinfo;
//源地址转换只能在POST_ROUTING中
IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
//获取连接信息
ct = ip_conntrack_get(*pskb, &ctinfo); // 只有新连接才进行NAT info的建立
// targinfo实际是struct ip_nat_multi_range_compat结构指针,记录转换后的地址、端口等信息
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
IP_NF_ASSERT(out); return ip_nat_setup_info(ct, &mr->range[], hooknum);
}
unsigned int ip_nat_setup_info(struct ip_conntrack *conntrack, const struct ip_nat_range *range, unsigned int hooknum)
{
struct ip_conntrack_tuple curr_tuple, new_tuple;
struct ip_nat_info *info = &conntrack->nat.info;
int have_to_hash = !(conntrack->status & IPS_NAT_DONE_MASK);
enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum); //对当前状态的应答方向的tuple调用invert_tuplepr取反,得到一个curr_tupe,
//如果之前没有进行过地址或端口转换,通常这里得到的curr_tupe就等于初始方向的tuple
invert_tuplepr(&curr_tuple, &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple); //参看ip_conntrack实现 //找一个未使用的进行了转换后的tuple结构参数,其中参数range是转换后的ip地址和端口范围
//new_tuple保持转换后的连接原始方向的tuple
get_unique_tuple(&new_tuple, &curr_tuple, range, conntrack, maniptype); //检查转换前后的tuple值是否相同,new_tuple是NAT后的新的原始方向的tuple
if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct ip_conntrack_tuple reply; //建立连接地址转换后的反向的tuple
invert_tuplepr(&reply, &new_tuple);
//修改连接中的响应方向的tuple值
//即conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = *reply
ip_conntrack_alter_reply(conntrack, &reply);
//设置标志
if (maniptype == IP_NAT_MANIP_SRC)
conntrack->status |= IPS_SRC_NAT;
else
conntrack->status |= IPS_DST_NAT;
}
if (have_to_hash) {
//连接到基于起始方向源IP的HASH链表中
unsigned int srchash = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
write_lock_bh(&ip_nat_lock);
list_add(&info->bysource, &bysource[srchash]);
write_unlock_bh(&ip_nat_lock);
}
//在连接的状态值中设置源或目的NAT完成标志
if (maniptype == IP_NAT_MANIP_DST)
set_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
else
set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status); return NF_ACCEPT;
}
static void get_unique_tuple(struct ip_conntrack_tuple *tuple, const struct ip_conntrack_tuple *orig_tuple,
const struct ip_nat_range *range, struct ip_conntrack *conntrack, enum ip_nat_manip_type maniptype)
{
struct ip_nat_protocol *proto; //如果是做SNAT,并且此源地址(包括ip地址和端口等信息)已经做过转换,而且这样产生的tuple仍然是唯一的话,那么转换成功结束
if (maniptype == IP_NAT_MANIP_SRC) {
if (find_appropriate_src(orig_tuple, tuple, range)) {//找到合适的源地址的NAT
if (!ip_nat_used_tuple(tuple, conntrack))
return;
}
}
*tuple = *orig_tuple;
//选择一个最少使用的ip
find_best_ips_proto(tuple, range, conntrack, maniptype);
//查找协议看上面注册部分
proto = ip_nat_proto_find_get(orig_tuple->dst.protonum); //如果端口不限或在指定的端口范围内,并且此tuple唯一,那么转换成功
if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) || proto->in_range(tuple, maniptype, &range->min, &range->max))
&& !ip_nat_used_tuple(tuple, conntrack)) {
ip_nat_proto_put(proto);
return;
}
//做端口转换,看下面协议实现部分
proto->unique_tuple(tuple, range, maniptype, conntrack);
ip_nat_proto_put(proto);
}
static void find_best_ips_proto(struct ip_conntrack_tuple *tuple, const struct ip_nat_range *range,
const struct ip_conntrack *conntrack, enum ip_nat_manip_type maniptype)
{
u_int32_t *var_ipp;
u_int32_t minip, maxip, j;
//就没作 ip NAT
if (!(range->flags & IP_NAT_RANGE_MAP_IPS))
return; if (maniptype == IP_NAT_MANIP_SRC) //指向要修改的ip
var_ipp = &tuple->src.ip;
else
var_ipp = &tuple->dst.ip; if (range->min_ip == range->max_ip) { //只有一个选择
*var_ipp = range->min_ip;
return;
}
//选择一个ip
minip = ntohl(range->min_ip);
maxip = ntohl(range->max_ip);
j = jhash_2words(tuple->src.ip, tuple->dst.ip, );
*var_ipp = htonl(minip + j % (maxip - minip + ));
}
static unsigned int ipt_dnat_target(struct sk_buff **pskb, const struct net_device *in, const struct net_device *out,
unsigned int hooknum, const struct ipt_target *target, const void *targinfo, void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
const struct ip_nat_multi_range_compat *mr = targinfo; ct = ip_conntrack_get(*pskb, &ctinfo);
//连接必须是新的和有效的
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)); if (hooknum == NF_IP_LOCAL_OUT && mr->range[].flags & IP_NAT_RANGE_MAP_IPS)
warn_if_extra_mangle((*pskb)->nh.iph->daddr, mr->range[].min_ip); //还是调用这函数
return ip_nat_setup_info(ct, &mr->range[], hooknum);
}
下面我们还是继续ip_nat_fn函数,在最后一步调用
unsigned int ip_nat_packet(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, unsigned int hooknum, struct sk_buff **pskb)
{
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); //方向
unsigned long statusbit;
enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum); if (mtype == IP_NAT_MANIP_SRC) //是源还是目的nat
statusbit = IPS_SRC_NAT;
else
statusbit = IPS_DST_NAT; //翻转映射位如果是应答方向, 源改为目的,目的改为源
if (dir == IP_CT_DIR_REPLY)
statusbit ^= IPS_NAT_MASK; //异或 相同为0 不同为1
//ct->status中NAT类型是在建立NAT信息的ip_nat_setup_info()函数中设置的
if (ct->status & statusbit) {
struct ip_conntrack_tuple target;
//根据当前数据的反方向tuple,获取转换后的地址端口的tuple信息到target中
//如果dir是原始方向那么得到的target是修改后的原始方向
invert_tuplepr(&target, &ct->tuplehash[!dir].tuple); //根据target中信息修改当前包中的信息
if (!manip_pkt(target.dst.protonum, pskb, , &target, mtype))
return NF_DROP;
}
}
static int manip_pkt(u_int16_t proto, struct sk_buff **pskb, unsigned int iphdroff, const struct ip_conntrack_tuple *target,
enum ip_nat_manip_type maniptype)
{
struct iphdr *iph;
struct ip_nat_protocol *p; //由于2.6.1*内核netfilter架构重组IP包后不进行线性化操作,所以不能直接用skb中的协议头获取各协议字段头信息,
//必须用skb_header_pointer()函数来获取.同样,在进行NAT操作时,对数据的修改也不能直接修改,必须采用新函数预先进行处理,使skb包可写
//这个函数就是实现此功能
if(!skb_make_writable(pskb, iphdroff + sizeof(*iph)))
return ;
//获取ip头
iph = (void *)(*pskb)->data + iphdroff;
//查找相关协议,看初始化时怎样注册的协议
p = ip_nat_proto_find_get(proto);
//调用协议函数处理数据包,看下面协议实现部分
if (!p->manip_pkt(pskb, iphdroff, target, maniptype)) {
ip_nat_proto_put(p);
return ;
}
ip_nat_proto_put(p); //根据NAT类型,基于新地址从新计算校验和,然后修改源或目的IP地址
if (maniptype == IP_NAT_MANIP_SRC) {
iph->check = ip_nat_cheat_check(~iph->saddr, target->src.ip, iph->check);
iph->saddr = target->src.ip;
} else {
iph->check = ip_nat_cheat_check(~iph->daddr, target->dst.ip, iph->check);
iph->daddr = target->dst.ip;
}
return ;
}
我们继续看NF_IP_POST_ROUTING的hook
static unsigned int ip_nat_out(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
...... //忽略IPSEC
unsigned int ret;
//检测原始数据包
if ((*pskb)->len < sizeof(struct iphdr) || (*pskb)->nh.iph->ihl * < sizeof(struct iphdr))
return NF_ACCEPT; ret = ip_nat_fn(hooknum, pskb, in, out, okfn); //已经看到过
......
return ret;
}
我们继续看NF_IP_POST_ROUTING的
static unsigned int ip_nat_adjust(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
//获取conntrack
ct = ip_conntrack_get(*pskb, &ctinfo);
if (ct && test_bit(IPS_SEQ_ADJUST_BIT, &ct->status)) {
//调整tcp序号,重新计算效验和等(忽略)
if (!ip_nat_seq_adjust(pskb, ct, ctinfo))
return NF_DROP;
}
return NF_ACCEPT;
}
NF_IP_LOCAL_OUT的hook
static unsigned int ip_nat_local_fn(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
unsigned int ret;
//处理原始数据包
if ((*pskb)->len < sizeof(struct iphdr) || (*pskb)->nh.iph->ihl * < sizeof(struct iphdr)) return NF_ACCEPT;
ret = ip_nat_fn(hooknum, pskb, in, out, okfn); //调用这最重要的函数 if (ret != NF_DROP && ret != NF_STOLEN && (ct = ip_conntrack_get(*pskb, &ctinfo)) != NULL) {
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
if (ct->tuplehash[dir].tuple.dst.ip != ct->tuplehash[!dir].tuple.src.ip) //目的地址进行了NAT
if (__ip_route_me_harder(pskb, RTN_UNSPEC)) //重新绑定输出包路由
ret = NF_DROP;
}
}
现在我们知道其最主要的核心函数就是ip_nat_fn,主要要把这个函数看懂.
=============================================================
[协议实现部分]
struct ip_nat_protocol ip_nat_protocol_tcp = {
.name = "TCP", //协议名称,字符串常量
.protonum = IPPROTO_TCP, //协议号
.me = THIS_MODULE,
.manip_pkt = tcp_manip_pkt, //修改协议相关数据,根据NAT规则来确定是修改源部分还是目的部分
.in_range = tcp_in_range, //判断数据包是否是要进行NAT修改
.unique_tuple = tcp_unique_tuple, //构造一个新tuple,处理将原tuple在进行NAT后对应的连接参数,
//如TCP源NAT时,除了源地址必须要修改外,一般还要修改源端口,
//这个连接的后续包的源端口就都改这个端口值,而修改后的这个端口值必须是唯一的,和
//这个连接绑定,其他连接就不能再使用这个端口,如果找不到合适的tuple值,NAT将失败,
//也就是说,对于多对一的NAT转换,理论上最多只能处理65535个TCP连接,
//超过此数的新的TCP连接就无法进行NAT了,对于 TCP和UDP,
//就是检测查找一个新的未用端口生成一个新的tuple结构对应该连接,
//对应ICMP,则是找一个未用的 ID值
#if defined(CONFIG_IP_NF_CONNTRACK_NETLINK) || defined(CONFIG_IP_NF_CONNTRACK_NETLINK_MODULE)
.range_to_nfattr = ip_nat_port_range_to_nfattr,
.nfattr_to_range = ip_nat_port_nfattr_to_range,
#endif
}; static int tcp_manip_pkt(struct sk_buff **pskb, unsigned int iphdroff,
const struct ip_conntrack_tuple *tuple, enum ip_nat_manip_type maniptype)
{
struct iphdr *iph = (struct iphdr *)((*pskb)->data + iphdroff);
struct tcphdr *hdr;
unsigned int hdroff = iphdroff + iph->ihl*; //tcp头位置
u32 oldip, newip;
u16 *portptr, newport, oldport;
int hdrsize = ; //skb包含了完整的tcp头
if ((*pskb)->len >= hdroff + sizeof(struct tcphdr))
hdrsize = sizeof(struct tcphdr); if (!skb_make_writable(pskb, hdroff + hdrsize)) //已经看到过
return ; iph = (struct iphdr *)((*pskb)->data + iphdroff);
hdr = (struct tcphdr *)((*pskb)->data + hdroff); if (maniptype == IP_NAT_MANIP_SRC) {
oldip = iph->saddr;
newip = tuple->src.ip;
newport = tuple->src.u.tcp.port;
portptr = &hdr->source;
} else {
oldip = iph->daddr;
newip = tuple->dst.ip;
newport = tuple->dst.u.tcp.port;
portptr = &hdr->dest;
}
//修改端口
oldport = *portptr;
*portptr = newport; if (hdrsize < sizeof(*hdr))
return ;
//更新校验和
hdr->check = ip_nat_cheat_check(~oldip, newip, ip_nat_cheat_check(oldport ^ 0xFFFF, newport, hdr->check));
}
static int tcp_in_range(const struct ip_conntrack_tuple *tuple, enum ip_nat_manip_type maniptype,
const union ip_conntrack_manip_proto *min, const union ip_conntrack_manip_proto *max)
{
u_int16_t port; if (maniptype == IP_NAT_MANIP_SRC)
port = tuple->src.u.tcp.port;
else
port = tuple->dst.u.tcp.port;
//在最大和最小之间
return ntohs(port) >= ntohs(min->tcp.port) && ntohs(port) <= ntohs(max->tcp.port);
}
static int tcp_unique_tuple(struct ip_conntrack_tuple *tuple, const struct ip_nat_range *range,
enum ip_nat_manip_type maniptype, const struct ip_conntrack *conntrack)
{
static u_int16_t port;
u_int16_t *portptr;
unsigned int range_size, min, i;
//指向相应的端口
if (maniptype == IP_NAT_MANIP_SRC)
portptr = &tuple->src.u.tcp.port;
else
portptr = &tuple->dst.u.tcp.port; //没有指定范围
if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)) {
if (maniptype == IP_NAT_MANIP_DST) //是目的NAT,不改变端口
return ; if (ntohs(*portptr) < ) { //端口小于1024
if (ntohs(*portptr) < ) { //小于512
min = ;
range_size = - min + ;
} else { //大于512
min = ;
range_size = - min + ;
}
} else { //大于1024
min = ;
range_size = - + ;
} } else { //指定了范围
min = ntohs(range->min.tcp.port);
range_size = ntohs(range->max.tcp.port) - min + ;
}
for (i = ; i < range_size; i++, port++) { //循环直到找到一个未使用的tuple
*portptr = htons(min + port % range_size); //取一个随机端口,在范围内的
if (!ip_nat_used_tuple(tuple, conntrack)) { 在ip_conntrack_hash全局表中查找相同的tuple
return ;
}
}
return ;
}
[/协议实现部分]
Linux网络地址转换分析的更多相关文章
-
linux c 网络编程:用域名获取IP地址或者用IP获取域名 网络地址转换成整型 主机字符顺序与网络字节顺序的转换
用域名获取IP地址或者用IP获取域名 #include<stdio.h> #include<sys/socket.h> #include<netdb.h> int ...
-
CCNA学习 NAT网络地址转换
CCNA基础 NAT网络地址转换 在计算机网络中,网络地址转换(Network Address Translation,缩写为NAT),也叫做网络掩蔽或者IP掩蔽(IP masquerading),是 ...
-
RHCE 系列(二):如何进行包过滤、网络地址转换和设置内核运行时参数
正如第一部分(“设置静态网络路由”)提到的,在这篇文章(RHCE 系列第二部分),我们首先介绍红帽企业版 Linux 7(RHEL)中包过滤和网络地址转换(NAT)的原理,然后再介绍在某些条件发生变化 ...
-
Linux input子系统分析
输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分 ...
-
TCP/IP 笔记 - 防火墙和网络地址转换
防火墙是位于内部网和外部网之间的屏障,是系统的第一套防线,作用是防止非法用户的进入. 网络地址转换是一种IP数据包通过路由器或防火墙时通过重写来源IP地址或目的地址的技术,可以用来隐藏或保护内部网络, ...
-
网络地址转换-NAT
网络地址转换-NAT 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.NAT组网和常用术语 私网:局域网内IP 公网:因特网的公网ip地址 NAT设备:就是讲私网地址转换为公网的 ...
-
第11章 拾遗1:网络地址转换(NAT)和端口映射
1. 网络地址转换(NAT) 1.1 NAT的应用场景 (1)应用场景:允许将私有IP地址映射到公网地址,以减缓IP地址空间的消耗 ①需要连接Internet,但主机没有公网IP地址 ②更换了一个新的 ...
-
linux系统瓶颈分析(精)
linux系统瓶颈分析(精) (2013-09-17 14:22:00) 分类: linux服务器瓶颈分析 1.0 性能监控介绍 性能优化就是找到系统处理中的瓶颈以及去除这些的过程,多数管理员相信 ...
-
linux用户进程分析
经过实验3的介绍.我们须要来点实在的.所以将我们理解的流程用于linux系统的分析.换句话说.通过类比的方式去进行描写叙述与理解linux相关的部分. 本节的内容非常详实.并且也分析 ...
随机推荐
-
web安全之文件上传漏洞
成因: 当文件上传时,若服务端脚本语言未对上传的文件进行严格验证和过滤,若恶意用户上传恶意的 脚本文件时,就有可能控制整个网站甚至是服务器,这就是文件上传漏洞. 权限: 1. 后台权限:登陆了后台,可 ...
-
IOS-时间与字符串互相转换
有时会遇到这种问题,须要把时间和时间戳互相转换 比方把"这种格式 或者是把""转换成"2014-07-16 15:54:36" 首先来第一个: 当前时 ...
-
编程实现任意长度整数的加法(整数可以长度超出C++中int范围)
#include <iostream> #include<string> using namespace std; string add(string s1,string s2 ...
-
Stitch Fix 融资1200万美元,又一个时尚创业的哈佛女MBA |华丽志
Stitch Fix 融资1200万美元,又一个时尚创业的哈佛女MBA |华丽志 Stitch Fix 融资1200万美元,又一个时尚创业的哈佛女MBA
-
Jsoup后台解析html、jsp网页
在一些网络爬虫或者从第三方网站抓取信息的程序都面临1个问题,如何从网页中把所需的信息提取出来,Jsoup是个比较好的选择,它能把网站内容解析成Document,再从document中取element就 ...
-
在ElasticSearch中使用 IK 中文分词插件
我这里集成好了一个自带IK的版本,下载即用, https://github.com/xlb378917466/elasticsearch5.2.include_IK 添加了IK插件意味着你可以使用ik ...
-
Tutorial 03_分布式数据库HBASE
(一)编程实现一下内容,并用Hadoop提供的Shell命令完成相同任务: 编程实现: (1)列出HBase所有表的相关信息,例如表名; package tutorial01; import java ...
-
【转】什么是.Net以及.Net的基本语法
什么是.Net? 1. 通常意义所说的.net有5个组成部分,但最主要的部分是.NET Framework, .NET Framework实际上是运行在Windows操作系统的一个应用程序,一个可供二 ...
-
[pytorch修改]dataloader.py 实现darknet中的subdivision功能
dataloader.py import random import torch import torch.multiprocessing as multiprocessing from torch. ...
-
IIS 修改并发连接数
http://www.cnblogs.com/dudumao/p/4078687.html