1 /* 2 * IP6 tables REJECT target module 3 * Linux INET6 implementation 4 * 5 * Copyright (C)2003 USAGI/WIDE Project 6 * 7 * Authors: 8 * Yasuyuki Kozakai <yasuyuki.kozakai@toshiba.co.jp> 9 * 10 * Copyright (c) 2005-2007 Patrick McHardy <kaber@trash.net> 11 * 12 * Based on net/ipv4/netfilter/ipt_REJECT.c 13 * 14 * This program is free software; you can redistribute it and/or 15 * modify it under the terms of the GNU General Public License 16 * as published by the Free Software Foundation; either version 17 * 2 of the License, or (at your option) any later version. 18 */ 19 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 20 21 #include <linux/gfp.h> 22 #include <linux/module.h> 23 #include <linux/skbuff.h> 24 #include <linux/icmpv6.h> 25 #include <linux/netdevice.h> 26 #include <net/ipv6.h> 27 #include <net/tcp.h> 28 #include <net/icmp.h> 29 #include <net/ip6_checksum.h> 30 #include <net/ip6_fib.h> 31 #include <net/ip6_route.h> 32 #include <net/flow.h> 33 #include <linux/netfilter/x_tables.h> 34 #include <linux/netfilter_ipv6/ip6_tables.h> 35 #include <linux/netfilter_ipv6/ip6t_REJECT.h> 36 37 MODULE_AUTHOR("Yasuyuki KOZAKAI <yasuyuki.kozakai@toshiba.co.jp>"); 38 MODULE_DESCRIPTION("Xtables: packet \"rejection\" target for IPv6"); 39 MODULE_LICENSE("GPL"); 40 41 /* Send RST reply */ 42 static void send_reset(struct net *net, struct sk_buff *oldskb) 43 { 44 struct sk_buff *nskb; 45 struct tcphdr otcph, *tcph; 46 unsigned int otcplen, hh_len; 47 int tcphoff, needs_ack; 48 const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); 49 struct ipv6hdr *ip6h; 50 #define DEFAULT_TOS_VALUE 0x0U 51 const __u8 tclass = DEFAULT_TOS_VALUE; 52 struct dst_entry *dst = NULL; 53 u8 proto; 54 __be16 frag_off; 55 struct flowi6 fl6; 56 57 if ((!(ipv6_addr_type(&oip6h->saddr) & IPV6_ADDR_UNICAST)) || 58 (!(ipv6_addr_type(&oip6h->daddr) & IPV6_ADDR_UNICAST))) { 59 pr_debug("addr is not unicast.\n"); 60 return; 61 } 62 63 proto = oip6h->nexthdr; 64 tcphoff = ipv6_skip_exthdr(oldskb, ((u8*)(oip6h+1) - oldskb->data), &proto, &frag_off); 65 66 if ((tcphoff < 0) || (tcphoff > oldskb->len)) { 67 pr_debug("Cannot get TCP header.\n"); 68 return; 69 } 70 71 otcplen = oldskb->len - tcphoff; 72 73 /* IP header checks: fragment, too short. */ 74 if (proto != IPPROTO_TCP || otcplen < sizeof(struct tcphdr)) { 75 pr_debug("proto(%d) != IPPROTO_TCP, " 76 "or too short. otcplen = %d\n", 77 proto, otcplen); 78 return; 79 } 80 81 if (skb_copy_bits(oldskb, tcphoff, &otcph, sizeof(struct tcphdr))) 82 BUG(); 83 84 /* No RST for RST. */ 85 if (otcph.rst) { 86 pr_debug("RST is set\n"); 87 return; 88 } 89 90 /* Check checksum. */ 91 if (csum_ipv6_magic(&oip6h->saddr, &oip6h->daddr, otcplen, IPPROTO_TCP, 92 skb_checksum(oldskb, tcphoff, otcplen, 0))) { 93 pr_debug("TCP checksum is invalid\n"); 94 return; 95 } 96 97 memset(&fl6, 0, sizeof(fl6)); 98 fl6.flowi6_proto = IPPROTO_TCP; 99 fl6.saddr = oip6h->daddr; 100 fl6.daddr = oip6h->saddr; 101 fl6.fl6_sport = otcph.dest; 102 fl6.fl6_dport = otcph.source; 103 security_skb_classify_flow(oldskb, flowi6_to_flowi(&fl6)); 104 dst = ip6_route_output(net, NULL, &fl6); 105 if (dst == NULL || dst->error) { 106 dst_release(dst); 107 return; 108 } 109 dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), NULL, 0); 110 if (IS_ERR(dst)) 111 return; 112 113 hh_len = (dst->dev->hard_header_len + 15)&~15; 114 nskb = alloc_skb(hh_len + 15 + dst->header_len + sizeof(struct ipv6hdr) 115 + sizeof(struct tcphdr) + dst->trailer_len, 116 GFP_ATOMIC); 117 118 if (!nskb) { 119 net_dbg_ratelimited("cannot alloc skb\n"); 120 dst_release(dst); 121 return; 122 } 123 124 skb_dst_set(nskb, dst); 125 126 skb_reserve(nskb, hh_len + dst->header_len); 127 128 skb_put(nskb, sizeof(struct ipv6hdr)); 129 skb_reset_network_header(nskb); 130 ip6h = ipv6_hdr(nskb); 131 ip6_flow_hdr(ip6h, tclass, 0); 132 ip6h->hop_limit = ip6_dst_hoplimit(dst); 133 ip6h->nexthdr = IPPROTO_TCP; 134 ip6h->saddr = oip6h->daddr; 135 ip6h->daddr = oip6h->saddr; 136 137 skb_reset_transport_header(nskb); 138 tcph = (struct tcphdr *)skb_put(nskb, sizeof(struct tcphdr)); 139 /* Truncate to length (no data) */ 140 tcph->doff = sizeof(struct tcphdr)/4; 141 tcph->source = otcph.dest; 142 tcph->dest = otcph.source; 143 144 if (otcph.ack) { 145 needs_ack = 0; 146 tcph->seq = otcph.ack_seq; 147 tcph->ack_seq = 0; 148 } else { 149 needs_ack = 1; 150 tcph->ack_seq = htonl(ntohl(otcph.seq) + otcph.syn + otcph.fin 151 + otcplen - (otcph.doff<<2)); 152 tcph->seq = 0; 153 } 154 155 /* Reset flags */ 156 ((u_int8_t *)tcph)[13] = 0; 157 tcph->rst = 1; 158 tcph->ack = needs_ack; 159 tcph->window = 0; 160 tcph->urg_ptr = 0; 161 tcph->check = 0; 162 163 /* Adjust TCP checksum */ 164 tcph->check = csum_ipv6_magic(&ipv6_hdr(nskb)->saddr, 165 &ipv6_hdr(nskb)->daddr, 166 sizeof(struct tcphdr), IPPROTO_TCP, 167 csum_partial(tcph, 168 sizeof(struct tcphdr), 0)); 169 170 nf_ct_attach(nskb, oldskb); 171 172 #ifdef CONFIG_BRIDGE_NETFILTER 173 /* If we use ip6_local_out for bridged traffic, the MAC source on 174 * the RST will be ours, instead of the destination's. This confuses 175 * some routers/firewalls, and they drop the packet. So we need to 176 * build the eth header using the original destination's MAC as the 177 * source, and send the RST packet directly. 178 */ 179 if (oldskb->nf_bridge) { 180 struct ethhdr *oeth = eth_hdr(oldskb); 181 nskb->dev = oldskb->nf_bridge->physindev; 182 nskb->protocol = htons(ETH_P_IPV6); 183 ip6h->payload_len = htons(sizeof(struct tcphdr)); 184 if (dev_hard_header(nskb, nskb->dev, ntohs(nskb->protocol), 185 oeth->h_source, oeth->h_dest, nskb->len) < 0) 186 return; 187 dev_queue_xmit(nskb); 188 } else 189 #endif 190 ip6_local_out(nskb); 191 } 192 193 static inline void 194 send_unreach(struct net *net, struct sk_buff *skb_in, unsigned char code, 195 unsigned int hooknum) 196 { 197 if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) 198 skb_in->dev = net->loopback_dev; 199 200 icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0); 201 } 202 203 static unsigned int 204 reject_tg6(struct sk_buff *skb, const struct xt_action_param *par) 205 { 206 const struct ip6t_reject_info *reject = par->targinfo; 207 struct net *net = dev_net((par->in != NULL) ? par->in : par->out); 208 209 pr_debug("%s: medium point\n", __func__); 210 switch (reject->with) { 211 case IP6T_ICMP6_NO_ROUTE: 212 send_unreach(net, skb, ICMPV6_NOROUTE, par->hooknum); 213 break; 214 case IP6T_ICMP6_ADM_PROHIBITED: 215 send_unreach(net, skb, ICMPV6_ADM_PROHIBITED, par->hooknum); 216 break; 217 case IP6T_ICMP6_NOT_NEIGHBOUR: 218 send_unreach(net, skb, ICMPV6_NOT_NEIGHBOUR, par->hooknum); 219 break; 220 case IP6T_ICMP6_ADDR_UNREACH: 221 send_unreach(net, skb, ICMPV6_ADDR_UNREACH, par->hooknum); 222 break; 223 case IP6T_ICMP6_PORT_UNREACH: 224 send_unreach(net, skb, ICMPV6_PORT_UNREACH, par->hooknum); 225 break; 226 case IP6T_ICMP6_ECHOREPLY: 227 /* Do nothing */ 228 break; 229 case IP6T_TCP_RESET: 230 send_reset(net, skb); 231 break; 232 default: 233 net_info_ratelimited("case %u not handled yet\n", reject->with); 234 break; 235 } 236 237 return NF_DROP; 238 } 239 240 static int reject_tg6_check(const struct xt_tgchk_param *par) 241 { 242 const struct ip6t_reject_info *rejinfo = par->targinfo; 243 const struct ip6t_entry *e = par->entryinfo; 244 245 if (rejinfo->with == IP6T_ICMP6_ECHOREPLY) { 246 pr_info("ECHOREPLY is not supported.\n"); 247 return -EINVAL; 248 } else if (rejinfo->with == IP6T_TCP_RESET) { 249 /* Must specify that it's a TCP packet */ 250 if (e->ipv6.proto != IPPROTO_TCP || 251 (e->ipv6.invflags & XT_INV_PROTO)) { 252 pr_info("TCP_RESET illegal for non-tcp\n"); 253 return -EINVAL; 254 } 255 } 256 return 0; 257 } 258 259 static struct xt_target reject_tg6_reg __read_mostly = { 260 .name = "REJECT", 261 .family = NFPROTO_IPV6, 262 .target = reject_tg6, 263 .targetsize = sizeof(struct ip6t_reject_info), 264 .table = "filter", 265 .hooks = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD) | 266 (1 << NF_INET_LOCAL_OUT), 267 .checkentry = reject_tg6_check, 268 .me = THIS_MODULE 269 }; 270 271 static int __init reject_tg6_init(void) 272 { 273 return xt_register_target(&reject_tg6_reg); 274 } 275 276 static void __exit reject_tg6_exit(void) 277 { 278 xt_unregister_target(&reject_tg6_reg); 279 } 280 281 module_init(reject_tg6_init); 282 module_exit(reject_tg6_exit); 283
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.