|
| 1 | +/* |
| 2 | + * Copyright (C) 2007-2008 BalaBit IT Ltd. |
| 3 | + * Author: Krisztian Kovacs |
| 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 | + */ |
| 10 | +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 11 | +#include <linux/module.h> |
| 12 | +#include <linux/skbuff.h> |
| 13 | +#include <net/tcp.h> |
| 14 | +#include <net/udp.h> |
| 15 | +#include <net/icmp.h> |
| 16 | +#include <net/sock.h> |
| 17 | +#include <net/inet_sock.h> |
| 18 | +#include <net/netfilter/nf_socket.h> |
| 19 | +#if IS_ENABLED(CONFIG_NF_CONNTRACK) |
| 20 | +#include <net/netfilter/nf_conntrack.h> |
| 21 | +#endif |
| 22 | + |
| 23 | +static int |
| 24 | +extract_icmp4_fields(const struct sk_buff *skb, u8 *protocol, |
| 25 | + __be32 *raddr, __be32 *laddr, |
| 26 | + __be16 *rport, __be16 *lport) |
| 27 | +{ |
| 28 | + unsigned int outside_hdrlen = ip_hdrlen(skb); |
| 29 | + struct iphdr *inside_iph, _inside_iph; |
| 30 | + struct icmphdr *icmph, _icmph; |
| 31 | + __be16 *ports, _ports[2]; |
| 32 | + |
| 33 | + icmph = skb_header_pointer(skb, outside_hdrlen, |
| 34 | + sizeof(_icmph), &_icmph); |
| 35 | + if (icmph == NULL) |
| 36 | + return 1; |
| 37 | + |
| 38 | + switch (icmph->type) { |
| 39 | + case ICMP_DEST_UNREACH: |
| 40 | + case ICMP_SOURCE_QUENCH: |
| 41 | + case ICMP_REDIRECT: |
| 42 | + case ICMP_TIME_EXCEEDED: |
| 43 | + case ICMP_PARAMETERPROB: |
| 44 | + break; |
| 45 | + default: |
| 46 | + return 1; |
| 47 | + } |
| 48 | + |
| 49 | + inside_iph = skb_header_pointer(skb, outside_hdrlen + |
| 50 | + sizeof(struct icmphdr), |
| 51 | + sizeof(_inside_iph), &_inside_iph); |
| 52 | + if (inside_iph == NULL) |
| 53 | + return 1; |
| 54 | + |
| 55 | + if (inside_iph->protocol != IPPROTO_TCP && |
| 56 | + inside_iph->protocol != IPPROTO_UDP) |
| 57 | + return 1; |
| 58 | + |
| 59 | + ports = skb_header_pointer(skb, outside_hdrlen + |
| 60 | + sizeof(struct icmphdr) + |
| 61 | + (inside_iph->ihl << 2), |
| 62 | + sizeof(_ports), &_ports); |
| 63 | + if (ports == NULL) |
| 64 | + return 1; |
| 65 | + |
| 66 | + /* the inside IP packet is the one quoted from our side, thus |
| 67 | + * its saddr is the local address */ |
| 68 | + *protocol = inside_iph->protocol; |
| 69 | + *laddr = inside_iph->saddr; |
| 70 | + *lport = ports[0]; |
| 71 | + *raddr = inside_iph->daddr; |
| 72 | + *rport = ports[1]; |
| 73 | + |
| 74 | + return 0; |
| 75 | +} |
| 76 | + |
| 77 | +static struct sock * |
| 78 | +nf_socket_get_sock_v4(struct net *net, struct sk_buff *skb, const int doff, |
| 79 | + const u8 protocol, |
| 80 | + const __be32 saddr, const __be32 daddr, |
| 81 | + const __be16 sport, const __be16 dport, |
| 82 | + const struct net_device *in) |
| 83 | +{ |
| 84 | + switch (protocol) { |
| 85 | + case IPPROTO_TCP: |
| 86 | + return inet_lookup(net, &tcp_hashinfo, skb, doff, |
| 87 | + saddr, sport, daddr, dport, |
| 88 | + in->ifindex); |
| 89 | + case IPPROTO_UDP: |
| 90 | + return udp4_lib_lookup(net, saddr, sport, daddr, dport, |
| 91 | + in->ifindex); |
| 92 | + } |
| 93 | + return NULL; |
| 94 | +} |
| 95 | + |
| 96 | +struct sock *nf_sk_lookup_slow_v4(struct net *net, const struct sk_buff *skb, |
| 97 | + const struct net_device *indev) |
| 98 | +{ |
| 99 | + __be32 uninitialized_var(daddr), uninitialized_var(saddr); |
| 100 | + __be16 uninitialized_var(dport), uninitialized_var(sport); |
| 101 | + const struct iphdr *iph = ip_hdr(skb); |
| 102 | + struct sk_buff *data_skb = NULL; |
| 103 | + u8 uninitialized_var(protocol); |
| 104 | +#if IS_ENABLED(CONFIG_NF_CONNTRACK) |
| 105 | + enum ip_conntrack_info ctinfo; |
| 106 | + struct nf_conn const *ct; |
| 107 | +#endif |
| 108 | + int doff = 0; |
| 109 | + |
| 110 | + if (iph->protocol == IPPROTO_UDP || iph->protocol == IPPROTO_TCP) { |
| 111 | + struct udphdr _hdr, *hp; |
| 112 | + |
| 113 | + hp = skb_header_pointer(skb, ip_hdrlen(skb), |
| 114 | + sizeof(_hdr), &_hdr); |
| 115 | + if (hp == NULL) |
| 116 | + return NULL; |
| 117 | + |
| 118 | + protocol = iph->protocol; |
| 119 | + saddr = iph->saddr; |
| 120 | + sport = hp->source; |
| 121 | + daddr = iph->daddr; |
| 122 | + dport = hp->dest; |
| 123 | + data_skb = (struct sk_buff *)skb; |
| 124 | + doff = iph->protocol == IPPROTO_TCP ? |
| 125 | + ip_hdrlen(skb) + __tcp_hdrlen((struct tcphdr *)hp) : |
| 126 | + ip_hdrlen(skb) + sizeof(*hp); |
| 127 | + |
| 128 | + } else if (iph->protocol == IPPROTO_ICMP) { |
| 129 | + if (extract_icmp4_fields(skb, &protocol, &saddr, &daddr, |
| 130 | + &sport, &dport)) |
| 131 | + return NULL; |
| 132 | + } else { |
| 133 | + return NULL; |
| 134 | + } |
| 135 | + |
| 136 | +#if IS_ENABLED(CONFIG_NF_CONNTRACK) |
| 137 | + /* Do the lookup with the original socket address in |
| 138 | + * case this is a reply packet of an established |
| 139 | + * SNAT-ted connection. |
| 140 | + */ |
| 141 | + ct = nf_ct_get(skb, &ctinfo); |
| 142 | + if (ct && !nf_ct_is_untracked(ct) && |
| 143 | + ((iph->protocol != IPPROTO_ICMP && |
| 144 | + ctinfo == IP_CT_ESTABLISHED_REPLY) || |
| 145 | + (iph->protocol == IPPROTO_ICMP && |
| 146 | + ctinfo == IP_CT_RELATED_REPLY)) && |
| 147 | + (ct->status & IPS_SRC_NAT_DONE)) { |
| 148 | + |
| 149 | + daddr = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip; |
| 150 | + dport = (iph->protocol == IPPROTO_TCP) ? |
| 151 | + ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.tcp.port : |
| 152 | + ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.udp.port; |
| 153 | + } |
| 154 | +#endif |
| 155 | + |
| 156 | + return nf_socket_get_sock_v4(net, data_skb, doff, protocol, saddr, |
| 157 | + daddr, sport, dport, indev); |
| 158 | +} |
| 159 | +EXPORT_SYMBOL_GPL(nf_sk_lookup_slow_v4); |
| 160 | + |
| 161 | +MODULE_LICENSE("GPL"); |
| 162 | +MODULE_AUTHOR("Krisztian Kovacs, Balazs Scheidler"); |
| 163 | +MODULE_DESCRIPTION("Netfilter IPv4 socket lookup infrastructure"); |
0 commit comments