ioam6_iptunnel.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. // SPDX-License-Identifier: GPL-2.0+
  2. /*
  3. * IPv6 IOAM Lightweight Tunnel implementation
  4. *
  5. * Author:
  6. * Justin Iurman <justin.iurman@uliege.be>
  7. */
  8. #include <linux/kernel.h>
  9. #include <linux/skbuff.h>
  10. #include <linux/net.h>
  11. #include <linux/in6.h>
  12. #include <linux/ioam6.h>
  13. #include <linux/ioam6_iptunnel.h>
  14. #include <net/dst.h>
  15. #include <net/sock.h>
  16. #include <net/lwtunnel.h>
  17. #include <net/ioam6.h>
  18. #include <net/netlink.h>
  19. #include <net/ipv6.h>
  20. #include <net/dst_cache.h>
  21. #include <net/ip6_route.h>
  22. #include <net/addrconf.h>
  23. struct ioam6_lwt_encap {
  24. struct ipv6_hopopt_hdr eh;
  25. u8 pad[2]; /* 2-octet padding for 4n-alignment */
  26. struct ioam6_hdr ioamh;
  27. struct ioam6_trace_hdr traceh;
  28. } __packed;
  29. struct ioam6_lwt_freq {
  30. u32 k;
  31. u32 n;
  32. };
  33. struct ioam6_lwt {
  34. struct dst_entry null_dst;
  35. struct dst_cache cache;
  36. struct ioam6_lwt_freq freq;
  37. atomic_t pkt_cnt;
  38. u8 mode;
  39. bool has_tunsrc;
  40. struct in6_addr tunsrc;
  41. struct in6_addr tundst;
  42. struct ioam6_lwt_encap tuninfo;
  43. };
  44. static const struct netlink_range_validation freq_range = {
  45. .min = IOAM6_IPTUNNEL_FREQ_MIN,
  46. .max = IOAM6_IPTUNNEL_FREQ_MAX,
  47. };
  48. static struct ioam6_lwt *ioam6_lwt_state(struct lwtunnel_state *lwt)
  49. {
  50. return (struct ioam6_lwt *)lwt->data;
  51. }
  52. static struct ioam6_lwt_encap *ioam6_lwt_info(struct lwtunnel_state *lwt)
  53. {
  54. return &ioam6_lwt_state(lwt)->tuninfo;
  55. }
  56. static struct ioam6_trace_hdr *ioam6_lwt_trace(struct lwtunnel_state *lwt)
  57. {
  58. return &(ioam6_lwt_state(lwt)->tuninfo.traceh);
  59. }
  60. static const struct nla_policy ioam6_iptunnel_policy[IOAM6_IPTUNNEL_MAX + 1] = {
  61. [IOAM6_IPTUNNEL_FREQ_K] = NLA_POLICY_FULL_RANGE(NLA_U32, &freq_range),
  62. [IOAM6_IPTUNNEL_FREQ_N] = NLA_POLICY_FULL_RANGE(NLA_U32, &freq_range),
  63. [IOAM6_IPTUNNEL_MODE] = NLA_POLICY_RANGE(NLA_U8,
  64. IOAM6_IPTUNNEL_MODE_MIN,
  65. IOAM6_IPTUNNEL_MODE_MAX),
  66. [IOAM6_IPTUNNEL_SRC] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
  67. [IOAM6_IPTUNNEL_DST] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)),
  68. [IOAM6_IPTUNNEL_TRACE] = NLA_POLICY_EXACT_LEN(
  69. sizeof(struct ioam6_trace_hdr)),
  70. };
  71. static bool ioam6_validate_trace_hdr(struct ioam6_trace_hdr *trace)
  72. {
  73. u32 fields;
  74. if (!trace->type_be32 || !trace->remlen ||
  75. trace->remlen > IOAM6_TRACE_DATA_SIZE_MAX / 4 ||
  76. trace->type.bit12 | trace->type.bit13 | trace->type.bit14 |
  77. trace->type.bit15 | trace->type.bit16 | trace->type.bit17 |
  78. trace->type.bit18 | trace->type.bit19 | trace->type.bit20 |
  79. trace->type.bit21 | trace->type.bit23)
  80. return false;
  81. fields = be32_to_cpu(trace->type_be32);
  82. trace->nodelen = ioam6_trace_compute_nodelen(fields);
  83. return true;
  84. }
  85. static int ioam6_build_state(struct net *net, struct nlattr *nla,
  86. unsigned int family, const void *cfg,
  87. struct lwtunnel_state **ts,
  88. struct netlink_ext_ack *extack)
  89. {
  90. struct nlattr *tb[IOAM6_IPTUNNEL_MAX + 1];
  91. struct ioam6_lwt_encap *tuninfo;
  92. struct ioam6_trace_hdr *trace;
  93. struct lwtunnel_state *lwt;
  94. struct ioam6_lwt *ilwt;
  95. int len_aligned, err;
  96. u32 freq_k, freq_n;
  97. u8 mode;
  98. if (family != AF_INET6)
  99. return -EINVAL;
  100. err = nla_parse_nested(tb, IOAM6_IPTUNNEL_MAX, nla,
  101. ioam6_iptunnel_policy, extack);
  102. if (err < 0)
  103. return err;
  104. if ((!tb[IOAM6_IPTUNNEL_FREQ_K] && tb[IOAM6_IPTUNNEL_FREQ_N]) ||
  105. (tb[IOAM6_IPTUNNEL_FREQ_K] && !tb[IOAM6_IPTUNNEL_FREQ_N])) {
  106. NL_SET_ERR_MSG(extack, "freq: missing parameter");
  107. return -EINVAL;
  108. } else if (!tb[IOAM6_IPTUNNEL_FREQ_K] && !tb[IOAM6_IPTUNNEL_FREQ_N]) {
  109. freq_k = IOAM6_IPTUNNEL_FREQ_MIN;
  110. freq_n = IOAM6_IPTUNNEL_FREQ_MIN;
  111. } else {
  112. freq_k = nla_get_u32(tb[IOAM6_IPTUNNEL_FREQ_K]);
  113. freq_n = nla_get_u32(tb[IOAM6_IPTUNNEL_FREQ_N]);
  114. if (freq_k > freq_n) {
  115. NL_SET_ERR_MSG(extack, "freq: k > n is forbidden");
  116. return -EINVAL;
  117. }
  118. }
  119. mode = nla_get_u8_default(tb[IOAM6_IPTUNNEL_MODE],
  120. IOAM6_IPTUNNEL_MODE_INLINE);
  121. if (tb[IOAM6_IPTUNNEL_SRC] && mode == IOAM6_IPTUNNEL_MODE_INLINE) {
  122. NL_SET_ERR_MSG(extack, "no tunnel src expected with this mode");
  123. return -EINVAL;
  124. }
  125. if (!tb[IOAM6_IPTUNNEL_DST] && mode != IOAM6_IPTUNNEL_MODE_INLINE) {
  126. NL_SET_ERR_MSG(extack, "this mode needs a tunnel destination");
  127. return -EINVAL;
  128. }
  129. if (!tb[IOAM6_IPTUNNEL_TRACE]) {
  130. NL_SET_ERR_MSG(extack, "missing trace");
  131. return -EINVAL;
  132. }
  133. trace = nla_data(tb[IOAM6_IPTUNNEL_TRACE]);
  134. if (!ioam6_validate_trace_hdr(trace)) {
  135. NL_SET_ERR_MSG_ATTR(extack, tb[IOAM6_IPTUNNEL_TRACE],
  136. "invalid trace validation");
  137. return -EINVAL;
  138. }
  139. len_aligned = ALIGN(trace->remlen * 4, 8);
  140. lwt = lwtunnel_state_alloc(sizeof(*ilwt) + len_aligned);
  141. if (!lwt)
  142. return -ENOMEM;
  143. ilwt = ioam6_lwt_state(lwt);
  144. err = dst_cache_init(&ilwt->cache, GFP_ATOMIC);
  145. if (err)
  146. goto free_lwt;
  147. /* This "fake" dst_entry will be stored in a dst_cache, which will call
  148. * dst_hold() and dst_release() on it. We must ensure that dst_destroy()
  149. * will never be called. For that, its initial refcount is 1 and +1 when
  150. * it is stored in the cache. Then, +1/-1 each time we read the cache
  151. * and release it. Long story short, we're fine.
  152. */
  153. dst_init(&ilwt->null_dst, NULL, NULL, DST_OBSOLETE_NONE, DST_NOCOUNT);
  154. atomic_set(&ilwt->pkt_cnt, 0);
  155. ilwt->freq.k = freq_k;
  156. ilwt->freq.n = freq_n;
  157. ilwt->mode = mode;
  158. if (!tb[IOAM6_IPTUNNEL_SRC]) {
  159. ilwt->has_tunsrc = false;
  160. } else {
  161. ilwt->has_tunsrc = true;
  162. ilwt->tunsrc = nla_get_in6_addr(tb[IOAM6_IPTUNNEL_SRC]);
  163. if (ipv6_addr_any(&ilwt->tunsrc)) {
  164. NL_SET_ERR_MSG_ATTR(extack, tb[IOAM6_IPTUNNEL_SRC],
  165. "invalid tunnel source address");
  166. err = -EINVAL;
  167. goto free_cache;
  168. }
  169. }
  170. if (tb[IOAM6_IPTUNNEL_DST]) {
  171. ilwt->tundst = nla_get_in6_addr(tb[IOAM6_IPTUNNEL_DST]);
  172. if (ipv6_addr_any(&ilwt->tundst)) {
  173. NL_SET_ERR_MSG_ATTR(extack, tb[IOAM6_IPTUNNEL_DST],
  174. "invalid tunnel dest address");
  175. err = -EINVAL;
  176. goto free_cache;
  177. }
  178. }
  179. tuninfo = ioam6_lwt_info(lwt);
  180. tuninfo->eh.hdrlen = ((sizeof(*tuninfo) + len_aligned) >> 3) - 1;
  181. tuninfo->pad[0] = IPV6_TLV_PADN;
  182. tuninfo->ioamh.type = IOAM6_TYPE_PREALLOC;
  183. tuninfo->ioamh.opt_type = IPV6_TLV_IOAM;
  184. tuninfo->ioamh.opt_len = sizeof(tuninfo->ioamh) - 2 + sizeof(*trace)
  185. + trace->remlen * 4;
  186. memcpy(&tuninfo->traceh, trace, sizeof(*trace));
  187. if (len_aligned - trace->remlen * 4) {
  188. tuninfo->traceh.data[trace->remlen * 4] = IPV6_TLV_PADN;
  189. tuninfo->traceh.data[trace->remlen * 4 + 1] = 2;
  190. }
  191. lwt->type = LWTUNNEL_ENCAP_IOAM6;
  192. lwt->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT;
  193. *ts = lwt;
  194. return 0;
  195. free_cache:
  196. dst_cache_destroy(&ilwt->cache);
  197. free_lwt:
  198. kfree(lwt);
  199. return err;
  200. }
  201. static int ioam6_do_fill(struct net *net, struct sk_buff *skb)
  202. {
  203. struct ioam6_trace_hdr *trace;
  204. struct ioam6_namespace *ns;
  205. trace = (struct ioam6_trace_hdr *)(skb_transport_header(skb)
  206. + sizeof(struct ipv6_hopopt_hdr) + 2
  207. + sizeof(struct ioam6_hdr));
  208. ns = ioam6_namespace(net, trace->namespace_id);
  209. if (ns)
  210. ioam6_fill_trace_data(skb, ns, trace, false);
  211. return 0;
  212. }
  213. static int ioam6_do_inline(struct net *net, struct sk_buff *skb,
  214. struct ioam6_lwt_encap *tuninfo,
  215. struct dst_entry *cache_dst)
  216. {
  217. struct ipv6hdr *oldhdr, *hdr;
  218. int hdrlen, err;
  219. hdrlen = (tuninfo->eh.hdrlen + 1) << 3;
  220. err = skb_cow_head(skb, hdrlen + dst_dev_overhead(cache_dst, skb));
  221. if (unlikely(err))
  222. return err;
  223. oldhdr = ipv6_hdr(skb);
  224. skb_pull(skb, sizeof(*oldhdr));
  225. skb_postpull_rcsum(skb, skb_network_header(skb), sizeof(*oldhdr));
  226. skb_push(skb, sizeof(*oldhdr) + hdrlen);
  227. skb_reset_network_header(skb);
  228. skb_mac_header_rebuild(skb);
  229. hdr = ipv6_hdr(skb);
  230. memmove(hdr, oldhdr, sizeof(*oldhdr));
  231. tuninfo->eh.nexthdr = hdr->nexthdr;
  232. skb_set_transport_header(skb, sizeof(*hdr));
  233. skb_postpush_rcsum(skb, hdr, sizeof(*hdr) + hdrlen);
  234. memcpy(skb_transport_header(skb), (u8 *)tuninfo, hdrlen);
  235. hdr->nexthdr = NEXTHDR_HOP;
  236. hdr->payload_len = cpu_to_be16(skb->len - sizeof(*hdr));
  237. return ioam6_do_fill(net, skb);
  238. }
  239. static int ioam6_do_encap(struct net *net, struct sk_buff *skb,
  240. struct ioam6_lwt_encap *tuninfo,
  241. bool has_tunsrc,
  242. struct in6_addr *tunsrc,
  243. struct in6_addr *tundst,
  244. struct dst_entry *cache_dst)
  245. {
  246. struct dst_entry *dst = skb_dst(skb);
  247. struct ipv6hdr *hdr, *inner_hdr;
  248. int hdrlen, len, err;
  249. hdrlen = (tuninfo->eh.hdrlen + 1) << 3;
  250. len = sizeof(*hdr) + hdrlen;
  251. err = skb_cow_head(skb, len + dst_dev_overhead(cache_dst, skb));
  252. if (unlikely(err))
  253. return err;
  254. inner_hdr = ipv6_hdr(skb);
  255. skb_push(skb, len);
  256. skb_reset_network_header(skb);
  257. skb_mac_header_rebuild(skb);
  258. skb_set_transport_header(skb, sizeof(*hdr));
  259. tuninfo->eh.nexthdr = NEXTHDR_IPV6;
  260. memcpy(skb_transport_header(skb), (u8 *)tuninfo, hdrlen);
  261. hdr = ipv6_hdr(skb);
  262. memcpy(hdr, inner_hdr, sizeof(*hdr));
  263. hdr->nexthdr = NEXTHDR_HOP;
  264. hdr->payload_len = cpu_to_be16(skb->len - sizeof(*hdr));
  265. hdr->daddr = *tundst;
  266. if (has_tunsrc)
  267. memcpy(&hdr->saddr, tunsrc, sizeof(*tunsrc));
  268. else
  269. ipv6_dev_get_saddr(net, dst_dev(dst), &hdr->daddr,
  270. IPV6_PREFER_SRC_PUBLIC, &hdr->saddr);
  271. skb_postpush_rcsum(skb, hdr, len);
  272. return ioam6_do_fill(net, skb);
  273. }
  274. static int ioam6_output(struct net *net, struct sock *sk, struct sk_buff *skb)
  275. {
  276. struct dst_entry *orig_dst = skb_dst(skb);
  277. struct dst_entry *dst = NULL;
  278. struct ioam6_lwt *ilwt;
  279. int err = -EINVAL;
  280. u32 pkt_cnt;
  281. if (skb->protocol != htons(ETH_P_IPV6))
  282. goto drop;
  283. ilwt = ioam6_lwt_state(orig_dst->lwtstate);
  284. /* Check for insertion frequency (i.e., "k over n" insertions) */
  285. pkt_cnt = atomic_fetch_inc(&ilwt->pkt_cnt);
  286. if (pkt_cnt % ilwt->freq.n >= ilwt->freq.k)
  287. goto out;
  288. local_bh_disable();
  289. dst = dst_cache_get(&ilwt->cache);
  290. local_bh_enable();
  291. /* This is how we notify that the destination does not change after
  292. * transformation and that we need to use orig_dst instead of the cache
  293. */
  294. if (dst == &ilwt->null_dst) {
  295. dst_release(dst);
  296. dst = orig_dst;
  297. /* keep refcount balance: dst_release() is called at the end */
  298. dst_hold(dst);
  299. }
  300. switch (ilwt->mode) {
  301. case IOAM6_IPTUNNEL_MODE_INLINE:
  302. do_inline:
  303. /* Direct insertion - if there is no Hop-by-Hop yet */
  304. if (ipv6_hdr(skb)->nexthdr == NEXTHDR_HOP)
  305. goto out;
  306. err = ioam6_do_inline(net, skb, &ilwt->tuninfo, dst);
  307. if (unlikely(err))
  308. goto drop;
  309. break;
  310. case IOAM6_IPTUNNEL_MODE_ENCAP:
  311. do_encap:
  312. /* Encapsulation (ip6ip6) */
  313. err = ioam6_do_encap(net, skb, &ilwt->tuninfo,
  314. ilwt->has_tunsrc, &ilwt->tunsrc,
  315. &ilwt->tundst, dst);
  316. if (unlikely(err))
  317. goto drop;
  318. break;
  319. case IOAM6_IPTUNNEL_MODE_AUTO:
  320. /* Automatic (RFC8200 compliant):
  321. * - local packets -> INLINE mode
  322. * - in-transit packets -> ENCAP mode
  323. */
  324. if (!skb->dev)
  325. goto do_inline;
  326. goto do_encap;
  327. default:
  328. goto drop;
  329. }
  330. if (unlikely(!dst)) {
  331. struct ipv6hdr *hdr = ipv6_hdr(skb);
  332. struct flowi6 fl6;
  333. memset(&fl6, 0, sizeof(fl6));
  334. fl6.daddr = hdr->daddr;
  335. fl6.saddr = hdr->saddr;
  336. fl6.flowlabel = ip6_flowinfo(hdr);
  337. fl6.flowi6_mark = skb->mark;
  338. fl6.flowi6_proto = hdr->nexthdr;
  339. dst = ip6_route_output(net, NULL, &fl6);
  340. if (dst->error) {
  341. err = dst->error;
  342. goto drop;
  343. }
  344. /* If the destination is the same after transformation (which is
  345. * a valid use case for IOAM), then we don't want to add it to
  346. * the cache in order to avoid a reference loop. Instead, we add
  347. * our fake dst_entry to the cache as a way to detect this case.
  348. * Otherwise, we add the resolved destination to the cache.
  349. */
  350. local_bh_disable();
  351. if (orig_dst->lwtstate == dst->lwtstate)
  352. dst_cache_set_ip6(&ilwt->cache,
  353. &ilwt->null_dst, &fl6.saddr);
  354. else
  355. dst_cache_set_ip6(&ilwt->cache, dst, &fl6.saddr);
  356. local_bh_enable();
  357. err = skb_cow_head(skb, LL_RESERVED_SPACE(dst_dev(dst)));
  358. if (unlikely(err))
  359. goto drop;
  360. }
  361. /* avoid lwtunnel_output() reentry loop when destination is the same
  362. * after transformation (e.g., with the inline mode)
  363. */
  364. if (orig_dst->lwtstate != dst->lwtstate) {
  365. skb_dst_drop(skb);
  366. skb_dst_set(skb, dst);
  367. return dst_output(net, sk, skb);
  368. }
  369. out:
  370. dst_release(dst);
  371. return orig_dst->lwtstate->orig_output(net, sk, skb);
  372. drop:
  373. dst_release(dst);
  374. kfree_skb(skb);
  375. return err;
  376. }
  377. static void ioam6_destroy_state(struct lwtunnel_state *lwt)
  378. {
  379. /* Since the refcount of per-cpu dst_entry caches will never be 0 (see
  380. * why above) when our "fake" dst_entry is used, it is not necessary to
  381. * remove them before calling dst_cache_destroy()
  382. */
  383. dst_cache_destroy(&ioam6_lwt_state(lwt)->cache);
  384. }
  385. static int ioam6_fill_encap_info(struct sk_buff *skb,
  386. struct lwtunnel_state *lwtstate)
  387. {
  388. struct ioam6_lwt *ilwt = ioam6_lwt_state(lwtstate);
  389. int err;
  390. err = nla_put_u32(skb, IOAM6_IPTUNNEL_FREQ_K, ilwt->freq.k);
  391. if (err)
  392. goto ret;
  393. err = nla_put_u32(skb, IOAM6_IPTUNNEL_FREQ_N, ilwt->freq.n);
  394. if (err)
  395. goto ret;
  396. err = nla_put_u8(skb, IOAM6_IPTUNNEL_MODE, ilwt->mode);
  397. if (err)
  398. goto ret;
  399. if (ilwt->mode != IOAM6_IPTUNNEL_MODE_INLINE) {
  400. if (ilwt->has_tunsrc) {
  401. err = nla_put_in6_addr(skb, IOAM6_IPTUNNEL_SRC,
  402. &ilwt->tunsrc);
  403. if (err)
  404. goto ret;
  405. }
  406. err = nla_put_in6_addr(skb, IOAM6_IPTUNNEL_DST, &ilwt->tundst);
  407. if (err)
  408. goto ret;
  409. }
  410. err = nla_put(skb, IOAM6_IPTUNNEL_TRACE, sizeof(ilwt->tuninfo.traceh),
  411. &ilwt->tuninfo.traceh);
  412. ret:
  413. return err;
  414. }
  415. static int ioam6_encap_nlsize(struct lwtunnel_state *lwtstate)
  416. {
  417. struct ioam6_lwt *ilwt = ioam6_lwt_state(lwtstate);
  418. int nlsize;
  419. nlsize = nla_total_size(sizeof(ilwt->freq.k)) +
  420. nla_total_size(sizeof(ilwt->freq.n)) +
  421. nla_total_size(sizeof(ilwt->mode)) +
  422. nla_total_size(sizeof(ilwt->tuninfo.traceh));
  423. if (ilwt->mode != IOAM6_IPTUNNEL_MODE_INLINE) {
  424. if (ilwt->has_tunsrc)
  425. nlsize += nla_total_size(sizeof(ilwt->tunsrc));
  426. nlsize += nla_total_size(sizeof(ilwt->tundst));
  427. }
  428. return nlsize;
  429. }
  430. static int ioam6_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b)
  431. {
  432. struct ioam6_trace_hdr *trace_a = ioam6_lwt_trace(a);
  433. struct ioam6_trace_hdr *trace_b = ioam6_lwt_trace(b);
  434. struct ioam6_lwt *ilwt_a = ioam6_lwt_state(a);
  435. struct ioam6_lwt *ilwt_b = ioam6_lwt_state(b);
  436. return (ilwt_a->freq.k != ilwt_b->freq.k ||
  437. ilwt_a->freq.n != ilwt_b->freq.n ||
  438. ilwt_a->mode != ilwt_b->mode ||
  439. ilwt_a->has_tunsrc != ilwt_b->has_tunsrc ||
  440. (ilwt_a->mode != IOAM6_IPTUNNEL_MODE_INLINE &&
  441. !ipv6_addr_equal(&ilwt_a->tundst, &ilwt_b->tundst)) ||
  442. (ilwt_a->mode != IOAM6_IPTUNNEL_MODE_INLINE &&
  443. ilwt_a->has_tunsrc &&
  444. !ipv6_addr_equal(&ilwt_a->tunsrc, &ilwt_b->tunsrc)) ||
  445. trace_a->namespace_id != trace_b->namespace_id);
  446. }
  447. static const struct lwtunnel_encap_ops ioam6_iptun_ops = {
  448. .build_state = ioam6_build_state,
  449. .destroy_state = ioam6_destroy_state,
  450. .output = ioam6_output,
  451. .fill_encap = ioam6_fill_encap_info,
  452. .get_encap_size = ioam6_encap_nlsize,
  453. .cmp_encap = ioam6_encap_cmp,
  454. .owner = THIS_MODULE,
  455. };
  456. int __init ioam6_iptunnel_init(void)
  457. {
  458. return lwtunnel_encap_add_ops(&ioam6_iptun_ops, LWTUNNEL_ENCAP_IOAM6);
  459. }
  460. void ioam6_iptunnel_exit(void)
  461. {
  462. lwtunnel_encap_del_ops(&ioam6_iptun_ops, LWTUNNEL_ENCAP_IOAM6);
  463. }