1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /**
3*4882a593Smuzhiyun * Authors:
4*4882a593Smuzhiyun * (C) 2020 Alexander Aring <alex.aring@gmail.com>
5*4882a593Smuzhiyun */
6*4882a593Smuzhiyun
7*4882a593Smuzhiyun #include <linux/rpl_iptunnel.h>
8*4882a593Smuzhiyun
9*4882a593Smuzhiyun #include <net/dst_cache.h>
10*4882a593Smuzhiyun #include <net/ip6_route.h>
11*4882a593Smuzhiyun #include <net/lwtunnel.h>
12*4882a593Smuzhiyun #include <net/ipv6.h>
13*4882a593Smuzhiyun #include <net/rpl.h>
14*4882a593Smuzhiyun
15*4882a593Smuzhiyun struct rpl_iptunnel_encap {
16*4882a593Smuzhiyun struct ipv6_rpl_sr_hdr srh[0];
17*4882a593Smuzhiyun };
18*4882a593Smuzhiyun
19*4882a593Smuzhiyun struct rpl_lwt {
20*4882a593Smuzhiyun struct dst_cache cache;
21*4882a593Smuzhiyun struct rpl_iptunnel_encap tuninfo;
22*4882a593Smuzhiyun };
23*4882a593Smuzhiyun
rpl_lwt_lwtunnel(struct lwtunnel_state * lwt)24*4882a593Smuzhiyun static inline struct rpl_lwt *rpl_lwt_lwtunnel(struct lwtunnel_state *lwt)
25*4882a593Smuzhiyun {
26*4882a593Smuzhiyun return (struct rpl_lwt *)lwt->data;
27*4882a593Smuzhiyun }
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun static inline struct rpl_iptunnel_encap *
rpl_encap_lwtunnel(struct lwtunnel_state * lwt)30*4882a593Smuzhiyun rpl_encap_lwtunnel(struct lwtunnel_state *lwt)
31*4882a593Smuzhiyun {
32*4882a593Smuzhiyun return &rpl_lwt_lwtunnel(lwt)->tuninfo;
33*4882a593Smuzhiyun }
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun static const struct nla_policy rpl_iptunnel_policy[RPL_IPTUNNEL_MAX + 1] = {
36*4882a593Smuzhiyun [RPL_IPTUNNEL_SRH] = { .type = NLA_BINARY },
37*4882a593Smuzhiyun };
38*4882a593Smuzhiyun
rpl_validate_srh(struct net * net,struct ipv6_rpl_sr_hdr * srh,size_t seglen)39*4882a593Smuzhiyun static bool rpl_validate_srh(struct net *net, struct ipv6_rpl_sr_hdr *srh,
40*4882a593Smuzhiyun size_t seglen)
41*4882a593Smuzhiyun {
42*4882a593Smuzhiyun int err;
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun if ((srh->hdrlen << 3) != seglen)
45*4882a593Smuzhiyun return false;
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun /* check at least one segment and seglen fit with segments_left */
48*4882a593Smuzhiyun if (!srh->segments_left ||
49*4882a593Smuzhiyun (srh->segments_left * sizeof(struct in6_addr)) != seglen)
50*4882a593Smuzhiyun return false;
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun if (srh->cmpri || srh->cmpre)
53*4882a593Smuzhiyun return false;
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun err = ipv6_chk_rpl_srh_loop(net, srh->rpl_segaddr,
56*4882a593Smuzhiyun srh->segments_left);
57*4882a593Smuzhiyun if (err)
58*4882a593Smuzhiyun return false;
59*4882a593Smuzhiyun
60*4882a593Smuzhiyun if (ipv6_addr_type(&srh->rpl_segaddr[srh->segments_left - 1]) &
61*4882a593Smuzhiyun IPV6_ADDR_MULTICAST)
62*4882a593Smuzhiyun return false;
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun return true;
65*4882a593Smuzhiyun }
66*4882a593Smuzhiyun
rpl_build_state(struct net * net,struct nlattr * nla,unsigned int family,const void * cfg,struct lwtunnel_state ** ts,struct netlink_ext_ack * extack)67*4882a593Smuzhiyun static int rpl_build_state(struct net *net, struct nlattr *nla,
68*4882a593Smuzhiyun unsigned int family, const void *cfg,
69*4882a593Smuzhiyun struct lwtunnel_state **ts,
70*4882a593Smuzhiyun struct netlink_ext_ack *extack)
71*4882a593Smuzhiyun {
72*4882a593Smuzhiyun struct nlattr *tb[RPL_IPTUNNEL_MAX + 1];
73*4882a593Smuzhiyun struct lwtunnel_state *newts;
74*4882a593Smuzhiyun struct ipv6_rpl_sr_hdr *srh;
75*4882a593Smuzhiyun struct rpl_lwt *rlwt;
76*4882a593Smuzhiyun int err, srh_len;
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun if (family != AF_INET6)
79*4882a593Smuzhiyun return -EINVAL;
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun err = nla_parse_nested(tb, RPL_IPTUNNEL_MAX, nla,
82*4882a593Smuzhiyun rpl_iptunnel_policy, extack);
83*4882a593Smuzhiyun if (err < 0)
84*4882a593Smuzhiyun return err;
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun if (!tb[RPL_IPTUNNEL_SRH])
87*4882a593Smuzhiyun return -EINVAL;
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun srh = nla_data(tb[RPL_IPTUNNEL_SRH]);
90*4882a593Smuzhiyun srh_len = nla_len(tb[RPL_IPTUNNEL_SRH]);
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun if (srh_len < sizeof(*srh))
93*4882a593Smuzhiyun return -EINVAL;
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun /* verify that SRH is consistent */
96*4882a593Smuzhiyun if (!rpl_validate_srh(net, srh, srh_len - sizeof(*srh)))
97*4882a593Smuzhiyun return -EINVAL;
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun newts = lwtunnel_state_alloc(srh_len + sizeof(*rlwt));
100*4882a593Smuzhiyun if (!newts)
101*4882a593Smuzhiyun return -ENOMEM;
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun rlwt = rpl_lwt_lwtunnel(newts);
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun err = dst_cache_init(&rlwt->cache, GFP_ATOMIC);
106*4882a593Smuzhiyun if (err) {
107*4882a593Smuzhiyun kfree(newts);
108*4882a593Smuzhiyun return err;
109*4882a593Smuzhiyun }
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun memcpy(&rlwt->tuninfo.srh, srh, srh_len);
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun newts->type = LWTUNNEL_ENCAP_RPL;
114*4882a593Smuzhiyun newts->flags |= LWTUNNEL_STATE_INPUT_REDIRECT;
115*4882a593Smuzhiyun newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT;
116*4882a593Smuzhiyun
117*4882a593Smuzhiyun *ts = newts;
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun return 0;
120*4882a593Smuzhiyun }
121*4882a593Smuzhiyun
rpl_destroy_state(struct lwtunnel_state * lwt)122*4882a593Smuzhiyun static void rpl_destroy_state(struct lwtunnel_state *lwt)
123*4882a593Smuzhiyun {
124*4882a593Smuzhiyun dst_cache_destroy(&rpl_lwt_lwtunnel(lwt)->cache);
125*4882a593Smuzhiyun }
126*4882a593Smuzhiyun
rpl_do_srh_inline(struct sk_buff * skb,const struct rpl_lwt * rlwt,const struct ipv6_rpl_sr_hdr * srh)127*4882a593Smuzhiyun static int rpl_do_srh_inline(struct sk_buff *skb, const struct rpl_lwt *rlwt,
128*4882a593Smuzhiyun const struct ipv6_rpl_sr_hdr *srh)
129*4882a593Smuzhiyun {
130*4882a593Smuzhiyun struct ipv6_rpl_sr_hdr *isrh, *csrh;
131*4882a593Smuzhiyun const struct ipv6hdr *oldhdr;
132*4882a593Smuzhiyun struct ipv6hdr *hdr;
133*4882a593Smuzhiyun unsigned char *buf;
134*4882a593Smuzhiyun size_t hdrlen;
135*4882a593Smuzhiyun int err;
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun oldhdr = ipv6_hdr(skb);
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun buf = kcalloc(struct_size(srh, segments.addr, srh->segments_left), 2, GFP_ATOMIC);
140*4882a593Smuzhiyun if (!buf)
141*4882a593Smuzhiyun return -ENOMEM;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun isrh = (struct ipv6_rpl_sr_hdr *)buf;
144*4882a593Smuzhiyun csrh = (struct ipv6_rpl_sr_hdr *)(buf + ((srh->hdrlen + 1) << 3));
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun memcpy(isrh, srh, sizeof(*isrh));
147*4882a593Smuzhiyun memcpy(isrh->rpl_segaddr, &srh->rpl_segaddr[1],
148*4882a593Smuzhiyun (srh->segments_left - 1) * 16);
149*4882a593Smuzhiyun isrh->rpl_segaddr[srh->segments_left - 1] = oldhdr->daddr;
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun ipv6_rpl_srh_compress(csrh, isrh, &srh->rpl_segaddr[0],
152*4882a593Smuzhiyun isrh->segments_left - 1);
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun hdrlen = ((csrh->hdrlen + 1) << 3);
155*4882a593Smuzhiyun
156*4882a593Smuzhiyun err = skb_cow_head(skb, hdrlen + skb->mac_len);
157*4882a593Smuzhiyun if (unlikely(err)) {
158*4882a593Smuzhiyun kfree(buf);
159*4882a593Smuzhiyun return err;
160*4882a593Smuzhiyun }
161*4882a593Smuzhiyun
162*4882a593Smuzhiyun skb_pull(skb, sizeof(struct ipv6hdr));
163*4882a593Smuzhiyun skb_postpull_rcsum(skb, skb_network_header(skb),
164*4882a593Smuzhiyun sizeof(struct ipv6hdr));
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun skb_push(skb, sizeof(struct ipv6hdr) + hdrlen);
167*4882a593Smuzhiyun skb_reset_network_header(skb);
168*4882a593Smuzhiyun skb_mac_header_rebuild(skb);
169*4882a593Smuzhiyun
170*4882a593Smuzhiyun hdr = ipv6_hdr(skb);
171*4882a593Smuzhiyun memmove(hdr, oldhdr, sizeof(*hdr));
172*4882a593Smuzhiyun isrh = (void *)hdr + sizeof(*hdr);
173*4882a593Smuzhiyun memcpy(isrh, csrh, hdrlen);
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun isrh->nexthdr = hdr->nexthdr;
176*4882a593Smuzhiyun hdr->nexthdr = NEXTHDR_ROUTING;
177*4882a593Smuzhiyun hdr->daddr = srh->rpl_segaddr[0];
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
180*4882a593Smuzhiyun skb_set_transport_header(skb, sizeof(struct ipv6hdr));
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun skb_postpush_rcsum(skb, hdr, sizeof(struct ipv6hdr) + hdrlen);
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun kfree(buf);
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun return 0;
187*4882a593Smuzhiyun }
188*4882a593Smuzhiyun
rpl_do_srh(struct sk_buff * skb,const struct rpl_lwt * rlwt)189*4882a593Smuzhiyun static int rpl_do_srh(struct sk_buff *skb, const struct rpl_lwt *rlwt)
190*4882a593Smuzhiyun {
191*4882a593Smuzhiyun struct dst_entry *dst = skb_dst(skb);
192*4882a593Smuzhiyun struct rpl_iptunnel_encap *tinfo;
193*4882a593Smuzhiyun int err = 0;
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun if (skb->protocol != htons(ETH_P_IPV6))
196*4882a593Smuzhiyun return -EINVAL;
197*4882a593Smuzhiyun
198*4882a593Smuzhiyun tinfo = rpl_encap_lwtunnel(dst->lwtstate);
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun err = rpl_do_srh_inline(skb, rlwt, tinfo->srh);
201*4882a593Smuzhiyun if (err)
202*4882a593Smuzhiyun return err;
203*4882a593Smuzhiyun
204*4882a593Smuzhiyun return 0;
205*4882a593Smuzhiyun }
206*4882a593Smuzhiyun
rpl_output(struct net * net,struct sock * sk,struct sk_buff * skb)207*4882a593Smuzhiyun static int rpl_output(struct net *net, struct sock *sk, struct sk_buff *skb)
208*4882a593Smuzhiyun {
209*4882a593Smuzhiyun struct dst_entry *orig_dst = skb_dst(skb);
210*4882a593Smuzhiyun struct dst_entry *dst = NULL;
211*4882a593Smuzhiyun struct rpl_lwt *rlwt;
212*4882a593Smuzhiyun int err;
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun rlwt = rpl_lwt_lwtunnel(orig_dst->lwtstate);
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun err = rpl_do_srh(skb, rlwt);
217*4882a593Smuzhiyun if (unlikely(err))
218*4882a593Smuzhiyun goto drop;
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun preempt_disable();
221*4882a593Smuzhiyun dst = dst_cache_get(&rlwt->cache);
222*4882a593Smuzhiyun preempt_enable();
223*4882a593Smuzhiyun
224*4882a593Smuzhiyun if (unlikely(!dst)) {
225*4882a593Smuzhiyun struct ipv6hdr *hdr = ipv6_hdr(skb);
226*4882a593Smuzhiyun struct flowi6 fl6;
227*4882a593Smuzhiyun
228*4882a593Smuzhiyun memset(&fl6, 0, sizeof(fl6));
229*4882a593Smuzhiyun fl6.daddr = hdr->daddr;
230*4882a593Smuzhiyun fl6.saddr = hdr->saddr;
231*4882a593Smuzhiyun fl6.flowlabel = ip6_flowinfo(hdr);
232*4882a593Smuzhiyun fl6.flowi6_mark = skb->mark;
233*4882a593Smuzhiyun fl6.flowi6_proto = hdr->nexthdr;
234*4882a593Smuzhiyun
235*4882a593Smuzhiyun dst = ip6_route_output(net, NULL, &fl6);
236*4882a593Smuzhiyun if (dst->error) {
237*4882a593Smuzhiyun err = dst->error;
238*4882a593Smuzhiyun dst_release(dst);
239*4882a593Smuzhiyun goto drop;
240*4882a593Smuzhiyun }
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun preempt_disable();
243*4882a593Smuzhiyun dst_cache_set_ip6(&rlwt->cache, dst, &fl6.saddr);
244*4882a593Smuzhiyun preempt_enable();
245*4882a593Smuzhiyun }
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun skb_dst_drop(skb);
248*4882a593Smuzhiyun skb_dst_set(skb, dst);
249*4882a593Smuzhiyun
250*4882a593Smuzhiyun err = skb_cow_head(skb, LL_RESERVED_SPACE(dst->dev));
251*4882a593Smuzhiyun if (unlikely(err))
252*4882a593Smuzhiyun goto drop;
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun return dst_output(net, sk, skb);
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun drop:
257*4882a593Smuzhiyun kfree_skb(skb);
258*4882a593Smuzhiyun return err;
259*4882a593Smuzhiyun }
260*4882a593Smuzhiyun
rpl_input(struct sk_buff * skb)261*4882a593Smuzhiyun static int rpl_input(struct sk_buff *skb)
262*4882a593Smuzhiyun {
263*4882a593Smuzhiyun struct dst_entry *orig_dst = skb_dst(skb);
264*4882a593Smuzhiyun struct dst_entry *dst = NULL;
265*4882a593Smuzhiyun struct rpl_lwt *rlwt;
266*4882a593Smuzhiyun int err;
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun rlwt = rpl_lwt_lwtunnel(orig_dst->lwtstate);
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun err = rpl_do_srh(skb, rlwt);
271*4882a593Smuzhiyun if (unlikely(err)) {
272*4882a593Smuzhiyun kfree_skb(skb);
273*4882a593Smuzhiyun return err;
274*4882a593Smuzhiyun }
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun preempt_disable();
277*4882a593Smuzhiyun dst = dst_cache_get(&rlwt->cache);
278*4882a593Smuzhiyun preempt_enable();
279*4882a593Smuzhiyun
280*4882a593Smuzhiyun skb_dst_drop(skb);
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun if (!dst) {
283*4882a593Smuzhiyun ip6_route_input(skb);
284*4882a593Smuzhiyun dst = skb_dst(skb);
285*4882a593Smuzhiyun if (!dst->error) {
286*4882a593Smuzhiyun preempt_disable();
287*4882a593Smuzhiyun dst_cache_set_ip6(&rlwt->cache, dst,
288*4882a593Smuzhiyun &ipv6_hdr(skb)->saddr);
289*4882a593Smuzhiyun preempt_enable();
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun } else {
292*4882a593Smuzhiyun skb_dst_set(skb, dst);
293*4882a593Smuzhiyun }
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun err = skb_cow_head(skb, LL_RESERVED_SPACE(dst->dev));
296*4882a593Smuzhiyun if (unlikely(err))
297*4882a593Smuzhiyun return err;
298*4882a593Smuzhiyun
299*4882a593Smuzhiyun return dst_input(skb);
300*4882a593Smuzhiyun }
301*4882a593Smuzhiyun
nla_put_rpl_srh(struct sk_buff * skb,int attrtype,struct rpl_iptunnel_encap * tuninfo)302*4882a593Smuzhiyun static int nla_put_rpl_srh(struct sk_buff *skb, int attrtype,
303*4882a593Smuzhiyun struct rpl_iptunnel_encap *tuninfo)
304*4882a593Smuzhiyun {
305*4882a593Smuzhiyun struct rpl_iptunnel_encap *data;
306*4882a593Smuzhiyun struct nlattr *nla;
307*4882a593Smuzhiyun int len;
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun len = RPL_IPTUNNEL_SRH_SIZE(tuninfo->srh);
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun nla = nla_reserve(skb, attrtype, len);
312*4882a593Smuzhiyun if (!nla)
313*4882a593Smuzhiyun return -EMSGSIZE;
314*4882a593Smuzhiyun
315*4882a593Smuzhiyun data = nla_data(nla);
316*4882a593Smuzhiyun memcpy(data, tuninfo->srh, len);
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun return 0;
319*4882a593Smuzhiyun }
320*4882a593Smuzhiyun
rpl_fill_encap_info(struct sk_buff * skb,struct lwtunnel_state * lwtstate)321*4882a593Smuzhiyun static int rpl_fill_encap_info(struct sk_buff *skb,
322*4882a593Smuzhiyun struct lwtunnel_state *lwtstate)
323*4882a593Smuzhiyun {
324*4882a593Smuzhiyun struct rpl_iptunnel_encap *tuninfo = rpl_encap_lwtunnel(lwtstate);
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun if (nla_put_rpl_srh(skb, RPL_IPTUNNEL_SRH, tuninfo))
327*4882a593Smuzhiyun return -EMSGSIZE;
328*4882a593Smuzhiyun
329*4882a593Smuzhiyun return 0;
330*4882a593Smuzhiyun }
331*4882a593Smuzhiyun
rpl_encap_nlsize(struct lwtunnel_state * lwtstate)332*4882a593Smuzhiyun static int rpl_encap_nlsize(struct lwtunnel_state *lwtstate)
333*4882a593Smuzhiyun {
334*4882a593Smuzhiyun struct rpl_iptunnel_encap *tuninfo = rpl_encap_lwtunnel(lwtstate);
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun return nla_total_size(RPL_IPTUNNEL_SRH_SIZE(tuninfo->srh));
337*4882a593Smuzhiyun }
338*4882a593Smuzhiyun
rpl_encap_cmp(struct lwtunnel_state * a,struct lwtunnel_state * b)339*4882a593Smuzhiyun static int rpl_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b)
340*4882a593Smuzhiyun {
341*4882a593Smuzhiyun struct rpl_iptunnel_encap *a_hdr = rpl_encap_lwtunnel(a);
342*4882a593Smuzhiyun struct rpl_iptunnel_encap *b_hdr = rpl_encap_lwtunnel(b);
343*4882a593Smuzhiyun int len = RPL_IPTUNNEL_SRH_SIZE(a_hdr->srh);
344*4882a593Smuzhiyun
345*4882a593Smuzhiyun if (len != RPL_IPTUNNEL_SRH_SIZE(b_hdr->srh))
346*4882a593Smuzhiyun return 1;
347*4882a593Smuzhiyun
348*4882a593Smuzhiyun return memcmp(a_hdr, b_hdr, len);
349*4882a593Smuzhiyun }
350*4882a593Smuzhiyun
351*4882a593Smuzhiyun static const struct lwtunnel_encap_ops rpl_ops = {
352*4882a593Smuzhiyun .build_state = rpl_build_state,
353*4882a593Smuzhiyun .destroy_state = rpl_destroy_state,
354*4882a593Smuzhiyun .output = rpl_output,
355*4882a593Smuzhiyun .input = rpl_input,
356*4882a593Smuzhiyun .fill_encap = rpl_fill_encap_info,
357*4882a593Smuzhiyun .get_encap_size = rpl_encap_nlsize,
358*4882a593Smuzhiyun .cmp_encap = rpl_encap_cmp,
359*4882a593Smuzhiyun .owner = THIS_MODULE,
360*4882a593Smuzhiyun };
361*4882a593Smuzhiyun
rpl_init(void)362*4882a593Smuzhiyun int __init rpl_init(void)
363*4882a593Smuzhiyun {
364*4882a593Smuzhiyun int err;
365*4882a593Smuzhiyun
366*4882a593Smuzhiyun err = lwtunnel_encap_add_ops(&rpl_ops, LWTUNNEL_ENCAP_RPL);
367*4882a593Smuzhiyun if (err)
368*4882a593Smuzhiyun goto out;
369*4882a593Smuzhiyun
370*4882a593Smuzhiyun pr_info("RPL Segment Routing with IPv6\n");
371*4882a593Smuzhiyun
372*4882a593Smuzhiyun return 0;
373*4882a593Smuzhiyun
374*4882a593Smuzhiyun out:
375*4882a593Smuzhiyun return err;
376*4882a593Smuzhiyun }
377*4882a593Smuzhiyun
rpl_exit(void)378*4882a593Smuzhiyun void rpl_exit(void)
379*4882a593Smuzhiyun {
380*4882a593Smuzhiyun lwtunnel_encap_del_ops(&rpl_ops, LWTUNNEL_ENCAP_RPL);
381*4882a593Smuzhiyun }
382