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 | __s32 disable_policy; | 76 | __s32 disable_policy; |
| 77 | __s32 ndisc_tclass; | 77 | __s32 ndisc_tclass; |
| 78 | __s32 rpl_seg_enabled; | 78 | __s32 rpl_seg_enabled; |
| 79 | __u32 ioam6_id; | ||
| 80 | __u32 ioam6_id_wide; | ||
| 81 | __u8 ioam6_enabled; | ||
| 79 | 82 | ||
| 80 | struct ctl_table_header *sysctl_header; | 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 | int max_dst_opts_len; | 51 | int max_dst_opts_len; |
| 52 | int max_hbh_opts_len; | 52 | int max_hbh_opts_len; |
| 53 | int seg6_flowlabel; | 53 | int seg6_flowlabel; |
| 54 | u32 ioam6_id; | ||
| 55 | u64 ioam6_id_wide; | ||
| 54 | bool skip_notify_on_dev_down; | 56 | bool skip_notify_on_dev_down; |
| 55 | u8 fib_notify_on_flag_change; | 57 | u8 fib_notify_on_flag_change; |
| 56 | }; | 58 | }; |
| @@ -110,6 +112,7 @@ struct netns_ipv6 { · 53 unmodified lines | |||
| 110 | spinlock_t lock; | 112 | spinlock_t lock; |
| 111 | u32 seq; | 113 | u32 seq; |
| 112 | } ip6addrlbl_table; | 114 | } ip6addrlbl_table; |
| 115 | struct ioam6_pernet_data *ioam6_data; | ||
| 113 | }; | 116 | }; |
| 114 | 117 | ||
| 115 | #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) | 118 | #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6) |
| @@ -145,6 +145,7 @@ struct in6_flowlabel_req { · 144 unmodified lines | |||
| 145 | #define IPV6_TLV_PADN 1 | 145 | #define IPV6_TLV_PADN 1 |
| 146 | #define IPV6_TLV_ROUTERALERT 5 | 146 | #define IPV6_TLV_ROUTERALERT 5 |
| 147 | #define IPV6_TLV_CALIPSO 7 /* RFC 5570 */ | 147 | #define IPV6_TLV_CALIPSO 7 /* RFC 5570 */ |
| 148 | #define IPV6_TLV_IOAM 49 /* TEMPORARY IANA allocation for IOAM */ | ||
| 148 | #define IPV6_TLV_JUMBO 194 | 149 | #define IPV6_TLV_JUMBO 194 |
| 149 | #define IPV6_TLV_HAO 201 /* home address option */ | 150 | #define IPV6_TLV_HAO 201 /* home address option */ |
| 150 | 151 | ||
| @@ -12,6 +12,15 @@ · 11 unmodified lines | |||
| 12 | #include <asm/byteorder.h> | 12 | #include <asm/byteorder.h> |
| 13 | #include <linux/types.h> | 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 | * IPv6 IOAM Option Header | 25 | * IPv6 IOAM Option Header |
| 17 | */ | 26 | */ |
| @@ -190,6 +190,9 @@ enum { · 189 unmodified lines | |||
| 190 | DEVCONF_NDISC_TCLASS, | 190 | DEVCONF_NDISC_TCLASS, |
| 191 | DEVCONF_RPL_SEG_ENABLED, | 191 | DEVCONF_RPL_SEG_ENABLED, |
| 192 | DEVCONF_RA_DEFRTR_METRIC, | 192 | DEVCONF_RA_DEFRTR_METRIC, |
| 193 | DEVCONF_IOAM6_ENABLED, | ||
| 194 | DEVCONF_IOAM6_ID, | ||
| 195 | DEVCONF_IOAM6_ID_WIDE, | ||
| 193 | DEVCONF_MAX | 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 | route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \ | 10 | route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \ |
| 11 | raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \ | 11 | raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \ |
| 12 | exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \ | 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 | ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o | 15 | ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o |
| 16 | 16 | ||
| @@ -89,12 +89,15 @@ · 88 unmodified lines | |||
| 89 | #include <linux/proc_fs.h> | 89 | #include <linux/proc_fs.h> |
| 90 | #include <linux/seq_file.h> | 90 | #include <linux/seq_file.h> |
| 91 | #include <linux/export.h> | 91 | #include <linux/export.h> |
| 92 | #include <linux/ioam6.h> | ||
| 92 | 93 | ||
| 93 | #define INFINITY_LIFE_TIME 0xFFFFFFFF | 94 | #define INFINITY_LIFE_TIME 0xFFFFFFFF |
| 94 | 95 | ||
| 95 | #define IPV6_MAX_STRLEN \ | 96 | #define IPV6_MAX_STRLEN \ |
| 96 | sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") | 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 | static inline u32 cstamp_delta(unsigned long cstamp) | 101 | static inline u32 cstamp_delta(unsigned long cstamp) |
| 99 | { | 102 | { |
| 100 | return (cstamp - INITIAL_JIFFIES) * 100UL / HZ; | 103 | return (cstamp - INITIAL_JIFFIES) * 100UL / HZ; |
| @@ -237,6 +240,9 @@ static struct ipv6_devconf ipv6_devconf __read_mostly = { · 136 unmodified lines | |||
| 237 | .addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64, | 240 | .addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64, |
| 238 | .disable_policy = 0, | 241 | .disable_policy = 0, |
| 239 | .rpl_seg_enabled = 0, | 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 | static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = { | 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 | .addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64, | 299 | .addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64, |
| 294 | .disable_policy = 0, | 300 | .disable_policy = 0, |
| 295 | .rpl_seg_enabled = 0, | 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 | /* Check if link is ready: is it up and is a valid qdisc available */ | 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 | array[DEVCONF_DISABLE_POLICY] = cnf->disable_policy; | 5533 | array[DEVCONF_DISABLE_POLICY] = cnf->disable_policy; |
| 5525 | array[DEVCONF_NDISC_TCLASS] = cnf->ndisc_tclass; | 5534 | array[DEVCONF_NDISC_TCLASS] = cnf->ndisc_tclass; |
| 5526 | array[DEVCONF_RPL_SEG_ENABLED] = cnf->rpl_seg_enabled; | 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 | static inline size_t inet6_ifla6_size(void) | 5541 | static inline size_t inet6_ifla6_size(void) |
| @@ -6930,6 +6942,31 @@ static const struct ctl_table addrconf_sysctl[] = { · 1400 unmodified lines | |||
| 6930 | .mode = 0644, | 6942 | .mode = 0644, |
| 6931 | .proc_handler = proc_dointvec, | 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 | /* sentinel */ | 6971 | /* sentinel */ |
| 6935 | } | 6972 | } |
| @@ -62,6 +62,7 @@ · 61 unmodified lines | |||
| 62 | #include <net/rpl.h> | 62 | #include <net/rpl.h> |
| 63 | #include <net/compat.h> | 63 | #include <net/compat.h> |
| 64 | #include <net/xfrm.h> | 64 | #include <net/xfrm.h> |
| 65 | #include <net/ioam6.h> | ||
| 65 | 66 | ||
| 66 | #include <linux/uaccess.h> | 67 | #include <linux/uaccess.h> |
| 67 | #include <linux/mroute6.h> | 68 | #include <linux/mroute6.h> |
| @@ -961,6 +962,9 @@ static int __net_init inet6_net_init(struct net *net) · 893 unmodified lines | |||
| 961 | net->ipv6.sysctl.fib_notify_on_flag_change = 0; | 962 | net->ipv6.sysctl.fib_notify_on_flag_change = 0; |
| 962 | atomic_set(&net->ipv6.fib6_sernum, 1); | 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 | err = ipv6_init_mibs(net); | 968 | err = ipv6_init_mibs(net); |
| 965 | if (err) | 969 | if (err) |
| 966 | return err; | 970 | return err; |
| @@ -1191,6 +1195,10 @@ static int __init inet6_init(void) · 224 unmodified lines | |||
| 1191 | if (err) | 1195 | if (err) |
| 1192 | goto rpl_fail; | 1196 | goto rpl_fail; |
| 1193 | 1197 | ||
| 1198 | err = ioam6_init(); | ||
| 1199 | if (err) | ||
| 1200 | goto ioam6_fail; | ||
| 1201 | |||
| 1194 | err = igmp6_late_init(); | 1202 | err = igmp6_late_init(); |
| 1195 | if (err) | 1203 | if (err) |
| 1196 | goto igmp6_late_err; | 1204 | goto igmp6_late_err; |
| @@ -1213,6 +1221,8 @@ sysctl_fail: · 16 unmodified lines | |||
| 1213 | igmp6_late_cleanup(); | 1221 | igmp6_late_cleanup(); |
| 1214 | #endif | 1222 | #endif |
| 1215 | igmp6_late_err: | 1223 | igmp6_late_err: |
| 1224 | ioam6_exit(); | ||
| 1225 | ioam6_fail: | ||
| 1216 | rpl_exit(); | 1226 | rpl_exit(); |
| 1217 | rpl_fail: | 1227 | rpl_fail: |
| 1218 | seg6_exit(); | 1228 | seg6_exit(); |
| @@ -49,6 +49,9 @@ · 48 unmodified lines | |||
| 49 | #include <net/seg6_hmac.h> | 49 | #include <net/seg6_hmac.h> |
| 50 | #endif | 50 | #endif |
| 51 | #include <net/rpl.h> | 51 | #include <net/rpl.h> |
| 52 | #include <linux/ioam6.h> | ||
| 53 | #include <net/ioam6.h> | ||
| 54 | #include <net/dst_metadata.h> | ||
| 52 | 55 | ||
| 53 | #include <linux/uaccess.h> | 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 | return false; | 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 | /* Jumbo payload */ | 988 | /* Jumbo payload */ |
| 932 | 989 | ||
| 933 | static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) | 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 | .type = IPV6_TLV_ROUTERALERT, | 1056 | .type = IPV6_TLV_ROUTERALERT, |
| 1000 | .func = ipv6_hop_ra, | 1057 | .func = ipv6_hop_ra, |
| 1001 | }, | 1058 | }, |
| 1059 | { | ||
| 1060 | .type = IPV6_TLV_IOAM, | ||
| 1061 | .func = ipv6_hop_ioam, | ||
| 1062 | }, | ||
| 1002 | { | 1063 | { |
| 1003 | .type = IPV6_TLV_JUMBO, | 1064 | .type = IPV6_TLV_JUMBO, |
| 1004 | .func = ipv6_hop_jumbo, | 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 | #ifdef CONFIG_NETLABEL | 21 | #ifdef CONFIG_NETLABEL |
| 22 | #include <net/calipso.h> | 22 | #include <net/calipso.h> |
| 23 | #endif | 23 | #endif |
| 24 | #include <linux/ioam6.h> | ||
| 24 | 25 | ||
| 25 | static int two = 2; | 26 | static int two = 2; |
| 26 | static int three = 3; | 27 | static int three = 3; |
| @@ -28,6 +29,8 @@ static int flowlabel_reflect_max = 0x7; · 1 unmodified line | |||
| 28 | static int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX; | 29 | static int auto_flowlabels_max = IP6_AUTO_FLOW_LABEL_MAX; |
| 29 | static u32 rt6_multipath_hash_fields_all_mask = | 30 | static u32 rt6_multipath_hash_fields_all_mask = |
| 30 | FIB_MULTIPATH_HASH_FIELD_ALL_MASK; | 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 | static int proc_rt6_multipath_hash_policy(struct ctl_table *table, int write, | 35 | static int proc_rt6_multipath_hash_policy(struct ctl_table *table, int write, |
| 33 | void *buffer, size_t *lenp, loff_t *ppos) | 36 | void *buffer, size_t *lenp, loff_t *ppos) |
| @@ -196,6 +199,22 @@ static struct ctl_table ipv6_table_template[] = { · 162 unmodified lines | |||
| 196 | .extra1 = SYSCTL_ZERO, | 199 | .extra1 = SYSCTL_ZERO, |
| 197 | .extra2 = &two, | 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 | ||