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