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