blob: e9e7dbc7c3078b133f2233254293c23dbc6c72f6 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001/*
2 * Fastpath Ndisc
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version
7 * 2 of the License, or (at your option) any later version.
8 */
9
10#include <net/ndisc.h>
11#include <net/ipv6.h>
12#include <net/addrconf.h>
13#include <net/ip6_route.h>
14
15#include "fp_common.h"
16#include "fp_device.h"
17#include "fp_ndisc.h"
18
19#define IN6ADDR_LINKLOCAL_ADDR \
20 { { { 0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }
21
22const struct in6_addr in6addr_linklocal_addr = IN6ADDR_LINKLOCAL_ADDR;
23
24static struct nd_opt_hdr *fpnd_next_option(struct nd_opt_hdr *cur,
25 struct nd_opt_hdr *end)
26{
27 int type;
28 if (!cur || !end || cur >= end)
29 return NULL;
30 type = cur->nd_opt_type;
31 do {
32 cur = ((void *)cur) + (cur->nd_opt_len << 3);
33 } while (cur < end && cur->nd_opt_type != type);
34 return cur <= end && cur->nd_opt_type == type ? cur : NULL;
35}
36
37static inline bool fpnd_icmp6_type_eq(struct sk_buff *skb, u32 type)
38{
39 struct ipv6hdr *iph = (struct ipv6hdr *)skb->data;
40 struct icmp6hdr *icmph = (struct icmp6hdr *)(iph + 1);
41
42 if (likely(iph->nexthdr != IPPROTO_ICMPV6 ||
43 icmph->icmp6_code != 0 ||
44 icmph->icmp6_type != type)) {
45
46 return false;
47 }
48
49 return true;
50}
51
52bool fpnd_is_ra(struct sk_buff *skb)
53{
54 return fpnd_icmp6_type_eq(skb, NDISC_ROUTER_ADVERTISEMENT);
55}
56
57bool fpnd_is_rs(struct sk_buff *skb)
58{
59 return fpnd_icmp6_type_eq(skb, NDISC_ROUTER_SOLICITATION);
60}
61
62static void fpnd_set_pref(struct fp_net_device *src, u8 *opt, int len)
63{
64 struct prefix_info *pinfo;
65 __u32 valid_lft;
66 __u32 prefered_lft;
67 int addr_type;
68
69 pinfo = (struct prefix_info *) opt;
70
71 if (len < sizeof(struct prefix_info)) {
72 pr_debug("prefix option too short\n");
73 return;
74 }
75
76 addr_type = ipv6_addr_type(&pinfo->prefix);
77
78 if (addr_type & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL))
79 return;
80
81 valid_lft = ntohl(pinfo->valid);
82 prefered_lft = ntohl(pinfo->prefered);
83
84 if (prefered_lft > valid_lft) {
85 pr_debug("prefix option has invalid lifetime\n");
86 return;
87 }
88
89 src->prefixlen = pinfo->prefix_len;
90 memcpy(&src->gb6addr, &pinfo->prefix, sizeof(struct in6_addr));
91 fpdev_set_gb6(src);
92
93 pr_debug("prefix for dev (%s) is (%pI6c) len (%d), sending to USB\n",
94 src->dev->name, &pinfo->prefix, pinfo->prefix_len);
95}
96
97void fpnd_process_ra(struct net_device *src, struct sk_buff *skb)
98{
99 struct fp_net_device *fpdev;
100 struct ndisc_options ndopts;
101 struct nd_opt_hdr *p;
102 int optlen;
103 struct ipv6hdr *iph = (struct ipv6hdr *)skb->data;
104 struct ra_msg *ra_msg = (struct ra_msg *)(iph + 1);
105
106 __u8 *opt = (__u8 *)(ra_msg + 1);
107
108 optlen = (skb->tail - (u8 *)ra_msg) - sizeof(struct ra_msg);
109
110 if (!(ipv6_addr_type(&iph->saddr) & IPV6_ADDR_LINKLOCAL)) {
111 pr_debug("source address is not link-local\n");
112 return;
113 }
114
115 if (optlen < 0) {
116 pr_debug("packet too short\n");
117 return;
118 }
119
120 if (!ndisc_parse_options(skb->dev, opt, optlen, &ndopts)) {
121 pr_debug("invalid ND options\n");
122 return;
123 }
124
125 fpdev = fpdev_get_if(src);
126 if (unlikely(!fpdev))
127 return;
128
129 if (ndopts.nd_opts_mtu) {
130 struct mtu_option *mtuinfo = (struct mtu_option *) ndopts.nd_opts_mtu;
131
132 fpdev->mtu = ntohl(mtuinfo->mtu);
133 fpdev_set_mtu(fpdev);
134 }
135
136 if (ndopts.nd_opts_pi) {
137 for (p = ndopts.nd_opts_pi;
138 p;
139 p = fpnd_next_option(p, ndopts.nd_opts_pi_end)) {
140 fpnd_set_pref(fpdev, (u8 *)p, (p->nd_opt_len) << 3);
141 }
142 }
143
144 fpdev_put(fpdev);
145}
146
147static void fp_ip6_nd_hdr(struct sk_buff *skb,
148 const struct in6_addr *saddr,
149 const struct in6_addr *daddr,
150 int hop_limit, int len)
151{
152 struct ipv6hdr *hdr;
153
154 skb_push(skb, sizeof(*hdr));
155 skb_reset_network_header(skb);
156 hdr = ipv6_hdr(skb);
157
158 ip6_flow_hdr(hdr, 0, 0);
159
160 hdr->payload_len = htons(len);
161 hdr->nexthdr = IPPROTO_ICMPV6;
162 hdr->hop_limit = hop_limit;
163
164 hdr->saddr = *saddr;
165 hdr->daddr = *daddr;
166}
167
168static struct sk_buff *fp_ndisc_alloc_skb(struct net_device *dev,
169 int len)
170{
171 int hlen = LL_RESERVED_SPACE(dev);
172 int tlen = dev->needed_tailroom;
173 struct sock *sk = dev_net(dev)->ipv6.ndisc_sk;
174 struct sk_buff *skb;
175
176 skb = alloc_skb(hlen + sizeof(struct ipv6hdr) + len + tlen, GFP_ATOMIC);
177 if (!skb) {
178 printk("ndisc: %s failed to allocate an skb\n", __func__);
179 return NULL;
180 }
181
182 skb->protocol = htons(ETH_P_IPV6);
183 skb->dev = dev;
184
185 skb_reserve(skb, hlen + sizeof(struct ipv6hdr));
186 skb_reset_transport_header(skb);
187
188 /* Manually assign socket ownership as we avoid calling
189 * sock_alloc_send_pskb() to bypass wmem buffer limits
190 */
191 skb_set_owner_w(skb, sk);
192
193 return skb;
194}
195
196int fpnd_process_rs(struct sk_buff *skb)
197{
198 struct sk_buff *nskb;
199 struct icmp6hdr *icmp6h;
200 struct ra_msg *msg;
201 struct prefix_info *prefix;
202 struct mtu_option *mtu;
203 struct fp_net_device *fpdev;
204 int err;
205
206 fpdev = fpdev_get_ccinet();
207 if (unlikely(!fpdev))
208 return 0;
209
210 nskb = fp_ndisc_alloc_skb(fpdev->dev, sizeof(*msg) + sizeof(struct prefix_info) + sizeof(struct mtu_option));
211 if (!nskb) {
212 fpdev_put(fpdev);
213 return 0;
214 }
215
216 msg = (struct ra_msg *)skb_put(nskb, sizeof(*msg));
217 *msg = (struct ra_msg) {
218 .icmph = {
219 .icmp6_type = NDISC_ROUTER_ADVERTISEMENT,
220 .icmp6_hop_limit = 0xFF,
221 .icmp6_rt_lifetime = htons(0xFFFF),
222 },
223 };
224
225 prefix = (struct prefix_info *)skb_put(nskb, sizeof(struct prefix_info));
226 prefix->type = ND_OPT_PREFIX_INFO;
227 prefix->length = 4;
228 prefix->prefix_len = fpdev->prefixlen;
229 prefix->autoconf = 1;
230 prefix->valid = htonl(0xFFFFFFFF);
231 prefix->prefered = htonl(0xFFFFFFFF);
232 memcpy(&prefix->prefix, &fpdev->gb6addr, sizeof(struct in6_addr));
233
234 mtu = (struct mtu_option *)skb_put(nskb, sizeof(struct mtu_option));
235 mtu->type = ND_OPT_MTU;
236 mtu->length = 1;
237 mtu->mtu = htonl(fpdev->mtu);
238
239 icmp6h = icmp6_hdr(nskb);
240 icmp6h->icmp6_cksum = csum_ipv6_magic(&in6addr_linklocal_addr, &fpdev->ll6addr, nskb->len,
241 IPPROTO_ICMPV6,
242 csum_partial(icmp6h,
243 nskb->len, 0));
244
245 fp_ip6_nd_hdr(nskb, &in6addr_linklocal_addr, &fpdev->ll6addr, 0xFF, nskb->len);
246 err = netif_rx(nskb);
247 fpdev_put(fpdev);
248
249 if(!err) {
250 dev_kfree_skb_any(skb);
251 return 1;
252 }
253
254 return 0;
255}
256
257