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> |
8bfcdf66 | 17 | #include <net/netfilter/ipv6/nf_reject.h> |
c8d7b98b | 18 | |
8bfcdf66 PNA |
19 | const struct tcphdr *nf_reject_ip6_tcphdr_get(struct sk_buff *oldskb, |
20 | struct tcphdr *otcph, | |
21 | unsigned int *otcplen, int hook) | |
c8d7b98b | 22 | { |
c8d7b98b | 23 | const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); |
c8d7b98b PNA |
24 | u8 proto; |
25 | __be16 frag_off; | |
8bfcdf66 | 26 | int tcphoff; |
c8d7b98b PNA |
27 | |
28 | proto = oip6h->nexthdr; | |
8bfcdf66 PNA |
29 | tcphoff = ipv6_skip_exthdr(oldskb, ((u8*)(oip6h+1) - oldskb->data), |
30 | &proto, &frag_off); | |
c8d7b98b PNA |
31 | |
32 | if ((tcphoff < 0) || (tcphoff > oldskb->len)) { | |
33 | pr_debug("Cannot get TCP header.\n"); | |
8bfcdf66 | 34 | return NULL; |
c8d7b98b PNA |
35 | } |
36 | ||
8bfcdf66 | 37 | *otcplen = oldskb->len - tcphoff; |
c8d7b98b PNA |
38 | |
39 | /* IP header checks: fragment, too short. */ | |
8bfcdf66 PNA |
40 | if (proto != IPPROTO_TCP || *otcplen < sizeof(struct tcphdr)) { |
41 | pr_debug("proto(%d) != IPPROTO_TCP or too short (len = %d)\n", | |
42 | proto, *otcplen); | |
43 | return NULL; | |
c8d7b98b PNA |
44 | } |
45 | ||
8bfcdf66 PNA |
46 | otcph = skb_header_pointer(oldskb, tcphoff, sizeof(struct tcphdr), |
47 | otcph); | |
48 | if (otcph == NULL) | |
49 | return NULL; | |
c8d7b98b PNA |
50 | |
51 | /* No RST for RST. */ | |
8bfcdf66 | 52 | if (otcph->rst) { |
c8d7b98b | 53 | pr_debug("RST is set\n"); |
8bfcdf66 | 54 | return NULL; |
c8d7b98b PNA |
55 | } |
56 | ||
57 | /* Check checksum. */ | |
58 | if (nf_ip6_checksum(oldskb, hook, tcphoff, IPPROTO_TCP)) { | |
59 | pr_debug("TCP checksum is invalid\n"); | |
8bfcdf66 | 60 | return NULL; |
c8d7b98b PNA |
61 | } |
62 | ||
8bfcdf66 PNA |
63 | return otcph; |
64 | } | |
65 | EXPORT_SYMBOL_GPL(nf_reject_ip6_tcphdr_get); | |
c8d7b98b | 66 | |
8bfcdf66 PNA |
67 | struct ipv6hdr *nf_reject_ip6hdr_put(struct sk_buff *nskb, |
68 | const struct sk_buff *oldskb, | |
a03a8dbe | 69 | __u8 protocol, int hoplimit) |
8bfcdf66 PNA |
70 | { |
71 | struct ipv6hdr *ip6h; | |
72 | const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); | |
73 | #define DEFAULT_TOS_VALUE 0x0U | |
74 | const __u8 tclass = DEFAULT_TOS_VALUE; | |
c8d7b98b PNA |
75 | |
76 | skb_put(nskb, sizeof(struct ipv6hdr)); | |
77 | skb_reset_network_header(nskb); | |
78 | ip6h = ipv6_hdr(nskb); | |
79 | ip6_flow_hdr(ip6h, tclass, 0); | |
8bfcdf66 PNA |
80 | ip6h->hop_limit = hoplimit; |
81 | ip6h->nexthdr = protocol; | |
c8d7b98b PNA |
82 | ip6h->saddr = oip6h->daddr; |
83 | ip6h->daddr = oip6h->saddr; | |
84 | ||
8bfcdf66 PNA |
85 | nskb->protocol = htons(ETH_P_IPV6); |
86 | ||
87 | return ip6h; | |
88 | } | |
89 | EXPORT_SYMBOL_GPL(nf_reject_ip6hdr_put); | |
90 | ||
91 | void nf_reject_ip6_tcphdr_put(struct sk_buff *nskb, | |
92 | const struct sk_buff *oldskb, | |
93 | const struct tcphdr *oth, unsigned int otcplen) | |
94 | { | |
95 | struct tcphdr *tcph; | |
96 | int needs_ack; | |
97 | ||
c8d7b98b PNA |
98 | skb_reset_transport_header(nskb); |
99 | tcph = (struct tcphdr *)skb_put(nskb, sizeof(struct tcphdr)); | |
100 | /* Truncate to length (no data) */ | |
101 | tcph->doff = sizeof(struct tcphdr)/4; | |
8bfcdf66 PNA |
102 | tcph->source = oth->dest; |
103 | tcph->dest = oth->source; | |
c8d7b98b | 104 | |
8bfcdf66 | 105 | if (oth->ack) { |
c8d7b98b | 106 | needs_ack = 0; |
8bfcdf66 | 107 | tcph->seq = oth->ack_seq; |
c8d7b98b PNA |
108 | tcph->ack_seq = 0; |
109 | } else { | |
110 | needs_ack = 1; | |
8bfcdf66 PNA |
111 | tcph->ack_seq = htonl(ntohl(oth->seq) + oth->syn + oth->fin + |
112 | otcplen - (oth->doff<<2)); | |
c8d7b98b PNA |
113 | tcph->seq = 0; |
114 | } | |
115 | ||
116 | /* Reset flags */ | |
117 | ((u_int8_t *)tcph)[13] = 0; | |
118 | tcph->rst = 1; | |
119 | tcph->ack = needs_ack; | |
120 | tcph->window = 0; | |
121 | tcph->urg_ptr = 0; | |
122 | tcph->check = 0; | |
123 | ||
124 | /* Adjust TCP checksum */ | |
125 | tcph->check = csum_ipv6_magic(&ipv6_hdr(nskb)->saddr, | |
126 | &ipv6_hdr(nskb)->daddr, | |
127 | sizeof(struct tcphdr), IPPROTO_TCP, | |
128 | csum_partial(tcph, | |
129 | sizeof(struct tcphdr), 0)); | |
8bfcdf66 PNA |
130 | } |
131 | EXPORT_SYMBOL_GPL(nf_reject_ip6_tcphdr_put); | |
132 | ||
133 | void nf_send_reset6(struct net *net, struct sk_buff *oldskb, int hook) | |
134 | { | |
135 | struct sk_buff *nskb; | |
136 | struct tcphdr _otcph; | |
137 | const struct tcphdr *otcph; | |
138 | unsigned int otcplen, hh_len; | |
139 | const struct ipv6hdr *oip6h = ipv6_hdr(oldskb); | |
140 | struct ipv6hdr *ip6h; | |
141 | struct dst_entry *dst = NULL; | |
142 | struct flowi6 fl6; | |
143 | ||
144 | if ((!(ipv6_addr_type(&oip6h->saddr) & IPV6_ADDR_UNICAST)) || | |
145 | (!(ipv6_addr_type(&oip6h->daddr) & IPV6_ADDR_UNICAST))) { | |
146 | pr_debug("addr is not unicast.\n"); | |
147 | return; | |
148 | } | |
149 | ||
150 | otcph = nf_reject_ip6_tcphdr_get(oldskb, &_otcph, &otcplen, hook); | |
151 | if (!otcph) | |
152 | return; | |
153 | ||
154 | memset(&fl6, 0, sizeof(fl6)); | |
155 | fl6.flowi6_proto = IPPROTO_TCP; | |
156 | fl6.saddr = oip6h->daddr; | |
157 | fl6.daddr = oip6h->saddr; | |
158 | fl6.fl6_sport = otcph->dest; | |
159 | fl6.fl6_dport = otcph->source; | |
160 | security_skb_classify_flow(oldskb, flowi6_to_flowi(&fl6)); | |
161 | dst = ip6_route_output(net, NULL, &fl6); | |
162 | if (dst == NULL || dst->error) { | |
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 | |
79288330 | 209 | ip6_local_out(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; | |
227 | thoff = ipv6_skip_exthdr(skb, ((u8*)(ip6h+1) - skb->data), &proto, &fo); | |
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"); |