/* callbacks used for customizing the creation and destruction of a behavior */ struct seg6_local_lwtunnel_ops { int (*build_state)(struct seg6_local_lwt *slwt, constvoid *cfg, struct netlink_ext_ack *extack); void (*destroy_state)(struct seg6_local_lwt *slwt);
};
struct seg6_action_desc { int action; unsignedlong attrs;
/* The optattrs field is used for specifying all the optional * attributes supported by a specific behavior. * It means that if one of these attributes is not provided in the * netlink message during the behavior creation, no errors will be * returned to the userspace. * * Each attribute can be only of two types (mutually exclusive): * 1) required or 2) optional. * Every user MUST obey to this rule! If you set an attribute as * required the same attribute CANNOT be set as optional and vice * versa.
*/ unsignedlong optattrs;
int (*input)(struct sk_buff *skb, struct seg6_local_lwt *slwt); int static_headroom;
/* default length values (expressed in bits) for both Locator-Block and * Locator-Node Function. * * Both SEG6_LOCAL_LCBLOCK_DBITS and SEG6_LOCAL_LCNODE_FN_DBITS *must* be: * i) greater than 0; * ii) evenly divisible by 8. In other terms, the lengths of the * Locator-Block and Locator-Node Function must be byte-aligned (we can * relax this constraint in the future if really needed). * * Moreover, a third condition must hold: * iii) SEG6_LOCAL_LCBLOCK_DBITS + SEG6_LOCAL_LCNODE_FN_DBITS <= 128. * * The correctness of SEG6_LOCAL_LCBLOCK_DBITS and SEG6_LOCAL_LCNODE_FN_DBITS * values are checked during the kernel compilation. If the compilation stops, * check the value of these parameters to see if they meet conditions (i), (ii) * and (iii).
*/ #define SEG6_LOCAL_LCBLOCK_DBITS 32 #define SEG6_LOCAL_LCNODE_FN_DBITS 16
/* The following next_csid_chk_{cntr,lcblock,lcblock_fn}_bits macros can be * used directly to check whether the lengths (in bits) of Locator-Block and * Locator-Node Function are valid according to (i), (ii), (iii).
*/ #define next_csid_chk_cntr_bits(blen, flen) \
((blen) + (flen) > 128)
struct net *net; /* VRF device associated to the routing table used by the SRv6 * End.DT4/DT6 behavior for routing IPv4/IPv6 packets.
*/ int vrf_ifindex; int vrf_table;
/* tunneled packet family (IPv4 or IPv6). * Protocol and header length are inferred from family.
*/
u16 family;
};
/* This struct groups all the SRv6 Behavior counters supported so far. * * put_nla_counters() makes use of this data structure to collect all counter * values after the per-CPU counter evaluation has been performed. * Finally, each counter value (in seg6_local_counters) is stored in the * corresponding netlink attribute and sent to user space. * * NB: we don't want to expose this structure to user space!
*/ struct seg6_local_counters {
__u64 packets;
__u64 bytes;
__u64 errors;
};
int headroom; struct seg6_action_desc *desc; /* unlike the required attrs, we have to track the optional attributes * that have been effectively parsed.
*/ unsignedlong parsed_optattrs;
};
/* we want to discard traffic destined for local packet processing, * if @local_delivery is set to false.
*/ if (!local_delivery)
dev_flags |= IFF_LOOPBACK;
/* Processing of SRv6 End, End.X, and End.T behaviors can be extended through * the flavors framework. These behaviors must report the subset of (flavor) * operations they currently implement. In this way, if a user specifies a * flavor combination that is not supported by a given End* behavior, the * kernel refuses to instantiate the tunnel reporting the error.
*/ staticint seg6_flv_supp_ops_by_action(int action, __u32 *fops)
{ switch (action) { case SEG6_LOCAL_ACTION_END:
*fops = SEG6_LOCAL_END_FLV_SUPP_OPS; break; case SEG6_LOCAL_ACTION_END_X:
*fops = SEG6_LOCAL_END_X_FLV_SUPP_OPS; break; default: return -EOPNOTSUPP;
}
return 0;
}
/* We describe the packet state in relation to the absence/presence of the SRH * and the Segment Left (SL) field. * For our purposes, it is not necessary to record the exact value of the SL * when the SID List consists of two or more segments.
*/ enum seg6_local_pktinfo { /* the order really matters! */
SEG6_LOCAL_PKTINFO_NOHDR = 0,
SEG6_LOCAL_PKTINFO_SL_ZERO,
SEG6_LOCAL_PKTINFO_SL_ONE,
SEG6_LOCAL_PKTINFO_SL_MORE,
__SEG6_LOCAL_PKTINFO_MAX,
};
/* The action table for RFC8986 flavors (see the flv8986_act_tbl below) * contains the actions (i.e. processing operations) to be applied on packets * when flavors are configured for an End* behavior. * By combining the pkinfo data and from the flavors mask, the macro * computes the index used to access the elements (actions) stored in the * action table. The index is structured as follows: * * index * _______________/\________________ * / \ * +----------------+----------------+ * | pf | afm | * +----------------+----------------+ * ph-1 ... p1 p0 fk-1 ... f1 f0 * MSB LSB * * where: * - 'afm' (adjusted flavor mask) is the mask containing a combination of the * RFC8986 flavors currently supported. 'afm' corresponds to the @fm * argument of the macro whose value is righ-shifted by 1 bit. By doing so, * we discard the SEG6_LOCAL_FLV_OP_UNSPEC flag (bit 0 in @fm) which is * never used here; * - 'pf' encodes the packet info (pktinfo) regarding the presence/absence of * the SRH, SL = 0, etc. 'pf' is set with the value of @pf provided as * argument to the macro.
*/ #define flv8986_act_tbl_idx(pf, fm) \
((((pf) << bits_per(SEG6_LOCAL_FLV8986_SUPP_OPS)) | \
((fm) & SEG6_LOCAL_FLV8986_SUPP_OPS)) >> SEG6_LOCAL_FLV_OP_PSP)
/* We compute the size of the action table by considering the RFC8986 flavors * actually supported by the kernel. In this way, the size is automatically * adjusted when new flavors are supported.
*/ #define FLV8986_ACT_TBL_SIZE \
roundup_pow_of_two(flv8986_act_tbl_idx(SEG6_LOCAL_PKTINFO_MAX, \
SEG6_LOCAL_FLV8986_SUPP_OPS))
/* tbl_cfg(act, pf, fm) macro is used to easily configure the action * table; it accepts 3 arguments: * i) @act, the suffix from SEG6_LOCAL_FLV_ACT_{act} representing * the action that should be applied on the packet; * ii) @pf, the suffix from SEG6_LOCAL_PKTINFO_{pf} reporting the packet * info about the lack/presence of SRH, SRH with SL = 0, etc; * iii) @fm, the mask of flavors.
*/ #define tbl_cfg(act, pf, fm) \
[flv8986_act_tbl_idx(SEG6_LOCAL_PKTINFO_##pf, \
(fm))] = SEG6_LOCAL_FLV_ACT_##act
/* shorthand for improving readability */ #define F_PSP SEG6_F_LOCAL_FLV_PSP
/* The table contains, for each combination of the pktinfo data and * flavors, the action that should be taken on a packet (e.g. * "standard" Endpoint processing, Penultimate Segment Pop, etc). * * By default, table entries not explicitly configured are initialized with the * SEG6_LOCAL_FLV_ACT_UNSPEC action, which generally has the effect of * discarding the processed packet.
*/ staticconst u8 flv8986_act_tbl[FLV8986_ACT_TBL_SIZE] = { /* PSP variant for packet where SRH with SL = 1 */
tbl_cfg(PSP, SL_ONE, F_PSP), /* End for packet where the SRH with SL > 1*/
tbl_cfg(END, SL_MORE, F_PSP),
};
#undef F_PSP #undef tbl_cfg
/* For each flavor defined in RFC8986 (or a combination of them) an action is * performed on the packet. The specific action depends on: * - info extracted from the packet (i.e. pktinfo data) regarding the * lack/presence of the SRH, and if the SRH is available, on the value of * Segment Left field; * - the mask of flavors configured for the specific SRv6 End* behavior. * * The function combines both the pkinfo and the flavors mask to evaluate the * corresponding action to be taken on the packet.
*/ staticenum seg6_local_flv_action
seg6_local_flv8986_act_lookup(enum seg6_local_pktinfo pinfo, __u32 flvmask)
{ unsignedlong index;
/* check if the provided mask of flavors is supported */ if (unlikely(flvmask & ~SEG6_LOCAL_FLV8986_SUPP_OPS)) return SEG6_LOCAL_FLV_ACT_UNSPEC;
index = flv8986_act_tbl_idx(pinfo, flvmask); if (unlikely(index >= FLV8986_ACT_TBL_SIZE)) return SEG6_LOCAL_FLV_ACT_UNSPEC;
return flv8986_act_tbl[index];
}
/* skb->data must be aligned with skb->network_header */ staticbool seg6_pop_srh(struct sk_buff *skb, int srhoff)
{ struct ipv6_sr_hdr *srh; struct ipv6hdr *iph;
__u8 srh_nexthdr; int thoff = -1; int srhlen; int nhlen;
if (unlikely(srhoff < sizeof(*iph) ||
!pskb_may_pull(skb, srhoff + sizeof(*srh)))) returnfalse;
/* we are about to mangle the pkt, let's check if we can write on it */ if (unlikely(skb_ensure_writable(skb, srhoff + srhlen))) returnfalse;
/* skb_ensure_writable() may change skb pointers; evaluate srh again */
srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
srh_nexthdr = srh->nexthdr;
if (unlikely(!skb_transport_header_was_set(skb))) goto pull;
nhlen = skb_network_header_len(skb); /* we have to deal with the transport header: it could be set before * the SRH, after the SRH, or within it (which is considered wrong, * however).
*/ if (likely(nhlen <= srhoff))
thoff = nhlen; elseif (nhlen >= srhoff + srhlen) /* transport_header is set after the SRH */
thoff = nhlen - srhlen; else /* transport_header falls inside the SRH; hence, we can't * restore the transport_header pointer properly after * SRH removing operation.
*/ returnfalse;
pull: /* we need to pop the SRH: * 1) first of all, we pull out everything from IPv6 header up to SRH * (included) evaluating also the rcsum; * 2) we overwrite (and then remove) the SRH by properly moving the * IPv6 along with any extension header that precedes the SRH; * 3) At the end, we push back the pulled headers (except for SRH, * obviously).
*/
skb_pull_rcsum(skb, srhoff + srhlen);
memmove(skb_network_header(skb) + srhlen, skb_network_header(skb),
srhoff);
skb_push(skb, srhoff);
skb_reset_network_header(skb);
skb_mac_header_rebuild(skb); if (likely(thoff >= 0))
skb_set_transport_header(skb, thoff);
iph = ipv6_hdr(skb); if (iph->nexthdr == NEXTHDR_ROUTING) {
iph->nexthdr = srh_nexthdr;
} else { /* we must look for the extension header (EXTH, for short) that * immediately precedes the SRH we have just removed. * Then, we update the value of the EXTH nexthdr with the one * contained in the SRH nexthdr.
*/ unsignedint off = sizeof(*iph); struct ipv6_opt_hdr *hp, _hdr;
__u8 nexthdr = iph->nexthdr;
for (;;) { if (unlikely(!ipv6_ext_hdr(nexthdr) ||
nexthdr == NEXTHDR_NONE)) returnfalse;
hp = skb_header_pointer(skb, off, sizeof(_hdr), &_hdr); if (unlikely(!hp)) returnfalse;
if (hp->nexthdr == NEXTHDR_ROUTING) {
hp->nexthdr = srh_nexthdr; break;
}
switch (nexthdr) { case NEXTHDR_FRAGMENT:
fallthrough; case NEXTHDR_AUTH: /* we expect SRH before FRAG and AUTH */ returnfalse; default:
off += ipv6_optlen(hp); break;
}
/* process the packet on the basis of the RFC8986 flavors set for the given * SRv6 End behavior instance.
*/ staticint end_flv8986_core(struct sk_buff *skb, struct seg6_local_lwt *slwt)
{ conststruct seg6_flavors_info *finfo = &slwt->flv_info; enum seg6_local_flv_action action; enum seg6_local_pktinfo pinfo; struct ipv6_sr_hdr *srh;
__u32 flvmask; int srhoff;
/* retrieve the action triggered by the combination of pktinfo data and * the flavors mask.
*/
action = seg6_local_flv8986_act_lookup(pinfo, flvmask); switch (action) { case SEG6_LOCAL_FLV_ACT_END: /* process the packet as the "standard" End behavior */
advance_nextseg(srh, &ipv6_hdr(skb)->daddr); break; case SEG6_LOCAL_FLV_ACT_PSP:
advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
if (unlikely(!seg6_pop_srh(skb, srhoff))) goto drop; break; case SEG6_LOCAL_FLV_ACT_UNSPEC:
fallthrough; default: /* by default, we drop the packet since we could not find a * suitable action.
*/ goto drop;
}
if (!fops) return input_action_end_core(skb, slwt);
/* check for the presence of NEXT-C-SID since it applies first */ if (seg6_next_csid_enabled(fops)) return end_next_csid_core(skb, slwt);
/* the specific processing function to be performed on the packet * depends on the combination of flavors defined in RFC8986 and some * information extracted from the packet, e.g. presence/absence of SRH, * Segment Left = 0, etc.
*/ return end_flv8986_core(skb, slwt);
}
srh = get_and_validate_srh(skb); if (!srh) goto drop;
advance_nextseg(srh, &ipv6_hdr(skb)->daddr);
seg6_lookup_nexthop(skb, NULL, slwt->table);
return dst_input(skb);
drop:
kfree_skb(skb); return -EINVAL;
}
/* decapsulate and forward inner L2 frame on specified interface */ staticint 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, IPPROTO_ETHERNET)) 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;
/* 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.
*/ if (!ipv6_addr_any(&slwt->nh6))
nhaddr = &slwt->nh6;
seg6_lookup_nexthop(skb, nhaddr, 0);
return dst_input(skb);
}
/* decapsulate and forward to specified nexthop */ staticint input_action_end_dx6(struct sk_buff *skb, struct seg6_local_lwt *slwt)
{ /* this function accepts IPv6 encapsulated packets, with either * an SRH with SL=0, or no SRH.
*/
if (!decap_and_validate(skb, IPPROTO_IPV6)) goto drop;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) goto drop;
/* note that vrf_table was already set by parse_nla_vrftable() */
vrf_ifindex = l3mdev_ifindex_lookup_by_table_id(L3MDEV_TYPE_VRF, net,
info->vrf_table); if (vrf_ifindex < 0) { if (vrf_ifindex == -EPERM) {
NL_SET_ERR_MSG(extack, "Strict mode for VRF is disabled");
} elseif (vrf_ifindex == -ENODEV) {
NL_SET_ERR_MSG(extack, "Table has no associated VRF device");
} else {
pr_debug("seg6local: SRv6 End.DT* creation error=%d\n",
vrf_ifindex);
}
return vrf_ifindex;
}
info->net = net;
info->vrf_ifindex = vrf_ifindex;
info->family = family;
info->mode = DT_VRF_MODE;
return 0;
}
/* The SRv6 End.DT4/DT6 behavior extracts the inner (IPv4/IPv6) packet and * routes the IPv4/IPv6 packet by looking at the configured routing table. * * In the SRv6 End.DT4/DT6 use case, we can receive traffic (IPv6+Segment * Routing Header packets) from several interfaces and the outer IPv6 * destination address (DA) is used for retrieving the specific instance of the * End.DT4/DT6 behavior that should process the packets. * * However, the inner IPv4/IPv6 packet is not really bound to any receiving * interface and thus the End.DT4/DT6 sets the VRF (associated with the * corresponding routing table) as the *receiving* interface. * In other words, the End.DT4/DT6 processes a packet as if it has been received * directly by the VRF (and not by one of its slave devices, if any). * In this way, the VRF interface is used for routing the IPv4/IPv6 packet in * according to the routing table configured by the End.DT4/DT6 instance. * * This design allows you to get some interesting features like: * 1) the statistics on rx packets; * 2) the possibility to install a packet sniffer on the receiving interface * (the VRF one) for looking at the incoming packets; * 3) the possibility to leverage the netfilter prerouting hook for the inner * IPv4 packet. * * This function returns: * - the sk_buff* when the VRF rcv handler has processed the packet correctly; * - NULL when the skb is consumed by the VRF rcv handler; * - a pointer which encodes a negative error number in case of error. * Note that in this case, the function takes care of freeing the skb.
*/ staticstruct sk_buff *end_dt_vrf_rcv(struct sk_buff *skb, u16 family, struct net_device *dev)
{ /* based on l3mdev_ip_rcv; we are only interested in the master */ if (unlikely(!netif_is_l3_master(dev) && !netif_has_l3_rx_handler(dev))) goto drop;
if (unlikely(!dev->l3mdev_ops->l3mdev_l3_rcv)) goto drop;
/* the decap packet IPv4/IPv6 does not come with any mac header info. * We must unset the mac header to allow the VRF device to rebuild it, * just in case there is a sniffer attached on the device.
*/
skb_unset_mac_header(skb);
skb = dev->l3mdev_ops->l3mdev_l3_rcv(dev, skb, family); if (!skb) /* the skb buffer was consumed by the handler */ return NULL;
/* when a packet is received by a VRF or by one of its slaves, the * master device reference is set into the skb.
*/ if (unlikely(skb->dev != dev || skb->skb_iif != dev->ifindex)) goto drop;
return skb;
drop:
kfree_skb(skb); return ERR_PTR(-EINVAL);
}
staticstruct net_device *end_dt_get_vrf_rcu(struct sk_buff *skb, struct seg6_end_dt_info *info)
{ int vrf_ifindex = info->vrf_ifindex; struct net *net = info->net;
if (unlikely(vrf_ifindex < 0)) goto error;
if (unlikely(!net_eq(dev_net(skb->dev), net))) goto error;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) goto drop;
#ifdef CONFIG_NET_L3_MASTER_DEV if (seg6_end_dt6_get_mode(slwt) == DT_LEGACY_MODE) goto legacy_mode;
/* DT6_VRF_MODE */
skb = end_dt_vrf_core(skb, slwt, AF_INET6); if (!skb) /* packet has been processed and consumed by the VRF */ return 0;
if (IS_ERR(skb)) return PTR_ERR(skb);
/* note: this time we do not need to specify the table because the VRF * takes care of selecting the correct table.
*/
seg6_lookup_any_nexthop(skb, NULL, 0, true, 0);
switch (nexthdr) { case IPPROTO_IPIP: return input_action_end_dt4(skb, slwt); case IPPROTO_IPV6: return input_action_end_dt6(skb, slwt);
}
drop:
kfree_skb(skb); return -EINVAL;
} #endif
/* push an SRH on top of the current one */ staticint 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;
/* The access to the per-CPU buffer srh_state is protected by running * always in softirq context (with disabled BH). On PREEMPT_RT the * required locking is provided by the following local_lock_nested_bh() * statement. It is also accessed by the bpf_lwt_seg6_* helpers via * bpf_prog_run_save_cb().
*/
local_lock_nested_bh(&seg6_bpf_srh_states.bh_lock);
srh_state = this_cpu_ptr(&seg6_bpf_srh_states);
srh_state->srh = srh;
srh_state->hdrlen = srh->hdrlen << 3;
srh_state->valid = true;
rcu_read_lock();
bpf_compute_data_pointers(skb);
ret = bpf_prog_run_save_cb(slwt->bpf.prog, skb);
rcu_read_unlock();
switch (ret) { case BPF_OK: case BPF_REDIRECT: break; case BPF_DROP: goto drop; default:
pr_warn_once("bpf-seg6local: Illegal return value %u\n", ret); goto drop;
}
if (srh_state->srh && !seg6_bpf_has_valid_srh(skb)) goto drop;
local_unlock_nested_bh(&seg6_bpf_srh_states.bh_lock);
if (ret != BPF_REDIRECT)
seg6_lookup_nexthop(skb, NULL, 0);
ret = nla_parse_nested_deprecated(tb, SEG6_LOCAL_CNT_MAX,
attrs[SEG6_LOCAL_COUNTERS],
seg6_local_counters_policy, NULL); if (ret < 0) return ret;
/* basic support for SRv6 Behavior counters requires at least: * packets, bytes and errors.
*/ if (!tb[SEG6_LOCAL_CNT_PACKETS] || !tb[SEG6_LOCAL_CNT_BYTES] ||
!tb[SEG6_LOCAL_CNT_ERRORS]) return -EINVAL;
/* counters are always zero initialized */
pcounters = seg6_local_alloc_pcpu_counters(GFP_KERNEL); if (!pcounters) return -ENOMEM;
staticint cmp_nla_counters(struct seg6_local_lwt *a, struct seg6_local_lwt *b)
{ /* a and b are equal if both have pcpu_counters set or not */ return (!!((unsignedlong)a->pcpu_counters)) ^
(!!((unsignedlong)b->pcpu_counters));
}
/* check whether the lengths of the Locator-Block and Locator-Node Function * are compatible with the dimension of a C-SID container.
*/ staticint seg6_chk_next_csid_cfg(__u8 block_len, __u8 func_len)
{ /* Locator-Block and Locator-Node Function cannot exceed 128 bits * (i.e. C-SID container length).
*/ if (next_csid_chk_cntr_bits(block_len, func_len)) return -EINVAL;
/* Locator-Block length must be greater than zero and evenly divisible * by 8. There must be room for a Locator-Node Function, at least.
*/ if (next_csid_chk_lcblock_bits(block_len)) return -EINVAL;
/* Locator-Node Function length must be greater than zero and evenly * divisible by 8. There must be room for the Locator-Block.
*/ if (next_csid_chk_lcnode_fn_bits(func_len)) return -EINVAL;
/* this attribute MUST always be present since it represents the Flavor * operation(s) to be carried out.
*/ if (!tb[SEG6_LOCAL_FLV_OPERATION]) return -EINVAL;
if (seg6_next_csid_enabled(fops)) { /* Locator-Block and Locator-Node Function lengths can be * provided by the user space. Otherwise, default values are * applied.
*/
rc = seg6_parse_nla_next_csid_cfg(tb, finfo, extack); if (rc < 0) return rc;
}
struct seg6_action_param { int (*parse)(struct nlattr **attrs, struct seg6_local_lwt *slwt, struct netlink_ext_ack *extack); int (*put)(struct sk_buff *skb, struct seg6_local_lwt *slwt); int (*cmp)(struct seg6_local_lwt *a, struct seg6_local_lwt *b);
/* optional destroy() callback useful for releasing resources which * have been previously acquired in the corresponding parse() * function.
*/ void (*destroy)(struct seg6_local_lwt *slwt);
};
/* call the destroy() callback (if available) for each set attribute in * @parsed_attrs, starting from the first attribute up to the @max_parsed * (excluded) attribute.
*/ staticvoid __destroy_attrs(unsignedlong parsed_attrs, int max_parsed, struct seg6_local_lwt *slwt)
{ struct seg6_action_param *param; int i;
/* Every required seg6local attribute is identified by an ID which is * encoded as a flag (i.e: 1 << ID) in the 'attrs' bitmask; * * We scan the 'parsed_attrs' bitmask, starting from the first attribute * up to the @max_parsed (excluded) attribute. * For each set attribute, we retrieve the corresponding destroy() * callback. If the callback is not available, then we skip to the next * attribute; otherwise, we call the destroy() callback.
*/ for (i = SEG6_LOCAL_SRH; i < max_parsed; ++i) { if (!(parsed_attrs & SEG6_F_ATTR(i))) continue;
param = &seg6_action_params[i];
if (param->destroy)
param->destroy(slwt);
}
}
/* release all the resources that may have been acquired during parsing * operations.
*/ staticvoid destroy_attrs(struct seg6_local_lwt *slwt)
{ unsignedlong attrs = slwt->desc->attrs | slwt->parsed_optattrs;
/* current attribute has been correctly parsed */
parsed_optattrs |= SEG6_F_ATTR(i);
}
/* store in the tunnel state all the optional attributed successfully * parsed.
*/
slwt->parsed_optattrs = parsed_optattrs;
return 0;
parse_optattrs_err:
__destroy_attrs(parsed_optattrs, i, slwt);
return err;
}
/* call the custom constructor of the behavior during its initialization phase * and after that all its attributes have been parsed successfully.
*/ staticint
seg6_local_lwtunnel_build_state(struct seg6_local_lwt *slwt, constvoid *cfg, struct netlink_ext_ack *extack)
{ struct seg6_action_desc *desc = slwt->desc; struct seg6_local_lwtunnel_ops *ops;
ops = &desc->slwt_ops; if (!ops->build_state) return 0;
return ops->build_state(slwt, cfg, extack);
}
/* call the custom destructor of the behavior which is invoked before the * tunnel is going to be destroyed.
*/ staticvoid seg6_local_lwtunnel_destroy_state(struct seg6_local_lwt *slwt)
{ struct seg6_action_desc *desc = slwt->desc; struct seg6_local_lwtunnel_ops *ops;
ops = &desc->slwt_ops; if (!ops->destroy_state) return;
/* Forcing the desc->optattrs *set* and the desc->attrs *set* to be * disjoined, this allow us to release acquired resources by optional * attributes and by required attributes independently from each other * without any interference. * In other terms, we are sure that we do not release some the acquired * resources twice. * * Note that if an attribute is configured both as required and as * optional, it means that the user has messed something up in the * seg6_action_table. Therefore, this check is required for SRv6 * behaviors to work properly.
*/
invalid_attrs = desc->attrs & desc->optattrs; if (invalid_attrs) {
WARN_ONCE(1, "An attribute cannot be both required AND optional"); return -EINVAL;
}
/* parse the required attributes */ for (i = SEG6_LOCAL_SRH; i < SEG6_LOCAL_MAX + 1; i++) { if (desc->attrs & SEG6_F_ATTR(i)) { if (!attrs[i]) return -EINVAL;