searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

netfilter之synproxy

2023-07-19 07:37:51
21
0

1、原理:

通过synproxy,由netfilter来代理客户端的三次握手过程;优点是netfilter的连接跟踪表可以等到三次握手完成后才创建,而不是收到客户端的syn后就立即创建;可以减少syn flood攻击导致的性能开销;

 

2、规则:

1)、iptables -t raw -A PREROUTING -I eth0 -p tcp --dport 80 --syn -j NOTRACK    ---- 对eth0收到的sync报文标记notrack

2)、iptables -A INPUT -I eth0 - ptcp --dport 80 -mstate --state UNTRACKED,INVALID -j SYNPROXY--sack-perm--timestamp--mss1480--wscale7--ecn  对eth0收到的untrack的包,将其导向synproxy模块处理;

3)、echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose  ----- 如果没有这条,client的第三个ack回来时会被丢弃

3、过程

3.1、收到客户端第一个sync报文

1)、首先上面的第一条iptable规则将报文设置notrack标记;

2)、报文进入input流程时,由上面的第二条iptable规则将其做synproxy处理;

 

ipt_do_table

synproxy_tg4

synproxy_parse_options(先解析tcp option

synproxy_send_client_synack(分配报文,初始化ip头部、tcp头部等信息)

synproxy_build_options(填充tcp option

synproxy_send_tcp(回复客户端)

3.2 收到客户端的3ardack

由于上面收到客户端第一个syn包的时候,没有创建连接跟踪;因此这里收到客户端的3ard ack时,内核会先创建一个连接跟踪;由于配置了echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose;因此这个3ardacktcp_new的时候可以通过;但是此时tcp_new会返回-NF_ACCEPT;这样nf_conntrack_handle_packet处理完成后会执行nf_conntrack_put释放新创建的连接跟踪;也就是说这时候skbct状态为INVALID;因此这个报文也会匹配上面添加的第二条iptable规则;进入synproxy处理流程里;

 

ipt_do_table

synproxy_tg4

synproxy_recv_client_ack(收到客户端的3ard ack

synproxy_send_server_syn(由synproxyserver发送syn

synproxy_send_tcp

ip_local_out(这里面由于目的ip是本机ip,因此最终走的是lo口的发送流程)

loopback_xmit

netif_rx

 

由于iptable规则里并没有对lo口的syn报文做notrack处理;因此synproxy通过lo口向server发送的syn报文按正常的创建连接跟踪的逻辑处理;然后执行的netfilterLOCAL_INchain的时候,进入ipv4_synproxy_hook处理流程;

针对synproxy发送给server的第一个报文,此时的ct状态为TCP_CONNTRACK_SYN_SENT,在synproxy里针对TCP_CONNTRACK_SYN_SENT状态没有做什么特殊处理,最终这个syn报文发给了server;然后由server回复syn ack

 

3.3、收到server回复的syn ack

server回复的syn ack在报文走到POST_ROUTING的时候,触发ipv4_synproxy_hookkook函数;此时连接跟踪的状态已经变成TCP_CONNTRACK_SYN_RECV

ipv4_synproxy_hook

ct状态为TCP_CONNTRACK_SYN_RECV,即收到server回复的syn ack的处理逻辑:

synproxy_parse_options

synproxy_send_server_acksynproxyserver 回复3ard ack

synproxy_send_client_acksynproxyclient回复ack ,通告client接收窗口更新)

 

4、其它:

1、synproxy如何保证client回复3ard ack收,不会立即向server请求数据?  因为当client回复3ard ack后,synproxy还需要进一步与server建立三次握手过程;如果与server的三次握手还没建立完成就收到clinet的正常请求,那连接会出现异常;

1)、 当synproxy收到client的syn报文时,会回复一个synack,这个synack里面,会将接收窗口设置为0,这样clinet在收到synproxy回复的synack后,由于server端接收窗口的限制,只会回复3ardack,并不会发送其它的请求数据;

static void
synproxy_send_client_synack(struct net *net,
			    const struct sk_buff *skb, const struct tcphdr *th,
			    const struct synproxy_options *opts)
{
	struct sk_buff *nskb;
	struct iphdr *iph, *niph;
	struct tcphdr *nth;
	unsigned int tcp_hdr_size;
	u16 mss = opts->mss_encode;

	iph = ip_hdr(skb);

	tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
	nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
			 GFP_ATOMIC);
	if (nskb == NULL)
		return;
	skb_reserve(nskb, MAX_TCP_HEADER);

	niph = synproxy_build_ip(net, nskb, iph->daddr, iph->saddr);

	skb_reset_transport_header(nskb);
	nth = skb_put(nskb, tcp_hdr_size);
	nth->source	= th->dest;
	nth->dest	= th->source;
	nth->seq	= htonl(__cookie_v4_init_sequence(iph, th, &mss));
	nth->ack_seq	= htonl(ntohl(th->seq) + 1);
	tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK;
	if (opts->options & XT_SYNPROXY_OPT_ECN)
		tcp_flag_word(nth) |= TCP_FLAG_ECE;
	nth->doff	= tcp_hdr_size / 4;
	// 这里synproxy给client回复synack时,将接收创建设置为0,这样可以保证client在回复
	// 3ardack后不会继续向server请求数据;因为client回复3ardack后,synproxy还需要进一步
	// 和server建立三次握手才能保证client和server之间可以正常通信;
	// synproxy在与server建立三次握手的过程中,当收到server的synack后,处理向server
	// 回复3ardack外,synproxy还会向client发送一个正常的ack报文,这个报文相当于是一个通过
	// 接收窗口的报文.
	nth->window	= 0;
	nth->check	= 0;
	nth->urg_ptr	= 0;

	synproxy_build_options(nth, opts);

	synproxy_send_tcp(net, skb, nskb, skb_nfct(skb),
			  IP_CT_ESTABLISHED_REPLY, niph, nth, tcp_hdr_size);
}

2)、当synproxy与server建立三次握手时,收到server的synack时,synproxy除了向server回复3ardack外,还会向client发送一个正常的ack报文,并且填充好正确的接收窗口,相当于这个ack报文是server端的一个窗口更新通知;这样client收到这个报文后就可以继续向server发送请求数据了;

 

2、当存在synproxy时,存在两次的三次握手情况,即synproxy和client之间以及synproxy和server之间,synproxy回复clinet synack的时候,synproxy会通过__cookie_v4_init_sequence生成一个序列号;synproxy和server之间建立三次握手的时候,server回复synack时也会生成一个序列号;那么后面client和server之间通信的时候,如何保证序列号一致性?

1)、当synproxy收到client的3ardack后,会向server发起syn请求;同时将之前与client交互时使用的序列号保存到该syn包的ack_seq里;

2)、该syn包经过lo口发送给server,并在prerouting的conntrack阶段创建新的ct连接跟踪;在创建连接跟踪的时候,通过nf_ct_add_synproxy为连接跟踪添加synproxy;

3)、syn请求在经过LOCAL_IN的hook点时,会触发ipv4_synproxy_hook的hook函数;在ipv4_synproxy_hook里,首先从ct里获取synproxy,然后将ack_seq作为synproxy的isn序列号保存下来;

4)、当server向synproxy回复的synack时,报文经过postrouting时,触发ipv4_synproxy_hook,在ipv4_synproxy_hook里,snyproxy计算isn序列号与server端当前使用的seq号的差异,然后通过nf_ct_seqadj_init将这两个序列号的差异记录下来,同时对ct->status设置IPS_SEQ_ADJUST_BIT标志位;

int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
		      s32 off)
{
	// synproxy的场景,这里是synproxy收到server的synack时候初始化的;因此这里的dir是reply
	// 方向
	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
	struct nf_conn_seqadj *seqadj;
	struct nf_ct_seqadj *this_way;

	if (off == 0)
		return 0;

	set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);

	// offset为synproxy、clinet交互使用的序列号与synproxy、server交互使用的序列号差值;
	// 后面server发给clinet的时候,将tcp->seq的值加上这个offset;
	// client发给server的时候,将tcp->ack_seq的值加上这个offset;
	seqadj = nfct_seqadj(ct);
	this_way = &seqadj->seq[dir];
	this_way->offset_before	 = off;
	this_way->offset_after	 = off;
	return 0;
}

5)、synproxy向client回复ack,通知client窗口更新,这个报文通过ip_local_out发送出去,因此会重新经历走一遍LOCAL_OUT、postrouting流程,在ipv4_conntrack_local的tcp_in_window流程里,通过nf_ct_seq_offset获取序列号的offset值,然后根据结合这个offset与当前的序列号判断序列号是否正常(这里只是判断序列号是否在窗口范围内,并没有去修改报文的序列号);

6)、报文走到postrouting的时候,进入ipv4_confirm流程;在ipv4_confirm里判断ct->state设置了IPS_SEQ_ADJUST_BIT标志,进入nf_ct_seq_adjust,根据ct记录的offset值修改报文的seq序列号;同样的道理当client发给server的时候,也会进入到这个流程,然后修改tcpack_seq;这样clientserver之间的序列号就能正常对的上了;

/* TCP sequence number adjustment.  Returns 1 on success, 0 on failure */
int nf_ct_seq_adjust(struct sk_buff *skb,
		     struct nf_conn *ct, enum ip_conntrack_info ctinfo,
		     unsigned int protoff)
{
	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
	struct tcphdr *tcph;
	__be32 newseq, newack;
	s32 seqoff, ackoff;
	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
	struct nf_ct_seqadj *this_way, *other_way;
	int res = 1;

	// nf_ct_seqadj_init的时候,将offset记录在reply方向上;
	// 当server发给clinet的时候,进来这里的dir为reply方向;因此这里是this_way里才有offset
	// 的值,然后根据this_way的值修改tcph->seq的值;
	// 当client发给server的时候,进来这里的dir为origin方向;因此这里other_way里才有offset
	// 的值,然后根据oterh_way的值修改tcph->ack_seq;
	this_way  = &seqadj->seq[dir];
	other_way = &seqadj->seq[!dir];

	if (!skb_make_writable(skb, protoff + sizeof(*tcph)))
		return 0;

	tcph = (void *)skb->data + protoff;
	spin_lock_bh(&ct->lock);
	if (after(ntohl(tcph->seq), this_way->correction_pos))
		seqoff = this_way->offset_after;
	else
		seqoff = this_way->offset_before;

	newseq = htonl(ntohl(tcph->seq) + seqoff);
	inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
	pr_debug("Adjusting sequence number from %u->%u\n",
		 ntohl(tcph->seq), ntohl(newseq));
	tcph->seq = newseq;

	if (!tcph->ack)
		goto out;

	if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
		  other_way->correction_pos))
		ackoff = other_way->offset_after;
	else
		ackoff = other_way->offset_before;

	newack = htonl(ntohl(tcph->ack_seq) - ackoff);
	inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
				 false);
	pr_debug("Adjusting ack number from %u->%u, ack from %u->%u\n",
		 ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
		 ntohl(newack));
	tcph->ack_seq = newack;

	res = nf_ct_sack_adjust(skb, protoff, ct, ctinfo);
out:
	spin_unlock_bh(&ct->lock);

	return res;
}
0条评论
0 / 1000
郑****勇
4文章数
0粉丝数
郑****勇
4 文章 | 0 粉丝
郑****勇
4文章数
0粉丝数
郑****勇
4 文章 | 0 粉丝
原创

netfilter之synproxy

2023-07-19 07:37:51
21
0

1、原理:

通过synproxy,由netfilter来代理客户端的三次握手过程;优点是netfilter的连接跟踪表可以等到三次握手完成后才创建,而不是收到客户端的syn后就立即创建;可以减少syn flood攻击导致的性能开销;

 

2、规则:

1)、iptables -t raw -A PREROUTING -I eth0 -p tcp --dport 80 --syn -j NOTRACK    ---- 对eth0收到的sync报文标记notrack

2)、iptables -A INPUT -I eth0 - ptcp --dport 80 -mstate --state UNTRACKED,INVALID -j SYNPROXY--sack-perm--timestamp--mss1480--wscale7--ecn  对eth0收到的untrack的包,将其导向synproxy模块处理;

3)、echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose  ----- 如果没有这条,client的第三个ack回来时会被丢弃

3、过程

3.1、收到客户端第一个sync报文

1)、首先上面的第一条iptable规则将报文设置notrack标记;

2)、报文进入input流程时,由上面的第二条iptable规则将其做synproxy处理;

 

ipt_do_table

synproxy_tg4

synproxy_parse_options(先解析tcp option

synproxy_send_client_synack(分配报文,初始化ip头部、tcp头部等信息)

synproxy_build_options(填充tcp option

synproxy_send_tcp(回复客户端)

3.2 收到客户端的3ardack

由于上面收到客户端第一个syn包的时候,没有创建连接跟踪;因此这里收到客户端的3ard ack时,内核会先创建一个连接跟踪;由于配置了echo 0 > /proc/sys/net/netfilter/nf_conntrack_tcp_loose;因此这个3ardacktcp_new的时候可以通过;但是此时tcp_new会返回-NF_ACCEPT;这样nf_conntrack_handle_packet处理完成后会执行nf_conntrack_put释放新创建的连接跟踪;也就是说这时候skbct状态为INVALID;因此这个报文也会匹配上面添加的第二条iptable规则;进入synproxy处理流程里;

 

ipt_do_table

synproxy_tg4

synproxy_recv_client_ack(收到客户端的3ard ack

synproxy_send_server_syn(由synproxyserver发送syn

synproxy_send_tcp

ip_local_out(这里面由于目的ip是本机ip,因此最终走的是lo口的发送流程)

loopback_xmit

netif_rx

 

由于iptable规则里并没有对lo口的syn报文做notrack处理;因此synproxy通过lo口向server发送的syn报文按正常的创建连接跟踪的逻辑处理;然后执行的netfilterLOCAL_INchain的时候,进入ipv4_synproxy_hook处理流程;

针对synproxy发送给server的第一个报文,此时的ct状态为TCP_CONNTRACK_SYN_SENT,在synproxy里针对TCP_CONNTRACK_SYN_SENT状态没有做什么特殊处理,最终这个syn报文发给了server;然后由server回复syn ack

 

3.3、收到server回复的syn ack

server回复的syn ack在报文走到POST_ROUTING的时候,触发ipv4_synproxy_hookkook函数;此时连接跟踪的状态已经变成TCP_CONNTRACK_SYN_RECV

ipv4_synproxy_hook

ct状态为TCP_CONNTRACK_SYN_RECV,即收到server回复的syn ack的处理逻辑:

synproxy_parse_options

synproxy_send_server_acksynproxyserver 回复3ard ack

synproxy_send_client_acksynproxyclient回复ack ,通告client接收窗口更新)

 

4、其它:

1、synproxy如何保证client回复3ard ack收,不会立即向server请求数据?  因为当client回复3ard ack后,synproxy还需要进一步与server建立三次握手过程;如果与server的三次握手还没建立完成就收到clinet的正常请求,那连接会出现异常;

1)、 当synproxy收到client的syn报文时,会回复一个synack,这个synack里面,会将接收窗口设置为0,这样clinet在收到synproxy回复的synack后,由于server端接收窗口的限制,只会回复3ardack,并不会发送其它的请求数据;

static void
synproxy_send_client_synack(struct net *net,
			    const struct sk_buff *skb, const struct tcphdr *th,
			    const struct synproxy_options *opts)
{
	struct sk_buff *nskb;
	struct iphdr *iph, *niph;
	struct tcphdr *nth;
	unsigned int tcp_hdr_size;
	u16 mss = opts->mss_encode;

	iph = ip_hdr(skb);

	tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
	nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
			 GFP_ATOMIC);
	if (nskb == NULL)
		return;
	skb_reserve(nskb, MAX_TCP_HEADER);

	niph = synproxy_build_ip(net, nskb, iph->daddr, iph->saddr);

	skb_reset_transport_header(nskb);
	nth = skb_put(nskb, tcp_hdr_size);
	nth->source	= th->dest;
	nth->dest	= th->source;
	nth->seq	= htonl(__cookie_v4_init_sequence(iph, th, &mss));
	nth->ack_seq	= htonl(ntohl(th->seq) + 1);
	tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK;
	if (opts->options & XT_SYNPROXY_OPT_ECN)
		tcp_flag_word(nth) |= TCP_FLAG_ECE;
	nth->doff	= tcp_hdr_size / 4;
	// 这里synproxy给client回复synack时,将接收创建设置为0,这样可以保证client在回复
	// 3ardack后不会继续向server请求数据;因为client回复3ardack后,synproxy还需要进一步
	// 和server建立三次握手才能保证client和server之间可以正常通信;
	// synproxy在与server建立三次握手的过程中,当收到server的synack后,处理向server
	// 回复3ardack外,synproxy还会向client发送一个正常的ack报文,这个报文相当于是一个通过
	// 接收窗口的报文.
	nth->window	= 0;
	nth->check	= 0;
	nth->urg_ptr	= 0;

	synproxy_build_options(nth, opts);

	synproxy_send_tcp(net, skb, nskb, skb_nfct(skb),
			  IP_CT_ESTABLISHED_REPLY, niph, nth, tcp_hdr_size);
}

2)、当synproxy与server建立三次握手时,收到server的synack时,synproxy除了向server回复3ardack外,还会向client发送一个正常的ack报文,并且填充好正确的接收窗口,相当于这个ack报文是server端的一个窗口更新通知;这样client收到这个报文后就可以继续向server发送请求数据了;

 

2、当存在synproxy时,存在两次的三次握手情况,即synproxy和client之间以及synproxy和server之间,synproxy回复clinet synack的时候,synproxy会通过__cookie_v4_init_sequence生成一个序列号;synproxy和server之间建立三次握手的时候,server回复synack时也会生成一个序列号;那么后面client和server之间通信的时候,如何保证序列号一致性?

1)、当synproxy收到client的3ardack后,会向server发起syn请求;同时将之前与client交互时使用的序列号保存到该syn包的ack_seq里;

2)、该syn包经过lo口发送给server,并在prerouting的conntrack阶段创建新的ct连接跟踪;在创建连接跟踪的时候,通过nf_ct_add_synproxy为连接跟踪添加synproxy;

3)、syn请求在经过LOCAL_IN的hook点时,会触发ipv4_synproxy_hook的hook函数;在ipv4_synproxy_hook里,首先从ct里获取synproxy,然后将ack_seq作为synproxy的isn序列号保存下来;

4)、当server向synproxy回复的synack时,报文经过postrouting时,触发ipv4_synproxy_hook,在ipv4_synproxy_hook里,snyproxy计算isn序列号与server端当前使用的seq号的差异,然后通过nf_ct_seqadj_init将这两个序列号的差异记录下来,同时对ct->status设置IPS_SEQ_ADJUST_BIT标志位;

int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
		      s32 off)
{
	// synproxy的场景,这里是synproxy收到server的synack时候初始化的;因此这里的dir是reply
	// 方向
	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
	struct nf_conn_seqadj *seqadj;
	struct nf_ct_seqadj *this_way;

	if (off == 0)
		return 0;

	set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);

	// offset为synproxy、clinet交互使用的序列号与synproxy、server交互使用的序列号差值;
	// 后面server发给clinet的时候,将tcp->seq的值加上这个offset;
	// client发给server的时候,将tcp->ack_seq的值加上这个offset;
	seqadj = nfct_seqadj(ct);
	this_way = &seqadj->seq[dir];
	this_way->offset_before	 = off;
	this_way->offset_after	 = off;
	return 0;
}

5)、synproxy向client回复ack,通知client窗口更新,这个报文通过ip_local_out发送出去,因此会重新经历走一遍LOCAL_OUT、postrouting流程,在ipv4_conntrack_local的tcp_in_window流程里,通过nf_ct_seq_offset获取序列号的offset值,然后根据结合这个offset与当前的序列号判断序列号是否正常(这里只是判断序列号是否在窗口范围内,并没有去修改报文的序列号);

6)、报文走到postrouting的时候,进入ipv4_confirm流程;在ipv4_confirm里判断ct->state设置了IPS_SEQ_ADJUST_BIT标志,进入nf_ct_seq_adjust,根据ct记录的offset值修改报文的seq序列号;同样的道理当client发给server的时候,也会进入到这个流程,然后修改tcpack_seq;这样clientserver之间的序列号就能正常对的上了;

/* TCP sequence number adjustment.  Returns 1 on success, 0 on failure */
int nf_ct_seq_adjust(struct sk_buff *skb,
		     struct nf_conn *ct, enum ip_conntrack_info ctinfo,
		     unsigned int protoff)
{
	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
	struct tcphdr *tcph;
	__be32 newseq, newack;
	s32 seqoff, ackoff;
	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
	struct nf_ct_seqadj *this_way, *other_way;
	int res = 1;

	// nf_ct_seqadj_init的时候,将offset记录在reply方向上;
	// 当server发给clinet的时候,进来这里的dir为reply方向;因此这里是this_way里才有offset
	// 的值,然后根据this_way的值修改tcph->seq的值;
	// 当client发给server的时候,进来这里的dir为origin方向;因此这里other_way里才有offset
	// 的值,然后根据oterh_way的值修改tcph->ack_seq;
	this_way  = &seqadj->seq[dir];
	other_way = &seqadj->seq[!dir];

	if (!skb_make_writable(skb, protoff + sizeof(*tcph)))
		return 0;

	tcph = (void *)skb->data + protoff;
	spin_lock_bh(&ct->lock);
	if (after(ntohl(tcph->seq), this_way->correction_pos))
		seqoff = this_way->offset_after;
	else
		seqoff = this_way->offset_before;

	newseq = htonl(ntohl(tcph->seq) + seqoff);
	inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
	pr_debug("Adjusting sequence number from %u->%u\n",
		 ntohl(tcph->seq), ntohl(newseq));
	tcph->seq = newseq;

	if (!tcph->ack)
		goto out;

	if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
		  other_way->correction_pos))
		ackoff = other_way->offset_after;
	else
		ackoff = other_way->offset_before;

	newack = htonl(ntohl(tcph->ack_seq) - ackoff);
	inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
				 false);
	pr_debug("Adjusting ack number from %u->%u, ack from %u->%u\n",
		 ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
		 ntohl(newack));
	tcph->ack_seq = newack;

	res = nf_ct_sack_adjust(skb, protoff, ct, ctinfo);
out:
	spin_unlock_bh(&ct->lock);

	return res;
}
文章来自个人专栏
内核 && 网络
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0