连接跟踪之UDP

udp是一个非常简单的协议,连接跟踪对其处理非常简单。

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内容。

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信息。简单的源目端口的对调。

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。

/* 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

超时时间获取。

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。

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唯一。

/*
如果没有指定范围,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转换。

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

你可能感兴趣的:(ubuntu,linux,c,c++)