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#include <linux/netfilter/xt_socket.h> 26 27#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) 28#define XT_SOCKET_HAVE_CONNTRACK 1 29#include <net/netfilter/nf_conntrack.h> 30#endif 31 32static int 33extract_icmp_fields(const struct sk_buff *skb, 34 u8 *protocol, 35 __be32 *raddr, 36 __be32 *laddr, 37 __be16 *rport, 38 __be16 *lport) 39{ 40 unsigned int outside_hdrlen = ip_hdrlen(skb); 41 struct iphdr *inside_iph, _inside_iph; 42 struct icmphdr *icmph, _icmph; 43 __be16 *ports, _ports[2]; 44 45 icmph = skb_header_pointer(skb, outside_hdrlen, 46 sizeof(_icmph), &_icmph); 47 if (icmph == NULL) 48 return 1; 49 50 switch (icmph->type) { 51 case ICMP_DEST_UNREACH: 52 case ICMP_SOURCE_QUENCH: 53 case ICMP_REDIRECT: 54 case ICMP_TIME_EXCEEDED: 55 case ICMP_PARAMETERPROB: 56 break; 57 default: 58 return 1; 59 } 60 61 inside_iph = skb_header_pointer(skb, outside_hdrlen + 62 sizeof(struct icmphdr), 63 sizeof(_inside_iph), &_inside_iph); 64 if (inside_iph == NULL) 65 return 1; 66 67 if (inside_iph->protocol != IPPROTO_TCP && 68 inside_iph->protocol != IPPROTO_UDP) 69 return 1; 70 71 ports = skb_header_pointer(skb, outside_hdrlen + 72 sizeof(struct icmphdr) + 73 (inside_iph->ihl << 2), 74 sizeof(_ports), &_ports); 75 if (ports == NULL) 76 return 1; 77 78 /* the inside IP packet is the one quoted from our side, thus 79 * its saddr is the local address */ 80 *protocol = inside_iph->protocol; 81 *laddr = inside_iph->saddr; 82 *lport = ports[0]; 83 *raddr = inside_iph->daddr; 84 *rport = ports[1]; 85 86 return 0; 87} 88 89 90static bool 91socket_match(const struct sk_buff *skb, struct xt_action_param *par, 92 const struct xt_socket_mtinfo1 *info) 93{ 94 const struct iphdr *iph = ip_hdr(skb); 95 struct udphdr _hdr, *hp = NULL; 96 struct sock *sk; 97 __be32 daddr, saddr; 98 __be16 dport, sport; 99 u8 protocol; 100#ifdef XT_SOCKET_HAVE_CONNTRACK 101 struct nf_conn const *ct; 102 enum ip_conntrack_info ctinfo; 103#endif 104 105 if (iph->protocol == IPPROTO_UDP || iph->protocol == IPPROTO_TCP) { 106 hp = skb_header_pointer(skb, ip_hdrlen(skb), 107 sizeof(_hdr), &_hdr); 108 if (hp == NULL) 109 return false; 110 111 protocol = iph->protocol; 112 saddr = iph->saddr; 113 sport = hp->source; 114 daddr = iph->daddr; 115 dport = hp->dest; 116 117 } else if (iph->protocol == IPPROTO_ICMP) { 118 if (extract_icmp_fields(skb, &protocol, &saddr, &daddr, 119 &sport, &dport)) 120 return false; 121 } else { 122 return false; 123 } 124 125#ifdef XT_SOCKET_HAVE_CONNTRACK 126 /* Do the lookup with the original socket address in case this is a 127 * reply packet of an established SNAT-ted connection. */ 128 129 ct = nf_ct_get(skb, &ctinfo); 130 if (ct && !nf_ct_is_untracked(ct) && 131 ((iph->protocol != IPPROTO_ICMP && 132 ctinfo == IP_CT_IS_REPLY + IP_CT_ESTABLISHED) || 133 (iph->protocol == IPPROTO_ICMP && 134 ctinfo == IP_CT_IS_REPLY + IP_CT_RELATED)) && 135 (ct->status & IPS_SRC_NAT_DONE)) { 136 137 daddr = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip; 138 dport = (iph->protocol == IPPROTO_TCP) ? 139 ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.tcp.port : 140 ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port; 141 } 142#endif 143 144 sk = nf_tproxy_get_sock_v4(dev_net(skb->dev), protocol, 145 saddr, daddr, sport, dport, par->in, false); 146 if (sk != NULL) { 147 bool wildcard; 148 bool transparent = true; 149 150 /* Ignore sockets listening on INADDR_ANY */ 151 wildcard = (sk->sk_state != TCP_TIME_WAIT && 152 inet_sk(sk)->inet_rcv_saddr == 0); 153 154 /* Ignore non-transparent sockets, 155 if XT_SOCKET_TRANSPARENT is used */ 156 if (info && info->flags & XT_SOCKET_TRANSPARENT) 157 transparent = ((sk->sk_state != TCP_TIME_WAIT && 158 inet_sk(sk)->transparent) || 159 (sk->sk_state == TCP_TIME_WAIT && 160 inet_twsk(sk)->tw_transparent)); 161 162 nf_tproxy_put_sock(sk); 163 164 if (wildcard || !transparent) 165 sk = NULL; 166 } 167 168 pr_debug("proto %u %08x:%u -> %08x:%u (orig %08x:%u) sock %p\n", 169 protocol, ntohl(saddr), ntohs(sport), 170 ntohl(daddr), ntohs(dport), 171 ntohl(iph->daddr), hp ? ntohs(hp->dest) : 0, sk); 172 173 return (sk != NULL); 174} 175 176static bool 177socket_mt_v0(const struct sk_buff *skb, struct xt_action_param *par) 178{ 179 return socket_match(skb, par, NULL); 180} 181 182static bool 183socket_mt_v1(const struct sk_buff *skb, struct xt_action_param *par) 184{ 185 return socket_match(skb, par, par->matchinfo); 186} 187 188static struct xt_match socket_mt_reg[] __read_mostly = { 189 { 190 .name = "socket", 191 .revision = 0, 192 .family = NFPROTO_IPV4, 193 .match = socket_mt_v0, 194 .hooks = (1 << NF_INET_PRE_ROUTING) | 195 (1 << NF_INET_LOCAL_IN), 196 .me = THIS_MODULE, 197 }, 198 { 199 .name = "socket", 200 .revision = 1, 201 .family = NFPROTO_IPV4, 202 .match = socket_mt_v1, 203 .matchsize = sizeof(struct xt_socket_mtinfo1), 204 .hooks = (1 << NF_INET_PRE_ROUTING) | 205 (1 << NF_INET_LOCAL_IN), 206 .me = THIS_MODULE, 207 }, 208}; 209 210static int __init socket_mt_init(void) 211{ 212 nf_defrag_ipv4_enable(); 213 return xt_register_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg)); 214} 215 216static void __exit socket_mt_exit(void) 217{ 218 xt_unregister_matches(socket_mt_reg, ARRAY_SIZE(socket_mt_reg)); 219} 220 221module_init(socket_mt_init); 222module_exit(socket_mt_exit); 223 224MODULE_LICENSE("GPL"); 225MODULE_AUTHOR("Krisztian Kovacs, Balazs Scheidler"); 226MODULE_DESCRIPTION("x_tables socket match module"); 227MODULE_ALIAS("ipt_socket"); 228