| rjw | 1f88458 | 2022-01-06 17:20:42 +0800 | [diff] [blame] | 1 | /* | 
 | 2 |  *  SR-IPv6 implementation | 
 | 3 |  * | 
 | 4 |  *  Author: | 
 | 5 |  *  David Lebrun <david.lebrun@uclouvain.be> | 
 | 6 |  * | 
 | 7 |  * | 
 | 8 |  *  This program is free software; you can redistribute it and/or | 
 | 9 |  *        modify it under the terms of the GNU General Public License | 
 | 10 |  *        as published by the Free Software Foundation; either version | 
 | 11 |  *        2 of the License, or (at your option) any later version. | 
 | 12 |  */ | 
 | 13 |  | 
 | 14 | #include <linux/types.h> | 
 | 15 | #include <linux/skbuff.h> | 
 | 16 | #include <linux/net.h> | 
 | 17 | #include <linux/module.h> | 
 | 18 | #include <net/ip.h> | 
 | 19 | #include <net/ip_tunnels.h> | 
 | 20 | #include <net/lwtunnel.h> | 
 | 21 | #include <net/netevent.h> | 
 | 22 | #include <net/netns/generic.h> | 
 | 23 | #include <net/ip6_fib.h> | 
 | 24 | #include <net/route.h> | 
 | 25 | #include <net/seg6.h> | 
 | 26 | #include <linux/seg6.h> | 
 | 27 | #include <linux/seg6_iptunnel.h> | 
 | 28 | #include <net/addrconf.h> | 
 | 29 | #include <net/ip6_route.h> | 
 | 30 | #include <net/dst_cache.h> | 
 | 31 | #ifdef CONFIG_IPV6_SEG6_HMAC | 
 | 32 | #include <net/seg6_hmac.h> | 
 | 33 | #endif | 
 | 34 |  | 
 | 35 | struct seg6_lwt { | 
 | 36 | 	struct dst_cache cache; | 
 | 37 | 	struct seg6_iptunnel_encap tuninfo[0]; | 
 | 38 | }; | 
 | 39 |  | 
 | 40 | static inline struct seg6_lwt *seg6_lwt_lwtunnel(struct lwtunnel_state *lwt) | 
 | 41 | { | 
 | 42 | 	return (struct seg6_lwt *)lwt->data; | 
 | 43 | } | 
 | 44 |  | 
 | 45 | static inline struct seg6_iptunnel_encap * | 
 | 46 | seg6_encap_lwtunnel(struct lwtunnel_state *lwt) | 
 | 47 | { | 
 | 48 | 	return seg6_lwt_lwtunnel(lwt)->tuninfo; | 
 | 49 | } | 
 | 50 |  | 
 | 51 | static const struct nla_policy seg6_iptunnel_policy[SEG6_IPTUNNEL_MAX + 1] = { | 
 | 52 | 	[SEG6_IPTUNNEL_SRH]	= { .type = NLA_BINARY }, | 
 | 53 | }; | 
 | 54 |  | 
 | 55 | static int nla_put_srh(struct sk_buff *skb, int attrtype, | 
 | 56 | 		       struct seg6_iptunnel_encap *tuninfo) | 
 | 57 | { | 
 | 58 | 	struct seg6_iptunnel_encap *data; | 
 | 59 | 	struct nlattr *nla; | 
 | 60 | 	int len; | 
 | 61 |  | 
 | 62 | 	len = SEG6_IPTUN_ENCAP_SIZE(tuninfo); | 
 | 63 |  | 
 | 64 | 	nla = nla_reserve(skb, attrtype, len); | 
 | 65 | 	if (!nla) | 
 | 66 | 		return -EMSGSIZE; | 
 | 67 |  | 
 | 68 | 	data = nla_data(nla); | 
 | 69 | 	memcpy(data, tuninfo, len); | 
 | 70 |  | 
 | 71 | 	return 0; | 
 | 72 | } | 
 | 73 |  | 
 | 74 | static void set_tun_src(struct net *net, struct net_device *dev, | 
 | 75 | 			struct in6_addr *daddr, struct in6_addr *saddr) | 
 | 76 | { | 
 | 77 | 	struct seg6_pernet_data *sdata = seg6_pernet(net); | 
 | 78 | 	struct in6_addr *tun_src; | 
 | 79 |  | 
 | 80 | 	rcu_read_lock(); | 
 | 81 |  | 
 | 82 | 	tun_src = rcu_dereference(sdata->tun_src); | 
 | 83 |  | 
 | 84 | 	if (!ipv6_addr_any(tun_src)) { | 
 | 85 | 		memcpy(saddr, tun_src, sizeof(struct in6_addr)); | 
 | 86 | 	} else { | 
 | 87 | 		ipv6_dev_get_saddr(net, dev, daddr, IPV6_PREFER_SRC_PUBLIC, | 
 | 88 | 				   saddr); | 
 | 89 | 	} | 
 | 90 |  | 
 | 91 | 	rcu_read_unlock(); | 
 | 92 | } | 
 | 93 |  | 
 | 94 | /* encapsulate an IPv6 packet within an outer IPv6 header with a given SRH */ | 
 | 95 | int seg6_do_srh_encap(struct sk_buff *skb, struct ipv6_sr_hdr *osrh, int proto) | 
 | 96 | { | 
 | 97 | 	struct dst_entry *dst = skb_dst(skb); | 
 | 98 | 	struct net *net = dev_net(dst->dev); | 
 | 99 | 	struct ipv6hdr *hdr, *inner_hdr; | 
 | 100 | 	struct ipv6_sr_hdr *isrh; | 
 | 101 | 	int hdrlen, tot_len, err; | 
 | 102 |  | 
 | 103 | 	hdrlen = (osrh->hdrlen + 1) << 3; | 
 | 104 | 	tot_len = hdrlen + sizeof(*hdr); | 
 | 105 |  | 
 | 106 | 	err = skb_cow_head(skb, tot_len + skb->mac_len); | 
 | 107 | 	if (unlikely(err)) | 
 | 108 | 		return err; | 
 | 109 |  | 
 | 110 | 	inner_hdr = ipv6_hdr(skb); | 
 | 111 |  | 
 | 112 | 	skb_push(skb, tot_len); | 
 | 113 | 	skb_reset_network_header(skb); | 
 | 114 | 	skb_mac_header_rebuild(skb); | 
 | 115 | 	hdr = ipv6_hdr(skb); | 
 | 116 |  | 
 | 117 | 	/* inherit tc, flowlabel and hlim | 
 | 118 | 	 * hlim will be decremented in ip6_forward() afterwards and | 
 | 119 | 	 * decapsulation will overwrite inner hlim with outer hlim | 
 | 120 | 	 */ | 
 | 121 |  | 
 | 122 | 	if (skb->protocol == htons(ETH_P_IPV6)) { | 
 | 123 | 		ip6_flow_hdr(hdr, ip6_tclass(ip6_flowinfo(inner_hdr)), | 
 | 124 | 			     ip6_flowlabel(inner_hdr)); | 
 | 125 | 		hdr->hop_limit = inner_hdr->hop_limit; | 
 | 126 | 	} else { | 
 | 127 | 		ip6_flow_hdr(hdr, 0, 0); | 
 | 128 | 		hdr->hop_limit = ip6_dst_hoplimit(skb_dst(skb)); | 
 | 129 |  | 
 | 130 | 		memset(IP6CB(skb), 0, sizeof(*IP6CB(skb))); | 
 | 131 | 	} | 
 | 132 |  | 
 | 133 | 	hdr->nexthdr = NEXTHDR_ROUTING; | 
 | 134 |  | 
 | 135 | 	isrh = (void *)hdr + sizeof(*hdr); | 
 | 136 | 	memcpy(isrh, osrh, hdrlen); | 
 | 137 |  | 
 | 138 | 	isrh->nexthdr = proto; | 
 | 139 |  | 
 | 140 | 	hdr->daddr = isrh->segments[isrh->first_segment]; | 
 | 141 | 	set_tun_src(net, dst->dev, &hdr->daddr, &hdr->saddr); | 
 | 142 |  | 
 | 143 | #ifdef CONFIG_IPV6_SEG6_HMAC | 
 | 144 | 	if (sr_has_hmac(isrh)) { | 
 | 145 | 		err = seg6_push_hmac(net, &hdr->saddr, isrh); | 
 | 146 | 		if (unlikely(err)) | 
 | 147 | 			return err; | 
 | 148 | 	} | 
 | 149 | #endif | 
 | 150 |  | 
 | 151 | 	skb_postpush_rcsum(skb, hdr, tot_len); | 
 | 152 |  | 
 | 153 | 	return 0; | 
 | 154 | } | 
 | 155 | EXPORT_SYMBOL_GPL(seg6_do_srh_encap); | 
 | 156 |  | 
 | 157 | /* insert an SRH within an IPv6 packet, just after the IPv6 header */ | 
 | 158 | int seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh) | 
 | 159 | { | 
 | 160 | 	struct ipv6hdr *hdr, *oldhdr; | 
 | 161 | 	struct ipv6_sr_hdr *isrh; | 
 | 162 | 	int hdrlen, err; | 
 | 163 |  | 
 | 164 | 	hdrlen = (osrh->hdrlen + 1) << 3; | 
 | 165 |  | 
 | 166 | 	err = skb_cow_head(skb, hdrlen + skb->mac_len); | 
 | 167 | 	if (unlikely(err)) | 
 | 168 | 		return err; | 
 | 169 |  | 
 | 170 | 	oldhdr = ipv6_hdr(skb); | 
 | 171 |  | 
 | 172 | 	skb_pull(skb, sizeof(struct ipv6hdr)); | 
 | 173 | 	skb_postpull_rcsum(skb, skb_network_header(skb), | 
 | 174 | 			   sizeof(struct ipv6hdr)); | 
 | 175 |  | 
 | 176 | 	skb_push(skb, sizeof(struct ipv6hdr) + hdrlen); | 
 | 177 | 	skb_reset_network_header(skb); | 
 | 178 | 	skb_mac_header_rebuild(skb); | 
 | 179 |  | 
 | 180 | 	hdr = ipv6_hdr(skb); | 
 | 181 |  | 
 | 182 | 	memmove(hdr, oldhdr, sizeof(*hdr)); | 
 | 183 |  | 
 | 184 | 	isrh = (void *)hdr + sizeof(*hdr); | 
 | 185 | 	memcpy(isrh, osrh, hdrlen); | 
 | 186 |  | 
 | 187 | 	isrh->nexthdr = hdr->nexthdr; | 
 | 188 | 	hdr->nexthdr = NEXTHDR_ROUTING; | 
 | 189 |  | 
 | 190 | 	isrh->segments[0] = hdr->daddr; | 
 | 191 | 	hdr->daddr = isrh->segments[isrh->first_segment]; | 
 | 192 |  | 
 | 193 | #ifdef CONFIG_IPV6_SEG6_HMAC | 
 | 194 | 	if (sr_has_hmac(isrh)) { | 
 | 195 | 		struct net *net = dev_net(skb_dst(skb)->dev); | 
 | 196 |  | 
 | 197 | 		err = seg6_push_hmac(net, &hdr->saddr, isrh); | 
 | 198 | 		if (unlikely(err)) | 
 | 199 | 			return err; | 
 | 200 | 	} | 
 | 201 | #endif | 
 | 202 |  | 
 | 203 | 	skb_postpush_rcsum(skb, hdr, sizeof(struct ipv6hdr) + hdrlen); | 
 | 204 |  | 
 | 205 | 	return 0; | 
 | 206 | } | 
 | 207 | EXPORT_SYMBOL_GPL(seg6_do_srh_inline); | 
 | 208 |  | 
 | 209 | static int seg6_do_srh(struct sk_buff *skb) | 
 | 210 | { | 
 | 211 | 	struct dst_entry *dst = skb_dst(skb); | 
 | 212 | 	struct seg6_iptunnel_encap *tinfo; | 
 | 213 | 	int proto, err = 0; | 
 | 214 |  | 
 | 215 | 	tinfo = seg6_encap_lwtunnel(dst->lwtstate); | 
 | 216 |  | 
 | 217 | 	switch (tinfo->mode) { | 
 | 218 | 	case SEG6_IPTUN_MODE_INLINE: | 
 | 219 | 		if (skb->protocol != htons(ETH_P_IPV6)) | 
 | 220 | 			return -EINVAL; | 
 | 221 |  | 
 | 222 | 		err = seg6_do_srh_inline(skb, tinfo->srh); | 
 | 223 | 		if (err) | 
 | 224 | 			return err; | 
 | 225 | 		break; | 
 | 226 | 	case SEG6_IPTUN_MODE_ENCAP: | 
 | 227 | 		err = iptunnel_handle_offloads(skb, SKB_GSO_IPXIP6); | 
 | 228 | 		if (err) | 
 | 229 | 			return err; | 
 | 230 |  | 
 | 231 | 		if (skb->protocol == htons(ETH_P_IPV6)) | 
 | 232 | 			proto = IPPROTO_IPV6; | 
 | 233 | 		else if (skb->protocol == htons(ETH_P_IP)) | 
 | 234 | 			proto = IPPROTO_IPIP; | 
 | 235 | 		else | 
 | 236 | 			return -EINVAL; | 
 | 237 |  | 
 | 238 | 		err = seg6_do_srh_encap(skb, tinfo->srh, proto); | 
 | 239 | 		if (err) | 
 | 240 | 			return err; | 
 | 241 |  | 
 | 242 | 		skb_set_inner_transport_header(skb, skb_transport_offset(skb)); | 
 | 243 | 		skb_set_inner_protocol(skb, skb->protocol); | 
 | 244 | 		skb->protocol = htons(ETH_P_IPV6); | 
 | 245 | 		break; | 
 | 246 | 	case SEG6_IPTUN_MODE_L2ENCAP: | 
 | 247 | 		if (!skb_mac_header_was_set(skb)) | 
 | 248 | 			return -EINVAL; | 
 | 249 |  | 
 | 250 | 		if (pskb_expand_head(skb, skb->mac_len, 0, GFP_ATOMIC) < 0) | 
 | 251 | 			return -ENOMEM; | 
 | 252 |  | 
 | 253 | 		skb_mac_header_rebuild(skb); | 
 | 254 | 		skb_push(skb, skb->mac_len); | 
 | 255 |  | 
 | 256 | 		err = seg6_do_srh_encap(skb, tinfo->srh, NEXTHDR_NONE); | 
 | 257 | 		if (err) | 
 | 258 | 			return err; | 
 | 259 |  | 
 | 260 | 		skb->protocol = htons(ETH_P_IPV6); | 
 | 261 | 		break; | 
 | 262 | 	} | 
 | 263 |  | 
 | 264 | 	ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); | 
 | 265 | 	skb_set_transport_header(skb, sizeof(struct ipv6hdr)); | 
 | 266 |  | 
 | 267 | 	return 0; | 
 | 268 | } | 
 | 269 |  | 
 | 270 | static int seg6_input(struct sk_buff *skb) | 
 | 271 | { | 
 | 272 | 	struct dst_entry *orig_dst = skb_dst(skb); | 
 | 273 | 	struct dst_entry *dst = NULL; | 
 | 274 | 	struct seg6_lwt *slwt; | 
 | 275 | 	int err; | 
 | 276 |  | 
 | 277 | 	err = seg6_do_srh(skb); | 
 | 278 | 	if (unlikely(err)) { | 
 | 279 | 		kfree_skb(skb); | 
 | 280 | 		return err; | 
 | 281 | 	} | 
 | 282 |  | 
 | 283 | 	slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate); | 
 | 284 |  | 
 | 285 | 	preempt_disable(); | 
 | 286 | 	dst = dst_cache_get(&slwt->cache); | 
 | 287 | 	preempt_enable(); | 
 | 288 |  | 
 | 289 | 	skb_dst_drop(skb); | 
 | 290 |  | 
 | 291 | 	if (!dst) { | 
 | 292 | 		ip6_route_input(skb); | 
 | 293 | 		dst = skb_dst(skb); | 
 | 294 | 		if (!dst->error) { | 
 | 295 | 			preempt_disable(); | 
 | 296 | 			dst_cache_set_ip6(&slwt->cache, dst, | 
 | 297 | 					  &ipv6_hdr(skb)->saddr); | 
 | 298 | 			preempt_enable(); | 
 | 299 | 		} | 
 | 300 | 	} else { | 
 | 301 | 		skb_dst_set(skb, dst); | 
 | 302 | 	} | 
 | 303 |  | 
 | 304 | 	err = skb_cow_head(skb, LL_RESERVED_SPACE(dst->dev)); | 
 | 305 | 	if (unlikely(err)) | 
 | 306 | 		return err; | 
 | 307 |  | 
 | 308 | 	return dst_input(skb); | 
 | 309 | } | 
 | 310 |  | 
 | 311 | static int seg6_output(struct net *net, struct sock *sk, struct sk_buff *skb) | 
 | 312 | { | 
 | 313 | 	struct dst_entry *orig_dst = skb_dst(skb); | 
 | 314 | 	struct dst_entry *dst = NULL; | 
 | 315 | 	struct seg6_lwt *slwt; | 
 | 316 | 	int err = -EINVAL; | 
 | 317 |  | 
 | 318 | 	err = seg6_do_srh(skb); | 
 | 319 | 	if (unlikely(err)) | 
 | 320 | 		goto drop; | 
 | 321 |  | 
 | 322 | 	slwt = seg6_lwt_lwtunnel(orig_dst->lwtstate); | 
 | 323 |  | 
 | 324 | 	preempt_disable(); | 
 | 325 | 	dst = dst_cache_get(&slwt->cache); | 
 | 326 | 	preempt_enable(); | 
 | 327 |  | 
 | 328 | 	if (unlikely(!dst)) { | 
 | 329 | 		struct ipv6hdr *hdr = ipv6_hdr(skb); | 
 | 330 | 		struct flowi6 fl6; | 
 | 331 |  | 
 | 332 | 		memset(&fl6, 0, sizeof(fl6)); | 
 | 333 | 		fl6.daddr = hdr->daddr; | 
 | 334 | 		fl6.saddr = hdr->saddr; | 
 | 335 | 		fl6.flowlabel = ip6_flowinfo(hdr); | 
 | 336 | 		fl6.flowi6_mark = skb->mark; | 
 | 337 | 		fl6.flowi6_proto = hdr->nexthdr; | 
 | 338 |  | 
 | 339 | 		dst = ip6_route_output(net, NULL, &fl6); | 
 | 340 | 		if (dst->error) { | 
 | 341 | 			err = dst->error; | 
 | 342 | 			dst_release(dst); | 
 | 343 | 			goto drop; | 
 | 344 | 		} | 
 | 345 |  | 
 | 346 | 		preempt_disable(); | 
 | 347 | 		dst_cache_set_ip6(&slwt->cache, dst, &fl6.saddr); | 
 | 348 | 		preempt_enable(); | 
 | 349 | 	} | 
 | 350 |  | 
 | 351 | 	skb_dst_drop(skb); | 
 | 352 | 	skb_dst_set(skb, dst); | 
 | 353 |  | 
 | 354 | 	err = skb_cow_head(skb, LL_RESERVED_SPACE(dst->dev)); | 
 | 355 | 	if (unlikely(err)) | 
 | 356 | 		goto drop; | 
 | 357 |  | 
 | 358 | 	return dst_output(net, sk, skb); | 
 | 359 | drop: | 
 | 360 | 	kfree_skb(skb); | 
 | 361 | 	return err; | 
 | 362 | } | 
 | 363 |  | 
 | 364 | static int seg6_build_state(struct nlattr *nla, | 
 | 365 | 			    unsigned int family, const void *cfg, | 
 | 366 | 			    struct lwtunnel_state **ts, | 
 | 367 | 			    struct netlink_ext_ack *extack) | 
 | 368 | { | 
 | 369 | 	struct nlattr *tb[SEG6_IPTUNNEL_MAX + 1]; | 
 | 370 | 	struct seg6_iptunnel_encap *tuninfo; | 
 | 371 | 	struct lwtunnel_state *newts; | 
 | 372 | 	int tuninfo_len, min_size; | 
 | 373 | 	struct seg6_lwt *slwt; | 
 | 374 | 	int err; | 
 | 375 |  | 
 | 376 | 	if (family != AF_INET && family != AF_INET6) | 
 | 377 | 		return -EINVAL; | 
 | 378 |  | 
 | 379 | 	err = nla_parse_nested(tb, SEG6_IPTUNNEL_MAX, nla, | 
 | 380 | 			       seg6_iptunnel_policy, extack); | 
 | 381 |  | 
 | 382 | 	if (err < 0) | 
 | 383 | 		return err; | 
 | 384 |  | 
 | 385 | 	if (!tb[SEG6_IPTUNNEL_SRH]) | 
 | 386 | 		return -EINVAL; | 
 | 387 |  | 
 | 388 | 	tuninfo = nla_data(tb[SEG6_IPTUNNEL_SRH]); | 
 | 389 | 	tuninfo_len = nla_len(tb[SEG6_IPTUNNEL_SRH]); | 
 | 390 |  | 
 | 391 | 	/* tuninfo must contain at least the iptunnel encap structure, | 
 | 392 | 	 * the SRH and one segment | 
 | 393 | 	 */ | 
 | 394 | 	min_size = sizeof(*tuninfo) + sizeof(struct ipv6_sr_hdr) + | 
 | 395 | 		   sizeof(struct in6_addr); | 
 | 396 | 	if (tuninfo_len < min_size) | 
 | 397 | 		return -EINVAL; | 
 | 398 |  | 
 | 399 | 	switch (tuninfo->mode) { | 
 | 400 | 	case SEG6_IPTUN_MODE_INLINE: | 
 | 401 | 		if (family != AF_INET6) | 
 | 402 | 			return -EINVAL; | 
 | 403 |  | 
 | 404 | 		break; | 
 | 405 | 	case SEG6_IPTUN_MODE_ENCAP: | 
 | 406 | 		break; | 
 | 407 | 	case SEG6_IPTUN_MODE_L2ENCAP: | 
 | 408 | 		break; | 
 | 409 | 	default: | 
 | 410 | 		return -EINVAL; | 
 | 411 | 	} | 
 | 412 |  | 
 | 413 | 	/* verify that SRH is consistent */ | 
 | 414 | 	if (!seg6_validate_srh(tuninfo->srh, tuninfo_len - sizeof(*tuninfo))) | 
 | 415 | 		return -EINVAL; | 
 | 416 |  | 
 | 417 | 	newts = lwtunnel_state_alloc(tuninfo_len + sizeof(*slwt)); | 
 | 418 | 	if (!newts) | 
 | 419 | 		return -ENOMEM; | 
 | 420 |  | 
 | 421 | 	slwt = seg6_lwt_lwtunnel(newts); | 
 | 422 |  | 
 | 423 | 	err = dst_cache_init(&slwt->cache, GFP_ATOMIC); | 
 | 424 | 	if (err) { | 
 | 425 | 		kfree(newts); | 
 | 426 | 		return err; | 
 | 427 | 	} | 
 | 428 |  | 
 | 429 | 	memcpy(&slwt->tuninfo, tuninfo, tuninfo_len); | 
 | 430 |  | 
 | 431 | 	newts->type = LWTUNNEL_ENCAP_SEG6; | 
 | 432 | 	newts->flags |= LWTUNNEL_STATE_INPUT_REDIRECT; | 
 | 433 |  | 
 | 434 | 	if (tuninfo->mode != SEG6_IPTUN_MODE_L2ENCAP) | 
 | 435 | 		newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT; | 
 | 436 |  | 
 | 437 | 	newts->headroom = seg6_lwt_headroom(tuninfo); | 
 | 438 |  | 
 | 439 | 	*ts = newts; | 
 | 440 |  | 
 | 441 | 	return 0; | 
 | 442 | } | 
 | 443 |  | 
 | 444 | static void seg6_destroy_state(struct lwtunnel_state *lwt) | 
 | 445 | { | 
 | 446 | 	dst_cache_destroy(&seg6_lwt_lwtunnel(lwt)->cache); | 
 | 447 | } | 
 | 448 |  | 
 | 449 | static int seg6_fill_encap_info(struct sk_buff *skb, | 
 | 450 | 				struct lwtunnel_state *lwtstate) | 
 | 451 | { | 
 | 452 | 	struct seg6_iptunnel_encap *tuninfo = seg6_encap_lwtunnel(lwtstate); | 
 | 453 |  | 
 | 454 | 	if (nla_put_srh(skb, SEG6_IPTUNNEL_SRH, tuninfo)) | 
 | 455 | 		return -EMSGSIZE; | 
 | 456 |  | 
 | 457 | 	return 0; | 
 | 458 | } | 
 | 459 |  | 
 | 460 | static int seg6_encap_nlsize(struct lwtunnel_state *lwtstate) | 
 | 461 | { | 
 | 462 | 	struct seg6_iptunnel_encap *tuninfo = seg6_encap_lwtunnel(lwtstate); | 
 | 463 |  | 
 | 464 | 	return nla_total_size(SEG6_IPTUN_ENCAP_SIZE(tuninfo)); | 
 | 465 | } | 
 | 466 |  | 
 | 467 | static int seg6_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b) | 
 | 468 | { | 
 | 469 | 	struct seg6_iptunnel_encap *a_hdr = seg6_encap_lwtunnel(a); | 
 | 470 | 	struct seg6_iptunnel_encap *b_hdr = seg6_encap_lwtunnel(b); | 
 | 471 | 	int len = SEG6_IPTUN_ENCAP_SIZE(a_hdr); | 
 | 472 |  | 
 | 473 | 	if (len != SEG6_IPTUN_ENCAP_SIZE(b_hdr)) | 
 | 474 | 		return 1; | 
 | 475 |  | 
 | 476 | 	return memcmp(a_hdr, b_hdr, len); | 
 | 477 | } | 
 | 478 |  | 
 | 479 | static const struct lwtunnel_encap_ops seg6_iptun_ops = { | 
 | 480 | 	.build_state = seg6_build_state, | 
 | 481 | 	.destroy_state = seg6_destroy_state, | 
 | 482 | 	.output = seg6_output, | 
 | 483 | 	.input = seg6_input, | 
 | 484 | 	.fill_encap = seg6_fill_encap_info, | 
 | 485 | 	.get_encap_size = seg6_encap_nlsize, | 
 | 486 | 	.cmp_encap = seg6_encap_cmp, | 
 | 487 | 	.owner = THIS_MODULE, | 
 | 488 | }; | 
 | 489 |  | 
 | 490 | int __init seg6_iptunnel_init(void) | 
 | 491 | { | 
 | 492 | 	return lwtunnel_encap_add_ops(&seg6_iptun_ops, LWTUNNEL_ENCAP_SEG6); | 
 | 493 | } | 
 | 494 |  | 
 | 495 | void seg6_iptunnel_exit(void) | 
 | 496 | { | 
 | 497 | 	lwtunnel_encap_del_ops(&seg6_iptun_ops, LWTUNNEL_ENCAP_SEG6); | 
 | 498 | } |