1d1df6fd8SDavid Lebrun /* 2d1df6fd8SDavid Lebrun * SR-IPv6 implementation 3d1df6fd8SDavid Lebrun * 4d1df6fd8SDavid Lebrun * Author: 5d1df6fd8SDavid Lebrun * David Lebrun <david.lebrun@uclouvain.be> 6d1df6fd8SDavid Lebrun * 7d1df6fd8SDavid Lebrun * 8d1df6fd8SDavid Lebrun * This program is free software; you can redistribute it and/or 9d1df6fd8SDavid Lebrun * modify it under the terms of the GNU General Public License 10d1df6fd8SDavid Lebrun * as published by the Free Software Foundation; either version 11d1df6fd8SDavid Lebrun * 2 of the License, or (at your option) any later version. 12d1df6fd8SDavid Lebrun */ 13d1df6fd8SDavid Lebrun 14d1df6fd8SDavid Lebrun #include <linux/types.h> 15d1df6fd8SDavid Lebrun #include <linux/skbuff.h> 16d1df6fd8SDavid Lebrun #include <linux/net.h> 17d1df6fd8SDavid Lebrun #include <linux/module.h> 18d1df6fd8SDavid Lebrun #include <net/ip.h> 19d1df6fd8SDavid Lebrun #include <net/lwtunnel.h> 20d1df6fd8SDavid Lebrun #include <net/netevent.h> 21d1df6fd8SDavid Lebrun #include <net/netns/generic.h> 22d1df6fd8SDavid Lebrun #include <net/ip6_fib.h> 23d1df6fd8SDavid Lebrun #include <net/route.h> 24d1df6fd8SDavid Lebrun #include <net/seg6.h> 25d1df6fd8SDavid Lebrun #include <linux/seg6.h> 26d1df6fd8SDavid Lebrun #include <linux/seg6_local.h> 27d1df6fd8SDavid Lebrun #include <net/addrconf.h> 28d1df6fd8SDavid Lebrun #include <net/ip6_route.h> 29d1df6fd8SDavid Lebrun #include <net/dst_cache.h> 30d1df6fd8SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 31d1df6fd8SDavid Lebrun #include <net/seg6_hmac.h> 32d1df6fd8SDavid Lebrun #endif 33891ef8ddSDavid Lebrun #include <linux/etherdevice.h> 34d1df6fd8SDavid Lebrun 35d1df6fd8SDavid Lebrun struct seg6_local_lwt; 36d1df6fd8SDavid Lebrun 37d1df6fd8SDavid Lebrun struct seg6_action_desc { 38d1df6fd8SDavid Lebrun int action; 39d1df6fd8SDavid Lebrun unsigned long attrs; 40d1df6fd8SDavid Lebrun int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 41d1df6fd8SDavid Lebrun int static_headroom; 42d1df6fd8SDavid Lebrun }; 43d1df6fd8SDavid Lebrun 44d1df6fd8SDavid Lebrun struct seg6_local_lwt { 45d1df6fd8SDavid Lebrun int action; 46d1df6fd8SDavid Lebrun struct ipv6_sr_hdr *srh; 47d1df6fd8SDavid Lebrun int table; 48d1df6fd8SDavid Lebrun struct in_addr nh4; 49d1df6fd8SDavid Lebrun struct in6_addr nh6; 50d1df6fd8SDavid Lebrun int iif; 51d1df6fd8SDavid Lebrun int oif; 52d1df6fd8SDavid Lebrun 53d1df6fd8SDavid Lebrun int headroom; 54d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 55d1df6fd8SDavid Lebrun }; 56d1df6fd8SDavid Lebrun 57d1df6fd8SDavid Lebrun static struct seg6_local_lwt *seg6_local_lwtunnel(struct lwtunnel_state *lwt) 58d1df6fd8SDavid Lebrun { 59d1df6fd8SDavid Lebrun return (struct seg6_local_lwt *)lwt->data; 60d1df6fd8SDavid Lebrun } 61d1df6fd8SDavid Lebrun 62140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_srh(struct sk_buff *skb) 63140f04c3SDavid Lebrun { 64140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 655829d70bSAhmed Abdelsalam int len, srhoff = 0; 66140f04c3SDavid Lebrun 675829d70bSAhmed Abdelsalam if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) 68140f04c3SDavid Lebrun return NULL; 69140f04c3SDavid Lebrun 705829d70bSAhmed Abdelsalam if (!pskb_may_pull(skb, srhoff + sizeof(*srh))) 715829d70bSAhmed Abdelsalam return NULL; 725829d70bSAhmed Abdelsalam 735829d70bSAhmed Abdelsalam srh = (struct ipv6_sr_hdr *)(skb->data + srhoff); 745829d70bSAhmed Abdelsalam 75140f04c3SDavid Lebrun len = (srh->hdrlen + 1) << 3; 76140f04c3SDavid Lebrun 775829d70bSAhmed Abdelsalam if (!pskb_may_pull(skb, srhoff + len)) 78140f04c3SDavid Lebrun return NULL; 79140f04c3SDavid Lebrun 80140f04c3SDavid Lebrun if (!seg6_validate_srh(srh, len)) 81140f04c3SDavid Lebrun return NULL; 82140f04c3SDavid Lebrun 83140f04c3SDavid Lebrun return srh; 84140f04c3SDavid Lebrun } 85140f04c3SDavid Lebrun 86140f04c3SDavid Lebrun static struct ipv6_sr_hdr *get_and_validate_srh(struct sk_buff *skb) 87140f04c3SDavid Lebrun { 88140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 89140f04c3SDavid Lebrun 90140f04c3SDavid Lebrun srh = get_srh(skb); 91140f04c3SDavid Lebrun if (!srh) 92140f04c3SDavid Lebrun return NULL; 93140f04c3SDavid Lebrun 94140f04c3SDavid Lebrun if (srh->segments_left == 0) 95140f04c3SDavid Lebrun return NULL; 96140f04c3SDavid Lebrun 97140f04c3SDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 98140f04c3SDavid Lebrun if (!seg6_hmac_validate_skb(skb)) 99140f04c3SDavid Lebrun return NULL; 100140f04c3SDavid Lebrun #endif 101140f04c3SDavid Lebrun 102140f04c3SDavid Lebrun return srh; 103140f04c3SDavid Lebrun } 104140f04c3SDavid Lebrun 105d7a669ddSDavid Lebrun static bool decap_and_validate(struct sk_buff *skb, int proto) 106d7a669ddSDavid Lebrun { 107d7a669ddSDavid Lebrun struct ipv6_sr_hdr *srh; 108d7a669ddSDavid Lebrun unsigned int off = 0; 109d7a669ddSDavid Lebrun 110d7a669ddSDavid Lebrun srh = get_srh(skb); 111d7a669ddSDavid Lebrun if (srh && srh->segments_left > 0) 112d7a669ddSDavid Lebrun return false; 113d7a669ddSDavid Lebrun 114d7a669ddSDavid Lebrun #ifdef CONFIG_IPV6_SEG6_HMAC 115d7a669ddSDavid Lebrun if (srh && !seg6_hmac_validate_skb(skb)) 116d7a669ddSDavid Lebrun return false; 117d7a669ddSDavid Lebrun #endif 118d7a669ddSDavid Lebrun 119d7a669ddSDavid Lebrun if (ipv6_find_hdr(skb, &off, proto, NULL, NULL) < 0) 120d7a669ddSDavid Lebrun return false; 121d7a669ddSDavid Lebrun 122d7a669ddSDavid Lebrun if (!pskb_pull(skb, off)) 123d7a669ddSDavid Lebrun return false; 124d7a669ddSDavid Lebrun 125d7a669ddSDavid Lebrun skb_postpull_rcsum(skb, skb_network_header(skb), off); 126d7a669ddSDavid Lebrun 127d7a669ddSDavid Lebrun skb_reset_network_header(skb); 128d7a669ddSDavid Lebrun skb_reset_transport_header(skb); 129d7a669ddSDavid Lebrun skb->encapsulation = 0; 130d7a669ddSDavid Lebrun 131d7a669ddSDavid Lebrun return true; 132d7a669ddSDavid Lebrun } 133d7a669ddSDavid Lebrun 134d7a669ddSDavid Lebrun static void advance_nextseg(struct ipv6_sr_hdr *srh, struct in6_addr *daddr) 135d7a669ddSDavid Lebrun { 136d7a669ddSDavid Lebrun struct in6_addr *addr; 137d7a669ddSDavid Lebrun 138d7a669ddSDavid Lebrun srh->segments_left--; 139d7a669ddSDavid Lebrun addr = srh->segments + srh->segments_left; 140d7a669ddSDavid Lebrun *daddr = *addr; 141d7a669ddSDavid Lebrun } 142d7a669ddSDavid Lebrun 143d7a669ddSDavid Lebrun static void lookup_nexthop(struct sk_buff *skb, struct in6_addr *nhaddr, 144d7a669ddSDavid Lebrun u32 tbl_id) 145d7a669ddSDavid Lebrun { 146d7a669ddSDavid Lebrun struct net *net = dev_net(skb->dev); 147d7a669ddSDavid Lebrun struct ipv6hdr *hdr = ipv6_hdr(skb); 148d7a669ddSDavid Lebrun int flags = RT6_LOOKUP_F_HAS_SADDR; 149d7a669ddSDavid Lebrun struct dst_entry *dst = NULL; 150d7a669ddSDavid Lebrun struct rt6_info *rt; 151d7a669ddSDavid Lebrun struct flowi6 fl6; 152d7a669ddSDavid Lebrun 153d7a669ddSDavid Lebrun fl6.flowi6_iif = skb->dev->ifindex; 154d7a669ddSDavid Lebrun fl6.daddr = nhaddr ? *nhaddr : hdr->daddr; 155d7a669ddSDavid Lebrun fl6.saddr = hdr->saddr; 156d7a669ddSDavid Lebrun fl6.flowlabel = ip6_flowinfo(hdr); 157d7a669ddSDavid Lebrun fl6.flowi6_mark = skb->mark; 158d7a669ddSDavid Lebrun fl6.flowi6_proto = hdr->nexthdr; 159d7a669ddSDavid Lebrun 160d7a669ddSDavid Lebrun if (nhaddr) 161d7a669ddSDavid Lebrun fl6.flowi6_flags = FLOWI_FLAG_KNOWN_NH; 162d7a669ddSDavid Lebrun 163d7a669ddSDavid Lebrun if (!tbl_id) { 164d7a669ddSDavid Lebrun dst = ip6_route_input_lookup(net, skb->dev, &fl6, flags); 165d7a669ddSDavid Lebrun } else { 166d7a669ddSDavid Lebrun struct fib6_table *table; 167d7a669ddSDavid Lebrun 168d7a669ddSDavid Lebrun table = fib6_get_table(net, tbl_id); 169d7a669ddSDavid Lebrun if (!table) 170d7a669ddSDavid Lebrun goto out; 171d7a669ddSDavid Lebrun 172d7a669ddSDavid Lebrun rt = ip6_pol_route(net, table, 0, &fl6, flags); 173d7a669ddSDavid Lebrun dst = &rt->dst; 174d7a669ddSDavid Lebrun } 175d7a669ddSDavid Lebrun 176d7a669ddSDavid Lebrun if (dst && dst->dev->flags & IFF_LOOPBACK && !dst->error) { 177d7a669ddSDavid Lebrun dst_release(dst); 178d7a669ddSDavid Lebrun dst = NULL; 179d7a669ddSDavid Lebrun } 180d7a669ddSDavid Lebrun 181d7a669ddSDavid Lebrun out: 182d7a669ddSDavid Lebrun if (!dst) { 183d7a669ddSDavid Lebrun rt = net->ipv6.ip6_blk_hole_entry; 184d7a669ddSDavid Lebrun dst = &rt->dst; 185d7a669ddSDavid Lebrun dst_hold(dst); 186d7a669ddSDavid Lebrun } 187d7a669ddSDavid Lebrun 188d7a669ddSDavid Lebrun skb_dst_drop(skb); 189d7a669ddSDavid Lebrun skb_dst_set(skb, dst); 190d7a669ddSDavid Lebrun } 191d7a669ddSDavid Lebrun 192140f04c3SDavid Lebrun /* regular endpoint function */ 193140f04c3SDavid Lebrun static int input_action_end(struct sk_buff *skb, struct seg6_local_lwt *slwt) 194140f04c3SDavid Lebrun { 195140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 196140f04c3SDavid Lebrun 197140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 198140f04c3SDavid Lebrun if (!srh) 199140f04c3SDavid Lebrun goto drop; 200140f04c3SDavid Lebrun 201d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 202140f04c3SDavid Lebrun 203d7a669ddSDavid Lebrun lookup_nexthop(skb, NULL, 0); 204140f04c3SDavid Lebrun 205140f04c3SDavid Lebrun return dst_input(skb); 206140f04c3SDavid Lebrun 207140f04c3SDavid Lebrun drop: 208140f04c3SDavid Lebrun kfree_skb(skb); 209140f04c3SDavid Lebrun return -EINVAL; 210140f04c3SDavid Lebrun } 211140f04c3SDavid Lebrun 212140f04c3SDavid Lebrun /* regular endpoint, and forward to specified nexthop */ 213140f04c3SDavid Lebrun static int input_action_end_x(struct sk_buff *skb, struct seg6_local_lwt *slwt) 214140f04c3SDavid Lebrun { 215140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 216140f04c3SDavid Lebrun 217140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 218140f04c3SDavid Lebrun if (!srh) 219140f04c3SDavid Lebrun goto drop; 220140f04c3SDavid Lebrun 221d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 222140f04c3SDavid Lebrun 223d7a669ddSDavid Lebrun lookup_nexthop(skb, &slwt->nh6, 0); 224140f04c3SDavid Lebrun 225140f04c3SDavid Lebrun return dst_input(skb); 226140f04c3SDavid Lebrun 227140f04c3SDavid Lebrun drop: 228140f04c3SDavid Lebrun kfree_skb(skb); 229140f04c3SDavid Lebrun return -EINVAL; 230140f04c3SDavid Lebrun } 231140f04c3SDavid Lebrun 232891ef8ddSDavid Lebrun static int input_action_end_t(struct sk_buff *skb, struct seg6_local_lwt *slwt) 233891ef8ddSDavid Lebrun { 234891ef8ddSDavid Lebrun struct ipv6_sr_hdr *srh; 235891ef8ddSDavid Lebrun 236891ef8ddSDavid Lebrun srh = get_and_validate_srh(skb); 237891ef8ddSDavid Lebrun if (!srh) 238891ef8ddSDavid Lebrun goto drop; 239891ef8ddSDavid Lebrun 240891ef8ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 241891ef8ddSDavid Lebrun 242891ef8ddSDavid Lebrun lookup_nexthop(skb, NULL, slwt->table); 243891ef8ddSDavid Lebrun 244891ef8ddSDavid Lebrun return dst_input(skb); 245891ef8ddSDavid Lebrun 246891ef8ddSDavid Lebrun drop: 247891ef8ddSDavid Lebrun kfree_skb(skb); 248891ef8ddSDavid Lebrun return -EINVAL; 249891ef8ddSDavid Lebrun } 250891ef8ddSDavid Lebrun 251891ef8ddSDavid Lebrun /* decapsulate and forward inner L2 frame on specified interface */ 252891ef8ddSDavid Lebrun static int input_action_end_dx2(struct sk_buff *skb, 253891ef8ddSDavid Lebrun struct seg6_local_lwt *slwt) 254891ef8ddSDavid Lebrun { 255891ef8ddSDavid Lebrun struct net *net = dev_net(skb->dev); 256891ef8ddSDavid Lebrun struct net_device *odev; 257891ef8ddSDavid Lebrun struct ethhdr *eth; 258891ef8ddSDavid Lebrun 259891ef8ddSDavid Lebrun if (!decap_and_validate(skb, NEXTHDR_NONE)) 260891ef8ddSDavid Lebrun goto drop; 261891ef8ddSDavid Lebrun 262891ef8ddSDavid Lebrun if (!pskb_may_pull(skb, ETH_HLEN)) 263891ef8ddSDavid Lebrun goto drop; 264891ef8ddSDavid Lebrun 265891ef8ddSDavid Lebrun skb_reset_mac_header(skb); 266891ef8ddSDavid Lebrun eth = (struct ethhdr *)skb->data; 267891ef8ddSDavid Lebrun 268891ef8ddSDavid Lebrun /* To determine the frame's protocol, we assume it is 802.3. This avoids 269891ef8ddSDavid Lebrun * a call to eth_type_trans(), which is not really relevant for our 270891ef8ddSDavid Lebrun * use case. 271891ef8ddSDavid Lebrun */ 272891ef8ddSDavid Lebrun if (!eth_proto_is_802_3(eth->h_proto)) 273891ef8ddSDavid Lebrun goto drop; 274891ef8ddSDavid Lebrun 275891ef8ddSDavid Lebrun odev = dev_get_by_index_rcu(net, slwt->oif); 276891ef8ddSDavid Lebrun if (!odev) 277891ef8ddSDavid Lebrun goto drop; 278891ef8ddSDavid Lebrun 279891ef8ddSDavid Lebrun /* As we accept Ethernet frames, make sure the egress device is of 280891ef8ddSDavid Lebrun * the correct type. 281891ef8ddSDavid Lebrun */ 282891ef8ddSDavid Lebrun if (odev->type != ARPHRD_ETHER) 283891ef8ddSDavid Lebrun goto drop; 284891ef8ddSDavid Lebrun 285891ef8ddSDavid Lebrun if (!(odev->flags & IFF_UP) || !netif_carrier_ok(odev)) 286891ef8ddSDavid Lebrun goto drop; 287891ef8ddSDavid Lebrun 288891ef8ddSDavid Lebrun skb_orphan(skb); 289891ef8ddSDavid Lebrun 290891ef8ddSDavid Lebrun if (skb_warn_if_lro(skb)) 291891ef8ddSDavid Lebrun goto drop; 292891ef8ddSDavid Lebrun 293891ef8ddSDavid Lebrun skb_forward_csum(skb); 294891ef8ddSDavid Lebrun 295891ef8ddSDavid Lebrun if (skb->len - ETH_HLEN > odev->mtu) 296891ef8ddSDavid Lebrun goto drop; 297891ef8ddSDavid Lebrun 298891ef8ddSDavid Lebrun skb->dev = odev; 299891ef8ddSDavid Lebrun skb->protocol = eth->h_proto; 300891ef8ddSDavid Lebrun 301891ef8ddSDavid Lebrun return dev_queue_xmit(skb); 302891ef8ddSDavid Lebrun 303891ef8ddSDavid Lebrun drop: 304891ef8ddSDavid Lebrun kfree_skb(skb); 305891ef8ddSDavid Lebrun return -EINVAL; 306891ef8ddSDavid Lebrun } 307891ef8ddSDavid Lebrun 308140f04c3SDavid Lebrun /* decapsulate and forward to specified nexthop */ 309140f04c3SDavid Lebrun static int input_action_end_dx6(struct sk_buff *skb, 310140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 311140f04c3SDavid Lebrun { 312d7a669ddSDavid Lebrun struct in6_addr *nhaddr = NULL; 313140f04c3SDavid Lebrun 314140f04c3SDavid Lebrun /* this function accepts IPv6 encapsulated packets, with either 315140f04c3SDavid Lebrun * an SRH with SL=0, or no SRH. 316140f04c3SDavid Lebrun */ 317140f04c3SDavid Lebrun 318d7a669ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPV6)) 319140f04c3SDavid Lebrun goto drop; 320140f04c3SDavid Lebrun 321d7a669ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) 322140f04c3SDavid Lebrun goto drop; 323140f04c3SDavid Lebrun 324140f04c3SDavid Lebrun /* The inner packet is not associated to any local interface, 325140f04c3SDavid Lebrun * so we do not call netif_rx(). 326140f04c3SDavid Lebrun * 327140f04c3SDavid Lebrun * If slwt->nh6 is set to ::, then lookup the nexthop for the 328140f04c3SDavid Lebrun * inner packet's DA. Otherwise, use the specified nexthop. 329140f04c3SDavid Lebrun */ 330140f04c3SDavid Lebrun 331d7a669ddSDavid Lebrun if (!ipv6_addr_any(&slwt->nh6)) 332d7a669ddSDavid Lebrun nhaddr = &slwt->nh6; 333140f04c3SDavid Lebrun 334d7a669ddSDavid Lebrun lookup_nexthop(skb, nhaddr, 0); 335140f04c3SDavid Lebrun 336140f04c3SDavid Lebrun return dst_input(skb); 337140f04c3SDavid Lebrun drop: 338140f04c3SDavid Lebrun kfree_skb(skb); 339140f04c3SDavid Lebrun return -EINVAL; 340140f04c3SDavid Lebrun } 341140f04c3SDavid Lebrun 342891ef8ddSDavid Lebrun static int input_action_end_dx4(struct sk_buff *skb, 343891ef8ddSDavid Lebrun struct seg6_local_lwt *slwt) 344891ef8ddSDavid Lebrun { 345891ef8ddSDavid Lebrun struct iphdr *iph; 346891ef8ddSDavid Lebrun __be32 nhaddr; 347891ef8ddSDavid Lebrun int err; 348891ef8ddSDavid Lebrun 349891ef8ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPIP)) 350891ef8ddSDavid Lebrun goto drop; 351891ef8ddSDavid Lebrun 352891ef8ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct iphdr))) 353891ef8ddSDavid Lebrun goto drop; 354891ef8ddSDavid Lebrun 355891ef8ddSDavid Lebrun skb->protocol = htons(ETH_P_IP); 356891ef8ddSDavid Lebrun 357891ef8ddSDavid Lebrun iph = ip_hdr(skb); 358891ef8ddSDavid Lebrun 359891ef8ddSDavid Lebrun nhaddr = slwt->nh4.s_addr ?: iph->daddr; 360891ef8ddSDavid Lebrun 361891ef8ddSDavid Lebrun skb_dst_drop(skb); 362891ef8ddSDavid Lebrun 363891ef8ddSDavid Lebrun err = ip_route_input(skb, nhaddr, iph->saddr, 0, skb->dev); 364891ef8ddSDavid Lebrun if (err) 365891ef8ddSDavid Lebrun goto drop; 366891ef8ddSDavid Lebrun 367891ef8ddSDavid Lebrun return dst_input(skb); 368891ef8ddSDavid Lebrun 369891ef8ddSDavid Lebrun drop: 370891ef8ddSDavid Lebrun kfree_skb(skb); 371891ef8ddSDavid Lebrun return -EINVAL; 372891ef8ddSDavid Lebrun } 373891ef8ddSDavid Lebrun 374891ef8ddSDavid Lebrun static int input_action_end_dt6(struct sk_buff *skb, 375891ef8ddSDavid Lebrun struct seg6_local_lwt *slwt) 376891ef8ddSDavid Lebrun { 377891ef8ddSDavid Lebrun if (!decap_and_validate(skb, IPPROTO_IPV6)) 378891ef8ddSDavid Lebrun goto drop; 379891ef8ddSDavid Lebrun 380891ef8ddSDavid Lebrun if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) 381891ef8ddSDavid Lebrun goto drop; 382891ef8ddSDavid Lebrun 383891ef8ddSDavid Lebrun lookup_nexthop(skb, NULL, slwt->table); 384891ef8ddSDavid Lebrun 385891ef8ddSDavid Lebrun return dst_input(skb); 386891ef8ddSDavid Lebrun 387891ef8ddSDavid Lebrun drop: 388891ef8ddSDavid Lebrun kfree_skb(skb); 389891ef8ddSDavid Lebrun return -EINVAL; 390891ef8ddSDavid Lebrun } 391891ef8ddSDavid Lebrun 392140f04c3SDavid Lebrun /* push an SRH on top of the current one */ 393140f04c3SDavid Lebrun static int input_action_end_b6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 394140f04c3SDavid Lebrun { 395140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 396140f04c3SDavid Lebrun int err = -EINVAL; 397140f04c3SDavid Lebrun 398140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 399140f04c3SDavid Lebrun if (!srh) 400140f04c3SDavid Lebrun goto drop; 401140f04c3SDavid Lebrun 402140f04c3SDavid Lebrun err = seg6_do_srh_inline(skb, slwt->srh); 403140f04c3SDavid Lebrun if (err) 404140f04c3SDavid Lebrun goto drop; 405140f04c3SDavid Lebrun 406140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 407140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 408140f04c3SDavid Lebrun 409d7a669ddSDavid Lebrun lookup_nexthop(skb, NULL, 0); 410140f04c3SDavid Lebrun 411140f04c3SDavid Lebrun return dst_input(skb); 412140f04c3SDavid Lebrun 413140f04c3SDavid Lebrun drop: 414140f04c3SDavid Lebrun kfree_skb(skb); 415140f04c3SDavid Lebrun return err; 416140f04c3SDavid Lebrun } 417140f04c3SDavid Lebrun 418140f04c3SDavid Lebrun /* encapsulate within an outer IPv6 header and a specified SRH */ 419140f04c3SDavid Lebrun static int input_action_end_b6_encap(struct sk_buff *skb, 420140f04c3SDavid Lebrun struct seg6_local_lwt *slwt) 421140f04c3SDavid Lebrun { 422140f04c3SDavid Lebrun struct ipv6_sr_hdr *srh; 423140f04c3SDavid Lebrun int err = -EINVAL; 424140f04c3SDavid Lebrun 425140f04c3SDavid Lebrun srh = get_and_validate_srh(skb); 426140f04c3SDavid Lebrun if (!srh) 427140f04c3SDavid Lebrun goto drop; 428140f04c3SDavid Lebrun 429d7a669ddSDavid Lebrun advance_nextseg(srh, &ipv6_hdr(skb)->daddr); 430140f04c3SDavid Lebrun 431140f04c3SDavid Lebrun skb_reset_inner_headers(skb); 432140f04c3SDavid Lebrun skb->encapsulation = 1; 433140f04c3SDavid Lebrun 43432d99d0bSDavid Lebrun err = seg6_do_srh_encap(skb, slwt->srh, IPPROTO_IPV6); 435140f04c3SDavid Lebrun if (err) 436140f04c3SDavid Lebrun goto drop; 437140f04c3SDavid Lebrun 438140f04c3SDavid Lebrun ipv6_hdr(skb)->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); 439140f04c3SDavid Lebrun skb_set_transport_header(skb, sizeof(struct ipv6hdr)); 440140f04c3SDavid Lebrun 441d7a669ddSDavid Lebrun lookup_nexthop(skb, NULL, 0); 442140f04c3SDavid Lebrun 443140f04c3SDavid Lebrun return dst_input(skb); 444140f04c3SDavid Lebrun 445140f04c3SDavid Lebrun drop: 446140f04c3SDavid Lebrun kfree_skb(skb); 447140f04c3SDavid Lebrun return err; 448140f04c3SDavid Lebrun } 449140f04c3SDavid Lebrun 450d1df6fd8SDavid Lebrun static struct seg6_action_desc seg6_action_table[] = { 451d1df6fd8SDavid Lebrun { 452d1df6fd8SDavid Lebrun .action = SEG6_LOCAL_ACTION_END, 453d1df6fd8SDavid Lebrun .attrs = 0, 454140f04c3SDavid Lebrun .input = input_action_end, 455d1df6fd8SDavid Lebrun }, 456140f04c3SDavid Lebrun { 457140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_X, 458140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 459140f04c3SDavid Lebrun .input = input_action_end_x, 460140f04c3SDavid Lebrun }, 461140f04c3SDavid Lebrun { 462891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_T, 463891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_TABLE), 464891ef8ddSDavid Lebrun .input = input_action_end_t, 465891ef8ddSDavid Lebrun }, 466891ef8ddSDavid Lebrun { 467891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX2, 468891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_OIF), 469891ef8ddSDavid Lebrun .input = input_action_end_dx2, 470891ef8ddSDavid Lebrun }, 471891ef8ddSDavid Lebrun { 472140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX6, 473140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH6), 474140f04c3SDavid Lebrun .input = input_action_end_dx6, 475140f04c3SDavid Lebrun }, 476140f04c3SDavid Lebrun { 477891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DX4, 478891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_NH4), 479891ef8ddSDavid Lebrun .input = input_action_end_dx4, 480891ef8ddSDavid Lebrun }, 481891ef8ddSDavid Lebrun { 482891ef8ddSDavid Lebrun .action = SEG6_LOCAL_ACTION_END_DT6, 483891ef8ddSDavid Lebrun .attrs = (1 << SEG6_LOCAL_TABLE), 484891ef8ddSDavid Lebrun .input = input_action_end_dt6, 485891ef8ddSDavid Lebrun }, 486891ef8ddSDavid Lebrun { 487140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6, 488140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 489140f04c3SDavid Lebrun .input = input_action_end_b6, 490140f04c3SDavid Lebrun }, 491140f04c3SDavid Lebrun { 492140f04c3SDavid Lebrun .action = SEG6_LOCAL_ACTION_END_B6_ENCAP, 493140f04c3SDavid Lebrun .attrs = (1 << SEG6_LOCAL_SRH), 494140f04c3SDavid Lebrun .input = input_action_end_b6_encap, 495140f04c3SDavid Lebrun .static_headroom = sizeof(struct ipv6hdr), 496140f04c3SDavid Lebrun } 497d1df6fd8SDavid Lebrun }; 498d1df6fd8SDavid Lebrun 499d1df6fd8SDavid Lebrun static struct seg6_action_desc *__get_action_desc(int action) 500d1df6fd8SDavid Lebrun { 501d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 502d1df6fd8SDavid Lebrun int i, count; 503d1df6fd8SDavid Lebrun 504*709af180SColin Ian King count = ARRAY_SIZE(seg6_action_table); 505d1df6fd8SDavid Lebrun for (i = 0; i < count; i++) { 506d1df6fd8SDavid Lebrun desc = &seg6_action_table[i]; 507d1df6fd8SDavid Lebrun if (desc->action == action) 508d1df6fd8SDavid Lebrun return desc; 509d1df6fd8SDavid Lebrun } 510d1df6fd8SDavid Lebrun 511d1df6fd8SDavid Lebrun return NULL; 512d1df6fd8SDavid Lebrun } 513d1df6fd8SDavid Lebrun 514d1df6fd8SDavid Lebrun static int seg6_local_input(struct sk_buff *skb) 515d1df6fd8SDavid Lebrun { 516d1df6fd8SDavid Lebrun struct dst_entry *orig_dst = skb_dst(skb); 517d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 518d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 519d1df6fd8SDavid Lebrun 5206285217fSDavid Lebrun if (skb->protocol != htons(ETH_P_IPV6)) { 5216285217fSDavid Lebrun kfree_skb(skb); 5226285217fSDavid Lebrun return -EINVAL; 5236285217fSDavid Lebrun } 5246285217fSDavid Lebrun 525d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(orig_dst->lwtstate); 526d1df6fd8SDavid Lebrun desc = slwt->desc; 527d1df6fd8SDavid Lebrun 528d1df6fd8SDavid Lebrun return desc->input(skb, slwt); 529d1df6fd8SDavid Lebrun } 530d1df6fd8SDavid Lebrun 531d1df6fd8SDavid Lebrun static const struct nla_policy seg6_local_policy[SEG6_LOCAL_MAX + 1] = { 532d1df6fd8SDavid Lebrun [SEG6_LOCAL_ACTION] = { .type = NLA_U32 }, 533d1df6fd8SDavid Lebrun [SEG6_LOCAL_SRH] = { .type = NLA_BINARY }, 534d1df6fd8SDavid Lebrun [SEG6_LOCAL_TABLE] = { .type = NLA_U32 }, 535d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH4] = { .type = NLA_BINARY, 536d1df6fd8SDavid Lebrun .len = sizeof(struct in_addr) }, 537d1df6fd8SDavid Lebrun [SEG6_LOCAL_NH6] = { .type = NLA_BINARY, 538d1df6fd8SDavid Lebrun .len = sizeof(struct in6_addr) }, 539d1df6fd8SDavid Lebrun [SEG6_LOCAL_IIF] = { .type = NLA_U32 }, 540d1df6fd8SDavid Lebrun [SEG6_LOCAL_OIF] = { .type = NLA_U32 }, 541d1df6fd8SDavid Lebrun }; 542d1df6fd8SDavid Lebrun 5432d9cc60aSDavid Lebrun static int parse_nla_srh(struct nlattr **attrs, struct seg6_local_lwt *slwt) 5442d9cc60aSDavid Lebrun { 5452d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 5462d9cc60aSDavid Lebrun int len; 5472d9cc60aSDavid Lebrun 5482d9cc60aSDavid Lebrun srh = nla_data(attrs[SEG6_LOCAL_SRH]); 5492d9cc60aSDavid Lebrun len = nla_len(attrs[SEG6_LOCAL_SRH]); 5502d9cc60aSDavid Lebrun 5512d9cc60aSDavid Lebrun /* SRH must contain at least one segment */ 5522d9cc60aSDavid Lebrun if (len < sizeof(*srh) + sizeof(struct in6_addr)) 5532d9cc60aSDavid Lebrun return -EINVAL; 5542d9cc60aSDavid Lebrun 5552d9cc60aSDavid Lebrun if (!seg6_validate_srh(srh, len)) 5562d9cc60aSDavid Lebrun return -EINVAL; 5572d9cc60aSDavid Lebrun 5582d9cc60aSDavid Lebrun slwt->srh = kmalloc(len, GFP_KERNEL); 5592d9cc60aSDavid Lebrun if (!slwt->srh) 5602d9cc60aSDavid Lebrun return -ENOMEM; 5612d9cc60aSDavid Lebrun 5622d9cc60aSDavid Lebrun memcpy(slwt->srh, srh, len); 5632d9cc60aSDavid Lebrun 5642d9cc60aSDavid Lebrun slwt->headroom += len; 5652d9cc60aSDavid Lebrun 5662d9cc60aSDavid Lebrun return 0; 5672d9cc60aSDavid Lebrun } 5682d9cc60aSDavid Lebrun 5692d9cc60aSDavid Lebrun static int put_nla_srh(struct sk_buff *skb, struct seg6_local_lwt *slwt) 5702d9cc60aSDavid Lebrun { 5712d9cc60aSDavid Lebrun struct ipv6_sr_hdr *srh; 5722d9cc60aSDavid Lebrun struct nlattr *nla; 5732d9cc60aSDavid Lebrun int len; 5742d9cc60aSDavid Lebrun 5752d9cc60aSDavid Lebrun srh = slwt->srh; 5762d9cc60aSDavid Lebrun len = (srh->hdrlen + 1) << 3; 5772d9cc60aSDavid Lebrun 5782d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_SRH, len); 5792d9cc60aSDavid Lebrun if (!nla) 5802d9cc60aSDavid Lebrun return -EMSGSIZE; 5812d9cc60aSDavid Lebrun 5822d9cc60aSDavid Lebrun memcpy(nla_data(nla), srh, len); 5832d9cc60aSDavid Lebrun 5842d9cc60aSDavid Lebrun return 0; 5852d9cc60aSDavid Lebrun } 5862d9cc60aSDavid Lebrun 5872d9cc60aSDavid Lebrun static int cmp_nla_srh(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 5882d9cc60aSDavid Lebrun { 5892d9cc60aSDavid Lebrun int len = (a->srh->hdrlen + 1) << 3; 5902d9cc60aSDavid Lebrun 5912d9cc60aSDavid Lebrun if (len != ((b->srh->hdrlen + 1) << 3)) 5922d9cc60aSDavid Lebrun return 1; 5932d9cc60aSDavid Lebrun 5942d9cc60aSDavid Lebrun return memcmp(a->srh, b->srh, len); 5952d9cc60aSDavid Lebrun } 5962d9cc60aSDavid Lebrun 5972d9cc60aSDavid Lebrun static int parse_nla_table(struct nlattr **attrs, struct seg6_local_lwt *slwt) 5982d9cc60aSDavid Lebrun { 5992d9cc60aSDavid Lebrun slwt->table = nla_get_u32(attrs[SEG6_LOCAL_TABLE]); 6002d9cc60aSDavid Lebrun 6012d9cc60aSDavid Lebrun return 0; 6022d9cc60aSDavid Lebrun } 6032d9cc60aSDavid Lebrun 6042d9cc60aSDavid Lebrun static int put_nla_table(struct sk_buff *skb, struct seg6_local_lwt *slwt) 6052d9cc60aSDavid Lebrun { 6062d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_TABLE, slwt->table)) 6072d9cc60aSDavid Lebrun return -EMSGSIZE; 6082d9cc60aSDavid Lebrun 6092d9cc60aSDavid Lebrun return 0; 6102d9cc60aSDavid Lebrun } 6112d9cc60aSDavid Lebrun 6122d9cc60aSDavid Lebrun static int cmp_nla_table(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 6132d9cc60aSDavid Lebrun { 6142d9cc60aSDavid Lebrun if (a->table != b->table) 6152d9cc60aSDavid Lebrun return 1; 6162d9cc60aSDavid Lebrun 6172d9cc60aSDavid Lebrun return 0; 6182d9cc60aSDavid Lebrun } 6192d9cc60aSDavid Lebrun 6202d9cc60aSDavid Lebrun static int parse_nla_nh4(struct nlattr **attrs, struct seg6_local_lwt *slwt) 6212d9cc60aSDavid Lebrun { 6222d9cc60aSDavid Lebrun memcpy(&slwt->nh4, nla_data(attrs[SEG6_LOCAL_NH4]), 6232d9cc60aSDavid Lebrun sizeof(struct in_addr)); 6242d9cc60aSDavid Lebrun 6252d9cc60aSDavid Lebrun return 0; 6262d9cc60aSDavid Lebrun } 6272d9cc60aSDavid Lebrun 6282d9cc60aSDavid Lebrun static int put_nla_nh4(struct sk_buff *skb, struct seg6_local_lwt *slwt) 6292d9cc60aSDavid Lebrun { 6302d9cc60aSDavid Lebrun struct nlattr *nla; 6312d9cc60aSDavid Lebrun 6322d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH4, sizeof(struct in_addr)); 6332d9cc60aSDavid Lebrun if (!nla) 6342d9cc60aSDavid Lebrun return -EMSGSIZE; 6352d9cc60aSDavid Lebrun 6362d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh4, sizeof(struct in_addr)); 6372d9cc60aSDavid Lebrun 6382d9cc60aSDavid Lebrun return 0; 6392d9cc60aSDavid Lebrun } 6402d9cc60aSDavid Lebrun 6412d9cc60aSDavid Lebrun static int cmp_nla_nh4(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 6422d9cc60aSDavid Lebrun { 6432d9cc60aSDavid Lebrun return memcmp(&a->nh4, &b->nh4, sizeof(struct in_addr)); 6442d9cc60aSDavid Lebrun } 6452d9cc60aSDavid Lebrun 6462d9cc60aSDavid Lebrun static int parse_nla_nh6(struct nlattr **attrs, struct seg6_local_lwt *slwt) 6472d9cc60aSDavid Lebrun { 6482d9cc60aSDavid Lebrun memcpy(&slwt->nh6, nla_data(attrs[SEG6_LOCAL_NH6]), 6492d9cc60aSDavid Lebrun sizeof(struct in6_addr)); 6502d9cc60aSDavid Lebrun 6512d9cc60aSDavid Lebrun return 0; 6522d9cc60aSDavid Lebrun } 6532d9cc60aSDavid Lebrun 6542d9cc60aSDavid Lebrun static int put_nla_nh6(struct sk_buff *skb, struct seg6_local_lwt *slwt) 6552d9cc60aSDavid Lebrun { 6562d9cc60aSDavid Lebrun struct nlattr *nla; 6572d9cc60aSDavid Lebrun 6582d9cc60aSDavid Lebrun nla = nla_reserve(skb, SEG6_LOCAL_NH6, sizeof(struct in6_addr)); 6592d9cc60aSDavid Lebrun if (!nla) 6602d9cc60aSDavid Lebrun return -EMSGSIZE; 6612d9cc60aSDavid Lebrun 6622d9cc60aSDavid Lebrun memcpy(nla_data(nla), &slwt->nh6, sizeof(struct in6_addr)); 6632d9cc60aSDavid Lebrun 6642d9cc60aSDavid Lebrun return 0; 6652d9cc60aSDavid Lebrun } 6662d9cc60aSDavid Lebrun 6672d9cc60aSDavid Lebrun static int cmp_nla_nh6(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 6682d9cc60aSDavid Lebrun { 6692d9cc60aSDavid Lebrun return memcmp(&a->nh6, &b->nh6, sizeof(struct in6_addr)); 6702d9cc60aSDavid Lebrun } 6712d9cc60aSDavid Lebrun 6722d9cc60aSDavid Lebrun static int parse_nla_iif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 6732d9cc60aSDavid Lebrun { 6742d9cc60aSDavid Lebrun slwt->iif = nla_get_u32(attrs[SEG6_LOCAL_IIF]); 6752d9cc60aSDavid Lebrun 6762d9cc60aSDavid Lebrun return 0; 6772d9cc60aSDavid Lebrun } 6782d9cc60aSDavid Lebrun 6792d9cc60aSDavid Lebrun static int put_nla_iif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 6802d9cc60aSDavid Lebrun { 6812d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_IIF, slwt->iif)) 6822d9cc60aSDavid Lebrun return -EMSGSIZE; 6832d9cc60aSDavid Lebrun 6842d9cc60aSDavid Lebrun return 0; 6852d9cc60aSDavid Lebrun } 6862d9cc60aSDavid Lebrun 6872d9cc60aSDavid Lebrun static int cmp_nla_iif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 6882d9cc60aSDavid Lebrun { 6892d9cc60aSDavid Lebrun if (a->iif != b->iif) 6902d9cc60aSDavid Lebrun return 1; 6912d9cc60aSDavid Lebrun 6922d9cc60aSDavid Lebrun return 0; 6932d9cc60aSDavid Lebrun } 6942d9cc60aSDavid Lebrun 6952d9cc60aSDavid Lebrun static int parse_nla_oif(struct nlattr **attrs, struct seg6_local_lwt *slwt) 6962d9cc60aSDavid Lebrun { 6972d9cc60aSDavid Lebrun slwt->oif = nla_get_u32(attrs[SEG6_LOCAL_OIF]); 6982d9cc60aSDavid Lebrun 6992d9cc60aSDavid Lebrun return 0; 7002d9cc60aSDavid Lebrun } 7012d9cc60aSDavid Lebrun 7022d9cc60aSDavid Lebrun static int put_nla_oif(struct sk_buff *skb, struct seg6_local_lwt *slwt) 7032d9cc60aSDavid Lebrun { 7042d9cc60aSDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_OIF, slwt->oif)) 7052d9cc60aSDavid Lebrun return -EMSGSIZE; 7062d9cc60aSDavid Lebrun 7072d9cc60aSDavid Lebrun return 0; 7082d9cc60aSDavid Lebrun } 7092d9cc60aSDavid Lebrun 7102d9cc60aSDavid Lebrun static int cmp_nla_oif(struct seg6_local_lwt *a, struct seg6_local_lwt *b) 7112d9cc60aSDavid Lebrun { 7122d9cc60aSDavid Lebrun if (a->oif != b->oif) 7132d9cc60aSDavid Lebrun return 1; 7142d9cc60aSDavid Lebrun 7152d9cc60aSDavid Lebrun return 0; 7162d9cc60aSDavid Lebrun } 7172d9cc60aSDavid Lebrun 718d1df6fd8SDavid Lebrun struct seg6_action_param { 719d1df6fd8SDavid Lebrun int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt); 720d1df6fd8SDavid Lebrun int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt); 721d1df6fd8SDavid Lebrun int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b); 722d1df6fd8SDavid Lebrun }; 723d1df6fd8SDavid Lebrun 724d1df6fd8SDavid Lebrun static struct seg6_action_param seg6_action_params[SEG6_LOCAL_MAX + 1] = { 7252d9cc60aSDavid Lebrun [SEG6_LOCAL_SRH] = { .parse = parse_nla_srh, 7262d9cc60aSDavid Lebrun .put = put_nla_srh, 7272d9cc60aSDavid Lebrun .cmp = cmp_nla_srh }, 728d1df6fd8SDavid Lebrun 7292d9cc60aSDavid Lebrun [SEG6_LOCAL_TABLE] = { .parse = parse_nla_table, 7302d9cc60aSDavid Lebrun .put = put_nla_table, 7312d9cc60aSDavid Lebrun .cmp = cmp_nla_table }, 732d1df6fd8SDavid Lebrun 7332d9cc60aSDavid Lebrun [SEG6_LOCAL_NH4] = { .parse = parse_nla_nh4, 7342d9cc60aSDavid Lebrun .put = put_nla_nh4, 7352d9cc60aSDavid Lebrun .cmp = cmp_nla_nh4 }, 736d1df6fd8SDavid Lebrun 7372d9cc60aSDavid Lebrun [SEG6_LOCAL_NH6] = { .parse = parse_nla_nh6, 7382d9cc60aSDavid Lebrun .put = put_nla_nh6, 7392d9cc60aSDavid Lebrun .cmp = cmp_nla_nh6 }, 740d1df6fd8SDavid Lebrun 7412d9cc60aSDavid Lebrun [SEG6_LOCAL_IIF] = { .parse = parse_nla_iif, 7422d9cc60aSDavid Lebrun .put = put_nla_iif, 7432d9cc60aSDavid Lebrun .cmp = cmp_nla_iif }, 744d1df6fd8SDavid Lebrun 7452d9cc60aSDavid Lebrun [SEG6_LOCAL_OIF] = { .parse = parse_nla_oif, 7462d9cc60aSDavid Lebrun .put = put_nla_oif, 7472d9cc60aSDavid Lebrun .cmp = cmp_nla_oif }, 748d1df6fd8SDavid Lebrun }; 749d1df6fd8SDavid Lebrun 750d1df6fd8SDavid Lebrun static int parse_nla_action(struct nlattr **attrs, struct seg6_local_lwt *slwt) 751d1df6fd8SDavid Lebrun { 752d1df6fd8SDavid Lebrun struct seg6_action_param *param; 753d1df6fd8SDavid Lebrun struct seg6_action_desc *desc; 754d1df6fd8SDavid Lebrun int i, err; 755d1df6fd8SDavid Lebrun 756d1df6fd8SDavid Lebrun desc = __get_action_desc(slwt->action); 757d1df6fd8SDavid Lebrun if (!desc) 758d1df6fd8SDavid Lebrun return -EINVAL; 759d1df6fd8SDavid Lebrun 760d1df6fd8SDavid Lebrun if (!desc->input) 761d1df6fd8SDavid Lebrun return -EOPNOTSUPP; 762d1df6fd8SDavid Lebrun 763d1df6fd8SDavid Lebrun slwt->desc = desc; 764d1df6fd8SDavid Lebrun slwt->headroom += desc->static_headroom; 765d1df6fd8SDavid Lebrun 766d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 767d1df6fd8SDavid Lebrun if (desc->attrs & (1 << i)) { 768d1df6fd8SDavid Lebrun if (!attrs[i]) 769d1df6fd8SDavid Lebrun return -EINVAL; 770d1df6fd8SDavid Lebrun 771d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 772d1df6fd8SDavid Lebrun 773d1df6fd8SDavid Lebrun err = param->parse(attrs, slwt); 774d1df6fd8SDavid Lebrun if (err < 0) 775d1df6fd8SDavid Lebrun return err; 776d1df6fd8SDavid Lebrun } 777d1df6fd8SDavid Lebrun } 778d1df6fd8SDavid Lebrun 779d1df6fd8SDavid Lebrun return 0; 780d1df6fd8SDavid Lebrun } 781d1df6fd8SDavid Lebrun 782d1df6fd8SDavid Lebrun static int seg6_local_build_state(struct nlattr *nla, unsigned int family, 783d1df6fd8SDavid Lebrun const void *cfg, struct lwtunnel_state **ts, 784d1df6fd8SDavid Lebrun struct netlink_ext_ack *extack) 785d1df6fd8SDavid Lebrun { 786d1df6fd8SDavid Lebrun struct nlattr *tb[SEG6_LOCAL_MAX + 1]; 787d1df6fd8SDavid Lebrun struct lwtunnel_state *newts; 788d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt; 789d1df6fd8SDavid Lebrun int err; 790d1df6fd8SDavid Lebrun 7916285217fSDavid Lebrun if (family != AF_INET6) 7926285217fSDavid Lebrun return -EINVAL; 7936285217fSDavid Lebrun 794d1df6fd8SDavid Lebrun err = nla_parse_nested(tb, SEG6_LOCAL_MAX, nla, seg6_local_policy, 795d1df6fd8SDavid Lebrun extack); 796d1df6fd8SDavid Lebrun 797d1df6fd8SDavid Lebrun if (err < 0) 798d1df6fd8SDavid Lebrun return err; 799d1df6fd8SDavid Lebrun 800d1df6fd8SDavid Lebrun if (!tb[SEG6_LOCAL_ACTION]) 801d1df6fd8SDavid Lebrun return -EINVAL; 802d1df6fd8SDavid Lebrun 803d1df6fd8SDavid Lebrun newts = lwtunnel_state_alloc(sizeof(*slwt)); 804d1df6fd8SDavid Lebrun if (!newts) 805d1df6fd8SDavid Lebrun return -ENOMEM; 806d1df6fd8SDavid Lebrun 807d1df6fd8SDavid Lebrun slwt = seg6_local_lwtunnel(newts); 808d1df6fd8SDavid Lebrun slwt->action = nla_get_u32(tb[SEG6_LOCAL_ACTION]); 809d1df6fd8SDavid Lebrun 810d1df6fd8SDavid Lebrun err = parse_nla_action(tb, slwt); 811d1df6fd8SDavid Lebrun if (err < 0) 812d1df6fd8SDavid Lebrun goto out_free; 813d1df6fd8SDavid Lebrun 814d1df6fd8SDavid Lebrun newts->type = LWTUNNEL_ENCAP_SEG6_LOCAL; 815d1df6fd8SDavid Lebrun newts->flags = LWTUNNEL_STATE_INPUT_REDIRECT; 816d1df6fd8SDavid Lebrun newts->headroom = slwt->headroom; 817d1df6fd8SDavid Lebrun 818d1df6fd8SDavid Lebrun *ts = newts; 819d1df6fd8SDavid Lebrun 820d1df6fd8SDavid Lebrun return 0; 821d1df6fd8SDavid Lebrun 822d1df6fd8SDavid Lebrun out_free: 823d1df6fd8SDavid Lebrun kfree(slwt->srh); 824d1df6fd8SDavid Lebrun kfree(newts); 825d1df6fd8SDavid Lebrun return err; 826d1df6fd8SDavid Lebrun } 827d1df6fd8SDavid Lebrun 828d1df6fd8SDavid Lebrun static void seg6_local_destroy_state(struct lwtunnel_state *lwt) 829d1df6fd8SDavid Lebrun { 830d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 831d1df6fd8SDavid Lebrun 832d1df6fd8SDavid Lebrun kfree(slwt->srh); 833d1df6fd8SDavid Lebrun } 834d1df6fd8SDavid Lebrun 835d1df6fd8SDavid Lebrun static int seg6_local_fill_encap(struct sk_buff *skb, 836d1df6fd8SDavid Lebrun struct lwtunnel_state *lwt) 837d1df6fd8SDavid Lebrun { 838d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 839d1df6fd8SDavid Lebrun struct seg6_action_param *param; 840d1df6fd8SDavid Lebrun int i, err; 841d1df6fd8SDavid Lebrun 842d1df6fd8SDavid Lebrun if (nla_put_u32(skb, SEG6_LOCAL_ACTION, slwt->action)) 843d1df6fd8SDavid Lebrun return -EMSGSIZE; 844d1df6fd8SDavid Lebrun 845d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 846d1df6fd8SDavid Lebrun if (slwt->desc->attrs & (1 << i)) { 847d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 848d1df6fd8SDavid Lebrun err = param->put(skb, slwt); 849d1df6fd8SDavid Lebrun if (err < 0) 850d1df6fd8SDavid Lebrun return err; 851d1df6fd8SDavid Lebrun } 852d1df6fd8SDavid Lebrun } 853d1df6fd8SDavid Lebrun 854d1df6fd8SDavid Lebrun return 0; 855d1df6fd8SDavid Lebrun } 856d1df6fd8SDavid Lebrun 857d1df6fd8SDavid Lebrun static int seg6_local_get_encap_size(struct lwtunnel_state *lwt) 858d1df6fd8SDavid Lebrun { 859d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt = seg6_local_lwtunnel(lwt); 860d1df6fd8SDavid Lebrun unsigned long attrs; 861d1df6fd8SDavid Lebrun int nlsize; 862d1df6fd8SDavid Lebrun 863d1df6fd8SDavid Lebrun nlsize = nla_total_size(4); /* action */ 864d1df6fd8SDavid Lebrun 865d1df6fd8SDavid Lebrun attrs = slwt->desc->attrs; 866d1df6fd8SDavid Lebrun 867d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_SRH)) 868d1df6fd8SDavid Lebrun nlsize += nla_total_size((slwt->srh->hdrlen + 1) << 3); 869d1df6fd8SDavid Lebrun 870d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_TABLE)) 871d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 872d1df6fd8SDavid Lebrun 873d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH4)) 874d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 875d1df6fd8SDavid Lebrun 876d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_NH6)) 877d1df6fd8SDavid Lebrun nlsize += nla_total_size(16); 878d1df6fd8SDavid Lebrun 879d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_IIF)) 880d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 881d1df6fd8SDavid Lebrun 882d1df6fd8SDavid Lebrun if (attrs & (1 << SEG6_LOCAL_OIF)) 883d1df6fd8SDavid Lebrun nlsize += nla_total_size(4); 884d1df6fd8SDavid Lebrun 885d1df6fd8SDavid Lebrun return nlsize; 886d1df6fd8SDavid Lebrun } 887d1df6fd8SDavid Lebrun 888d1df6fd8SDavid Lebrun static int seg6_local_cmp_encap(struct lwtunnel_state *a, 889d1df6fd8SDavid Lebrun struct lwtunnel_state *b) 890d1df6fd8SDavid Lebrun { 891d1df6fd8SDavid Lebrun struct seg6_local_lwt *slwt_a, *slwt_b; 892d1df6fd8SDavid Lebrun struct seg6_action_param *param; 893d1df6fd8SDavid Lebrun int i; 894d1df6fd8SDavid Lebrun 895d1df6fd8SDavid Lebrun slwt_a = seg6_local_lwtunnel(a); 896d1df6fd8SDavid Lebrun slwt_b = seg6_local_lwtunnel(b); 897d1df6fd8SDavid Lebrun 898d1df6fd8SDavid Lebrun if (slwt_a->action != slwt_b->action) 899d1df6fd8SDavid Lebrun return 1; 900d1df6fd8SDavid Lebrun 901d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs != slwt_b->desc->attrs) 902d1df6fd8SDavid Lebrun return 1; 903d1df6fd8SDavid Lebrun 904d1df6fd8SDavid Lebrun for (i = 0; i < SEG6_LOCAL_MAX + 1; i++) { 905d1df6fd8SDavid Lebrun if (slwt_a->desc->attrs & (1 << i)) { 906d1df6fd8SDavid Lebrun param = &seg6_action_params[i]; 907d1df6fd8SDavid Lebrun if (param->cmp(slwt_a, slwt_b)) 908d1df6fd8SDavid Lebrun return 1; 909d1df6fd8SDavid Lebrun } 910d1df6fd8SDavid Lebrun } 911d1df6fd8SDavid Lebrun 912d1df6fd8SDavid Lebrun return 0; 913d1df6fd8SDavid Lebrun } 914d1df6fd8SDavid Lebrun 915d1df6fd8SDavid Lebrun static const struct lwtunnel_encap_ops seg6_local_ops = { 916d1df6fd8SDavid Lebrun .build_state = seg6_local_build_state, 917d1df6fd8SDavid Lebrun .destroy_state = seg6_local_destroy_state, 918d1df6fd8SDavid Lebrun .input = seg6_local_input, 919d1df6fd8SDavid Lebrun .fill_encap = seg6_local_fill_encap, 920d1df6fd8SDavid Lebrun .get_encap_size = seg6_local_get_encap_size, 921d1df6fd8SDavid Lebrun .cmp_encap = seg6_local_cmp_encap, 922d1df6fd8SDavid Lebrun .owner = THIS_MODULE, 923d1df6fd8SDavid Lebrun }; 924d1df6fd8SDavid Lebrun 925d1df6fd8SDavid Lebrun int __init seg6_local_init(void) 926d1df6fd8SDavid Lebrun { 927d1df6fd8SDavid Lebrun return lwtunnel_encap_add_ops(&seg6_local_ops, 928d1df6fd8SDavid Lebrun LWTUNNEL_ENCAP_SEG6_LOCAL); 929d1df6fd8SDavid Lebrun } 930d1df6fd8SDavid Lebrun 931d1df6fd8SDavid Lebrun void seg6_local_exit(void) 932d1df6fd8SDavid Lebrun { 933d1df6fd8SDavid Lebrun lwtunnel_encap_del_ops(&seg6_local_ops, LWTUNNEL_ENCAP_SEG6_LOCAL); 934d1df6fd8SDavid Lebrun } 935