Re: ip_masq_ftp: I wish it could keep the control socket from timing out.

Keith Owens (kaos@ocs.com.au)
Mon, 05 May 1997 16:24:09 +1000


On Mon, 5 May 1997 02:06:47 -0400 (EDT),
Sameer Anja <sameer@securities.com> wrote:
>Where is the patch available for this??

Masq patches can be found at http://www.indyramp.com/masq. here is the
ftp timeout patch anyway, it should patch clean from 2.0.1 through 2.0.29.

--- linux-2.0.8/include/net/ip_masq.h Mon Jul 22 16:55:30 1996
+++ linux/include/net/ip_masq.h Mon Jul 22 17:32:50 1996
@@ -20,6 +20,7 @@
#define MASQUERADE_EXPIRE_TCP 15*60*HZ
#define MASQUERADE_EXPIRE_TCP_FIN 2*60*HZ
#define MASQUERADE_EXPIRE_UDP 5*60*HZ
+#define MASQUERADE_EXPIRE_CONTROL 15*60*HZ

#define IP_MASQ_F_OUT_SEQ 0x01 /* must do output seq adjust */
#define IP_MASQ_F_IN_SEQ 0x02 /* must do input seq adjust */
@@ -32,6 +33,9 @@
#define IP_MASQ_F_SAW_FIN (IP_MASQ_F_SAW_FIN_IN | \
IP_MASQ_F_SAW_FIN_OUT)
/* tcp fin pkts seen */
+#define IP_MASQ_F_CONTROL 0x100 /* this is a control channel */
+#define IP_MASQ_F_NO_SPORT 0x200 /* no sport set yet */
+#define IP_MASQ_F_FTP_PASV 0x400 /* ftp PASV command just issued */

#ifdef __KERNEL__

@@ -59,6 +63,7 @@
struct ip_masq_app *app; /* bound ip_masq_app object */
void *app_data; /* Application private data */
unsigned flags; /* status flags */
+ struct ip_masq *control; /* Corresponding control connection */
};

/*
@@ -69,6 +74,7 @@
int tcp_timeout;
int tcp_fin_timeout;
int udp_timeout;
+ int control_timeout;
};

extern struct ip_fw_masq *ip_masq_expire;
diff -ur linux-2.0.8/net/ipv4/ip_fw.c linux/net/ipv4/ip_fw.c
--- linux-2.0.8/net/ipv4/ip_fw.c Mon Jul 22 16:55:30 1996
+++ linux/net/ipv4/ip_fw.c Mon Jul 22 17:00:56 1996
@@ -37,6 +37,8 @@
* Willy Konynenberg <willy@xos.nl> 10/5/96.
* Make separate accounting on incoming and outgoing packets possible.
* Jos Vos <jos@xos.nl> 18/5/1996.
+ * Add timeout reprieve for idle control channels.
+ * Keith Owens <kaos@ocs.com.au> 05/07/1996.
*
*
* Masquerading functionality
@@ -997,6 +999,11 @@
if (masq->udp_timeout)
{
ip_masq_expire->udp_timeout = masq->udp_timeout;
+ }
+
+ if (masq->control_timeout)
+ {
+ ip_masq_expire->control_timeout = masq->control_timeout;
}

return 0;
diff -ur linux-2.0.8/net/ipv4/ip_masq.c linux/net/ipv4/ip_masq.c
--- linux-2.0.8/net/ipv4/ip_masq.c Mon Jul 22 16:55:30 1996
+++ linux/net/ipv4/ip_masq.c Mon Jul 22 17:00:56 1996
@@ -15,6 +15,7 @@
* Nigel Metheringham : Added ICMP handling for demasquerade
* Nigel Metheringham : Checksum checking of masqueraded data
* Nigel Metheringham : Better handling of timeouts of TCP conns
+ * Keith Owens : Keep control channels alive if any related data entries.
*
*
*/
@@ -98,7 +99,8 @@
static struct ip_fw_masq ip_masq_dummy = {
MASQUERADE_EXPIRE_TCP,
MASQUERADE_EXPIRE_TCP_FIN,
- MASQUERADE_EXPIRE_UDP
+ MASQUERADE_EXPIRE_UDP,
+ MASQUERADE_EXPIRE_CONTROL
};

struct ip_fw_masq *ip_masq_expire = &ip_masq_dummy;
@@ -219,7 +221,7 @@
* broken out of the ip/tcp headers or directly supplied for those
* pathological protocols with address/port in the data stream
* (ftp, irc). addresses and ports are in network order.
- * called for pkts coming from INside-to-outside the firewall.
+ * called for pkts coming from outside-to-INside the firewall.
*
* NB. Cannot check destination address, just for the incoming port.
* reason: archie.doc.ac.uk has 6 interfaces, you send to
@@ -274,6 +276,12 @@
* pathological protocols with address/port in the data stream
* (ftp, irc). addresses and ports are in network order.
* called for pkts coming from inside-to-OUTside the firewall.
+ *
+ * Normally we know the source address and port but for some protocols
+ * (e.g. ftp PASV) we do not know the source port initially. Alas the
+ * hash is keyed on source port so if the first lookup fails then try again
+ * with a zero port, this time only looking at entries marked "no source
+ * port".
*/

struct ip_masq *
@@ -289,6 +297,14 @@
d_addr == ms->daddr && d_port == ms->dport )
return ms;
}
+ hash = ip_masq_hash_key(protocol, s_addr, 0);
+ for(ms = ip_masq_s_tab[hash]; ms ; ms = ms->s_link) {
+ if (ms->flags & IP_MASQ_F_NO_SPORT &&
+ protocol == ms->protocol &&
+ s_addr == ms->saddr &&
+ d_addr == ms->daddr && d_port == ms->dport )
+ return ms;
+ }

return NULL;
}
@@ -315,9 +331,46 @@

static void masq_expire(unsigned long data)
{
- struct ip_masq *ms = (struct ip_masq *)data;
+ struct ip_masq *ms = (struct ip_masq *)data, *ms_data;
unsigned long flags;

+ if (ms->flags & IP_MASQ_F_CONTROL) {
+ /* a control channel is about to expire */
+ int idx = 0, reprieve = 0;
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+ printk("Masquerade control %s %lX:%X about to expire\n",
+ masq_proto_name(ms->protocol),
+ ntohl(ms->saddr),ntohs(ms->sport));
+#endif
+ save_flags(flags);
+ cli();
+
+ /*
+ * If any other masquerade entry claims that the expiring entry
+ * is its control channel then keep the control entry alive.
+ * Useful for long running data channels with inactive control
+ * links which we don't want to lose, e.g. ftp.
+ * Assumption: loops such as a->b->a or a->a will never occur.
+ */
+ for (idx = 0; idx < IP_MASQ_TAB_SIZE && !reprieve; idx++) {
+ for (ms_data = ip_masq_m_tab[idx]; ms_data ; ms_data = ms_data->m_link) {
+ if (ms_data->control == ms) {
+ reprieve = 1; /* this control connection can live a bit longer */
+ ip_masq_set_expire(ms, ip_masq_expire->control_timeout);
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+ printk("Masquerade control %s %lX:%X expiry reprieved\n",
+ masq_proto_name(ms->protocol),
+ ntohl(ms->saddr),ntohs(ms->sport));
+#endif
+ break;
+ }
+ }
+ }
+ restore_flags(flags);
+ if (reprieve)
+ return;
+ }
+
#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("Masqueraded %s %lX:%X expired\n",
masq_proto_name(ms->protocol),
@@ -375,6 +428,7 @@
ms->dport = dport;
ms->flags = mflags;
ms->app_data = NULL;
+ ms->control = NULL;

if (proto == IPPROTO_UDP)
ms->flags |= IP_MASQ_F_NO_DADDR;
@@ -483,9 +537,31 @@
#endif

ms = ip_masq_out_get(iph);
- if (ms!=NULL)
+ if (ms!=NULL) {
ip_masq_set_expire(ms,0);

+ /*
+ * Set sport if not defined yet (e.g. ftp PASV). Because
+ * masq entries are hashed on sport, unhash with old value
+ * and hash with new.
+ */
+
+ if ( ms->flags & IP_MASQ_F_NO_SPORT && ms->protocol == IPPROTO_TCP ) {
+ unsigned long flags;
+ ms->flags &= ~IP_MASQ_F_NO_SPORT;
+ save_flags(flags);
+ cli();
+ ip_masq_unhash(ms);
+ ms->sport = portptr[0];
+ ip_masq_hash(ms); /* hash on new sport */
+ restore_flags(flags);
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+ printk("ip_fw_masquerade(): filled sport=%d\n",
+ ntohs(ms->sport));
+#endif
+ }
+ }
+
/*
* Nope, not found, create a new entry for it
*/
@@ -848,7 +924,7 @@
if ( ms->flags & IP_MASQ_F_NO_DPORT && ms->protocol == IPPROTO_TCP ) {
ms->flags &= ~IP_MASQ_F_NO_DPORT;
ms->dport = portptr[0];
-#if DEBUG_CONFIG_IP_MASQUERADE
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("ip_fw_demasquerade(): filled dport=%d\n",
ntohs(ms->dport));
#endif
@@ -856,7 +932,7 @@
if (ms->flags & IP_MASQ_F_NO_DADDR && ms->protocol == IPPROTO_TCP) {
ms->flags &= ~IP_MASQ_F_NO_DADDR;
ms->daddr = iph->saddr;
-#if DEBUG_CONFIG_IP_MASQUERADE
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
printk("ip_fw_demasquerade(): filled daddr=%X\n",
ntohs(ms->daddr));
#endif
diff -ur linux-2.0.8/net/ipv4/ip_masq_ftp.c linux/net/ipv4/ip_masq_ftp.c
--- linux-2.0.8/net/ipv4/ip_masq_ftp.c Mon Jul 22 16:55:30 1996
+++ linux/net/ipv4/ip_masq_ftp.c Mon Jul 22 17:00:56 1996
@@ -10,6 +10,7 @@
* Fixes:
* Wouter Gadeyne : Fixed masquerading support of ftp PORT commands
* Juan Jose Ciarlante : Code moved and adapted from ip_fw.c
+ * Keith Owens : Add keep alive for ftp control channel
*
*
*
@@ -66,8 +67,9 @@
iph = skb->h.iph;
th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
data = (char *)&th[1];
-
data_limit = skb->h.raw + skb->len - 18;
+ if (skb->len >= 6 && (memcmp(data, "PASV\r\n", 6) == 0 || memcmp(data, "pasv\r\n", 6) == 0))
+ ms->flags |= IP_MASQ_F_FTP_PASV;

while (data < data_limit)
{
@@ -123,6 +125,8 @@

if (n_ms==NULL)
return 0;
+ n_ms->control = ms; /* keepalive from data to the control channel */
+ ms->flags |= IP_MASQ_F_CONTROL; /* this is a control channel */
}

/*
@@ -171,6 +175,108 @@

}

+/*
+ * Look at incoming ftp packets to catch the response to a PASV command. When
+ * we see one we build a masquerading entry for the client address, client port
+ * 0 (unknown at the moment), the server address and the server port. Mark the
+ * current masquerade entry as a control channel and point the new entry at the
+ * control entry. All this work just for ftp keepalive across masquerading.
+ *
+ * The incoming packet should be something like
+ * "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)".
+ * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number.
+ * ncftp 2.3.0 cheats by skipping the leading number then going 22 bytes into
+ * the data so we do the same. If it's good enough for ncftp then it's good
+ * enough for me.
+ *
+ * In this case, the client is the source machine being masqueraded, the server
+ * is the destination for ftp requests. It all depends on your point of view ...
+ */
+
+int
+masq_ftp_in (struct ip_masq_app *mapp, struct ip_masq *ms, struct sk_buff **skb_p, struct device *dev)
+{
+ struct sk_buff *skb;
+ struct iphdr *iph;
+ struct tcphdr *th;
+ char *data, *data_limit;
+ unsigned char p1,p2,p3,p4,p5,p6;
+ __u32 to;
+ __u16 port;
+ struct ip_masq *n_ms;
+
+ if (! ms->flags & IP_MASQ_F_FTP_PASV)
+ return 0; /* quick exit if no outstanding PASV */
+
+ skb = *skb_p;
+ iph = skb->h.iph;
+ th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
+ data = (char *)&th[1];
+ data_limit = skb->h.raw + skb->len;
+
+ while (data < data_limit && *data != ' ')
+ ++data;
+ while (data < data_limit && *data == ' ')
+ ++data;
+ data += 22;
+ if (data >= data_limit || *data != '(')
+ return 0;
+ p1 = simple_strtoul(data+1, &data, 10);
+ if (data >= data_limit || *data != ',')
+ return 0;
+ p2 = simple_strtoul(data+1, &data, 10);
+ if (data >= data_limit || *data != ',')
+ return 0;
+ p3 = simple_strtoul(data+1, &data, 10);
+ if (data >= data_limit || *data != ',')
+ return 0;
+ p4 = simple_strtoul(data+1, &data, 10);
+ if (data >= data_limit || *data != ',')
+ return 0;
+ p5 = simple_strtoul(data+1, &data, 10);
+ if (data >= data_limit || *data != ',')
+ return 0;
+ p6 = simple_strtoul(data+1, &data, 10);
+ if (data >= data_limit || *data != ')')
+ return 0;
+
+ to = (p1<<24) | (p2<<16) | (p3<<8) | p4;
+ port = (p5<<8) | p6;
+
+ /*
+ * Now update or create an masquerade entry for it
+ */
+#if DEBUG_CONFIG_IP_MASQ_FTP
+ printk("PASV response %lX:%X %X:%X detected\n", ntohl(ms->saddr), 0, to, port);
+#endif
+ n_ms = ip_masq_out_get_2(iph->protocol,
+ ms->saddr, 0,
+ htonl(to), htons(port));
+ if (n_ms) {
+ /* existing masquerade, clear timer */
+ ip_masq_set_expire(n_ms,0);
+ }
+ else {
+ n_ms = ip_masq_new(dev, IPPROTO_TCP,
+ ms->saddr, 0,
+ htonl(to), htons(port),
+ IP_MASQ_F_NO_SPORT);
+
+ if (n_ms==NULL)
+ return 0;
+ n_ms->control = ms; /* keepalive from data to the control channel */
+ ms->flags |= IP_MASQ_F_CONTROL; /* this is a control channel */
+ }
+
+ /*
+ * keep for a bit longer than tcp_fin, client may not issue open
+ * to server port before tcp_fin_timeout.
+ */
+ ip_masq_set_expire(n_ms, ip_masq_expire->tcp_fin_timeout*3);
+ ms->flags &= ~IP_MASQ_F_FTP_PASV;
+ return 0; /* no diff required for incoming packets, thank goodness */
+}
+
struct ip_masq_app ip_masq_ftp = {
NULL, /* next */
"ftp", /* name */
@@ -179,7 +285,7 @@
masq_ftp_init_1, /* ip_masq_init_1 */
masq_ftp_done_1, /* ip_masq_done_1 */
masq_ftp_out, /* pkt_out */
- NULL /* pkt_in */
+ masq_ftp_in, /* pkt_in */
};

/*
--- linux/net/ipv4/ip_masq.c.orig Thu Jun 6 23:23:49 1996
+++ linux/net/ipv4/ip_masq.c Wed Sep 11 17:23:05 1996
@@ -949,7 +949,7 @@
if (offset < 128)
{
sprintf(temp,
- "Prc FromIP FPrt ToIP TPrt Masq Init-seq Delta PDelta Expires (free=%d,%d)",
+ "Prc FromIP FPrt ToIP TPrt Masq Init-seq Delta PDelta Expires Flags (free=%d,%d)",
ip_masq_free_ports[0], ip_masq_free_ports[1]);
len = sprintf(buffer, "%-127s\n", temp);
}
@@ -968,7 +968,7 @@
timer_active = del_timer(&ms->timer);
if (!timer_active)
ms->timer.expires = jiffies;
- sprintf(temp,"%s %08lX:%04X %08lX:%04X %04X %08X %6d %6d %7lu",
+ sprintf(temp,"%s %08lX:%04X %08lX:%04X %04X %08X %6d %6d %7lu %08X",
masq_proto_name(ms->protocol),
ntohl(ms->saddr), ntohs(ms->sport),
ntohl(ms->daddr), ntohs(ms->dport),
@@ -976,7 +976,8 @@
ms->out_seq.init_seq,
ms->out_seq.delta,
ms->out_seq.previous_delta,
- ms->timer.expires-jiffies);
+ ms->timer.expires-jiffies,
+ ms->flags);
if (timer_active)
add_timer(&ms->timer);
len += sprintf(buffer+len, "%-127s\n", temp);