Skip to contentSkip to conversation

Netfilter: Call NAT Helper Modules Directly from Conntrack Modules, fixup FTP

PATCH In review Rusty Russell 2 participants 3 messages Jan 11, 2005

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 2/8 Netfilter: Call NAT Helper Modules Directly from Conntrack Modules, fixup FTP

Name: Call NAT Helper Modules Directly from Conntrack Modules, fixup FTP

Status: Tested under nfsim

Currently connection tracking and NAT helper modules for a protocol interact only indirectly (the conntrack module places information in the conntrack structure, which the NAT module pulls out).

This leads to several issues:
1) Both modules must know what port to watch, and must match.
2) Identifying the particular packet which created the connection is cumbersome (TCP) or impossible (UDP).
3) The connection tracking code sets up an expectation which the NAT code then has to change.
4) The lack of direct symbol dependencies means we have to contrive one, since they are functionally dependent.

Here is the current code flow:
FTP CONTROL PACKET:
NF_IP_PRE_ROUTING:
ip_conntrack_in

      resolve_normal_ct
         init_conntrack: sets ct->helper to ip_conntrack_ftp.c:help()

ct->help(): if PORT/PASV command:
Sets exp->help.exp_ftp_info to tcp seq number of data. ip_conntrack_expect(): expects the connection

ip_nat_setup_info: sets ct->nat.info->helper to ip_nat_ftp.c:help() ip_nat_fn:
proto->exp_matches_pkt: if packet matches expectation ct->nat.info->helper(): If packet going client->server,
and packet data is one in ct_ftp_info:
ftp_data_fixup():
ip_conntrack_change_expect(): change the expectation Modify packet contents with new address.

NF_IP_POST_ROUTING:
ip_nat_fn
ct->nat.info->helper(): If packet going server->client,
and packet data is one in ct_ftp_info:
ftp_data_fixup():
ip_conntrack_change_expect(): change the expectation Modify packet contents with new address.

FTP DATA (EXPECTED) CONNECTION FIRST PACKET:
NF_IP_PRE_ROUTING:
ip_conntrack_in

      resolve_normal_ct
         init_conntrack: set ct->master.

ip_nat_fn:
master->nat.info.helper->expect()
Set up source NAT mapping to match FTP control connection.

NF_IP_PRE_ROUTING:
ip_nat_fn:
master->nat.info.helper->expect()
Set up dest NAT mapping to match FTP control connection.

The new flow looks like this:
FTP CONTROL PACKET:
NF_IP_PRE_ROUTING:
ip_conntrack_in

      resolve_normal_ct
         init_conntrack: sets ct->helper to ip_conntrack_ftp.c:help()

NF_IP_POST_ROUTING:
ip_confirm:

      ct->helper->help:
         If !ip_nat_ftp_hook: ip_conntrack_expect().
         ip_nat_ftp: 
            set exp->oldproto to old port.
            ip_conntrack_change_expect(): change the expectation
            set exp->expectfn to ftp_nat_expected.
            Modify packet contents with new address.

FTP DATA (EXPECTED) CONNECTION FIRST PACKET:
NF_IP_PRE_ROUTING:
ip_conntrack_in

      resolve_normal_ct
         init_conntrack: set ct->master.
         call exp->expectfn (ftp_nat_expected):
             call ip_nat_follow_master().

The big changes are that the ip_nat_ftp module sets ip_conntrack_ftp's ip_nat_ftp_hook when it initializes, so it calls the NAT code directly when a packet containing the expect information is found by the conntrack helper: and this interface can carry all the information these two want to share. Also, that conntrack helper is called as the packet leaves the box, so there are no issues with expectations being set up before the packet has been filtered. The NAT helper doesn't need to register and duplicate the conntrack ports.

The other trick is ip_nat_follow_master(), which does the NAT setup all at once (source and destination NAT as required) such that the expected connection is NATed the same way the master connection was.

We also call ip_conntrack_tcp_update() (which I incidentally neatened) after mangling a TCP packet; ip_nat_seq_adjust() does this, but now mangling is done at the last possible moment, after ip_nat_seq_adjust() was already called.

net/ipv4/netfilter/ip_nat_core.c +18 -76
Index: linux-2.6.10-bk8-Netfilter/net/ipv4/netfilter/ip_nat_core.c
===================================================================
@@ -490,20 +490,6 @@
 	return 1;
 }
 
-static inline int exp_for_packet(struct ip_conntrack_expect *exp,
-			         struct sk_buff *skb)
-{
-	struct ip_conntrack_protocol *proto;
-	int ret = 1;
-
-	MUST_BE_READ_LOCKED(&ip_conntrack_lock);
-	proto = ip_ct_find_proto(skb->nh.iph->protocol);
-	if (proto->exp_matches_pkt)
-		ret = proto->exp_matches_pkt(exp, skb);
-
-	return ret;
-}
-
 /* Do packet manipulations according to binding. */
 unsigned int
 do_bindings(struct ip_conntrack *ct,
@@ -512,8 +498,7 @@
 	    unsigned int hooknum,
 	    struct sk_buff **pskb)
 {
-	unsigned int i;
-	struct ip_nat_helper *helper;
+	int i, ret = NF_ACCEPT;
 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 	int proto = (*pskb)->nh.iph->protocol;
 
@@ -538,75 +523,32 @@
 			}
 		}
 	}
-	helper = info->helper;
-	READ_UNLOCK(&ip_nat_lock);
-
-	if (helper) {
-		struct ip_conntrack_expect *exp = NULL;
-		struct list_head *cur_item;
-		int ret = NF_ACCEPT;
-		int helper_called = 0;
 
+	if (info->helper) {
 		DEBUGP("do_bindings: helper existing for (%p)\n", ct);
 
 		/* Always defragged for helpers */
 		IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
 			       & htons(IP_MF|IP_OFFSET)));
 
-		/* Have to grab read lock before sibling_list traversal */
-		READ_LOCK(&ip_conntrack_lock);
-		list_for_each_prev(cur_item, &ct->sibling_list) { 
-			exp = list_entry(cur_item, struct ip_conntrack_expect, 
-					 expected_list);
-					 
-			/* if this expectation is already established, skip */
-			if (exp->sibling)
-				continue;
-
-			if (exp_for_packet(exp, *pskb)) {
-				/* FIXME: May be true multiple times in the
-				 * case of UDP!! */
-				DEBUGP("calling nat helper (exp=%p) for packet\n", exp);
-				ret = helper->help(ct, exp, info, ctinfo, 
-						   hooknum, pskb);
-				if (ret != NF_ACCEPT) {
-					READ_UNLOCK(&ip_conntrack_lock);
-					return ret;
-				}
-				helper_called = 1;
-			}
-		}
-		/* Helper might want to manip the packet even when there is no
-		 * matching expectation for this packet */
-		if (!helper_called && helper->flags & IP_NAT_HELPER_F_ALWAYS) {
-			DEBUGP("calling nat helper for packet without expectation\n");
-			ret = helper->help(ct, NULL, info, ctinfo, 
-					   hooknum, pskb);
-			if (ret != NF_ACCEPT) {
-				READ_UNLOCK(&ip_conntrack_lock);
-				return ret;
-			}
-		}
-		READ_UNLOCK(&ip_conntrack_lock);
-		
-		/* Adjust sequence number only once per packet 
-		 * (helper is called at all hooks) */
-		if (proto == IPPROTO_TCP
-		    && (hooknum == NF_IP_POST_ROUTING
-			|| hooknum == NF_IP_LOCAL_IN)) {
-			DEBUGP("ip_nat_core: adjusting sequence number\n");
-			/* future: put this in a l4-proto specific function,
-			 * and call this function here. */
-			if (!ip_nat_seq_adjust(pskb, ct, ctinfo))
-				ret = NF_DROP;
-		}
-
-		return ret;
+		ret = info->helper->help(ct, NULL, info, ctinfo, hooknum,pskb);
+	}
+	READ_UNLOCK(&ip_nat_lock);
 
-	} else 
-		return NF_ACCEPT;
+	/* FIXME: NAT/conntrack helpers should set ctinfo &
+	 * CT_INFO_RESYNC on packets, so we don't have to adjust all
+	 * connections with conntrack helpers --RR */
+	if (ct->helper
+	    && proto == IPPROTO_TCP
+	    && (hooknum == NF_IP_POST_ROUTING || hooknum == NF_IP_LOCAL_IN)) {
+		DEBUGP("ip_nat_core: adjusting sequence number\n");
+		/* future: put this in a l4-proto specific function,
+		 * and call this function here. */
+		if (!ip_nat_seq_adjust(pskb, ct, ctinfo))
+			ret = NF_DROP;
+	}
 
-	/* not reached */
+	return ret;
 }
 
 static inline int tuple_src_equal_dst(const struct ip_conntrack_tuple *t1,
include/linux/netfilter_ipv4/ip_nat_helper.h +4 -6
Index: linux-2.6.10-bk8-Netfilter/include/linux/netfilter_ipv4/ip_nat_helper.h
===================================================================
@@ -30,12 +30,6 @@
 			     enum ip_conntrack_info ctinfo,
 			     unsigned int hooknum,
 			     struct sk_buff **pskb);
-
-	/* Returns verdict and sets up NAT for this connection */
-	unsigned int (*expect)(struct sk_buff **pskb,
-			       unsigned int hooknum,
-			       struct ip_conntrack *ct,
-			       struct ip_nat_info *info);
 };
 
 extern int ip_nat_helper_register(struct ip_nat_helper *me);
@@ -65,4 +59,8 @@
 extern int ip_nat_seq_adjust(struct sk_buff **pskb, 
 			     struct ip_conntrack *ct, 
 			     enum ip_conntrack_info ctinfo);
+
+/* Setup NAT on this expected conntrack so it follows master, but goes
+ * to port ct->master->saved_proto. */
+extern void ip_nat_follow_master(struct ip_conntrack *ct);
 #endif
net/ipv4/netfilter/ip_nat_helper.c +45 -0

57 lines not on this page · raw message

net/ipv4/netfilter/ip_conntrack_standalone.c +13 -2
Index: linux-2.6.10-bk8-Netfilter/net/ipv4/netfilter/ip_conntrack_standalone.c
===================================================================
@@ -364,8 +364,20 @@
 			       const struct net_device *out,
 			       int (*okfn)(struct sk_buff *))
 {
+	struct ip_conntrack *ct;
+	enum ip_conntrack_info ctinfo;
+
+	/* This is where we call the helper: as the packet goes out. */
+	ct = ip_conntrack_get(*pskb, &ctinfo);
+	if (ct && ct->helper) {
+		unsigned int ret;
+		ret = ct->helper->help(pskb, ct, ctinfo);
+		if (ret != NF_ACCEPT)
+			return ret;
+	}
+
 	/* We've seen it coming out the other side: confirm it */
-	return ip_conntrack_confirm(*pskb);
+	return ip_conntrack_confirm(pskb);
 }
 
 static unsigned int ip_conntrack_defrag(unsigned int hooknum,
@@ -899,7 +911,6 @@
 EXPORT_SYMBOL(ip_ct_find_helper);
 EXPORT_SYMBOL(ip_conntrack_expect_alloc);
 EXPORT_SYMBOL(ip_conntrack_expect_related);
-EXPORT_SYMBOL(ip_conntrack_change_expect);
 EXPORT_SYMBOL(ip_conntrack_unexpect_related);
 EXPORT_SYMBOL_GPL(ip_conntrack_expect_find_get);
 EXPORT_SYMBOL_GPL(ip_conntrack_expect_put);
net/ipv4/netfilter/ip_nat_ftp.c +51 -222

355 lines not on this page · raw message

net/ipv4/netfilter/ip_conntrack_proto_tcp.c +3 -22
Index: linux-2.6.10-bk8-Netfilter/net/ipv4/netfilter/ip_conntrack_proto_tcp.c
===================================================================
@@ -707,9 +707,9 @@
 
 #ifdef CONFIG_IP_NF_NAT_NEEDED
 /* Update sender->td_end after NAT successfully mangled the packet */
-int ip_conntrack_tcp_update(struct sk_buff *skb,
-			    struct ip_conntrack *conntrack, 
-			    int dir)
+void ip_conntrack_tcp_update(struct sk_buff *skb,
+			     struct ip_conntrack *conntrack, 
+			     enum ip_conntrack_dir dir)
 {
 	struct iphdr *iph = skb->nh.iph;
 	struct tcphdr *tcph = (void *)skb->nh.iph + skb->nh.iph->ihl*4;
@@ -735,8 +735,6 @@
 		sender->td_scale, 
 		receiver->td_end, receiver->td_maxend, receiver->td_maxwin,
 		receiver->td_scale);
-		
-	return 1;
 }
  
 #endif
@@ -1061,22 +1059,6 @@
 	return 1;
 }
   
-static int tcp_exp_matches_pkt(struct ip_conntrack_expect *exp,
-			       const struct sk_buff *skb)
-{
-	const struct iphdr *iph = skb->nh.iph;
-	struct tcphdr *th, _tcph;
-	unsigned int datalen;
-
-	th = skb_header_pointer(skb, iph->ihl * 4,
-				sizeof(_tcph), &_tcph);
-	if (th == NULL)
-		return 0;
-	datalen = skb->len - iph->ihl*4 - th->doff*4;
-
-	return between(exp->seq, ntohl(th->seq), ntohl(th->seq) + datalen);
-}
-
 struct ip_conntrack_protocol ip_conntrack_protocol_tcp =
 {
 	.proto 			= IPPROTO_TCP,
@@ -1087,6 +1069,5 @@
 	.print_conntrack 	= tcp_print_conntrack,
 	.packet 		= tcp_packet,
 	.new 			= tcp_new,
-	.exp_matches_pkt	= tcp_exp_matches_pkt,
 	.error			= tcp_error,
 };
include/linux/netfilter_ipv4/ip_conntrack.h +19 -32

89 lines not on this page · raw message

net/ipv4/netfilter/ip_conntrack_ftp.c +95 -58

240 lines not on this page · raw message

include/linux/netfilter_ipv4/ip_conntrack_ftp.h +16 -15
Index: linux-2.6.10-bk8-Netfilter/include/linux/netfilter_ipv4/ip_conntrack_ftp.h
===================================================================
@@ -20,24 +20,25 @@
 	IP_CT_FTP_EPSV,
 };
 
-/* This structure is per expected connection */
-struct ip_ct_ftp_expect
-{
-	/* We record seq number and length of ftp ip/port text here: all in
-	 * host order. */
-
- 	/* sequence number of IP address in packet is in ip_conntrack_expect */
-	u_int32_t len;			/* length of IP address */
-	enum ip_ct_ftp_type ftptype;	/* PORT or PASV ? */
-	u_int16_t port; 		/* TCP port that was to be used */
-};
-
+#define NUM_SEQ_TO_REMEMBER 2
 /* This structure exists only once per master */
 struct ip_ct_ftp_master {
-	/* Next valid seq position for cmd matching after newline */
-	u_int32_t seq_aft_nl[IP_CT_DIR_MAX];
+	/* Valid seq positions for cmd matching after newline */
+	u_int32_t seq_aft_nl[IP_CT_DIR_MAX][NUM_SEQ_TO_REMEMBER];
 	/* 0 means seq_match_aft_nl not set */
-	int seq_aft_nl_set[IP_CT_DIR_MAX];
+	int seq_aft_nl_num[IP_CT_DIR_MAX];
 };
 
+struct ip_conntrack_expect;
+
+/* For NAT to hook in when we find a packet which describes what other
+ * connection we should expect. */
+extern unsigned int (*ip_nat_ftp_hook)(struct sk_buff **pskb,
+				       struct ip_conntrack *ct,
+				       enum ip_conntrack_info ctinfo,
+				       enum ip_ct_ftp_type type,
+				       unsigned int matchoff,
+				       unsigned int matchlen,
+				       struct ip_conntrack_expect *exp,
+				       u32 *seq);
 #endif /* _IP_CONNTRACK_FTP_H */
net/ipv4/netfilter/ip_nat_standalone.c +8 -24
Index: linux-2.6.10-bk8-Netfilter/net/ipv4/netfilter/ip_nat_standalone.c
===================================================================
@@ -55,15 +55,6 @@
 			         : ((hooknum) == NF_IP_LOCAL_IN ? "LOCAL_IN"  \
 				    : "*ERROR*")))
 
-static inline int call_expect(struct ip_conntrack *master,
-			      struct sk_buff **pskb,
-			      unsigned int hooknum,
-			      struct ip_conntrack *ct,
-			      struct ip_nat_info *info)
-{
-	return master->nat.info.helper->expect(pskb, hooknum, ct, info);
-}
-
 static unsigned int
 ip_nat_fn(unsigned int hooknum,
 	  struct sk_buff **pskb,
@@ -131,21 +122,13 @@
 		if (!(info->initialized & (1 << maniptype))) {
 			unsigned int ret;
 
-			if (ct->master
-			    && master_ct(ct)->nat.info.helper
-			    && master_ct(ct)->nat.info.helper->expect) {
-				ret = call_expect(master_ct(ct), pskb, 
-						  hooknum, ct, info);
-			} else {
-				/* LOCAL_IN hook doesn't have a chain!  */
-				if (hooknum == NF_IP_LOCAL_IN)
-					ret = alloc_null_binding(ct, info,
-								 hooknum);
-				else
-					ret = ip_nat_rule_find(pskb, hooknum,
-					                       in, out, ct,
-					                       info);
-			}
+			/* LOCAL_IN hook doesn't have a chain!  */
+			if (hooknum == NF_IP_LOCAL_IN)
+				ret = alloc_null_binding(ct, info, hooknum);
+			else
+				ret = ip_nat_rule_find(pskb, hooknum,
+						       in, out, ct,
+						       info);
 
 			if (ret != NF_ACCEPT) {
 				WRITE_UNLOCK(&ip_nat_lock);
@@ -396,4 +379,5 @@
 EXPORT_SYMBOL(ip_nat_used_tuple);
 EXPORT_SYMBOL(ip_nat_find_helper);
 EXPORT_SYMBOL(__ip_nat_find_helper);
+EXPORT_SYMBOL(ip_nat_follow_master);
 MODULE_LICENSE("GPL");
include/linux/netfilter_ipv4/ip_conntrack_protocol.h +0 -4
Index: linux-2.6.10-bk8-Netfilter/include/linux/netfilter_ipv4/ip_conntrack_protocol.h
===================================================================
@@ -44,10 +44,6 @@
 	/* Called when a conntrack entry is destroyed */
 	void (*destroy)(struct ip_conntrack *conntrack);
 
-	/* Has to decide if a expectation matches one packet or not */
-	int (*exp_matches_pkt)(struct ip_conntrack_expect *exp,
-			       const struct sk_buff *skb);
-
 	int (*error)(struct sk_buff *skb, enum ip_conntrack_info *ctinfo,
 		     unsigned int hooknum);
 
net/ipv4/netfilter/ip_conntrack_proto_sctp.c +0 -8
Index: linux-2.6.10-bk8-Netfilter/net/ipv4/netfilter/ip_conntrack_proto_sctp.c
===================================================================
@@ -494,13 +494,6 @@
 	return 1;
 }
 
-static int sctp_exp_matches_pkt(struct ip_conntrack_expect *exp,
-				const struct sk_buff *skb)
-{
-	/* To be implemented */
-	return 0;
-}
-
 struct ip_conntrack_protocol ip_conntrack_protocol_sctp = { 
 	.proto 		 = IPPROTO_SCTP, 
 	.name 		 = "sctp",
@@ -511,7 +504,6 @@
 	.packet 	 = sctp_packet, 
 	.new 		 = sctp_new, 
 	.destroy 	 = NULL, 
-	.exp_matches_pkt = sctp_exp_matches_pkt, 
 	.me 		 = THIS_MODULE 
 };
 
include/linux/netfilter_ipv4/ip_conntrack_core.h +5 -5
Index: linux-2.6.10-bk8-Netfilter/include/linux/netfilter_ipv4/ip_conntrack_core.h
===================================================================
@@ -34,14 +34,14 @@
 ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple,
 		      const struct ip_conntrack *ignored_conntrack);
 
-extern int __ip_conntrack_confirm(struct sk_buff *skb);
+extern int __ip_conntrack_confirm(struct sk_buff **pskb);
 
 /* Confirm a connection: returns NF_DROP if packet must be dropped. */
-static inline int ip_conntrack_confirm(struct sk_buff *skb)
+static inline int ip_conntrack_confirm(struct sk_buff **pskb)
 {
-	if (skb->nfct
-	    && !is_confirmed((struct ip_conntrack *)skb->nfct))
-		return __ip_conntrack_confirm(skb);
+	if ((*pskb)->nfct
+	    && !is_confirmed((struct ip_conntrack *)(*pskb)->nfct))
+		return __ip_conntrack_confirm(pskb);
 	return NF_ACCEPT;
 }
 
include/linux/netfilter_ipv4/ip_conntrack_helper.h +1 -3
Index: linux-2.6.10-bk8-Netfilter/include/linux/netfilter_ipv4/ip_conntrack_helper.h
===================================================================
@@ -25,7 +25,7 @@
 	
 	/* Function to call when data passes; return verdict, or -1 to
            invalidate. */
-	int (*help)(struct sk_buff *skb,
+	int (*help)(struct sk_buff **pskb,
 		    struct ip_conntrack *ct,
 		    enum ip_conntrack_info conntrackinfo);
 };
@@ -42,8 +42,6 @@
 /* Add an expected connection: can have more than one per connection */
 extern int ip_conntrack_expect_related(struct ip_conntrack_expect *exp,
 				       struct ip_conntrack *related_to);
-extern int ip_conntrack_change_expect(struct ip_conntrack_expect *expect,
-				      struct ip_conntrack_tuple *newtuple);
 extern void ip_conntrack_unexpect_related(struct ip_conntrack_expect *exp);
 
 #endif /*_IP_CONNTRACK_HELPER_H*/
net/ipv4/netfilter/ip_conntrack_core.c +4 -55

94 lines not on this page · raw message

#1 lore raw
Quoted 3 lines · Rusty Russell

Name: Call NAT Helper Modules Directly from Conntrack Modules, fixup FTP

Status: Tested under nfsim

Thanks, Rusty.

I agree with all your changes but the removal of the copyright and GPL notice ;)

Signature · 5 lines
- Harald Welte <[email protected]>             http://www.netfilter.org/
============================================================================
  "Fragmentation is like classful addressing -- an interesting early
   architectural error that shows how much experimentation was going
   on while IP was being designed."                    -- Paul Vixie
#2 lore raw
Quoted 9 lines · Harald Welte
Quoted 3 lines · Rusty Russell

Name: Call NAT Helper Modules Directly from Conntrack Modules, fixup FTP

Status: Tested under nfsim

Thanks, Rusty.

I agree with all your changes but the removal of the copyright and GPL notice ;)

Oops.

I'd really like IRC and SNMP testcases, so we can at least check I haven't shattered them completed.

Rusty.
PS. In my tree at the moment conntrack entries are only 192 bytes on x86, vs. 352 in 2.6.10. But that needs more patches 8)

Signature · 1 line
A bad analogy is like a leaky screwdriver -- Richard Braakman