2017-08-05 18:38:26 +08:00
|
|
|
/*
|
|
|
|
* SR-IPv6 implementation
|
|
|
|
*
|
|
|
|
* Author:
|
|
|
|
* David Lebrun <david.lebrun@uclouvain.be>
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version
|
|
|
|
* 2 of the License, or (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/net.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <net/ip.h>
|
|
|
|
#include <net/lwtunnel.h>
|
|
|
|
#include <net/netevent.h>
|
|
|
|
#include <net/netns/generic.h>
|
|
|
|
#include <net/ip6_fib.h>
|
|
|
|
#include <net/route.h>
|
|
|
|
#include <net/seg6.h>
|
|
|
|
#include <linux/seg6.h>
|
|
|
|
#include <linux/seg6_local.h>
|
|
|
|
#include <net/addrconf.h>
|
|
|
|
#include <net/ip6_route.h>
|
|
|
|
#include <net/dst_cache.h>
|
|
|
|
#ifdef CONFIG_IPV6_SEG6_HMAC
|
|
|
|
#include <net/seg6_hmac.h>
|
|
|
|
#endif
|
2018-05-20 21:58:13 +08:00
|
|
|
#include <net/seg6_local.h>
|
2017-08-25 15:58:17 +08:00
|
|
|
#include <linux/etherdevice.h>
|
2017-08-05 18:38:26 +08:00
|
|
|
|
|
|
|
struct seg6_local_lwt;
|
|
|
|
|
|
|
|
struct seg6_action_desc {
|
|
|
|
int action;
|
|
|
|
unsigned long attrs;
|
|
|
|
int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
|
|
|
|
int static_headroom;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct seg6_local_lwt {
|
|
|
|
int action;
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
int table;
|
|
|
|
struct in_addr nh4;
|
|
|
|
struct in6_addr nh6;
|
|
|
|
int iif;
|
|
|
|
int oif;
|
|
|
|
|
|
|
|
int headroom;
|
|
|
|
struct seg6_action_desc *desc;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt)
|
|
|
|
{
|
|
|
|
return (struct seg6_local_lwt *)lwt->data;
|
|
|
|
}
|
|
|
|
|
2017-08-05 18:39:48 +08:00
|
|
|
static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
2017-08-30 16:50:37 +08:00
|
|
|
int len, srhoff = 0;
|
2017-08-05 18:39:48 +08:00
|
|
|
|
2017-08-30 16:50:37 +08:00
|
|
|
if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, srhoff + sizeof(*srh)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
|
|
|
|
|
2017-08-05 18:39:48 +08:00
|
|
|
len = (srh->hdrlen + 1) << 3;
|
|
|
|
|
2017-08-30 16:50:37 +08:00
|
|
|
if (!pskb_may_pull(skb, srhoff + len))
|
2017-08-05 18:39:48 +08:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!seg6_validate_srh(srh, len))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return srh;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
|
|
|
|
srh = get_srh(skb);
|
|
|
|
if (!srh)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (srh->segments_left == 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
#ifdef CONFIG_IPV6_SEG6_HMAC
|
|
|
|
if (!seg6_hmac_validate_skb(skb))
|
|
|
|
return NULL;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return srh;
|
|
|
|
}
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
static bool decap_and_validate(struct sk_buff *skb, int proto)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
unsigned int off = 0;
|
|
|
|
|
|
|
|
srh = get_srh(skb);
|
|
|
|
if (srh && srh->segments_left > 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
#ifdef CONFIG_IPV6_SEG6_HMAC
|
|
|
|
if (srh && !seg6_hmac_validate_skb(skb))
|
|
|
|
return false;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!pskb_pull(skb, off))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
skb_postpull_rcsum(skb, skb_network_header(skb), off);
|
|
|
|
|
|
|
|
skb_reset_network_header(skb);
|
|
|
|
skb_reset_transport_header(skb);
|
|
|
|
skb->encapsulation = 0;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr)
|
|
|
|
{
|
|
|
|
struct in6_addr *addr;
|
|
|
|
|
|
|
|
srh->segments_left--;
|
|
|
|
addr = srh->segments + srh->segments_left;
|
|
|
|
*daddr = *addr;
|
|
|
|
}
|
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
int seg6_lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr,
|
|
|
|
u32 tbl_id)
|
2017-08-25 15:56:47 +08:00
|
|
|
{
|
|
|
|
struct net *net = dev_net(skb->dev);
|
|
|
|
struct ipv6hdr *hdr = ipv6_hdr(skb);
|
|
|
|
int flags = RT6_LOOKUP_F_HAS_SADDR;
|
|
|
|
struct dst_entry *dst = NULL;
|
|
|
|
struct rt6_info *rt;
|
|
|
|
struct flowi6 fl6;
|
|
|
|
|
|
|
|
fl6.flowi6_iif = skb->dev->ifindex;
|
|
|
|
fl6.daddr = nhaddr ? *nhaddr : hdr->daddr;
|
|
|
|
fl6.saddr = hdr->saddr;
|
|
|
|
fl6.flowlabel = ip6_flowinfo(hdr);
|
|
|
|
fl6.flowi6_mark = skb->mark;
|
|
|
|
fl6.flowi6_proto = hdr->nexthdr;
|
|
|
|
|
|
|
|
if (nhaddr)
|
|
|
|
fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH;
|
|
|
|
|
|
|
|
if (!tbl_id) {
|
2018-03-03 00:32:17 +08:00
|
|
|
dst = ip6_route_input_lookup(net, skb->dev, &fl6, skb, flags);
|
2017-08-25 15:56:47 +08:00
|
|
|
} else {
|
|
|
|
struct fib6_table *table;
|
|
|
|
|
|
|
|
table = fib6_get_table(net, tbl_id);
|
|
|
|
if (!table)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-03 00:32:17 +08:00
|
|
|
rt = ip6_pol_route(net, table, 0, &fl6, skb, flags);
|
2017-08-25 15:56:47 +08:00
|
|
|
dst = &rt->dst;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) {
|
|
|
|
dst_release(dst);
|
|
|
|
dst = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (!dst) {
|
|
|
|
rt = net->ipv6.ip6_blk_hole_entry;
|
|
|
|
dst = &rt->dst;
|
|
|
|
dst_hold(dst);
|
|
|
|
}
|
|
|
|
|
|
|
|
skb_dst_drop(skb);
|
|
|
|
skb_dst_set(skb, dst);
|
2018-05-20 21:58:13 +08:00
|
|
|
return dst->error;
|
2017-08-25 15:56:47 +08:00
|
|
|
}
|
|
|
|
|
2017-08-05 18:39:48 +08:00
|
|
|
/* regular endpoint function */
|
|
|
|
static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
|
|
|
|
srh = get_and_validate_srh(skb);
|
|
|
|
if (!srh)
|
|
|
|
goto drop;
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, NULL, 0);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* regular endpoint, and forward to specified nexthop */
|
|
|
|
static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
|
|
|
|
srh = get_and_validate_srh(skb);
|
|
|
|
if (!srh)
|
|
|
|
goto drop;
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, &slwt->nh6, 0);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-08-25 15:58:17 +08:00
|
|
|
static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
|
|
|
|
srh = get_and_validate_srh(skb);
|
|
|
|
if (!srh)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
|
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, NULL, slwt->table);
|
2017-08-25 15:58:17 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* decapsulate and forward inner L2 frame on specified interface */
|
|
|
|
static int input_action_end_dx2(struct sk_buff *skb,
|
|
|
|
struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct net *net = dev_net(skb->dev);
|
|
|
|
struct net_device *odev;
|
|
|
|
struct ethhdr *eth;
|
|
|
|
|
|
|
|
if (!decap_and_validate(skb, NEXTHDR_NONE))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, ETH_HLEN))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
skb_reset_mac_header(skb);
|
|
|
|
eth = (struct ethhdr *)skb->data;
|
|
|
|
|
|
|
|
/* To determine the frame's protocol, we assume it is 802.3. This avoids
|
|
|
|
* a call to eth_type_trans(), which is not really relevant for our
|
|
|
|
* use case.
|
|
|
|
*/
|
|
|
|
if (!eth_proto_is_802_3(eth->h_proto))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
odev = dev_get_by_index_rcu(net, slwt->oif);
|
|
|
|
if (!odev)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
/* As we accept Ethernet frames, make sure the egress device is of
|
|
|
|
* the correct type.
|
|
|
|
*/
|
|
|
|
if (odev->type != ARPHRD_ETHER)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
skb_orphan(skb);
|
|
|
|
|
|
|
|
if (skb_warn_if_lro(skb))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
skb_forward_csum(skb);
|
|
|
|
|
|
|
|
if (skb->len - ETH_HLEN > odev->mtu)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
skb->dev = odev;
|
|
|
|
skb->protocol = eth->h_proto;
|
|
|
|
|
|
|
|
return dev_queue_xmit(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-08-05 18:39:48 +08:00
|
|
|
/* decapsulate and forward to specified nexthop */
|
|
|
|
static int input_action_end_dx6(struct sk_buff *skb,
|
|
|
|
struct seg6_local_lwt *slwt)
|
|
|
|
{
|
2017-08-25 15:56:47 +08:00
|
|
|
struct in6_addr *nhaddr = NULL;
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
/* this function accepts IPv6 encapsulated packets, with either
|
|
|
|
* an SRH with SL=0, or no SRH.
|
|
|
|
*/
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
if (!decap_and_validate(skb, IPPROTO_IPV6))
|
2017-08-05 18:39:48 +08:00
|
|
|
goto drop;
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
|
2017-08-05 18:39:48 +08:00
|
|
|
goto drop;
|
|
|
|
|
|
|
|
/* The inner packet is not associated to any local interface,
|
|
|
|
* so we do not call netif_rx().
|
|
|
|
*
|
|
|
|
* If slwt->nh6 is set to ::, then lookup the nexthop for the
|
|
|
|
* inner packet's DA. Otherwise, use the specified nexthop.
|
|
|
|
*/
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
if (!ipv6_addr_any(&slwt->nh6))
|
|
|
|
nhaddr = &slwt->nh6;
|
2017-08-05 18:39:48 +08:00
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, nhaddr, 0);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-08-25 15:58:17 +08:00
|
|
|
static int input_action_end_dx4(struct sk_buff *skb,
|
|
|
|
struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct iphdr *iph;
|
|
|
|
__be32 nhaddr;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!decap_and_validate(skb, IPPROTO_IPIP))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
skb->protocol = htons(ETH_P_IP);
|
|
|
|
|
|
|
|
iph = ip_hdr(skb);
|
|
|
|
|
|
|
|
nhaddr = slwt->nh4.s_addr ?: iph->daddr;
|
|
|
|
|
|
|
|
skb_dst_drop(skb);
|
|
|
|
|
|
|
|
err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev);
|
|
|
|
if (err)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int input_action_end_dt6(struct sk_buff *skb,
|
|
|
|
struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
if (!decap_and_validate(skb, IPPROTO_IPV6))
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
|
|
|
|
goto drop;
|
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, NULL, slwt->table);
|
2017-08-25 15:58:17 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-08-05 18:39:48 +08:00
|
|
|
/* push an SRH on top of the current one */
|
|
|
|
static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
int err = -EINVAL;
|
|
|
|
|
|
|
|
srh = get_and_validate_srh(skb);
|
|
|
|
if (!srh)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
err = seg6_do_srh_inline(skb, slwt->srh);
|
|
|
|
if (err)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
|
|
|
|
skb_set_transport_header(skb, sizeof(struct ipv6hdr));
|
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, NULL, 0);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* encapsulate within an outer IPv6 header and a specified SRH */
|
|
|
|
static int input_action_end_b6_encap(struct sk_buff *skb,
|
|
|
|
struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
int err = -EINVAL;
|
|
|
|
|
|
|
|
srh = get_and_validate_srh(skb);
|
|
|
|
if (!srh)
|
|
|
|
goto drop;
|
|
|
|
|
2017-08-25 15:56:47 +08:00
|
|
|
advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
skb_reset_inner_headers(skb);
|
|
|
|
skb->encapsulation = 1;
|
|
|
|
|
2017-08-25 15:56:44 +08:00
|
|
|
err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6);
|
2017-08-05 18:39:48 +08:00
|
|
|
if (err)
|
|
|
|
goto drop;
|
|
|
|
|
|
|
|
ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
|
|
|
|
skb_set_transport_header(skb, sizeof(struct ipv6hdr));
|
|
|
|
|
2018-05-20 21:58:13 +08:00
|
|
|
seg6_lookup_nexthop(skb, NULL, 0);
|
2017-08-05 18:39:48 +08:00
|
|
|
|
|
|
|
return dst_input(skb);
|
|
|
|
|
|
|
|
drop:
|
|
|
|
kfree_skb(skb);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
bpf: Add IPv6 Segment Routing helpers
The BPF seg6local hook should be powerful enough to enable users to
implement most of the use-cases one could think of. After some thinking,
we figured out that the following actions should be possible on a SRv6
packet, requiring 3 specific helpers :
- bpf_lwt_seg6_store_bytes: Modify non-sensitive fields of the SRH
- bpf_lwt_seg6_adjust_srh: Allow to grow or shrink a SRH
(to add/delete TLVs)
- bpf_lwt_seg6_action: Apply some SRv6 network programming actions
(specifically End.X, End.T, End.B6 and
End.B6.Encap)
The specifications of these helpers are provided in the patch (see
include/uapi/linux/bpf.h).
The non-sensitive fields of the SRH are the following : flags, tag and
TLVs. The other fields can not be modified, to maintain the SRH
integrity. Flags, tag and TLVs can easily be modified as their validity
can be checked afterwards via seg6_validate_srh. It is not allowed to
modify the segments directly. If one wants to add segments on the path,
he should stack a new SRH using the End.B6 action via
bpf_lwt_seg6_action.
Growing, shrinking or editing TLVs via the helpers will flag the SRH as
invalid, and it will have to be re-validated before re-entering the IPv6
layer. This flag is stored in a per-CPU buffer, along with the current
header length in bytes.
Storing the SRH len in bytes in the control block is mandatory when using
bpf_lwt_seg6_adjust_srh. The Header Ext. Length field contains the SRH
len rounded to 8 bytes (a padding TLV can be inserted to ensure the 8-bytes
boundary). When adding/deleting TLVs within the BPF program, the SRH may
temporary be in an invalid state where its length cannot be rounded to 8
bytes without remainder, hence the need to store the length in bytes
separately. The caller of the BPF program can then ensure that the SRH's
final length is valid using this value. Again, a final SRH modified by a
BPF program which doesn’t respect the 8-bytes boundary will be discarded
as it will be considered as invalid.
Finally, a fourth helper is provided, bpf_lwt_push_encap, which is
available from the LWT BPF IN hook, but not from the seg6local BPF one.
This helper allows to encapsulate a Segment Routing Header (either with
a new outer IPv6 header, or by inlining it directly in the existing IPv6
header) into a non-SRv6 packet. This helper is required if we want to
offer the possibility to dynamically encapsulate a SRH for non-SRv6 packet,
as the BPF seg6local hook only works on traffic already containing a SRH.
This is the BPF equivalent of the seg6 LWT infrastructure, which achieves
the same purpose but with a static SRH per route.
These helpers require CONFIG_IPV6=y (and not =m).
Signed-off-by: Mathieu Xhonneux <m.xhonneux@gmail.com>
Acked-by: David Lebrun <dlebrun@google.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
2018-05-20 21:58:14 +08:00
|
|
|
DEFINE_PER_CPU(struct seg6_bpf_srh_state, seg6_bpf_srh_states);
|
|
|
|
|
2017-08-05 18:38:26 +08:00
|
|
|
static struct seg6_action_desc seg6_action_table[] = {
|
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END,
|
|
|
|
.attrs = 0,
|
2017-08-05 18:39:48 +08:00
|
|
|
.input = input_action_end,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_X,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_NH6),
|
|
|
|
.input = input_action_end_x,
|
2017-08-05 18:38:26 +08:00
|
|
|
},
|
2017-08-25 15:58:17 +08:00
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_T,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_TABLE),
|
|
|
|
.input = input_action_end_t,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_DX2,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_OIF),
|
|
|
|
.input = input_action_end_dx2,
|
|
|
|
},
|
2017-08-05 18:39:48 +08:00
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_DX6,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_NH6),
|
|
|
|
.input = input_action_end_dx6,
|
|
|
|
},
|
2017-08-25 15:58:17 +08:00
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_DX4,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_NH4),
|
|
|
|
.input = input_action_end_dx4,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_DT6,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_TABLE),
|
|
|
|
.input = input_action_end_dt6,
|
|
|
|
},
|
2017-08-05 18:39:48 +08:00
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_B6,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_SRH),
|
|
|
|
.input = input_action_end_b6,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.action = SEG6_LOCAL_ACTION_END_B6_ENCAP,
|
|
|
|
.attrs = (1 << SEG6_LOCAL_SRH),
|
|
|
|
.input = input_action_end_b6_encap,
|
|
|
|
.static_headroom = sizeof(struct ipv6hdr),
|
|
|
|
}
|
2017-08-05 18:38:26 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct seg6_action_desc *__get_action_desc(int action)
|
|
|
|
{
|
|
|
|
struct seg6_action_desc *desc;
|
|
|
|
int i, count;
|
|
|
|
|
2018-01-08 07:50:26 +08:00
|
|
|
count = ARRAY_SIZE(seg6_action_table);
|
2017-08-05 18:38:26 +08:00
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
desc = &seg6_action_table[i];
|
|
|
|
if (desc->action == action)
|
|
|
|
return desc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int seg6_local_input(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct dst_entry *orig_dst = skb_dst(skb);
|
|
|
|
struct seg6_action_desc *desc;
|
|
|
|
struct seg6_local_lwt *slwt;
|
|
|
|
|
2017-08-25 15:56:46 +08:00
|
|
|
if (skb->protocol != htons(ETH_P_IPV6)) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-08-05 18:38:26 +08:00
|
|
|
slwt = seg6_local_lwtunnel(orig_dst->lwtstate);
|
|
|
|
desc = slwt->desc;
|
|
|
|
|
|
|
|
return desc->input(skb, slwt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = {
|
|
|
|
[SEG6_LOCAL_ACTION] = { .type = NLA_U32 },
|
|
|
|
[SEG6_LOCAL_SRH] = { .type = NLA_BINARY },
|
|
|
|
[SEG6_LOCAL_TABLE] = { .type = NLA_U32 },
|
|
|
|
[SEG6_LOCAL_NH4] = { .type = NLA_BINARY,
|
|
|
|
.len = sizeof(struct in_addr) },
|
|
|
|
[SEG6_LOCAL_NH6] = { .type = NLA_BINARY,
|
|
|
|
.len = sizeof(struct in6_addr) },
|
|
|
|
[SEG6_LOCAL_IIF] = { .type = NLA_U32 },
|
|
|
|
[SEG6_LOCAL_OIF] = { .type = NLA_U32 },
|
|
|
|
};
|
|
|
|
|
2017-08-05 18:38:27 +08:00
|
|
|
static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
srh = nla_data(attrs[SEG6_LOCAL_SRH]);
|
|
|
|
len = nla_len(attrs[SEG6_LOCAL_SRH]);
|
|
|
|
|
|
|
|
/* SRH must contain at least one segment */
|
|
|
|
if (len < sizeof(*srh) + sizeof(struct in6_addr))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!seg6_validate_srh(srh, len))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
slwt->srh = kmalloc(len, GFP_KERNEL);
|
|
|
|
if (!slwt->srh)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
memcpy(slwt->srh, srh, len);
|
|
|
|
|
|
|
|
slwt->headroom += len;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct ipv6_sr_hdr *srh;
|
|
|
|
struct nlattr *nla;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
srh = slwt->srh;
|
|
|
|
len = (srh->hdrlen + 1) << 3;
|
|
|
|
|
|
|
|
nla = nla_reserve(skb, SEG6_LOCAL_SRH, len);
|
|
|
|
if (!nla)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
memcpy(nla_data(nla), srh, len);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
|
|
|
|
{
|
|
|
|
int len = (a->srh->hdrlen + 1) << 3;
|
|
|
|
|
|
|
|
if (len != ((b->srh->hdrlen + 1) << 3))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return memcmp(a->srh, b->srh, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table))
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
|
|
|
|
{
|
|
|
|
if (a->table != b->table)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]),
|
|
|
|
sizeof(struct in_addr));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct nlattr *nla;
|
|
|
|
|
|
|
|
nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr));
|
|
|
|
if (!nla)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
|
|
|
|
{
|
|
|
|
return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]),
|
|
|
|
sizeof(struct in6_addr));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct nlattr *nla;
|
|
|
|
|
|
|
|
nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr));
|
|
|
|
if (!nla)
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
|
|
|
|
{
|
|
|
|
return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif))
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
|
|
|
|
{
|
|
|
|
if (a->iif != b->iif)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif))
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
|
|
|
|
{
|
|
|
|
if (a->oif != b->oif)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-08-05 18:38:26 +08:00
|
|
|
struct seg6_action_param {
|
|
|
|
int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt);
|
|
|
|
int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt);
|
|
|
|
int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b);
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = {
|
2017-08-05 18:38:27 +08:00
|
|
|
[SEG6_LOCAL_SRH] = { .parse = parse_nla_srh,
|
|
|
|
.put = put_nla_srh,
|
|
|
|
.cmp = cmp_nla_srh },
|
2017-08-05 18:38:26 +08:00
|
|
|
|
2017-08-05 18:38:27 +08:00
|
|
|
[SEG6_LOCAL_TABLE] = { .parse = parse_nla_table,
|
|
|
|
.put = put_nla_table,
|
|
|
|
.cmp = cmp_nla_table },
|
2017-08-05 18:38:26 +08:00
|
|
|
|
2017-08-05 18:38:27 +08:00
|
|
|
[SEG6_LOCAL_NH4] = { .parse = parse_nla_nh4,
|
|
|
|
.put = put_nla_nh4,
|
|
|
|
.cmp = cmp_nla_nh4 },
|
2017-08-05 18:38:26 +08:00
|
|
|
|
2017-08-05 18:38:27 +08:00
|
|
|
[SEG6_LOCAL_NH6] = { .parse = parse_nla_nh6,
|
|
|
|
.put = put_nla_nh6,
|
|
|
|
.cmp = cmp_nla_nh6 },
|
2017-08-05 18:38:26 +08:00
|
|
|
|
2017-08-05 18:38:27 +08:00
|
|
|
[SEG6_LOCAL_IIF] = { .parse = parse_nla_iif,
|
|
|
|
.put = put_nla_iif,
|
|
|
|
.cmp = cmp_nla_iif },
|
2017-08-05 18:38:26 +08:00
|
|
|
|
2017-08-05 18:38:27 +08:00
|
|
|
[SEG6_LOCAL_OIF] = { .parse = parse_nla_oif,
|
|
|
|
.put = put_nla_oif,
|
|
|
|
.cmp = cmp_nla_oif },
|
2017-08-05 18:38:26 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt)
|
|
|
|
{
|
|
|
|
struct seg6_action_param *param;
|
|
|
|
struct seg6_action_desc *desc;
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
desc = __get_action_desc(slwt->action);
|
|
|
|
if (!desc)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (!desc->input)
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
slwt->desc = desc;
|
|
|
|
slwt->headroom += desc->static_headroom;
|
|
|
|
|
|
|
|
for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
|
|
|
|
if (desc->attrs & (1 << i)) {
|
|
|
|
if (!attrs[i])
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
param = &seg6_action_params[i];
|
|
|
|
|
|
|
|
err = param->parse(attrs, slwt);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int seg6_local_build_state(struct nlattr *nla, unsigned int family,
|
|
|
|
const void *cfg, struct lwtunnel_state **ts,
|
|
|
|
struct netlink_ext_ack *extack)
|
|
|
|
{
|
|
|
|
struct nlattr *tb[SEG6_LOCAL_MAX + 1];
|
|
|
|
struct lwtunnel_state *newts;
|
|
|
|
struct seg6_local_lwt *slwt;
|
|
|
|
int err;
|
|
|
|
|
2017-08-25 15:56:46 +08:00
|
|
|
if (family != AF_INET6)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-08-05 18:38:26 +08:00
|
|
|
err = nla_parse_nested(tb, SEG6_LOCAL_MAX, nla, seg6_local_policy,
|
|
|
|
extack);
|
|
|
|
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (!tb[SEG6_LOCAL_ACTION])
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
newts = lwtunnel_state_alloc(sizeof(*slwt));
|
|
|
|
if (!newts)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
slwt = seg6_local_lwtunnel(newts);
|
|
|
|
slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]);
|
|
|
|
|
|
|
|
err = parse_nla_action(tb, slwt);
|
|
|
|
if (err < 0)
|
|
|
|
goto out_free;
|
|
|
|
|
|
|
|
newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL;
|
|
|
|
newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT;
|
|
|
|
newts->headroom = slwt->headroom;
|
|
|
|
|
|
|
|
*ts = newts;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_free:
|
|
|
|
kfree(slwt->srh);
|
|
|
|
kfree(newts);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void seg6_local_destroy_state(struct lwtunnel_state *lwt)
|
|
|
|
{
|
|
|
|
struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
|
|
|
|
|
|
|
|
kfree(slwt->srh);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int seg6_local_fill_encap(struct sk_buff *skb,
|
|
|
|
struct lwtunnel_state *lwt)
|
|
|
|
{
|
|
|
|
struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
|
|
|
|
struct seg6_action_param *param;
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action))
|
|
|
|
return -EMSGSIZE;
|
|
|
|
|
|
|
|
for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
|
|
|
|
if (slwt->desc->attrs & (1 << i)) {
|
|
|
|
param = &seg6_action_params[i];
|
|
|
|
err = param->put(skb, slwt);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int seg6_local_get_encap_size(struct lwtunnel_state *lwt)
|
|
|
|
{
|
|
|
|
struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt);
|
|
|
|
unsigned long attrs;
|
|
|
|
int nlsize;
|
|
|
|
|
|
|
|
nlsize = nla_total_size(4); /* action */
|
|
|
|
|
|
|
|
attrs = slwt->desc->attrs;
|
|
|
|
|
|
|
|
if (attrs & (1 << SEG6_LOCAL_SRH))
|
|
|
|
nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3);
|
|
|
|
|
|
|
|
if (attrs & (1 << SEG6_LOCAL_TABLE))
|
|
|
|
nlsize += nla_total_size(4);
|
|
|
|
|
|
|
|
if (attrs & (1 << SEG6_LOCAL_NH4))
|
|
|
|
nlsize += nla_total_size(4);
|
|
|
|
|
|
|
|
if (attrs & (1 << SEG6_LOCAL_NH6))
|
|
|
|
nlsize += nla_total_size(16);
|
|
|
|
|
|
|
|
if (attrs & (1 << SEG6_LOCAL_IIF))
|
|
|
|
nlsize += nla_total_size(4);
|
|
|
|
|
|
|
|
if (attrs & (1 << SEG6_LOCAL_OIF))
|
|
|
|
nlsize += nla_total_size(4);
|
|
|
|
|
|
|
|
return nlsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int seg6_local_cmp_encap(struct lwtunnel_state *a,
|
|
|
|
struct lwtunnel_state *b)
|
|
|
|
{
|
|
|
|
struct seg6_local_lwt *slwt_a, *slwt_b;
|
|
|
|
struct seg6_action_param *param;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
slwt_a = seg6_local_lwtunnel(a);
|
|
|
|
slwt_b = seg6_local_lwtunnel(b);
|
|
|
|
|
|
|
|
if (slwt_a->action != slwt_b->action)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (slwt_a->desc->attrs != slwt_b->desc->attrs)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) {
|
|
|
|
if (slwt_a->desc->attrs & (1 << i)) {
|
|
|
|
param = &seg6_action_params[i];
|
|
|
|
if (param->cmp(slwt_a, slwt_b))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct lwtunnel_encap_ops seg6_local_ops = {
|
|
|
|
.build_state = seg6_local_build_state,
|
|
|
|
.destroy_state = seg6_local_destroy_state,
|
|
|
|
.input = seg6_local_input,
|
|
|
|
.fill_encap = seg6_local_fill_encap,
|
|
|
|
.get_encap_size = seg6_local_get_encap_size,
|
|
|
|
.cmp_encap = seg6_local_cmp_encap,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
};
|
|
|
|
|
|
|
|
int __init seg6_local_init(void)
|
|
|
|
{
|
|
|
|
return lwtunnel_encap_add_ops(&seg6_local_ops,
|
|
|
|
LWTUNNEL_ENCAP_SEG6_LOCAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void seg6_local_exit(void)
|
|
|
|
{
|
|
|
|
lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL);
|
|
|
|
}
|