1/* 2 * This is a module which is used for setting the MSS option in TCP packets. 3 * 4 * Copyright (c) 2000 Marc Boucher 5 */ 6#include <linux/module.h> 7#include <linux/skbuff.h> 8 9#include <linux/ip.h> 10#include <net/tcp.h> 11 12#include <linux/netfilter_ipv4/ip_tables.h> 13#include <linux/netfilter_ipv4/ipt_TCPMSS.h> 14 15#define DEBUGP(format, args...) 16 17static u_int16_t 18cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck) 19{ 20 u_int32_t diffs[] = { oldvalinv, newval }; 21 return csum_fold(csum_partial((char *)diffs, sizeof(diffs), 22 oldcheck^0xFFFF)); 23} 24 25static inline unsigned int 26optlen(const u_int8_t *opt, unsigned int offset) 27{ 28 /* Beware zero-length options: make finite progress */ 29 if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0) return 1; 30 else return opt[offset+1]; 31} 32 33static unsigned int 34ipt_tcpmss_target(struct sk_buff **pskb, 35 unsigned int hooknum, 36 const struct net_device *in, 37 const struct net_device *out, 38 const void *targinfo, 39 void *userinfo) 40{ 41 const struct ipt_tcpmss_info *tcpmssinfo = targinfo; 42 struct tcphdr *tcph; 43 struct iphdr *iph; 44 u_int16_t tcplen, newtotlen, oldval, newmss; 45 unsigned int i; 46 u_int8_t *opt; 47 48 /* raw socket (tcpdump) may have clone of incoming skb: don't 49 disturb it --RR */ 50 if (skb_cloned(*pskb) && !(*pskb)->sk) { 51 struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC); 52 if (!nskb) 53 return NF_DROP; 54 kfree_skb(*pskb); 55 *pskb = nskb; 56 } 57 58 iph = (*pskb)->nh.iph; 59 tcplen = (*pskb)->len - iph->ihl*4; 60 61 tcph = (void *)iph + iph->ihl*4; 62 63 /* Since it passed flags test in tcp match, we know it is is 64 not a fragment, and has data >= tcp header length. SYN 65 packets should not contain data: if they did, then we risk 66 running over MTU, sending Frag Needed and breaking things 67 badly. --RR */ 68 if (tcplen != tcph->doff*4) { 69 if (net_ratelimit()) 70 printk(KERN_ERR 71 "ipt_tcpmss_target: bad length (%d bytes)\n", 72 (*pskb)->len); 73 return NF_DROP; 74 } 75 76 if(tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) { 77 if(!(*pskb)->dst) { 78 if (net_ratelimit()) 79 printk(KERN_ERR 80 "ipt_tcpmss_target: no dst?! can't determine path-MTU\n"); 81 return NF_DROP; /* or IPT_CONTINUE ?? */ 82 } 83 84 if((*pskb)->dst->pmtu <= (sizeof(struct iphdr) + sizeof(struct tcphdr))) { 85 if (net_ratelimit()) 86 printk(KERN_ERR 87 "ipt_tcpmss_target: unknown or invalid path-MTU (%d)\n", (*pskb)->dst->pmtu); 88 return NF_DROP; /* or IPT_CONTINUE ?? */ 89 } 90 91 newmss = (*pskb)->dst->pmtu - sizeof(struct iphdr) - sizeof(struct tcphdr); 92 } else 93 newmss = tcpmssinfo->mss; 94 95 opt = (u_int8_t *)tcph; 96 for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)){ 97 if ((opt[i] == TCPOPT_MSS) && 98 ((tcph->doff*4 - i) >= TCPOLEN_MSS) && 99 (opt[i+1] == TCPOLEN_MSS)) { 100 u_int16_t oldmss; 101 102 oldmss = (opt[i+2] << 8) | opt[i+3]; 103 104 if((tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) && 105 (oldmss <= newmss)) 106 return IPT_CONTINUE; 107 108 opt[i+2] = (newmss & 0xff00) >> 8; 109 opt[i+3] = (newmss & 0x00ff); 110 111 tcph->check = cheat_check(htons(oldmss)^0xFFFF, 112 htons(newmss), 113 tcph->check); 114 115 DEBUGP(KERN_INFO "ipt_tcpmss_target: %u.%u.%u.%u:%hu" 116 "->%u.%u.%u.%u:%hu changed TCP MSS option" 117 " (from %u to %u)\n", 118 NIPQUAD((*pskb)->nh.iph->saddr), 119 ntohs(tcph->source), 120 NIPQUAD((*pskb)->nh.iph->daddr), 121 ntohs(tcph->dest), 122 oldmss, newmss); 123 goto retmodified; 124 } 125 } 126 127 /* 128 * MSS Option not found ?! add it.. 129 */ 130 if (skb_tailroom((*pskb)) < TCPOLEN_MSS) { 131 struct sk_buff *newskb; 132 133 newskb = skb_copy_expand(*pskb, skb_headroom(*pskb), 134 TCPOLEN_MSS, GFP_ATOMIC); 135 if (!newskb) { 136 if (net_ratelimit()) 137 printk(KERN_ERR "ipt_tcpmss_target:" 138 " unable to allocate larger skb\n"); 139 return NF_DROP; 140 } 141 142 kfree_skb(*pskb); 143 *pskb = newskb; 144 iph = (*pskb)->nh.iph; 145 tcph = (void *)iph + iph->ihl*4; 146 } 147 148 skb_put((*pskb), TCPOLEN_MSS); 149 150 opt = (u_int8_t *)tcph + sizeof(struct tcphdr); 151 memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr)); 152 153 tcph->check = cheat_check(htons(tcplen) ^ 0xFFFF, 154 htons(tcplen + TCPOLEN_MSS), tcph->check); 155 tcplen += TCPOLEN_MSS; 156 157 opt[0] = TCPOPT_MSS; 158 opt[1] = TCPOLEN_MSS; 159 opt[2] = (newmss & 0xff00) >> 8; 160 opt[3] = (newmss & 0x00ff); 161 162 tcph->check = cheat_check(~0, *((u_int32_t *)opt), tcph->check); 163 164 oldval = ((u_int16_t *)tcph)[6]; 165 tcph->doff += TCPOLEN_MSS/4; 166 tcph->check = cheat_check(oldval ^ 0xFFFF, 167 ((u_int16_t *)tcph)[6], tcph->check); 168 169 newtotlen = htons(ntohs(iph->tot_len) + TCPOLEN_MSS); 170 iph->check = cheat_check(iph->tot_len ^ 0xFFFF, 171 newtotlen, iph->check); 172 iph->tot_len = newtotlen; 173 174 DEBUGP(KERN_INFO "ipt_tcpmss_target: %u.%u.%u.%u:%hu" 175 "->%u.%u.%u.%u:%hu added TCP MSS option (%u)\n", 176 NIPQUAD((*pskb)->nh.iph->saddr), 177 ntohs(tcph->source), 178 NIPQUAD((*pskb)->nh.iph->daddr), 179 ntohs(tcph->dest), 180 newmss); 181 182 retmodified: 183 /* If we had a hardware checksum before, it's now invalid */ 184 (*pskb)->ip_summed = CHECKSUM_NONE; 185 (*pskb)->nfcache |= NFC_UNKNOWN | NFC_ALTERED; 186 return IPT_CONTINUE; 187} 188 189#define TH_SYN 0x02 190 191static inline int find_syn_match(const struct ipt_entry_match *m) 192{ 193 const struct ipt_tcp *tcpinfo = (const struct ipt_tcp *)m->data; 194 195 if (strcmp(m->u.kernel.match->name, "tcp") == 0 196 && (tcpinfo->flg_cmp & TH_SYN) 197 && !(tcpinfo->invflags & IPT_TCP_INV_FLAGS)) 198 return 1; 199 200 return 0; 201} 202 203/* Must specify -p tcp --syn/--tcp-flags SYN */ 204static int 205ipt_tcpmss_checkentry(const char *tablename, 206 const struct ipt_entry *e, 207 void *targinfo, 208 unsigned int targinfosize, 209 unsigned int hook_mask) 210{ 211 const struct ipt_tcpmss_info *tcpmssinfo = targinfo; 212 213 if (targinfosize != IPT_ALIGN(sizeof(struct ipt_tcpmss_info))) { 214 DEBUGP("ipt_tcpmss_checkentry: targinfosize %u != %u\n", 215 targinfosize, IPT_ALIGN(sizeof(struct ipt_tcpmss_info))); 216 return 0; 217 } 218 219 220 if((tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) && 221 ((hook_mask & ~((1 << NF_IP_FORWARD) 222 | (1 << NF_IP_LOCAL_OUT) 223 | (1 << NF_IP_POST_ROUTING))) != 0)) { 224 printk("TCPMSS: path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n"); 225 return 0; 226 } 227 228 if (e->ip.proto == IPPROTO_TCP 229 && !(e->ip.invflags & IPT_INV_PROTO) 230 && IPT_MATCH_ITERATE(e, find_syn_match)) 231 return 1; 232 233 printk("TCPMSS: Only works on TCP SYN packets\n"); 234 return 0; 235} 236 237static struct ipt_target ipt_tcpmss_reg 238= { { NULL, NULL }, "TCPMSS", 239 ipt_tcpmss_target, ipt_tcpmss_checkentry, NULL, THIS_MODULE }; 240 241static int __init init(void) 242{ 243 return ipt_register_target(&ipt_tcpmss_reg); 244} 245 246static void __exit fini(void) 247{ 248 ipt_unregister_target(&ipt_tcpmss_reg); 249} 250 251module_init(init); 252module_exit(fini); 253MODULE_LICENSE("GPL"); 254