Commit cdd289a2 authored by Patrick McHardy's avatar Patrick McHardy Committed by David S. Miller

[NETFILTER]: add IPv6-capable TCPMSS target

Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent a8d0f952
...@@ -33,6 +33,7 @@ header-y += xt_tcpmss.h ...@@ -33,6 +33,7 @@ header-y += xt_tcpmss.h
header-y += xt_tcpudp.h header-y += xt_tcpudp.h
header-y += xt_SECMARK.h header-y += xt_SECMARK.h
header-y += xt_CONNSECMARK.h header-y += xt_CONNSECMARK.h
header-y += xt_TCPMSS.h
unifdef-y += nf_conntrack_common.h unifdef-y += nf_conntrack_common.h
unifdef-y += nf_conntrack_ftp.h unifdef-y += nf_conntrack_ftp.h
......
#ifndef _XT_TCPMSS_H
#define _XT_TCPMSS_H
struct xt_tcpmss_info {
u_int16_t mss;
};
#define XT_TCPMSS_CLAMP_PMTU 0xffff
#endif /* _XT_TCPMSS_H */
#ifndef _IPT_TCPMSS_H #ifndef _IPT_TCPMSS_H
#define _IPT_TCPMSS_H #define _IPT_TCPMSS_H
struct ipt_tcpmss_info { #include <linux/netfilter/xt_TCPMSS.h>
u_int16_t mss;
};
#define IPT_TCPMSS_CLAMP_PMTU 0xffff #define ipt_tcpmss_info xt_tcpmss_info
#define IPT_TCPMSS_CLAMP_PMTU XT_TCPMSS_CLAMP_PMTU
#endif /*_IPT_TCPMSS_H*/ #endif /*_IPT_TCPMSS_H*/
...@@ -361,32 +361,6 @@ config IP_NF_TARGET_ULOG ...@@ -361,32 +361,6 @@ config IP_NF_TARGET_ULOG
To compile it as a module, choose M here. If unsure, say N. To compile it as a module, choose M here. If unsure, say N.
config IP_NF_TARGET_TCPMSS
tristate "TCPMSS target support"
depends on IP_NF_IPTABLES
---help---
This option adds a `TCPMSS' target, which allows you to alter the
MSS value of TCP SYN packets, to control the maximum size for that
connection (usually limiting it to your outgoing interface's MTU
minus 40).
This is used to overcome criminally braindead ISPs or servers which
block ICMP Fragmentation Needed packets. The symptoms of this
problem are that everything works fine from your Linux
firewall/router, but machines behind it can never exchange large
packets:
1) Web browsers connect, then hang with no data received.
2) Small mail works fine, but large emails hang.
3) ssh works fine, but scp hangs after initial handshaking.
Workaround: activate this option and add a rule to your firewall
configuration like:
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
To compile it as a module, choose M here. If unsure, say N.
# NAT + specific targets: ip_conntrack # NAT + specific targets: ip_conntrack
config IP_NF_NAT config IP_NF_NAT
tristate "Full NAT" tristate "Full NAT"
......
...@@ -103,7 +103,6 @@ obj-$(CONFIG_IP_NF_TARGET_SAME) += ipt_SAME.o ...@@ -103,7 +103,6 @@ obj-$(CONFIG_IP_NF_TARGET_SAME) += ipt_SAME.o
obj-$(CONFIG_IP_NF_NAT_SNMP_BASIC) += ip_nat_snmp_basic.o obj-$(CONFIG_IP_NF_NAT_SNMP_BASIC) += ip_nat_snmp_basic.o
obj-$(CONFIG_IP_NF_TARGET_LOG) += ipt_LOG.o obj-$(CONFIG_IP_NF_TARGET_LOG) += ipt_LOG.o
obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o
obj-$(CONFIG_IP_NF_TARGET_TCPMSS) += ipt_TCPMSS.o
obj-$(CONFIG_IP_NF_TARGET_CLUSTERIP) += ipt_CLUSTERIP.o obj-$(CONFIG_IP_NF_TARGET_CLUSTERIP) += ipt_CLUSTERIP.o
obj-$(CONFIG_IP_NF_TARGET_TTL) += ipt_TTL.o obj-$(CONFIG_IP_NF_TARGET_TTL) += ipt_TTL.o
......
...@@ -395,6 +395,32 @@ config NETFILTER_XT_TARGET_CONNSECMARK ...@@ -395,6 +395,32 @@ config NETFILTER_XT_TARGET_CONNSECMARK
To compile it as a module, choose M here. If unsure, say N. To compile it as a module, choose M here. If unsure, say N.
config NETFILTER_XT_TARGET_TCPMSS
tristate '"TCPMSS" target support'
depends on NETFILTER_XTABLES && (IPV6 || IPV6=n)
---help---
This option adds a `TCPMSS' target, which allows you to alter the
MSS value of TCP SYN packets, to control the maximum size for that
connection (usually limiting it to your outgoing interface's MTU
minus 40).
This is used to overcome criminally braindead ISPs or servers which
block ICMP Fragmentation Needed packets. The symptoms of this
problem are that everything works fine from your Linux
firewall/router, but machines behind it can never exchange large
packets:
1) Web browsers connect, then hang with no data received.
2) Small mail works fine, but large emails hang.
3) ssh works fine, but scp hangs after initial handshaking.
Workaround: activate this option and add a rule to your firewall
configuration like:
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
To compile it as a module, choose M here. If unsure, say N.
config NETFILTER_XT_MATCH_COMMENT config NETFILTER_XT_MATCH_COMMENT
tristate '"comment" match support' tristate '"comment" match support'
depends on NETFILTER_XTABLES depends on NETFILTER_XTABLES
......
...@@ -45,6 +45,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_NFQUEUE) += xt_NFQUEUE.o ...@@ -45,6 +45,7 @@ obj-$(CONFIG_NETFILTER_XT_TARGET_NFQUEUE) += xt_NFQUEUE.o
obj-$(CONFIG_NETFILTER_XT_TARGET_NFLOG) += xt_NFLOG.o obj-$(CONFIG_NETFILTER_XT_TARGET_NFLOG) += xt_NFLOG.o
obj-$(CONFIG_NETFILTER_XT_TARGET_NOTRACK) += xt_NOTRACK.o obj-$(CONFIG_NETFILTER_XT_TARGET_NOTRACK) += xt_NOTRACK.o
obj-$(CONFIG_NETFILTER_XT_TARGET_SECMARK) += xt_SECMARK.o obj-$(CONFIG_NETFILTER_XT_TARGET_SECMARK) += xt_SECMARK.o
obj-$(CONFIG_NETFILTER_XT_TARGET_TCPMSS) += xt_TCPMSS.o
obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o obj-$(CONFIG_NETFILTER_XT_TARGET_CONNSECMARK) += xt_CONNSECMARK.o
# matches # matches
......
...@@ -10,16 +10,23 @@ ...@@ -10,16 +10,23 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <net/ipv6.h>
#include <net/tcp.h> #include <net/tcp.h>
#include <linux/netfilter_ipv4/ip_tables.h> #include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ipt_TCPMSS.h> #include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_tcpudp.h>
#include <linux/netfilter/xt_TCPMSS.h>
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>"); MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
MODULE_DESCRIPTION("iptables TCP MSS modification module"); MODULE_DESCRIPTION("x_tables TCP MSS modification module");
MODULE_ALIAS("ipt_TCPMSS");
MODULE_ALIAS("ip6t_TCPMSS");
static inline unsigned int static inline unsigned int
optlen(const u_int8_t *opt, unsigned int offset) optlen(const u_int8_t *opt, unsigned int offset)
...@@ -31,28 +38,23 @@ optlen(const u_int8_t *opt, unsigned int offset) ...@@ -31,28 +38,23 @@ optlen(const u_int8_t *opt, unsigned int offset)
return opt[offset+1]; return opt[offset+1];
} }
static unsigned int static int
ipt_tcpmss_target(struct sk_buff **pskb, tcpmss_mangle_packet(struct sk_buff **pskb,
const struct net_device *in, const struct xt_tcpmss_info *info,
const struct net_device *out, unsigned int tcphoff,
unsigned int hooknum, unsigned int minlen)
const struct xt_target *target,
const void *targinfo)
{ {
const struct ipt_tcpmss_info *tcpmssinfo = targinfo;
struct tcphdr *tcph; struct tcphdr *tcph;
struct iphdr *iph; unsigned int tcplen, i;
u_int16_t tcplen, newmss; __be16 oldval;
__be16 newtotlen, oldval; u16 newmss;
unsigned int i; u8 *opt;
u_int8_t *opt;
if (!skb_make_writable(pskb, (*pskb)->len)) if (!skb_make_writable(pskb, (*pskb)->len))
return NF_DROP; return -1;
iph = (*pskb)->nh.iph; tcplen = (*pskb)->len - tcphoff;
tcplen = (*pskb)->len - iph->ihl*4; tcph = (struct tcphdr *)((*pskb)->nh.raw + tcphoff);
tcph = (void *)iph + iph->ihl*4;
/* Since it passed flags test in tcp match, we know it is is /* Since it passed flags test in tcp match, we know it is is
not a fragment, and has data >= tcp header length. SYN not a fragment, and has data >= tcp header length. SYN
...@@ -61,28 +63,24 @@ ipt_tcpmss_target(struct sk_buff **pskb, ...@@ -61,28 +63,24 @@ ipt_tcpmss_target(struct sk_buff **pskb,
badly. --RR */ badly. --RR */
if (tcplen != tcph->doff*4) { if (tcplen != tcph->doff*4) {
if (net_ratelimit()) if (net_ratelimit())
printk(KERN_ERR printk(KERN_ERR "xt_TCPMSS: bad length (%u bytes)\n",
"ipt_tcpmss_target: bad length (%d bytes)\n",
(*pskb)->len); (*pskb)->len);
return NF_DROP; return -1;
} }
if (tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) { if (info->mss == XT_TCPMSS_CLAMP_PMTU) {
if (dst_mtu((*pskb)->dst) <= sizeof(struct iphdr) + if (dst_mtu((*pskb)->dst) <= minlen) {
sizeof(struct tcphdr)) {
if (net_ratelimit()) if (net_ratelimit())
printk(KERN_ERR "ipt_tcpmss_target: " printk(KERN_ERR "xt_TCPMSS: "
"unknown or invalid path-MTU (%d)\n", "unknown or invalid path-MTU (%u)\n",
dst_mtu((*pskb)->dst)); dst_mtu((*pskb)->dst));
return NF_DROP; /* or IPT_CONTINUE ?? */ return -1;
} }
newmss = dst_mtu((*pskb)->dst) - minlen;
newmss = dst_mtu((*pskb)->dst) - sizeof(struct iphdr) -
sizeof(struct tcphdr);
} else } else
newmss = tcpmssinfo->mss; newmss = info->mss;
opt = (u_int8_t *)tcph; opt = (u_int8_t *)tcph;
for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) { for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)) {
if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS && if (opt[i] == TCPOPT_MSS && tcph->doff*4 - i >= TCPOLEN_MSS &&
opt[i+1] == TCPOLEN_MSS) { opt[i+1] == TCPOLEN_MSS) {
...@@ -90,16 +88,16 @@ ipt_tcpmss_target(struct sk_buff **pskb, ...@@ -90,16 +88,16 @@ ipt_tcpmss_target(struct sk_buff **pskb,
oldmss = (opt[i+2] << 8) | opt[i+3]; oldmss = (opt[i+2] << 8) | opt[i+3];
if (tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU && if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
oldmss <= newmss) oldmss <= newmss)
return IPT_CONTINUE; return 0;
opt[i+2] = (newmss & 0xff00) >> 8; opt[i+2] = (newmss & 0xff00) >> 8;
opt[i+3] = (newmss & 0x00ff); opt[i+3] = (newmss & 0x00ff);
nf_proto_csum_replace2(&tcph->check, *pskb, nf_proto_csum_replace2(&tcph->check, *pskb,
htons(oldmss), htons(newmss), 0); htons(oldmss), htons(newmss), 0);
return IPT_CONTINUE; return 0;
} }
} }
...@@ -112,20 +110,19 @@ ipt_tcpmss_target(struct sk_buff **pskb, ...@@ -112,20 +110,19 @@ ipt_tcpmss_target(struct sk_buff **pskb,
newskb = skb_copy_expand(*pskb, skb_headroom(*pskb), newskb = skb_copy_expand(*pskb, skb_headroom(*pskb),
TCPOLEN_MSS, GFP_ATOMIC); TCPOLEN_MSS, GFP_ATOMIC);
if (!newskb) if (!newskb)
return NF_DROP; return -1;
kfree_skb(*pskb); kfree_skb(*pskb);
*pskb = newskb; *pskb = newskb;
iph = (*pskb)->nh.iph; tcph = (struct tcphdr *)((*pskb)->nh.raw + tcphoff);
tcph = (void *)iph + iph->ihl*4;
} }
skb_put((*pskb), TCPOLEN_MSS); skb_put((*pskb), TCPOLEN_MSS);
opt = (u_int8_t *)tcph + sizeof(struct tcphdr); opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr)); memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
nf_proto_csum_replace2(&tcph->check, *pskb, nf_proto_csum_replace2(&tcph->check, *pskb,
htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1); htons(tcplen), htons(tcplen + TCPOLEN_MSS), 1);
opt[0] = TCPOPT_MSS; opt[0] = TCPOPT_MSS;
opt[1] = TCPOLEN_MSS; opt[1] = TCPOLEN_MSS;
opt[2] = (newmss & 0xff00) >> 8; opt[2] = (newmss & 0xff00) >> 8;
...@@ -137,71 +134,163 @@ ipt_tcpmss_target(struct sk_buff **pskb, ...@@ -137,71 +134,163 @@ ipt_tcpmss_target(struct sk_buff **pskb,
tcph->doff += TCPOLEN_MSS/4; tcph->doff += TCPOLEN_MSS/4;
nf_proto_csum_replace2(&tcph->check, *pskb, nf_proto_csum_replace2(&tcph->check, *pskb,
oldval, ((__be16 *)tcph)[6], 0); oldval, ((__be16 *)tcph)[6], 0);
return TCPOLEN_MSS;
}
newtotlen = htons(ntohs(iph->tot_len) + TCPOLEN_MSS); static unsigned int
nf_csum_replace2(&iph->check, iph->tot_len, newtotlen); xt_tcpmss_target4(struct sk_buff **pskb,
iph->tot_len = newtotlen; const struct net_device *in,
return IPT_CONTINUE; const struct net_device *out,
unsigned int hooknum,
const struct xt_target *target,
const void *targinfo)
{
struct iphdr *iph = (*pskb)->nh.iph;
__be16 newlen;
int ret;
ret = tcpmss_mangle_packet(pskb, targinfo, iph->ihl * 4,
sizeof(*iph) + sizeof(struct tcphdr));
if (ret < 0)
return NF_DROP;
if (ret > 0) {
iph = (*pskb)->nh.iph;
newlen = htons(ntohs(iph->tot_len) + ret);
nf_csum_replace2(&iph->check, iph->tot_len, newlen);
iph->tot_len = newlen;
}
return XT_CONTINUE;
} }
#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
static unsigned int
xt_tcpmss_target6(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const struct xt_target *target,
const void *targinfo)
{
struct ipv6hdr *ipv6h = (*pskb)->nh.ipv6h;
u8 nexthdr;
int tcphoff;
int ret;
nexthdr = ipv6h->nexthdr;
tcphoff = ipv6_skip_exthdr(*pskb, sizeof(*ipv6h), &nexthdr);
if (tcphoff < 0) {
WARN_ON(1);
return NF_DROP;
}
ret = tcpmss_mangle_packet(pskb, targinfo, tcphoff,
sizeof(*ipv6h) + sizeof(struct tcphdr));
if (ret < 0)
return NF_DROP;
if (ret > 0) {
ipv6h = (*pskb)->nh.ipv6h;
ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) + ret);
}
return XT_CONTINUE;
}
#endif
#define TH_SYN 0x02 #define TH_SYN 0x02
static inline int find_syn_match(const struct ipt_entry_match *m) /* Must specify -p tcp --syn */
static inline int find_syn_match(const struct xt_entry_match *m)
{ {
const struct ipt_tcp *tcpinfo = (const struct ipt_tcp *)m->data; const struct xt_tcp *tcpinfo = (const struct xt_tcp *)m->data;
if (strcmp(m->u.kernel.match->name, "tcp") == 0 && if (strcmp(m->u.kernel.match->name, "tcp") == 0 &&
tcpinfo->flg_cmp & TH_SYN && tcpinfo->flg_cmp & TH_SYN &&
!(tcpinfo->invflags & IPT_TCP_INV_FLAGS)) !(tcpinfo->invflags & XT_TCP_INV_FLAGS))
return 1; return 1;
return 0; return 0;
} }
/* Must specify -p tcp --syn/--tcp-flags SYN */
static int static int
ipt_tcpmss_checkentry(const char *tablename, xt_tcpmss_checkentry4(const char *tablename,
const void *e_void, const void *entry,
const struct xt_target *target, const struct xt_target *target,
void *targinfo, void *targinfo,
unsigned int hook_mask) unsigned int hook_mask)
{ {
const struct ipt_tcpmss_info *tcpmssinfo = targinfo; const struct xt_tcpmss_info *info = targinfo;
const struct ipt_entry *e = e_void; const struct ipt_entry *e = entry;
if (tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU && if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
(hook_mask & ~((1 << NF_IP_FORWARD) | (hook_mask & ~((1 << NF_IP_FORWARD) |
(1 << NF_IP_LOCAL_OUT) | (1 << NF_IP_LOCAL_OUT) |
(1 << NF_IP_POST_ROUTING))) != 0) { (1 << NF_IP_POST_ROUTING))) != 0) {
printk("TCPMSS: path-MTU clamping only supported in " printk("xt_TCPMSS: path-MTU clamping only supported in "
"FORWARD, OUTPUT and POSTROUTING hooks\n"); "FORWARD, OUTPUT and POSTROUTING hooks\n");
return 0; return 0;
} }
if (IPT_MATCH_ITERATE(e, find_syn_match)) if (IPT_MATCH_ITERATE(e, find_syn_match))
return 1; return 1;
printk("TCPMSS: Only works on TCP SYN packets\n"); printk("xt_TCPMSS: Only works on TCP SYN packets\n");
return 0; return 0;
} }
static struct ipt_target ipt_tcpmss_reg = { #if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
.name = "TCPMSS", static int
.target = ipt_tcpmss_target, xt_tcpmss_checkentry6(const char *tablename,
.targetsize = sizeof(struct ipt_tcpmss_info), const void *entry,
.proto = IPPROTO_TCP, const struct xt_target *target,
.checkentry = ipt_tcpmss_checkentry, void *targinfo,
.me = THIS_MODULE, unsigned int hook_mask)
{
const struct xt_tcpmss_info *info = targinfo;
const struct ip6t_entry *e = entry;
if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
(hook_mask & ~((1 << NF_IP6_FORWARD) |
(1 << NF_IP6_LOCAL_OUT) |
(1 << NF_IP6_POST_ROUTING))) != 0) {
printk("xt_TCPMSS: path-MTU clamping only supported in "
"FORWARD, OUTPUT and POSTROUTING hooks\n");
return 0;
}
if (IP6T_MATCH_ITERATE(e, find_syn_match))
return 1;
printk("xt_TCPMSS: Only works on TCP SYN packets\n");
return 0;
}
#endif
static struct xt_target xt_tcpmss_reg[] = {
{
.family = AF_INET,
.name = "TCPMSS",
.checkentry = xt_tcpmss_checkentry4,
.target = xt_tcpmss_target4,
.targetsize = sizeof(struct xt_tcpmss_info),
.proto = IPPROTO_TCP,
.me = THIS_MODULE,
},
#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE)
{
.family = AF_INET6,
.name = "TCPMSS",
.checkentry = xt_tcpmss_checkentry6,
.target = xt_tcpmss_target6,
.targetsize = sizeof(struct xt_tcpmss_info),
.proto = IPPROTO_TCP,
.me = THIS_MODULE,
},
#endif
}; };
static int __init ipt_tcpmss_init(void) static int __init xt_tcpmss_init(void)
{ {
return ipt_register_target(&ipt_tcpmss_reg); return xt_register_targets(xt_tcpmss_reg, ARRAY_SIZE(xt_tcpmss_reg));
} }
static void __exit ipt_tcpmss_fini(void) static void __exit xt_tcpmss_fini(void)
{ {
ipt_unregister_target(&ipt_tcpmss_reg); xt_unregister_targets(xt_tcpmss_reg, ARRAY_SIZE(xt_tcpmss_reg));
} }
module_init(ipt_tcpmss_init); module_init(xt_tcpmss_init);
module_exit(ipt_tcpmss_fini); module_exit(xt_tcpmss_fini);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment