at v2.6.38-rc2 373 lines 9.7 kB view raw
1/* 2 * Transparent proxy support for Linux/iptables 3 * 4 * Copyright (C) 2007-2008 BalaBit IT Ltd. 5 * Author: Krisztian Kovacs 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 */ 12#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 13#include <linux/module.h> 14#include <linux/skbuff.h> 15#include <linux/netfilter/x_tables.h> 16#include <linux/netfilter_ipv4/ip_tables.h> 17#include <net/tcp.h> 18#include <net/udp.h> 19#include <net/icmp.h> 20#include <net/sock.h> 21#include <net/inet_sock.h> 22#include <net/netfilter/nf_tproxy_core.h> 23#include <net/netfilter/ipv4/nf_defrag_ipv4.h> 24 25#if defined(CONFIG_IP6_NF_IPTABLES) || defined(CONFIG_IP6_NF_IPTABLES_MODULE) 26#define XT_SOCKET_HAVE_IPV6 1 27#include <linux/netfilter_ipv6/ip6_tables.h> 28#include <net/netfilter/ipv6/nf_defrag_ipv6.h> 29#endif 30 31#include <linux/netfilter/xt_socket.h> 32 33#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) 34#define XT_SOCKET_HAVE_CONNTRACK 1 35#include <net/netfilter/nf_conntrack.h> 36#endif 37 38static int 39extract_icmp4_fields(const struct sk_buff *skb, 40 u8 *protocol, 41 __be32 *raddr, 42 __be32 *laddr, 43 __be16 *rport, 44 __be16 *lport) 45{ 46 unsigned int outside_hdrlen = ip_hdrlen(skb); 47 struct iphdr *inside_iph, _inside_iph; 48 struct icmphdr *icmph, _icmph; 49 __be16 *ports, _ports[2]; 50 51 icmph = skb_header_pointer(skb, outside_hdrlen, 52 sizeof(_icmph), &_icmph); 53 if (icmph == NULL) 54 return 1; 55 56 switch (icmph->type) { 57 case ICMP_DEST_UNREACH: 58 case ICMP_SOURCE_QUENCH: 59 case ICMP_REDIRECT: 60 case ICMP_TIME_EXCEEDED: 61 case ICMP_PARAMETERPROB: 62 break; 63 default: 64 return 1; 65 } 66 67 inside_iph = skb_header_pointer(skb, outside_hdrlen + 68 sizeof(struct icmphdr), 69 sizeof(_inside_iph), &_inside_iph); 70 if (inside_iph == NULL) 71 return 1; 72 73 if (inside_iph->protocol != IPPROTO_TCP && 74 inside_iph->protocol != IPPROTO_UDP) 75 return 1; 76 77 ports = skb_header_pointer(skb, outside_hdrlen + 78 sizeof(struct icmphdr) + 79 (inside_iph->ihl << 2), 80 sizeof(_ports), &_ports); 81 if (ports == NULL) 82 return 1; 83 84 /* the inside IP packet is the one quoted from our side, thus 85 * its saddr is the local address */ 86 *protocol = inside_iph->protocol; 87 *laddr = inside_iph->saddr; 88 *lport = ports[0]; 89 *raddr = inside_iph->daddr; 90 *rport = ports[1]; 91 92 return 0; 93} 94 95static bool 96socket_match(const struct sk_buff *skb, struct xt_action_param *par, 97 const struct xt_socket_mtinfo1 *info) 98{ 99 const struct iphdr *iph = ip_hdr(skb); 100 struct udphdr _hdr, *hp = NULL; 101 struct sock *sk; 102 __be32 daddr, saddr; 103 __be16 dport, sport; 104 u8 protocol; 105#ifdef XT_SOCKET_HAVE_CONNTRACK 106 struct nf_conn const *ct; 107 enum ip_conntrack_info ctinfo; 108#endif 109 110 if (iph->protocol == IPPROTO_UDP || iph->protocol == IPPROTO_TCP) { 111 hp = skb_header_pointer(skb, ip_hdrlen(skb), 112 sizeof(_hdr), &_hdr); 113 if (hp == NULL) 114 return false; 115 116 protocol = iph->protocol; 117 saddr = iph->saddr; 118 sport = hp->source; 119 daddr = iph->daddr; 120 dport = hp->dest; 121 122 } else if (iph->protocol == IPPROTO_ICMP) { 123 if (extract_icmp4_fields(skb, &protocol, &saddr, &daddr, 124 &sport, &dport)) 125 return false; 126 } else { 127 return false; 128 } 129 130#ifdef XT_SOCKET_HAVE_CONNTRACK 131 /* Do the lookup with the original socket address in case this is a 132 * reply packet of an established SNAT-ted connection. */ 133 134 ct = nf_ct_get(skb, &ctinfo); 135 if (ct && !nf_ct_is_untracked(ct) && 136 ((iph->protocol != IPPROTO_ICMP && 137 ctinfo == IP_CT_IS_REPLY + IP_CT_ESTABLISHED) || 138 (iph->protocol == IPPROTO_ICMP && 139 ctinfo == IP_CT_IS_REPLY + IP_CT_RELATED)) && 140 (ct->status & IPS_SRC_NAT_DONE)) { 141 142 daddr = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip; 143 dport = (iph->protocol == IPPROTO_TCP) ? 144 ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.tcp.port : 145 ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port; 146 } 147#endif 148 149 sk = nf_tproxy_get_sock_v4(dev_net(skb->dev), protocol, 150 saddr, daddr, sport, dport, par->in, NFT_LOOKUP_ANY); 151 if (sk != NULL) { 152 bool wildcard; 153 bool transparent = true; 154 155 /* Ignore sockets listening on INADDR_ANY */ 156 wildcard = (sk->sk_state != TCP_TIME_WAIT && 157 inet_sk(sk)->inet_rcv_saddr == 0); 158 159 /* Ignore non-transparent sockets, 160 if XT_SOCKET_TRANSPARENT is used */ 161 if (info && info->flags & XT_SOCKET_TRANSPARENT) 162 transparent = ((sk->sk_state != TCP_TIME_WAIT && 163 inet_sk(sk)->transparent) || 164 (sk->sk_state == TCP_TIME_WAIT && 165 inet_twsk(sk)->tw_transparent)); 166 167 nf_tproxy_put_sock(sk); 168 169 if (wildcard || !transparent) 170 sk = NULL; 171 } 172 173 pr_debug("proto %hhu %pI4:%hu -> %pI4:%hu (orig %pI4:%hu) sock %p\n", 174 protocol, &saddr, ntohs(sport), 175 &daddr, ntohs(dport), 176 &iph->daddr, hp ? ntohs(hp->dest) : 0, sk); 177 178 return (sk != NULL); 179} 180 181static bool 182socket_mt4_v0(const struct sk_buff *skb, struct xt_action_param *par) 183{ 184 return socket_match(skb, par, NULL); 185} 186 187static bool 188socket_mt4_v1(const struct sk_buff *skb, struct xt_action_param *par) 189{ 190 return socket_match(skb, par, par->matchinfo); 191} 192 193#ifdef XT_SOCKET_HAVE_IPV6 194 195static int 196extract_icmp6_fields(const struct sk_buff *skb, 197 unsigned int outside_hdrlen, 198 int *protocol, 199 struct in6_addr **raddr, 200 struct in6_addr **laddr, 201 __be16 *rport, 202 __be16 *lport) 203{ 204 struct ipv6hdr *inside_iph, _inside_iph; 205 struct icmp6hdr *icmph, _icmph; 206 __be16 *ports, _ports[2]; 207 u8 inside_nexthdr; 208 int inside_hdrlen; 209 210 icmph = skb_header_pointer(skb, outside_hdrlen, 211 sizeof(_icmph), &_icmph); 212 if (icmph == NULL) 213 return 1; 214 215 if (icmph->icmp6_type & ICMPV6_INFOMSG_MASK) 216 return 1; 217 218 inside_iph = skb_header_pointer(skb, outside_hdrlen + sizeof(_icmph), sizeof(_inside_iph), &_inside_iph); 219 if (inside_iph == NULL) 220 return 1; 221 inside_nexthdr = inside_iph->nexthdr; 222 223 inside_hdrlen = ipv6_skip_exthdr(skb, outside_hdrlen + sizeof(_icmph) + sizeof(_inside_iph), &inside_nexthdr); 224 if (inside_hdrlen < 0) 225 return 1; /* hjm: Packet has no/incomplete transport layer headers. */ 226 227 if (inside_nexthdr != IPPROTO_TCP && 228 inside_nexthdr != IPPROTO_UDP) 229 return 1; 230 231 ports = skb_header_pointer(skb, inside_hdrlen, 232 sizeof(_ports), &_ports); 233 if (ports == NULL) 234 return 1; 235 236 /* the inside IP packet is the one quoted from our side, thus 237 * its saddr is the local address */ 238 *protocol = inside_nexthdr; 239 *laddr = &inside_iph->saddr; 240 *lport = ports[0]; 241 *raddr = &inside_iph->daddr; 242 *rport = ports[1]; 243 244 return 0; 245} 246 247static bool 248socket_mt6_v1(const struct sk_buff *skb, struct xt_action_param *par) 249{ 250 struct ipv6hdr *iph = ipv6_hdr(skb); 251 struct udphdr _hdr, *hp = NULL; 252 struct sock *sk; 253 struct in6_addr *daddr, *saddr; 254 __be16 dport, sport; 255 int thoff, tproto; 256 const struct xt_socket_mtinfo1 *info = (struct xt_socket_mtinfo1 *) par->matchinfo; 257 258 tproto = ipv6_find_hdr(skb, &thoff, -1, NULL); 259 if (tproto < 0) { 260 pr_debug("unable to find transport header in IPv6 packet, dropping\n"); 261 return NF_DROP; 262 } 263 264 if (tproto == IPPROTO_UDP || tproto == IPPROTO_TCP) { 265 hp = skb_header_pointer(skb, thoff, 266 sizeof(_hdr), &_hdr); 267 if (hp == NULL) 268 return false; 269 270 saddr = &iph->saddr; 271 sport = hp->source; 272 daddr = &iph->daddr; 273 dport = hp->dest; 274 275 } else if (tproto == IPPROTO_ICMPV6) { 276 if (extract_icmp6_fields(skb, thoff, &tproto, &saddr, &daddr, 277 &sport, &dport)) 278 return false; 279 } else { 280 return false; 281 } 282 283 sk = nf_tproxy_get_sock_v6(dev_net(skb->dev), tproto, 284 saddr, daddr, sport, dport, par->in, NFT_LOOKUP_ANY); 285 if (sk != NULL) { 286 bool wildcard; 287 bool transparent = true; 288 289 /* Ignore sockets listening on INADDR_ANY */ 290 wildcard = (sk->sk_state != TCP_TIME_WAIT && 291 ipv6_addr_any(&inet6_sk(sk)->rcv_saddr)); 292 293 /* Ignore non-transparent sockets, 294 if XT_SOCKET_TRANSPARENT is used */ 295 if (info && info->flags & XT_SOCKET_TRANSPARENT) 296 transparent = ((sk->sk_state != TCP_TIME_WAIT && 297 inet_sk(sk)->transparent) || 298 (sk->sk_state == TCP_TIME_WAIT && 299 inet_twsk(sk)->tw_transparent)); 300 301 nf_tproxy_put_sock(sk); 302 303 if (wildcard || !transparent) 304 sk = NULL; 305 } 306 307 pr_debug("proto %hhd %pI6:%hu -> %pI6:%hu " 308 "(orig %pI6:%hu) sock %p\n", 309 tproto, saddr, ntohs(sport), 310 daddr, ntohs(dport), 311 &iph->daddr, hp ? ntohs(hp->dest) : 0, sk); 312 313 return (sk != NULL); 314} 315#endif 316 317static struct xt_match socket_mt_reg[] __read_mostly = { 318 { 319 .name = "socket", 320 .revision = 0, 321 .family = NFPROTO_IPV4, 322 .match = socket_mt4_v0, 323 .hooks = (1 << NF_INET_PRE_ROUTING) | 324 (1 << NF_INET_LOCAL_IN), 325 .me = THIS_MODULE, 326 }, 327 { 328 .name = "socket", 329 .revision = 1, 330 .family = NFPROTO_IPV4, 331 .match = socket_mt4_v1, 332 .matchsize = sizeof(struct xt_socket_mtinfo1), 333 .hooks = (1 << NF_INET_PRE_ROUTING) | 334 (1 << NF_INET_LOCAL_IN), 335 .me = THIS_MODULE, 336 }, 337#ifdef XT_SOCKET_HAVE_IPV6 338 { 339 .name = "socket", 340 .revision = 1, 341 .family = NFPROTO_IPV6, 342 .match = socket_mt6_v1, 343 .matchsize = sizeof(struct xt_socket_mtinfo1), 344 .hooks = (1 << NF_INET_PRE_ROUTING) | 345 (1 << NF_INET_LOCAL_IN), 346 .me = THIS_MODULE, 347 }, 348#endif 349}; 350 351static int __init socket_mt_init(void) 352{ 353 nf_defrag_ipv4_enable(); 354#ifdef XT_SOCKET_HAVE_IPV6 355 nf_defrag_ipv6_enable(); 356#endif 357 358 return xt_register_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg)); 359} 360 361static void __exit socket_mt_exit(void) 362{ 363 xt_unregister_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg)); 364} 365 366module_init(socket_mt_init); 367module_exit(socket_mt_exit); 368 369MODULE_LICENSE("GPL"); 370MODULE_AUTHOR("Krisztian Kovacs, Balazs Scheidler"); 371MODULE_DESCRIPTION("x_tables socket match module"); 372MODULE_ALIAS("ipt_socket"); 373MODULE_ALIAS("ip6t_socket");