鏈接跟蹤之UDP

udp是一個很是簡單的協議,鏈接跟蹤對其處理很是簡單。dom

UDP鏈接跟蹤控制塊

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

第一個執行的是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;
}

udp_pkt_to_tuple

提取傳輸層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;
}

udp_invert_tuple

根據當前報文的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_new

請求方向首包檢查,因爲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_packet

後續報文檢查,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;
}

udp_get_timeouts

超時時間獲取。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;
}

UDP nat控制塊

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

nf_nat_l4proto_in_range

是一個傳輸層通用函數,判斷源或者目的端口是否在指定的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);

udp_unique_tuple

從指定範圍獲取一個端口,使得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;
    }
}

udp_manip_pkt

進行傳輸層的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;
}
相關文章
相關標籤/搜索