treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 500
[linux-2.6-block.git] / net / netfilter / ipset / ip_set_getport.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (C) 2003-2011 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
3  */
4
5 /* Get Layer-4 data from the packets */
6
7 #include <linux/ip.h>
8 #include <linux/skbuff.h>
9 #include <linux/icmp.h>
10 #include <linux/icmpv6.h>
11 #include <linux/sctp.h>
12 #include <linux/netfilter_ipv6/ip6_tables.h>
13 #include <net/ip.h>
14 #include <net/ipv6.h>
15
16 #include <linux/netfilter/ipset/ip_set_getport.h>
17 #include <linux/export.h>
18
19 /* We must handle non-linear skbs */
20 static bool
21 get_port(const struct sk_buff *skb, int protocol, unsigned int protooff,
22          bool src, __be16 *port, u8 *proto)
23 {
24         switch (protocol) {
25         case IPPROTO_TCP: {
26                 struct tcphdr _tcph;
27                 const struct tcphdr *th;
28
29                 th = skb_header_pointer(skb, protooff, sizeof(_tcph), &_tcph);
30                 if (!th)
31                         /* No choice either */
32                         return false;
33
34                 *port = src ? th->source : th->dest;
35                 break;
36         }
37         case IPPROTO_SCTP: {
38                 struct sctphdr _sh;
39                 const struct sctphdr *sh;
40
41                 sh = skb_header_pointer(skb, protooff, sizeof(_sh), &_sh);
42                 if (!sh)
43                         /* No choice either */
44                         return false;
45
46                 *port = src ? sh->source : sh->dest;
47                 break;
48         }
49         case IPPROTO_UDP:
50         case IPPROTO_UDPLITE: {
51                 struct udphdr _udph;
52                 const struct udphdr *uh;
53
54                 uh = skb_header_pointer(skb, protooff, sizeof(_udph), &_udph);
55                 if (!uh)
56                         /* No choice either */
57                         return false;
58
59                 *port = src ? uh->source : uh->dest;
60                 break;
61         }
62         case IPPROTO_ICMP: {
63                 struct icmphdr _ich;
64                 const struct icmphdr *ic;
65
66                 ic = skb_header_pointer(skb, protooff, sizeof(_ich), &_ich);
67                 if (!ic)
68                         return false;
69
70                 *port = (__force __be16)htons((ic->type << 8) | ic->code);
71                 break;
72         }
73         case IPPROTO_ICMPV6: {
74                 struct icmp6hdr _ich;
75                 const struct icmp6hdr *ic;
76
77                 ic = skb_header_pointer(skb, protooff, sizeof(_ich), &_ich);
78                 if (!ic)
79                         return false;
80
81                 *port = (__force __be16)
82                         htons((ic->icmp6_type << 8) | ic->icmp6_code);
83                 break;
84         }
85         default:
86                 break;
87         }
88         *proto = protocol;
89
90         return true;
91 }
92
93 bool
94 ip_set_get_ip4_port(const struct sk_buff *skb, bool src,
95                     __be16 *port, u8 *proto)
96 {
97         const struct iphdr *iph = ip_hdr(skb);
98         unsigned int protooff = skb_network_offset(skb) + ip_hdrlen(skb);
99         int protocol = iph->protocol;
100
101         /* See comments at tcp_match in ip_tables.c */
102         if (protocol <= 0)
103                 return false;
104
105         if (ntohs(iph->frag_off) & IP_OFFSET)
106                 switch (protocol) {
107                 case IPPROTO_TCP:
108                 case IPPROTO_SCTP:
109                 case IPPROTO_UDP:
110                 case IPPROTO_UDPLITE:
111                 case IPPROTO_ICMP:
112                         /* Port info not available for fragment offset > 0 */
113                         return false;
114                 default:
115                         /* Other protocols doesn't have ports,
116                          * so we can match fragments.
117                          */
118                         *proto = protocol;
119                         return true;
120                 }
121
122         return get_port(skb, protocol, protooff, src, port, proto);
123 }
124 EXPORT_SYMBOL_GPL(ip_set_get_ip4_port);
125
126 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
127 bool
128 ip_set_get_ip6_port(const struct sk_buff *skb, bool src,
129                     __be16 *port, u8 *proto)
130 {
131         int protoff;
132         u8 nexthdr;
133         __be16 frag_off = 0;
134
135         nexthdr = ipv6_hdr(skb)->nexthdr;
136         protoff = ipv6_skip_exthdr(skb,
137                                    skb_network_offset(skb) +
138                                         sizeof(struct ipv6hdr), &nexthdr,
139                                    &frag_off);
140         if (protoff < 0 || (frag_off & htons(~0x7)) != 0)
141                 return false;
142
143         return get_port(skb, nexthdr, protoff, src, port, proto);
144 }
145 EXPORT_SYMBOL_GPL(ip_set_get_ip6_port);
146 #endif
147
148 bool
149 ip_set_get_ip_port(const struct sk_buff *skb, u8 pf, bool src, __be16 *port)
150 {
151         bool ret;
152         u8 proto;
153
154         switch (pf) {
155         case NFPROTO_IPV4:
156                 ret = ip_set_get_ip4_port(skb, src, port, &proto);
157                 break;
158         case NFPROTO_IPV6:
159                 ret = ip_set_get_ip6_port(skb, src, port, &proto);
160                 break;
161         default:
162                 return false;
163         }
164         if (!ret)
165                 return ret;
166         switch (proto) {
167         case IPPROTO_TCP:
168         case IPPROTO_UDP:
169                 return true;
170         default:
171                 return false;
172         }
173 }
174 EXPORT_SYMBOL_GPL(ip_set_get_ip_port);