Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
c8d7b98b PNA |
2 | /* (C) 1999-2001 Paul `Rusty' Russell |
3 | * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org> | |
c8d7b98b | 4 | */ |
ab2d7251 PNA |
5 | |
6 | #include <linux/module.h> | |
c8d7b98b PNA |
7 | #include <net/ipv6.h> |
8 | #include <net/ip6_route.h> | |
9 | #include <net/ip6_fib.h> | |
10 | #include <net/ip6_checksum.h> | |
56768644 | 11 | #include <net/netfilter/ipv6/nf_reject.h> |
c8d7b98b | 12 | #include <linux/netfilter_ipv6.h> |
c737b7c4 | 13 | #include <linux/netfilter_bridge.h> |
c8d7b98b | 14 | |
8bfcdf66 PNA |
15 | const struct tcphdr *nf_reject_ip6_tcphdr_get(struct sk_buff *oldskb, |
16 | struct tcphdr *otcph, | |
17 | unsigned int *otcplen, int hook) | |
c8d7b98b | 18 | { |
c8d7b98b | 19 | const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); |
c8d7b98b PNA |
20 | u8 proto; |
21 | __be16 frag_off; | |
8bfcdf66 | 22 | int tcphoff; |
c8d7b98b PNA |
23 | |
24 | proto = oip6h->nexthdr; | |
f9527ea9 | 25 | tcphoff = ipv6_skip_exthdr(oldskb, ((u8 *)(oip6h + 1) - oldskb->data), |
8bfcdf66 | 26 | &proto, &frag_off); |
c8d7b98b PNA |
27 | |
28 | if ((tcphoff < 0) || (tcphoff > oldskb->len)) { | |
29 | pr_debug("Cannot get TCP header.\n"); | |
8bfcdf66 | 30 | return NULL; |
c8d7b98b PNA |
31 | } |
32 | ||
8bfcdf66 | 33 | *otcplen = oldskb->len - tcphoff; |
c8d7b98b PNA |
34 | |
35 | /* IP header checks: fragment, too short. */ | |
8bfcdf66 PNA |
36 | if (proto != IPPROTO_TCP || *otcplen < sizeof(struct tcphdr)) { |
37 | pr_debug("proto(%d) != IPPROTO_TCP or too short (len = %d)\n", | |
38 | proto, *otcplen); | |
39 | return NULL; | |
c8d7b98b PNA |
40 | } |
41 | ||
8bfcdf66 PNA |
42 | otcph = skb_header_pointer(oldskb, tcphoff, sizeof(struct tcphdr), |
43 | otcph); | |
44 | if (otcph == NULL) | |
45 | return NULL; | |
c8d7b98b PNA |
46 | |
47 | /* No RST for RST. */ | |
8bfcdf66 | 48 | if (otcph->rst) { |
c8d7b98b | 49 | pr_debug("RST is set\n"); |
8bfcdf66 | 50 | return NULL; |
c8d7b98b PNA |
51 | } |
52 | ||
53 | /* Check checksum. */ | |
54 | if (nf_ip6_checksum(oldskb, hook, tcphoff, IPPROTO_TCP)) { | |
55 | pr_debug("TCP checksum is invalid\n"); | |
8bfcdf66 | 56 | return NULL; |
c8d7b98b PNA |
57 | } |
58 | ||
8bfcdf66 PNA |
59 | return otcph; |
60 | } | |
61 | EXPORT_SYMBOL_GPL(nf_reject_ip6_tcphdr_get); | |
c8d7b98b | 62 | |
8bfcdf66 PNA |
63 | struct ipv6hdr *nf_reject_ip6hdr_put(struct sk_buff *nskb, |
64 | const struct sk_buff *oldskb, | |
a03a8dbe | 65 | __u8 protocol, int hoplimit) |
8bfcdf66 PNA |
66 | { |
67 | struct ipv6hdr *ip6h; | |
68 | const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); | |
69 | #define DEFAULT_TOS_VALUE 0x0U | |
70 | const __u8 tclass = DEFAULT_TOS_VALUE; | |
c8d7b98b PNA |
71 | |
72 | skb_put(nskb, sizeof(struct ipv6hdr)); | |
73 | skb_reset_network_header(nskb); | |
74 | ip6h = ipv6_hdr(nskb); | |
75 | ip6_flow_hdr(ip6h, tclass, 0); | |
8bfcdf66 PNA |
76 | ip6h->hop_limit = hoplimit; |
77 | ip6h->nexthdr = protocol; | |
c8d7b98b PNA |
78 | ip6h->saddr = oip6h->daddr; |
79 | ip6h->daddr = oip6h->saddr; | |
80 | ||
8bfcdf66 PNA |
81 | nskb->protocol = htons(ETH_P_IPV6); |
82 | ||
83 | return ip6h; | |
84 | } | |
85 | EXPORT_SYMBOL_GPL(nf_reject_ip6hdr_put); | |
86 | ||
87 | void nf_reject_ip6_tcphdr_put(struct sk_buff *nskb, | |
88 | const struct sk_buff *oldskb, | |
89 | const struct tcphdr *oth, unsigned int otcplen) | |
90 | { | |
91 | struct tcphdr *tcph; | |
92 | int needs_ack; | |
93 | ||
c8d7b98b | 94 | skb_reset_transport_header(nskb); |
4df864c1 | 95 | tcph = skb_put(nskb, sizeof(struct tcphdr)); |
c8d7b98b PNA |
96 | /* Truncate to length (no data) */ |
97 | tcph->doff = sizeof(struct tcphdr)/4; | |
8bfcdf66 PNA |
98 | tcph->source = oth->dest; |
99 | tcph->dest = oth->source; | |
c8d7b98b | 100 | |
8bfcdf66 | 101 | if (oth->ack) { |
c8d7b98b | 102 | needs_ack = 0; |
8bfcdf66 | 103 | tcph->seq = oth->ack_seq; |
c8d7b98b PNA |
104 | tcph->ack_seq = 0; |
105 | } else { | |
106 | needs_ack = 1; | |
8bfcdf66 PNA |
107 | tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn + oth->fin + |
108 | otcplen - (oth->doff<<2)); | |
c8d7b98b PNA |
109 | tcph->seq = 0; |
110 | } | |
111 | ||
112 | /* Reset flags */ | |
113 | ((u_int8_t *)tcph)[13] = 0; | |
114 | tcph->rst = 1; | |
115 | tcph->ack = needs_ack; | |
116 | tcph->window = 0; | |
117 | tcph->urg_ptr = 0; | |
118 | tcph->check = 0; | |
119 | ||
120 | /* Adjust TCP checksum */ | |
121 | tcph->check = csum_ipv6_magic(&ipv6_hdr(nskb)->saddr, | |
122 | &ipv6_hdr(nskb)->daddr, | |
123 | sizeof(struct tcphdr), IPPROTO_TCP, | |
124 | csum_partial(tcph, | |
125 | sizeof(struct tcphdr), 0)); | |
8bfcdf66 PNA |
126 | } |
127 | EXPORT_SYMBOL_GPL(nf_reject_ip6_tcphdr_put); | |
128 | ||
129 | void nf_send_reset6(struct net *net, struct sk_buff *oldskb, int hook) | |
130 | { | |
c4b0e771 | 131 | struct net_device *br_indev __maybe_unused; |
8bfcdf66 PNA |
132 | struct sk_buff *nskb; |
133 | struct tcphdr _otcph; | |
134 | const struct tcphdr *otcph; | |
135 | unsigned int otcplen, hh_len; | |
136 | const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); | |
137 | struct ipv6hdr *ip6h; | |
138 | struct dst_entry *dst = NULL; | |
139 | struct flowi6 fl6; | |
140 | ||
141 | if ((!(ipv6_addr_type(&oip6h->saddr) & IPV6_ADDR_UNICAST)) || | |
142 | (!(ipv6_addr_type(&oip6h->daddr) & IPV6_ADDR_UNICAST))) { | |
143 | pr_debug("addr is not unicast.\n"); | |
144 | return; | |
145 | } | |
146 | ||
147 | otcph = nf_reject_ip6_tcphdr_get(oldskb, &_otcph, &otcplen, hook); | |
148 | if (!otcph) | |
149 | return; | |
150 | ||
151 | memset(&fl6, 0, sizeof(fl6)); | |
152 | fl6.flowi6_proto = IPPROTO_TCP; | |
153 | fl6.saddr = oip6h->daddr; | |
154 | fl6.daddr = oip6h->saddr; | |
155 | fl6.fl6_sport = otcph->dest; | |
156 | fl6.fl6_dport = otcph->source; | |
00b4422f | 157 | fl6.flowi6_oif = l3mdev_master_ifindex(skb_dst(oldskb)->dev); |
cc31d43b | 158 | fl6.flowi6_mark = IP6_REPLY_MARK(net, oldskb->mark); |
8bfcdf66 PNA |
159 | security_skb_classify_flow(oldskb, flowi6_to_flowi(&fl6)); |
160 | dst = ip6_route_output(net, NULL, &fl6); | |
85f1e7c2 | 161 | if (dst->error) { |
8bfcdf66 PNA |
162 | dst_release(dst); |
163 | return; | |
164 | } | |
165 | dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), NULL, 0); | |
166 | if (IS_ERR(dst)) | |
167 | return; | |
168 | ||
169 | hh_len = (dst->dev->hard_header_len + 15)&~15; | |
170 | nskb = alloc_skb(hh_len + 15 + dst->header_len + sizeof(struct ipv6hdr) | |
171 | + sizeof(struct tcphdr) + dst->trailer_len, | |
172 | GFP_ATOMIC); | |
173 | ||
174 | if (!nskb) { | |
175 | net_dbg_ratelimited("cannot alloc skb\n"); | |
176 | dst_release(dst); | |
177 | return; | |
178 | } | |
179 | ||
180 | skb_dst_set(nskb, dst); | |
181 | ||
cc31d43b PEP |
182 | nskb->mark = fl6.flowi6_mark; |
183 | ||
8bfcdf66 PNA |
184 | skb_reserve(nskb, hh_len + dst->header_len); |
185 | ip6h = nf_reject_ip6hdr_put(nskb, oldskb, IPPROTO_TCP, | |
186 | ip6_dst_hoplimit(dst)); | |
187 | nf_reject_ip6_tcphdr_put(nskb, oldskb, otcph, otcplen); | |
c8d7b98b PNA |
188 | |
189 | nf_ct_attach(nskb, oldskb); | |
190 | ||
191 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) | |
192 | /* If we use ip6_local_out for bridged traffic, the MAC source on | |
193 | * the RST will be ours, instead of the destination's. This confuses | |
194 | * some routers/firewalls, and they drop the packet. So we need to | |
195 | * build the eth header using the original destination's MAC as the | |
196 | * source, and send the RST packet directly. | |
197 | */ | |
c4b0e771 FW |
198 | br_indev = nf_bridge_get_physindev(oldskb); |
199 | if (br_indev) { | |
c8d7b98b | 200 | struct ethhdr *oeth = eth_hdr(oldskb); |
c737b7c4 | 201 | |
c4b0e771 | 202 | nskb->dev = br_indev; |
c8d7b98b PNA |
203 | nskb->protocol = htons(ETH_P_IPV6); |
204 | ip6h->payload_len = htons(sizeof(struct tcphdr)); | |
205 | if (dev_hard_header(nskb, nskb->dev, ntohs(nskb->protocol), | |
c4b0e771 FW |
206 | oeth->h_source, oeth->h_dest, nskb->len) < 0) { |
207 | kfree_skb(nskb); | |
c8d7b98b | 208 | return; |
c4b0e771 | 209 | } |
c8d7b98b PNA |
210 | dev_queue_xmit(nskb); |
211 | } else | |
212 | #endif | |
33224b16 | 213 | ip6_local_out(net, nskb->sk, nskb); |
c8d7b98b PNA |
214 | } |
215 | EXPORT_SYMBOL_GPL(nf_send_reset6); | |
ab2d7251 | 216 | |
ee586bbc FW |
217 | static bool reject6_csum_ok(struct sk_buff *skb, int hook) |
218 | { | |
219 | const struct ipv6hdr *ip6h = ipv6_hdr(skb); | |
220 | int thoff; | |
221 | __be16 fo; | |
222 | u8 proto; | |
223 | ||
ee586bbc FW |
224 | if (skb_csum_unnecessary(skb)) |
225 | return true; | |
226 | ||
227 | proto = ip6h->nexthdr; | |
f9527ea9 | 228 | thoff = ipv6_skip_exthdr(skb, ((u8 *)(ip6h + 1) - skb->data), &proto, &fo); |
ee586bbc FW |
229 | |
230 | if (thoff < 0 || thoff >= skb->len || (fo & htons(~0x7)) != 0) | |
231 | return false; | |
232 | ||
7fc38225 AN |
233 | if (!nf_reject_verify_csum(proto)) |
234 | return true; | |
235 | ||
ee586bbc FW |
236 | return nf_ip6_checksum(skb, hook, thoff, proto) == 0; |
237 | } | |
238 | ||
239 | void nf_send_unreach6(struct net *net, struct sk_buff *skb_in, | |
240 | unsigned char code, unsigned int hooknum) | |
241 | { | |
242 | if (!reject6_csum_ok(skb_in, hooknum)) | |
243 | return; | |
244 | ||
245 | if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL) | |
246 | skb_in->dev = net->loopback_dev; | |
247 | ||
248 | icmpv6_send(skb_in, ICMPV6_DEST_UNREACH, code, 0); | |
249 | } | |
250 | EXPORT_SYMBOL_GPL(nf_send_unreach6); | |
251 | ||
ab2d7251 | 252 | MODULE_LICENSE("GPL"); |