udp是一个很是简单的协议,链接跟踪对其处理很是简单。dom
const struct nf_conntrack_l4proto nf_conntrack_l4proto_udp4 = { .l3proto = PF_INET, .l4proto = IPPROTO_UDP, .allow_clash = true, .pkt_to_tuple = udp_pkt_to_tuple, .invert_tuple = udp_invert_tuple, .packet = udp_packet, .get_timeouts = udp_get_timeouts, .new = udp_new, .error = udp_error, #if IS_ENABLED(CONFIG_NF_CT_NETLINK) .tuple_to_nlattr = nf_ct_port_tuple_to_nlattr, .nlattr_to_tuple = nf_ct_port_nlattr_to_tuple, .nlattr_tuple_size = nf_ct_port_nlattr_tuple_size, .nla_policy = nf_ct_port_nla_policy, #endif #if IS_ENABLED(CONFIG_NF_CT_NETLINK_TIMEOUT) .ctnl_timeout = { .nlattr_to_obj = udp_timeout_nlattr_to_obj, .obj_to_nlattr = udp_timeout_obj_to_nlattr, .nlattr_max = CTA_TIMEOUT_UDP_MAX, .obj_size = sizeof(unsigned int) * CTA_TIMEOUT_UDP_MAX, .nla_policy = udp_timeout_nla_policy, }, #endif /* CONFIG_NF_CT_NETLINK_TIMEOUT */ .init_net = udp_init_net, .get_net_proto = udp_get_net_proto, };
第一个执行的是udp_error,该函数进行错误校验。函数
static int udp_error(struct net *net, struct nf_conn *tmpl, struct sk_buff *skb, unsigned int dataoff, u_int8_t pf, unsigned int hooknum) { unsigned int udplen = skb->len - dataoff; const struct udphdr *hdr; struct udphdr _hdr; /* Header is too small? 是否长度足够udp头的长度 */ hdr = skb_header_pointer(skb, dataoff, sizeof(_hdr), &_hdr); if (hdr == NULL) { udp_error_log(skb, net, pf, "short packet"); return -NF_ACCEPT; } /* Truncated/malformed packets 报文长度合法性校验 */ if (ntohs(hdr->len) > udplen || ntohs(hdr->len) < sizeof(*hdr)) { udp_error_log(skb, net, pf, "truncated/malformed packet"); return -NF_ACCEPT; } /* Packet with no checksum 没有校验码的话,直接返回 */ if (!hdr->check) return NF_ACCEPT; /* Checksum invalid? Ignore. * We skip checking packets on the outgoing path * because the checksum is assumed to be correct. * FIXME: Source route IP option packets --RR */ if (net->ct.sysctl_checksum && hooknum == NF_INET_PRE_ROUTING && nf_checksum(skb, hooknum, dataoff, IPPROTO_UDP, pf)) { udp_error_log(skb, net, pf, "bad checksum"); return -NF_ACCEPT; } return NF_ACCEPT; }
提取传输层tuple内容。this
static bool udp_pkt_to_tuple(const struct sk_buff *skb, unsigned int dataoff, struct net *net, struct nf_conntrack_tuple *tuple) { const struct udphdr *hp; struct udphdr _hdr; /* Actually only need first 4 bytes to get ports. */ hp = skb_header_pointer(skb, dataoff, 4, &_hdr); if (hp == NULL) return false; tuple->src.u.udp.port = hp->source; tuple->dst.u.udp.port = hp->dest; return true; }
根据当前报文的tuple信息获取传输层的反向tuple信息。简单的源目端口的对调。code
static bool udp_invert_tuple(struct nf_conntrack_tuple *tuple, const struct nf_conntrack_tuple *orig) { tuple->src.u.udp.port = orig->dst.u.udp.port; tuple->dst.u.udp.port = orig->src.u.udp.port; return true; }
请求方向首包检查,因为udp很是简单,直接返回true。orm
/* Called when a new connection for this protocol found. */ static bool udp_new(struct nf_conn *ct, const struct sk_buff *skb, unsigned int dataoff, unsigned int *timeouts) { return true; }
后续报文检查,udp很是简单,这里只是进行超时时间更新与报文统计更新。事件
/* Returns verdict for packet, and may modify conntracktype */ static int udp_packet(struct nf_conn *ct, const struct sk_buff *skb, unsigned int dataoff, enum ip_conntrack_info ctinfo, unsigned int *timeouts) { /* If we've seen traffic both ways, this is some kind of UDP stream. Extend timeout. * 刷新链接超时时间,已经更新报文统计 */ if (test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) { nf_ct_refresh_acct(ct, ctinfo, skb, timeouts[UDP_CT_REPLIED]); /* Also, more likely to be important, and not a probe */ /* 一旦双向报文已经遇到了,则设置ASSURED标志,表示不是一个探测报文 ** 发送一个ASSURD事件 */ if (!test_and_set_bit(IPS_ASSURED_BIT, &ct->status)) nf_conntrack_event_cache(IPCT_ASSURED, ct); } else { nf_ct_refresh_acct(ct, ctinfo, skb, timeouts[UDP_CT_UNREPLIED]); } return NF_ACCEPT; }
超时时间获取。ip
static const unsigned int udp_timeouts[UDP_CT_MAX] = { [UDP_CT_UNREPLIED] = 30*HZ, [UDP_CT_REPLIED] = 180*HZ, }; struct nf_udp_net { struct nf_proto_net pn; unsigned int timeouts[UDP_CT_MAX]; }; static unsigned int *udp_get_timeouts(struct net *net) { return udp_pernet(net)->timeouts; }
const struct nf_nat_l4proto nf_nat_l4proto_udp = { .l4proto = IPPROTO_UDP, .manip_pkt = udp_manip_pkt, .in_range = nf_nat_l4proto_in_range, .unique_tuple = udp_unique_tuple, #if IS_ENABLED(CONFIG_NF_CT_NETLINK) .nlattr_to_range = nf_nat_l4proto_nlattr_to_range, #endif };
是一个传输层通用函数,判断源或者目的端口是否在指定的range。ci
bool nf_nat_l4proto_in_range(const struct nf_conntrack_tuple *tuple, enum nf_nat_manip_type maniptype, const union nf_conntrack_man_proto *min, const union nf_conntrack_man_proto *max) { __be16 port; if (maniptype == NF_NAT_MANIP_SRC) port = tuple->src.u.all; else port = tuple->dst.u.all; return ntohs(port) >= ntohs(min->all) && ntohs(port) <= ntohs(max->all); } EXPORT_SYMBOL_GPL(nf_nat_l4proto_in_range);
从指定范围获取一个端口,使得tuple惟一。get
/* 若是没有指定范围,DNAT时目的端口不能改变,SNAT时源端口能够改变 端口的变化范围有几个限制,端口是512之内的映射范围是1-512,端口 是512-1024的映射范围是600-1024,1024以上的映射范围就是1024以上 若是指定了端口的变化范围,那就按照指定的来 若是是NF_NAT_RANGE_PROTO_RANDOM模式的话,调用L3的secure_port, 根据源目的IP和须要修改的端口计算一个hash值。 若是是NF_NAT_RANGE_PROTO_RANDOM_FULLY模式的话,直接计算随机数 根据获得的值根据范围取余,再加上最小值就获得的端口,而后断定是否已用, 用了的话加1再断定。 */ void nf_nat_l4proto_unique_tuple(const struct nf_nat_l3proto *l3proto, struct nf_conntrack_tuple *tuple, const struct nf_nat_range *range, enum nf_nat_manip_type maniptype, const struct nf_conn *ct, u16 *rover) { unsigned int range_size, min, max, i; __be16 *portptr; u_int16_t off; if (maniptype == NF_NAT_MANIP_SRC) portptr = &tuple->src.u.all; else portptr = &tuple->dst.u.all; /* If no range specified... 判断是否指定了具体的端口范围 */ if (!(range->flags & NF_NAT_RANGE_PROTO_SPECIFIED)) {/* 没有指定具体端口范围的话 */ /* If it's dst rewrite, can't change port 目的nat不改变端口 */ if (maniptype == NF_NAT_MANIP_DST) return; /* 源端口为保留端口,则须要保证nat后的源端口也为保留端口 */ if (ntohs(*portptr) < 1024) { /* Loose convention: >> 512 is credential passing */ /* 源端口小于512,那么在1-511之间进行选择 */ if (ntohs(*portptr) < 512) { min = 1; range_size = 511 - min + 1; } else { /* 大于512,则在600到1024之间进行选择 */ min = 600; range_size = 1023 - min + 1; } } else {//非保留端口则在1024到65536之间进行选择 min = 1024; range_size = 65535 - 1024 + 1; } } else {//指定了具体端口范围 min = ntohs(range->min_proto.all); max = ntohs(range->max_proto.all); if (unlikely(max < min)) swap(max, min); range_size = max - min + 1; } if (range->flags & NF_NAT_RANGE_PROTO_RANDOM) { off = l3proto->secure_port(tuple, maniptype == NF_NAT_MANIP_SRC ? tuple->dst.u.all : tuple->src.u.all); } else if (range->flags & NF_NAT_RANGE_PROTO_RANDOM_FULLY) { off = prandom_u32(); } else { off = *rover; } for (i = 0; ; ++off) { *portptr = htons(min + off % range_size); /* 端口已经被使用,则加1进行尝试,直到知足要求或者全部状况都应遍历完 */ if (++i != range_size && nf_nat_used_tuple(tuple, ct)) continue; /* 若是没有设置随机的话,设置当前选用的端口号 */ if (!(range->flags & NF_NAT_RANGE_PROTO_RANDOM_ALL)) *rover = off; return; } }
进行传输层的nat转换。hash
static bool udp_manip_pkt(struct sk_buff *skb, const struct nf_nat_l3proto *l3proto, unsigned int iphdroff, unsigned int hdroff, const struct nf_conntrack_tuple *tuple, enum nf_nat_manip_type maniptype) { struct udphdr *hdr; bool do_csum; if (!skb_make_writable(skb, hdroff + sizeof(*hdr))) return false; hdr = (struct udphdr *)(skb->data + hdroff); do_csum = hdr->check || skb->ip_summed == CHECKSUM_PARTIAL; __udp_manip_pkt(skb, l3proto, iphdroff, hdr, tuple, maniptype, do_csum); return true; } static void __udp_manip_pkt(struct sk_buff *skb, const struct nf_nat_l3proto *l3proto, unsigned int iphdroff, struct udphdr *hdr, const struct nf_conntrack_tuple *tuple, enum nf_nat_manip_type maniptype, bool do_csum) { __be16 *portptr, newport; if (maniptype == NF_NAT_MANIP_SRC) { /* Get rid of src port */ newport = tuple->src.u.udp.port; portptr = &hdr->source; } else { /* Get rid of dst port */ newport = tuple->dst.u.udp.port; portptr = &hdr->dest; } if (do_csum) { l3proto->csum_update(skb, iphdroff, &hdr->check, tuple, maniptype); inet_proto_csum_replace2(&hdr->check, skb, *portptr, newport, false); if (!hdr->check) hdr->check = CSUM_MANGLED_0; } *portptr = newport; }