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; }