1 /* iptables module for the IPv4 and TCP ECN bits, Version 1.5 2 * 3 * (C) 2002 by Harald Welte <laforge@netfilter.org> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 as 7 * published by the Free Software Foundation. 8 */ 9 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 10 #include <linux/in.h> 11 #include <linux/module.h> 12 #include <linux/skbuff.h> 13 #include <linux/ip.h> 14 #include <net/ip.h> 15 #include <linux/tcp.h> 16 #include <net/checksum.h> 17 18 #include <linux/netfilter/x_tables.h> 19 #include <linux/netfilter_ipv4/ip_tables.h> 20 #include <linux/netfilter_ipv4/ipt_ECN.h> 21 22 MODULE_LICENSE("GPL"); 23 MODULE_AUTHOR("Harald Welte <laforge@netfilter.org>"); 24 MODULE_DESCRIPTION("Xtables: Explicit Congestion Notification (ECN) flag modification"); 25 26 /* set ECT codepoint from IP header. 27 * return false if there was an error. */ 28 static inline bool 29 set_ect_ip(struct sk_buff *skb, const struct ipt_ECN_info *einfo) 30 { 31 struct iphdr *iph = ip_hdr(skb); 32 33 if ((iph->tos & IPT_ECN_IP_MASK) != (einfo->ip_ect & IPT_ECN_IP_MASK)) { 34 __u8 oldtos; 35 if (!skb_make_writable(skb, sizeof(struct iphdr))) 36 return false; 37 iph = ip_hdr(skb); 38 oldtos = iph->tos; 39 iph->tos &= ~IPT_ECN_IP_MASK; 40 iph->tos |= (einfo->ip_ect & IPT_ECN_IP_MASK); 41 csum_replace2(&iph->check, htons(oldtos), htons(iph->tos)); 42 } 43 return true; 44 } 45 46 /* Return false if there was an error. */ 47 static inline bool 48 set_ect_tcp(struct sk_buff *skb, const struct ipt_ECN_info *einfo) 49 { 50 struct tcphdr _tcph, *tcph; 51 __be16 oldval; 52 53 /* Not enough header? */ 54 tcph = skb_header_pointer(skb, ip_hdrlen(skb), sizeof(_tcph), &_tcph); 55 if (!tcph) 56 return false; 57 58 if ((!(einfo->operation & IPT_ECN_OP_SET_ECE) || 59 tcph->ece == einfo->proto.tcp.ece) && 60 (!(einfo->operation & IPT_ECN_OP_SET_CWR) || 61 tcph->cwr == einfo->proto.tcp.cwr)) 62 return true; 63 64 if (!skb_make_writable(skb, ip_hdrlen(skb) + sizeof(*tcph))) 65 return false; 66 tcph = (void *)ip_hdr(skb) + ip_hdrlen(skb); 67 68 oldval = ((__be16 *)tcph)[6]; 69 if (einfo->operation & IPT_ECN_OP_SET_ECE) 70 tcph->ece = einfo->proto.tcp.ece; 71 if (einfo->operation & IPT_ECN_OP_SET_CWR) 72 tcph->cwr = einfo->proto.tcp.cwr; 73 74 inet_proto_csum_replace2(&tcph->check, skb, 75 oldval, ((__be16 *)tcph)[6], false); 76 return true; 77 } 78 79 static unsigned int 80 ecn_tg(struct sk_buff *skb, const struct xt_action_param *par) 81 { 82 const struct ipt_ECN_info *einfo = par->targinfo; 83 84 if (einfo->operation & IPT_ECN_OP_SET_IP) 85 if (!set_ect_ip(skb, einfo)) 86 return NF_DROP; 87 88 if (einfo->operation & (IPT_ECN_OP_SET_ECE | IPT_ECN_OP_SET_CWR) && 89 ip_hdr(skb)->protocol == IPPROTO_TCP) 90 if (!set_ect_tcp(skb, einfo)) 91 return NF_DROP; 92 93 return XT_CONTINUE; 94 } 95 96 static int ecn_tg_check(const struct xt_tgchk_param *par) 97 { 98 const struct ipt_ECN_info *einfo = par->targinfo; 99 const struct ipt_entry *e = par->entryinfo; 100 101 if (einfo->operation & IPT_ECN_OP_MASK) { 102 pr_info("unsupported ECN operation %x\n", einfo->operation); 103 return -EINVAL; 104 } 105 if (einfo->ip_ect & ~IPT_ECN_IP_MASK) { 106 pr_info("new ECT codepoint %x out of mask\n", einfo->ip_ect); 107 return -EINVAL; 108 } 109 if ((einfo->operation & (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR)) && 110 (e->ip.proto != IPPROTO_TCP || (e->ip.invflags & XT_INV_PROTO))) { 111 pr_info("cannot use TCP operations on a non-tcp rule\n"); 112 return -EINVAL; 113 } 114 return 0; 115 } 116 117 static struct xt_target ecn_tg_reg __read_mostly = { 118 .name = "ECN", 119 .family = NFPROTO_IPV4, 120 .target = ecn_tg, 121 .targetsize = sizeof(struct ipt_ECN_info), 122 .table = "mangle", 123 .checkentry = ecn_tg_check, 124 .me = THIS_MODULE, 125 }; 126 127 static int __init ecn_tg_init(void) 128 { 129 return xt_register_target(&ecn_tg_reg); 130 } 131 132 static void __exit ecn_tg_exit(void) 133 { 134 xt_unregister_target(&ecn_tg_reg); 135 } 136 137 module_init(ecn_tg_init); 138 module_exit(ecn_tg_exit); 139