Implement support for processing the IOAM Pre-allocated Trace with IPv6, see [1] and [2]. Introduce a new IPv6 Hop-by-Hop TLV option, see IANA [3]. A new per-interface sysctl is introduced. The value is a boolean to accept (=1) or ignore (=0, by default) IPv6 IOAM options on ingress for an interface: - net.ipv6.conf.XXX.ioam6_enabled Two other sysctls are introduced to define IOAM IDs, represented by an integer. They are respectively per-namespace and per-interface: - net.ipv6.ioam6_id - net.ipv6.conf.XXX.ioam6_id The value of the first one represents the IOAM ID of the node itself (u32; max and default value = U32_MAX>>8, due to hop limit concatenation) while the other represents the IOAM ID of an interface (u16; max and default value = U16_MAX). Each "ioam6_id" sysctl has a "_wide" equivalent: - net.ipv6.ioam6_id_wide - net.ipv6.conf.XXX.ioam6_id_wide The value of the first one represents the wide IOAM ID of the node itself (u64; max and default value = U64_MAX>>8, due to hop limit concatenation) while the other represents the wide IOAM ID of an interface (u32; max and default value = U32_MAX). The use of short and wide equivalents is not exclusive, a deployment could choose to leverage both. For example, net.ipv6.conf.XXX.ioam6_id (short format) could be an identifier for a physical interface, whereas net.ipv6.conf.XXX.ioam6_id_wide (wide format) could be an identifier for a logical sub-interface. Documentation about new sysctls is provided at the end of this patchset. Two relativistic hash tables are used: one for IOAM namespaces, the other for IOAM schemas. A namespace can only have a single active schema and a schema can only be attached to a single namespace (1:1 relationship). [1] https://tools.ietf.org/html/draft-ietf-ippm-ioam-ipv6-options [2] https://tools.ietf.org/html/draft-ietf-ippm-ioam-data [3] https://www.iana.org/assignments/ipv6-parameters/ipv6-parameters.xhtml#ipv6-parameters-2
Unified · Split
| @@ -0,0 +1,13 @@ | ||
| 1 | +/* SPDX-License-Identifier: GPL-2.0+ */ | |
| 2 | +/* | |
| 3 | + * IPv6 IOAM | |
| 4 | + * | |
| 5 | + * Author: | |
| 6 | + * Justin Iurman <justin.iurman@uliege.be> | |
| 7 | + */ | |
| 8 | +#ifndef _LINUX_IOAM6_H | |
| 9 | +#define _LINUX_IOAM6_H | |
| 10 | + | |
| 11 | +#include <uapi/linux/ioam6.h> | |
| 12 | + | |
| 13 | +#endif /* _LINUX_IOAM6_H */ | |
| @@ -76,6 +76,9 @@ struct ipv6_devconf { · 75 unmodified lines | ||
| 76 | 76 | __s32 disable_policy; |
| 77 | 77 | __s32 ndisc_tclass; |
| 78 | 78 | __s32 rpl_seg_enabled; |
| 79 | + __u32 ioam6_id; | |
| 80 | + __u32 ioam6_id_wide; | |
| 81 | + __u8 ioam6_enabled; | |
| 79 | 82 | |
| 80 | 83 | struct ctl_table_header *sysctl_header; |
| 81 | 84 | }; |
| @@ -0,0 +1,64 @@ | ||
| 1 | +/* SPDX-License-Identifier: GPL-2.0+ */ | |
| 2 | +/* | |
| 3 | + * IPv6 IOAM implementation | |
| 4 | + * | |
| 5 | + * Author: | |
| 6 | + * Justin Iurman <justin.iurman@uliege.be> | |
| 7 | + */ | |
| 8 | + | |
| 9 | +#ifndef _NET_IOAM6_H | |
| 10 | +#define _NET_IOAM6_H | |
| 11 | + | |
| 12 | +#include <linux/net.h> | |
| 13 | +#include <linux/ipv6.h> | |
| 14 | +#include <linux/ioam6.h> | |
| 15 | +#include <linux/rhashtable-types.h> | |
| 16 | + | |
| 17 | +struct ioam6_namespace { | |
| 18 | + struct rhash_head head; | |
| 19 | + struct rcu_head rcu; | |
| 20 | + | |
| 21 | + struct ioam6_schema __rcu *schema; | |
| 22 | + | |
| 23 | + __be16 id; | |
| 24 | + __be32 data; | |
| 25 | + __be64 data_wide; | |
| 26 | +}; | |
| 27 | + | |
| 28 | +struct ioam6_schema { | |
| 29 | + struct rhash_head head; | |
| 30 | + struct rcu_head rcu; | |
| 31 | + | |
| 32 | + struct ioam6_namespace __rcu *ns; | |
| 33 | + | |
| 34 | + u32 id; | |
| 35 | + int len; | |
| 36 | + __be32 hdr; | |
| 37 | + | |
| 38 | + u8 data[0]; | |
| 39 | +}; | |
| 40 | + | |
| 41 | +struct ioam6_pernet_data { | |
| 42 | + struct mutex lock; | |
| 43 | + struct rhashtable namespaces; | |
| 44 | + struct rhashtable schemas; | |
| 45 | +}; | |
| 46 | + | |
| 47 | +static inline struct ioam6_pernet_data *ioam6_pernet(struct net *net) | |
| 48 | +{ | |
| 49 | +#if IS_ENABLED(CONFIG_IPV6) | |
| 50 | + return net->ipv6.ioam6_data; | |
| 51 | +#else | |
| 52 | + return NULL; | |
| 53 | +#endif | |
| 54 | +} | |
| 55 | + | |
| 56 | +struct ioam6_namespace *ioam6_namespace(struct net *net, __be16 id); | |
| 57 | +void ioam6_fill_trace_data(struct sk_buff *skb, | |
| 58 | + struct ioam6_namespace *ns, | |
| 59 | + struct ioam6_trace_hdr *trace); | |
| 60 | + | |
| 61 | +int ioam6_init(void); | |
| 62 | +void ioam6_exit(void); | |
| 63 | + | |
| 64 | +#endif /* _NET_IOAM6_H */ | |
| @@ -51,6 +51,8 @@ struct netns_sysctl_ipv6 { · 50 unmodified lines | ||
| 51 | 51 | int max_dst_opts_len; |
| 52 | 52 | int max_hbh_opts_len; |
| 53 | 53 | int seg6_flowlabel; |
| 54 | + u32 ioam6_id; | |
| 55 | + u64 ioam6_id_wide; | |
| 54 | 56 | bool skip_notify_on_dev_down; |
| 55 | 57 | u8 fib_notify_on_flag_change; |
| 56 | 58 | }; |
| @@ -110,6 +112,7 @@ struct netns_ipv6 { · 53 unmodified lines | ||
| 110 | 112 | spinlock_t lock; |
| 111 | 113 | u32 seq; |
| 112 | 114 | } ip6addrlbl_table; |
| 115 | + struct ioam6_pernet_data *ioam6_data; | |
| 113 | 116 | }; |
| 114 | 117 | |
| 115 | 118 | #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) |
| @@ -145,6 +145,7 @@ struct in6_flowlabel_req { · 144 unmodified lines | ||
| 145 | 145 | #define IPV6_TLV_PADN 1 |
| 146 | 146 | #define IPV6_TLV_ROUTERALERT 5 |
| 147 | 147 | #define IPV6_TLV_CALIPSO 7 /* RFC 5570 */ |
| 148 | +#define IPV6_TLV_IOAM 49 /* TEMPORARY IANA allocation for IOAM */ | |
| 148 | 149 | #define IPV6_TLV_JUMBO 194 |
| 149 | 150 | #define IPV6_TLV_HAO 201 /* home address option */ |
| 150 | 151 | |
| @@ -12,6 +12,15 @@ · 11 unmodified lines | ||
| 12 | 12 | #include <asm/byteorder.h> |
| 13 | 13 | #include <linux/types.h> |
| 14 | 14 | |
| 15 | +#define IOAM6_U16_UNAVAILABLE U16_MAX | |
| 16 | +#define IOAM6_U32_UNAVAILABLE U32_MAX | |
| 17 | +#define IOAM6_U64_UNAVAILABLE U64_MAX | |
| 18 | + | |
| 19 | +#define IOAM6_DEFAULT_ID (IOAM6_U32_UNAVAILABLE >> 8) | |
| 20 | +#define IOAM6_DEFAULT_ID_WIDE (IOAM6_U64_UNAVAILABLE >> 8) | |
| 21 | +#define IOAM6_DEFAULT_IF_ID IOAM6_U16_UNAVAILABLE | |
| 22 | +#define IOAM6_DEFAULT_IF_ID_WIDE IOAM6_U32_UNAVAILABLE | |
| 23 | + | |
| 15 | 24 | /* |
| 16 | 25 | * IPv6 IOAM Option Header |
| 17 | 26 | */ |
| @@ -190,6 +190,9 @@ enum { · 189 unmodified lines | ||
| 190 | 190 | DEVCONF_NDISC_TCLASS, |
| 191 | 191 | DEVCONF_RPL_SEG_ENABLED, |
| 192 | 192 | DEVCONF_RA_DEFRTR_METRIC, |
| 193 | + DEVCONF_IOAM6_ENABLED, | |
| 194 | + DEVCONF_IOAM6_ID, | |
| 195 | + DEVCONF_IOAM6_ID_WIDE, | |
| 193 | 196 | DEVCONF_MAX |
| 194 | 197 | }; |
| 195 | 198 | |
| @@ -10,7 +10,7 @@ ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \ · 9 unmodified lines | ||
| 10 | 10 | route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \ |
| 11 | 11 | raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \ |
| 12 | 12 | exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \ |
| 13 | − udp_offload.o seg6.o fib6_notifier.o rpl.o | |
| 13 | + udp_offload.o seg6.o fib6_notifier.o rpl.o ioam6.o | |
| 14 | 14 | |
| 15 | 15 | ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o |
| 16 | 16 | |
| @@ -89,12 +89,15 @@ · 88 unmodified lines | ||
| 89 | 89 | #include <linux/proc_fs.h> |
| 90 | 90 | #include <linux/seq_file.h> |
| 91 | 91 | #include <linux/export.h> |
| 92 | +#include <linux/ioam6.h> | |
| 92 | 93 | |
| 93 | 94 | #define INFINITY_LIFE_TIME 0xFFFFFFFF |
| 94 | 95 | |
| 95 | 96 | #define IPV6_MAX_STRLEN \ |
| 96 | 97 | sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") |
| 97 | 98 | |
| 99 | +static u32 ioam6_if_id_max = U16_MAX; | |
| 100 | + | |
| 98 | 101 | static inline u32 cstamp_delta(unsigned long cstamp) |
| 99 | 102 | { |
| 100 | 103 | return (cstamp - INITIAL_JIFFIES) * 100UL / HZ; |
| @@ -237,6 +240,9 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = { · 136 unmodified lines | ||
| 237 | 240 | .addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64, |
| 238 | 241 | .disable_policy = 0, |
| 239 | 242 | .rpl_seg_enabled = 0, |
| 243 | + .ioam6_enabled = 0, | |
| 244 | + .ioam6_id = IOAM6_DEFAULT_IF_ID, | |
| 245 | + .ioam6_id_wide = IOAM6_DEFAULT_IF_ID_WIDE, | |
| 240 | 246 | }; |
| 241 | 247 | |
| 242 | 248 | static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { |
| @@ -293,6 +299,9 @@ static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { · 50 unmodified lines | ||
| 293 | 299 | .addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64, |
| 294 | 300 | .disable_policy = 0, |
| 295 | 301 | .rpl_seg_enabled = 0, |
| 302 | + .ioam6_enabled = 0, | |
| 303 | + .ioam6_id = IOAM6_DEFAULT_IF_ID, | |
| 304 | + .ioam6_id_wide = IOAM6_DEFAULT_IF_ID_WIDE, | |
| 296 | 305 | }; |
| 297 | 306 | |
| 298 | 307 | /* Check if link is ready: is it up and is a valid qdisc available */ |
| @@ -5524,6 +5533,9 @@ static inline void ipv6_store_devconf(struct ipv6_devconf *cnf, · 5225 unmodified lines | ||
| 5524 | 5533 | array[DEVCONF_DISABLE_POLICY] = cnf->disable_policy; |
| 5525 | 5534 | array[DEVCONF_NDISC_TCLASS] = cnf->ndisc_tclass; |
| 5526 | 5535 | array[DEVCONF_RPL_SEG_ENABLED] = cnf->rpl_seg_enabled; |
| 5536 | + array[DEVCONF_IOAM6_ENABLED] = cnf->ioam6_enabled; | |
| 5537 | + array[DEVCONF_IOAM6_ID] = cnf->ioam6_id; | |
| 5538 | + array[DEVCONF_IOAM6_ID_WIDE] = cnf->ioam6_id_wide; | |
| 5527 | 5539 | } |
| 5528 | 5540 | |
| 5529 | 5541 | static inline size_t inet6_ifla6_size(void) |
| @@ -6930,6 +6942,31 @@ static const struct ctl_table addrconf_sysctl[] = { · 1400 unmodified lines | ||
| 6930 | 6942 | .mode = 0644, |
| 6931 | 6943 | .proc_handler = proc_dointvec, |
| 6932 | 6944 | }, |
| 6945 | + { | |
| 6946 | + .procname = "ioam6_enabled", | |
| 6947 | + .data = &ipv6_devconf.ioam6_enabled, | |
| 6948 | + .maxlen = sizeof(u8), | |
| 6949 | + .mode = 0644, | |
| 6950 | + .proc_handler = proc_dou8vec_minmax, | |
| 6951 | + .extra1 = (void *)SYSCTL_ZERO, | |
| 6952 | + .extra2 = (void *)SYSCTL_ONE, | |
| 6953 | + }, | |
| 6954 | + { | |
| 6955 | + .procname = "ioam6_id", | |
| 6956 | + .data = &ipv6_devconf.ioam6_id, | |
| 6957 | + .maxlen = sizeof(u32), | |
| 6958 | + .mode = 0644, | |
| 6959 | + .proc_handler = proc_douintvec_minmax, | |
| 6960 | + .extra1 = (void *)SYSCTL_ZERO, | |
| 6961 | + .extra2 = (void *)&ioam6_if_id_max, | |
| 6962 | + }, | |
| 6963 | + { | |
| 6964 | + .procname = "ioam6_id_wide", | |
| 6965 | + .data = &ipv6_devconf.ioam6_id_wide, | |
| 6966 | + .maxlen = sizeof(u32), | |
| 6967 | + .mode = 0644, | |
| 6968 | + .proc_handler = proc_douintvec, | |
| 6969 | + }, | |
| 6933 | 6970 | { |
| 6934 | 6971 | /* sentinel */ |
| 6935 | 6972 | } |
| @@ -62,6 +62,7 @@ · 61 unmodified lines | ||
| 62 | 62 | #include <net/rpl.h> |
| 63 | 63 | #include <net/compat.h> |
| 64 | 64 | #include <net/xfrm.h> |
| 65 | +#include <net/ioam6.h> | |
| 65 | 66 | |
| 66 | 67 | #include <linux/uaccess.h> |
| 67 | 68 | #include <linux/mroute6.h> |
| @@ -961,6 +962,9 @@ static int __net_init inet6_net_init(struct net *net) · 893 unmodified lines | ||
| 961 | 962 | net->ipv6.sysctl.fib_notify_on_flag_change = 0; |
| 962 | 963 | atomic_set(&net->ipv6.fib6_sernum, 1); |
| 963 | 964 | |
| 965 | + net->ipv6.sysctl.ioam6_id = IOAM6_DEFAULT_ID; | |
| 966 | + net->ipv6.sysctl.ioam6_id_wide = IOAM6_DEFAULT_ID_WIDE; | |
| 967 | + | |
| 964 | 968 | err = ipv6_init_mibs(net); |
| 965 | 969 | if (err) |
| 966 | 970 | return err; |
| @@ -1191,6 +1195,10 @@ static int __init inet6_init(void) · 224 unmodified lines | ||
| 1191 | 1195 | if (err) |
| 1192 | 1196 | goto rpl_fail; |
| 1193 | 1197 | |
| 1198 | + err = ioam6_init(); | |
| 1199 | + if (err) | |
| 1200 | + goto ioam6_fail; | |
| 1201 | + | |
| 1194 | 1202 | err = igmp6_late_init(); |
| 1195 | 1203 | if (err) |
| 1196 | 1204 | goto igmp6_late_err; |
| @@ -1213,6 +1221,8 @@ sysctl_fail: · 16 unmodified lines | ||
| 1213 | 1221 | igmp6_late_cleanup(); |
| 1214 | 1222 | #endif |
| 1215 | 1223 | igmp6_late_err: |
| 1224 | + ioam6_exit(); | |
| 1225 | +ioam6_fail: | |
| 1216 | 1226 | rpl_exit(); |
| 1217 | 1227 | rpl_fail: |
| 1218 | 1228 | seg6_exit(); |
| @@ -49,6 +49,9 @@ · 48 unmodified lines | ||
| 49 | 49 | #include <net/seg6_hmac.h> |
| 50 | 50 | #endif |
| 51 | 51 | #include <net/rpl.h> |
| 52 | +#include <linux/ioam6.h> | |
| 53 | +#include <net/ioam6.h> | |
| 54 | +#include <net/dst_metadata.h> | |
| 52 | 55 | |
| 53 | 56 | #include <linux/uaccess.h> |
| 54 | 57 | |
| @@ -928,6 +931,60 @@ static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) · 873 unmodified lines | ||
| 928 | 931 | return false; |
| 929 | 932 | } |
| 930 | 933 | |
| 934 | +/* IOAM */ | |
| 935 | + | |
| 936 | +static bool ipv6_hop_ioam(struct sk_buff *skb, int optoff) | |
| 937 | +{ | |
| 938 | + struct ioam6_trace_hdr *trace; | |
| 939 | + struct ioam6_namespace *ns; | |
| 940 | + struct ioam6_hdr *hdr; | |
| 941 | + | |
| 942 | + /* Bad alignment (must be 4n-aligned) */ | |
| 943 | + if (optoff & 3) | |
| 944 | + goto drop; | |
| 945 | + | |
| 946 | + /* Ignore if IOAM is not enabled on ingress */ | |
| 947 | + if (!__in6_dev_get(skb->dev)->cnf.ioam6_enabled) | |
| 948 | + goto ignore; | |
| 949 | + | |
| 950 | + /* Truncated Option header */ | |
| 951 | + hdr = (struct ioam6_hdr *)(skb_network_header(skb) + optoff); | |
| 952 | + if (hdr->opt_len < 2) | |
| 953 | + goto drop; | |
| 954 | + | |
| 955 | + switch (hdr->type) { | |
| 956 | + case IOAM6_TYPE_PREALLOC: | |
| 957 | + /* Truncated Pre-allocated Trace header */ | |
| 958 | + if (hdr->opt_len < 2 + sizeof(*trace)) | |
| 959 | + goto drop; | |
| 960 | + | |
| 961 | + /* Malformed Pre-allocated Trace header */ | |
| 962 | + trace = (struct ioam6_trace_hdr *)((u8 *)hdr + sizeof(*hdr)); | |
| 963 | + if (hdr->opt_len < 2 + sizeof(*trace) + trace->remlen * 4) | |
| 964 | + goto drop; | |
| 965 | + | |
| 966 | + /* Ignore if the IOAM namespace is unknown */ | |
| 967 | + ns = ioam6_namespace(ipv6_skb_net(skb), trace->namespace_id); | |
| 968 | + if (!ns) | |
| 969 | + goto ignore; | |
| 970 | + | |
| 971 | + if (!skb_valid_dst(skb)) | |
| 972 | + ip6_route_input(skb); | |
| 973 | + | |
| 974 | + ioam6_fill_trace_data(skb, ns, trace); | |
| 975 | + break; | |
| 976 | + default: | |
| 977 | + break; | |
| 978 | + } | |
| 979 | + | |
| 980 | +ignore: | |
| 981 | + return true; | |
| 982 | + | |
| 983 | +drop: | |
| 984 | + kfree_skb(skb); | |
| 985 | + return false; | |
| 986 | +} | |
| 987 | + | |
| 931 | 988 | /* Jumbo payload */ |
| 932 | 989 | |
| 933 | 990 | static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) |
| @@ -999,6 +1056,10 @@ static const struct tlvtype_proc tlvprochopopt_lst[] = { · 65 unmodified lines | ||
| 999 | 1056 | .type = IPV6_TLV_ROUTERALERT, |
| 1000 | 1057 | .func = ipv6_hop_ra, |
| 1001 | 1058 | }, |
| 1059 | + { | |
| 1060 | + .type = IPV6_TLV_IOAM, | |
| 1061 | + .func = ipv6_hop_ioam, | |
| 1062 | + }, | |
| 1002 | 1063 | { |
| 1003 | 1064 | .type = IPV6_TLV_JUMBO, |
| 1004 | 1065 | .func = ipv6_hop_jumbo, |
| @@ -0,0 +1,333 @@ | ||
| 1 | +// SPDX-License-Identifier: GPL-2.0+ | |
| 2 | +/* | |
| 3 | + * IPv6 IOAM implementation | |
| 4 | + * | |
| 5 | + * Author: | |
| 6 | + * Justin Iurman <justin.iurman@uliege.be> | |
| 7 | + */ | |
| 8 | + | |
| 9 | +#include <linux/errno.h> | |
| 10 | +#include <linux/types.h> | |
| 11 | +#include <linux/kernel.h> | |
| 12 | +#include <linux/net.h> | |
| 13 | +#include <linux/ioam6.h> | |
| 14 | +#include <linux/rhashtable.h> | |
| 15 | + | |
| 16 | +#include <net/addrconf.h> | |
| 17 | +#include <net/ioam6.h> | |
| 18 | + | |
| 19 | +static void ioam6_ns_release(struct ioam6_namespace *ns) | |
| 20 | +{ | |
| 21 | + kfree_rcu(ns, rcu); | |
| 22 | +} | |
| 23 | + | |
| 24 | +static void ioam6_sc_release(struct ioam6_schema *sc) | |
| 25 | +{ | |
| 26 | + kfree_rcu(sc, rcu); | |
| 27 | +} | |
| 28 | + | |
| 29 | +static void ioam6_free_ns(void *ptr, void *arg) | |
| 30 | +{ | |
| 31 | + struct ioam6_namespace *ns = (struct ioam6_namespace *)ptr; | |
| 32 | + | |
| 33 | + if (ns) | |
| 34 | + ioam6_ns_release(ns); | |
| 35 | +} | |
| 36 | + | |
| 37 | +static void ioam6_free_sc(void *ptr, void *arg) | |
| 38 | +{ | |
| 39 | + struct ioam6_schema *sc = (struct ioam6_schema *)ptr; | |
| 40 | + | |
| 41 | + if (sc) | |
| 42 | + ioam6_sc_release(sc); | |
| 43 | +} | |
| 44 | + | |
| 45 | +static int ioam6_ns_cmpfn(struct rhashtable_compare_arg *arg, const void *obj) | |
| 46 | +{ | |
| 47 | + const struct ioam6_namespace *ns = obj; | |
| 48 | + | |
| 49 | + return (ns->id != *(__be16 *)arg->key); | |
| 50 | +} | |
| 51 | + | |
| 52 | +static int ioam6_sc_cmpfn(struct rhashtable_compare_arg *arg, const void *obj) | |
| 53 | +{ | |
| 54 | + const struct ioam6_schema *sc = obj; | |
| 55 | + | |
| 56 | + return (sc->id != *(u32 *)arg->key); | |
| 57 | +} | |
| 58 | + | |
| 59 | +static const struct rhashtable_params rht_ns_params = { | |
| 60 | + .key_len = sizeof(__be16), | |
| 61 | + .key_offset = offsetof(struct ioam6_namespace, id), | |
| 62 | + .head_offset = offsetof(struct ioam6_namespace, head), | |
| 63 | + .automatic_shrinking = true, | |
| 64 | + .obj_cmpfn = ioam6_ns_cmpfn, | |
| 65 | +}; | |
| 66 | + | |
| 67 | +static const struct rhashtable_params rht_sc_params = { | |
| 68 | + .key_len = sizeof(u32), | |
| 69 | + .key_offset = offsetof(struct ioam6_schema, id), | |
| 70 | + .head_offset = offsetof(struct ioam6_schema, head), | |
| 71 | + .automatic_shrinking = true, | |
| 72 | + .obj_cmpfn = ioam6_sc_cmpfn, | |
| 73 | +}; | |
| 74 | + | |
| 75 | +struct ioam6_namespace *ioam6_namespace(struct net *net, __be16 id) | |
| 76 | +{ | |
| 77 | + struct ioam6_pernet_data *nsdata = ioam6_pernet(net); | |
| 78 | + | |
| 79 | + return rhashtable_lookup_fast(&nsdata->namespaces, &id, rht_ns_params); | |
| 80 | +} | |
| 81 | + | |
| 82 | +static void __ioam6_fill_trace_data(struct sk_buff *skb, | |
| 83 | + struct ioam6_namespace *ns, | |
| 84 | + struct ioam6_trace_hdr *trace, | |
| 85 | + struct ioam6_schema *sc, | |
| 86 | + u8 sclen) | |
| 87 | +{ | |
| 88 | + struct __kernel_sock_timeval ts; | |
| 89 | + u64 raw64; | |
| 90 | + u32 raw32; | |
| 91 | + u16 raw16; | |
| 92 | + u8 *data; | |
| 93 | + u8 byte; | |
| 94 | + | |
| 95 | + data = trace->data + trace->remlen * 4 - trace->nodelen * 4 - sclen * 4; | |
| 96 | + | |
| 97 | + /* hop_lim and node_id */ | |
| 98 | + if (trace->type.bit0) { | |
| 99 | + byte = ipv6_hdr(skb)->hop_limit; | |
| 100 | + if (skb->dev) | |
| 101 | + byte--; | |
| 102 | + | |
| 103 | + raw32 = dev_net(skb->dev)->ipv6.sysctl.ioam6_id; | |
| 104 | + | |
| 105 | + *(__be32 *)data = cpu_to_be32((byte << 24) | raw32); | |
| 106 | + data += sizeof(__be32); | |
| 107 | + } | |
| 108 | + | |
| 109 | + /* ingress_if_id and egress_if_id */ | |
| 110 | + if (trace->type.bit1) { | |
| 111 | + if (!skb->dev) | |
| 112 | + raw16 = IOAM6_U16_UNAVAILABLE; | |
| 113 | + else | |
| 114 | + raw16 = (__force u16)__in6_dev_get(skb->dev)->cnf.ioam6_id; | |
| 115 | + | |
| 116 | + *(__be16 *)data = cpu_to_be16(raw16); | |
| 117 | + data += sizeof(__be16); | |
| 118 | + | |
| 119 | + if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) | |
| 120 | + raw16 = IOAM6_U16_UNAVAILABLE; | |
| 121 | + else | |
| 122 | + raw16 = (__force u16)__in6_dev_get(skb_dst(skb)->dev)->cnf.ioam6_id; | |
| 123 | + | |
| 124 | + *(__be16 *)data = cpu_to_be16(raw16); | |
| 125 | + data += sizeof(__be16); | |
| 126 | + } | |
| 127 | + | |
| 128 | + /* timestamp seconds */ | |
| 129 | + if (trace->type.bit2) { | |
| 130 | + if (!skb->tstamp) | |
| 131 | + __net_timestamp(skb); | |
| 132 | + | |
| 133 | + skb_get_new_timestamp(skb, &ts); | |
| 134 | + | |
| 135 | + *(__be32 *)data = cpu_to_be32((u32)ts.tv_sec); | |
| 136 | + data += sizeof(__be32); | |
| 137 | + } | |
| 138 | + | |
| 139 | + /* timestamp subseconds */ | |
| 140 | + if (trace->type.bit3) { | |
| 141 | + if (!skb->tstamp) | |
| 142 | + __net_timestamp(skb); | |
| 143 | + | |
| 144 | + if (!trace->type.bit2) | |
| 145 | + skb_get_new_timestamp(skb, &ts); | |
| 146 | + | |
| 147 | + *(__be32 *)data = cpu_to_be32((u32)ts.tv_usec); | |
| 148 | + data += sizeof(__be32); | |
| 149 | + } | |
| 150 | + | |
| 151 | + /* transit delay */ | |
| 152 | + if (trace->type.bit4) { | |
| 153 | + *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); | |
| 154 | + data += sizeof(__be32); | |
| 155 | + } | |
| 156 | + | |
| 157 | + /* namespace data */ | |
| 158 | + if (trace->type.bit5) { | |
| 159 | + *(__be32 *)data = ns->data; | |
| 160 | + data += sizeof(__be32); | |
| 161 | + } | |
| 162 | + | |
| 163 | + /* queue depth */ | |
| 164 | + if (trace->type.bit6) { | |
| 165 | + *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); | |
| 166 | + data += sizeof(__be32); | |
| 167 | + } | |
| 168 | + | |
| 169 | + /* checksum complement */ | |
| 170 | + if (trace->type.bit7) { | |
| 171 | + *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); | |
| 172 | + data += sizeof(__be32); | |
| 173 | + } | |
| 174 | + | |
| 175 | + /* hop_lim and node_id (wide) */ | |
| 176 | + if (trace->type.bit8) { | |
| 177 | + byte = ipv6_hdr(skb)->hop_limit; | |
| 178 | + if (skb->dev) | |
| 179 | + byte--; | |
| 180 | + | |
| 181 | + raw64 = dev_net(skb->dev)->ipv6.sysctl.ioam6_id_wide; | |
| 182 | + | |
| 183 | + *(__be64 *)data = cpu_to_be64(((u64)byte << 56) | raw64); | |
| 184 | + data += sizeof(__be64); | |
| 185 | + } | |
| 186 | + | |
| 187 | + /* ingress_if_id and egress_if_id (wide) */ | |
| 188 | + if (trace->type.bit9) { | |
| 189 | + if (!skb->dev) | |
| 190 | + raw32 = IOAM6_U32_UNAVAILABLE; | |
| 191 | + else | |
| 192 | + raw32 = __in6_dev_get(skb->dev)->cnf.ioam6_id_wide; | |
| 193 | + | |
| 194 | + *(__be32 *)data = cpu_to_be32(raw32); | |
| 195 | + data += sizeof(__be32); | |
| 196 | + | |
| 197 | + if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) | |
| 198 | + raw32 = IOAM6_U32_UNAVAILABLE; | |
| 199 | + else | |
| 200 | + raw32 = __in6_dev_get(skb_dst(skb)->dev)->cnf.ioam6_id_wide; | |
| 201 | + | |
| 202 | + *(__be32 *)data = cpu_to_be32(raw32); | |
| 203 | + data += sizeof(__be32); | |
| 204 | + } | |
| 205 | + | |
| 206 | + /* namespace data (wide) */ | |
| 207 | + if (trace->type.bit10) { | |
| 208 | + *(__be64 *)data = ns->data_wide; | |
| 209 | + data += sizeof(__be64); | |
| 210 | + } | |
| 211 | + | |
| 212 | + /* buffer occupancy */ | |
| 213 | + if (trace->type.bit11) { | |
| 214 | + *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE); | |
| 215 | + data += sizeof(__be32); | |
| 216 | + } | |
| 217 | + | |
| 218 | + /* opaque state snapshot */ | |
| 219 | + if (trace->type.bit22) { | |
| 220 | + if (!sc) { | |
| 221 | + *(__be32 *)data = cpu_to_be32(IOAM6_U32_UNAVAILABLE >> 8); | |
| 222 | + } else { | |
| 223 | + *(__be32 *)data = sc->hdr; | |
| 224 | + data += sizeof(__be32); | |
| 225 | + | |
| 226 | + memcpy(data, sc->data, sc->len); | |
| 227 | + } | |
| 228 | + } | |
| 229 | +} | |
| 230 | + | |
| 231 | +/* called with rcu_read_lock() */ | |
| 232 | +void ioam6_fill_trace_data(struct sk_buff *skb, | |
| 233 | + struct ioam6_namespace *ns, | |
| 234 | + struct ioam6_trace_hdr *trace) | |
| 235 | +{ | |
| 236 | + struct ioam6_schema *sc; | |
| 237 | + u8 sclen = 0; | |
| 238 | + | |
| 239 | + /* Skip if Overflow flag is set OR | |
| 240 | + * if an unknown type (bit 12-21) is set | |
| 241 | + */ | |
| 242 | + if (trace->overflow || | |
| 243 | + trace->type.bit12 | trace->type.bit13 | trace->type.bit14 | | |
| 244 | + trace->type.bit15 | trace->type.bit16 | trace->type.bit17 | | |
| 245 | + trace->type.bit18 | trace->type.bit19 | trace->type.bit20 | | |
| 246 | + trace->type.bit21) { | |
| 247 | + return; | |
| 248 | + } | |
| 249 | + | |
| 250 | + /* NodeLen does not include Opaque State Snapshot length. We need to | |
| 251 | + * take it into account if the corresponding bit is set (bit 22) and | |
| 252 | + * if the current IOAM namespace has an active schema attached to it | |
| 253 | + */ | |
| 254 | + sc = rcu_dereference(ns->schema); | |
| 255 | + if (trace->type.bit22) { | |
| 256 | + sclen = sizeof_field(struct ioam6_schema, hdr) / 4; | |
| 257 | + | |
| 258 | + if (sc) | |
| 259 | + sclen += sc->len / 4; | |
| 260 | + } | |
| 261 | + | |
| 262 | + /* If there is no space remaining, we set the Overflow flag and we | |
| 263 | + * skip without filling the trace | |
| 264 | + */ | |
| 265 | + if (!trace->remlen || trace->remlen < trace->nodelen + sclen) { | |
| 266 | + trace->overflow = 1; | |
| 267 | + return; | |
| 268 | + } | |
| 269 | + | |
| 270 | + __ioam6_fill_trace_data(skb, ns, trace, sc, sclen); | |
| 271 | + trace->remlen -= trace->nodelen + sclen; | |
| 272 | +} | |
| 273 | + | |
| 274 | +static int __net_init ioam6_net_init(struct net *net) | |
| 275 | +{ | |
| 276 | + struct ioam6_pernet_data *nsdata; | |
| 277 | + int err = -ENOMEM; | |
| 278 | + | |
| 279 | + nsdata = kzalloc(sizeof(*nsdata), GFP_KERNEL); | |
| 280 | + if (!nsdata) | |
| 281 | + goto out; | |
| 282 | + | |
| 283 | + mutex_init(&nsdata->lock); | |
| 284 | + net->ipv6.ioam6_data = nsdata; | |
| 285 | + | |
| 286 | + err = rhashtable_init(&nsdata->namespaces, &rht_ns_params); | |
| 287 | + if (err) | |
| 288 | + goto free_nsdata; | |
| 289 | + | |
| 290 | + err = rhashtable_init(&nsdata->schemas, &rht_sc_params); | |
| 291 | + if (err) | |
| 292 | + goto free_rht_ns; | |
| 293 | + | |
| 294 | +out: | |
| 295 | + return err; | |
| 296 | +free_rht_ns: | |
| 297 | + rhashtable_destroy(&nsdata->namespaces); | |
| 298 | +free_nsdata: | |
| 299 | + kfree(nsdata); | |
| 300 | + net->ipv6.ioam6_data = NULL; | |
| 301 | + goto out; | |
| 302 | +} | |
| 303 | + | |
| 304 | +static void __net_exit ioam6_net_exit(struct net *net) | |
| 305 | +{ | |
| 306 | + struct ioam6_pernet_data *nsdata = ioam6_pernet(net); | |
| 307 | + | |
| 308 | + rhashtable_free_and_destroy(&nsdata->namespaces, ioam6_free_ns, NULL); | |
| 309 | + rhashtable_free_and_destroy(&nsdata->schemas, ioam6_free_sc, NULL); | |
| 310 | + | |
| 311 | + kfree(nsdata); | |
| 312 | +} | |
| 313 | + | |
| 314 | +static struct pernet_operations ioam6_net_ops = { | |
| 315 | + .init = ioam6_net_init, | |
| 316 | + .exit = ioam6_net_exit, | |
| 317 | +}; | |
| 318 | + | |
| 319 | +int __init ioam6_init(void) | |
| 320 | +{ | |
| 321 | + int err = register_pernet_subsys(&ioam6_net_ops); | |
| 322 | + | |
| 323 | + if (err) | |
| 324 | + return err; | |
| 325 | + | |
| 326 | + pr_info("In-situ OAM (IOAM) with IPv6\n"); | |
| 327 | + return 0; | |
| 328 | +} | |
| 329 | + | |
| 330 | +void ioam6_exit(void) | |
| 331 | +{ | |
| 332 | + unregister_pernet_subsys(&ioam6_net_ops); | |
| 333 | +} | |
| @@ -21,6 +21,7 @@ · 20 unmodified lines | ||
| 21 | 21 | #ifdef CONFIG_NETLABEL |
| 22 | 22 | #include <net/calipso.h> |
| 23 | 23 | #endif |
| 24 | +#include <linux/ioam6.h> | |
| 24 | 25 | |
| 25 | 26 | static int two = 2; |
| 26 | 27 | static int three = 3; |
| @@ -28,6 +29,8 @@ static int flowlabel_reflect_max = 0x7; · 1 unmodified line | ||
| 28 | 29 | static int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX; |
| 29 | 30 | static u32 rt6_multipath_hash_fields_all_mask = |
| 30 | 31 | FIB_MULTIPATH_HASH_FIELD_ALL_MASK; |
| 32 | +static u32 ioam6_id_max = IOAM6_DEFAULT_ID; | |
| 33 | +static u64 ioam6_id_wide_max = IOAM6_DEFAULT_ID_WIDE; | |
| 31 | 34 | |
| 32 | 35 | static int proc_rt6_multipath_hash_policy(struct ctl_table *table, int write, |
| 33 | 36 | void *buffer, size_t *lenp, loff_t *ppos) |
| @@ -196,6 +199,22 @@ static struct ctl_table ipv6_table_template[] = { · 162 unmodified lines | ||
| 196 | 199 | .extra1 = SYSCTL_ZERO, |
| 197 | 200 | .extra2 = &two, |
| 198 | 201 | }, |
| 202 | + { | |
| 203 | + .procname = "ioam6_id", | |
| 204 | + .data = &init_net.ipv6.sysctl.ioam6_id, | |
| 205 | + .maxlen = sizeof(u32), | |
| 206 | + .mode = 0644, | |
| 207 | + .proc_handler = proc_douintvec_minmax, | |
| 208 | + .extra2 = &ioam6_id_max, | |
| 209 | + }, | |
| 210 | + { | |
| 211 | + .procname = "ioam6_id_wide", | |
| 212 | + .data = &init_net.ipv6.sysctl.ioam6_id_wide, | |
| 213 | + .maxlen = sizeof(u64), | |
| 214 | + .mode = 0644, | |
| 215 | + .proc_handler = proc_doulongvec_minmax, | |
| 216 | + .extra2 = &ioam6_id_wide_max, | |
| 217 | + }, | |
| 199 | 218 | { } |
| 200 | 219 | }; |
| 201 | 220 | |