1// SPDX-License-Identifier: GPL-2.0-only 2#include <linux/netlink.h> 3#include <linux/nospec.h> 4#include <linux/rtnetlink.h> 5#include <linux/types.h> 6#include <net/ip.h> 7#include <net/net_namespace.h> 8#include <net/tcp.h> 9 10static int ip_metrics_convert(struct net *net, struct nlattr *fc_mx, 11 int fc_mx_len, u32 *metrics, 12 struct netlink_ext_ack *extack) 13{ 14 bool ecn_ca = false; 15 struct nlattr *nla; 16 int remaining; 17 18 if (!fc_mx) 19 return 0; 20 21 nla_for_each_attr(nla, fc_mx, fc_mx_len, remaining) { 22 int type = nla_type(nla); 23 u32 val; 24 25 if (!type) 26 continue; 27 if (type > RTAX_MAX) { 28 NL_SET_ERR_MSG(extack, "Invalid metric type"); 29 return -EINVAL; 30 } 31 32 type = array_index_nospec(type, RTAX_MAX + 1); 33 if (type == RTAX_CC_ALGO) { 34 char tmp[TCP_CA_NAME_MAX]; 35 36 nla_strlcpy(tmp, nla, sizeof(tmp)); 37 val = tcp_ca_get_key_by_name(net, tmp, &ecn_ca); 38 if (val == TCP_CA_UNSPEC) { 39 NL_SET_ERR_MSG(extack, "Unknown tcp congestion algorithm"); 40 return -EINVAL; 41 } 42 } else { 43 if (nla_len(nla) != sizeof(u32)) { 44 NL_SET_ERR_MSG_ATTR(extack, nla, 45 "Invalid attribute in metrics"); 46 return -EINVAL; 47 } 48 val = nla_get_u32(nla); 49 } 50 if (type == RTAX_ADVMSS && val > 65535 - 40) 51 val = 65535 - 40; 52 if (type == RTAX_MTU && val > 65535 - 15) 53 val = 65535 - 15; 54 if (type == RTAX_HOPLIMIT && val > 255) 55 val = 255; 56 if (type == RTAX_FEATURES && (val & ~RTAX_FEATURE_MASK)) { 57 NL_SET_ERR_MSG(extack, "Unknown flag set in feature mask in metrics attribute"); 58 return -EINVAL; 59 } 60 metrics[type - 1] = val; 61 } 62 63 if (ecn_ca) 64 metrics[RTAX_FEATURES - 1] |= DST_FEATURE_ECN_CA; 65 66 return 0; 67} 68 69struct dst_metrics *ip_fib_metrics_init(struct net *net, struct nlattr *fc_mx, 70 int fc_mx_len, 71 struct netlink_ext_ack *extack) 72{ 73 struct dst_metrics *fib_metrics; 74 int err; 75 76 if (!fc_mx) 77 return (struct dst_metrics *)&dst_default_metrics; 78 79 fib_metrics = kzalloc(sizeof(*fib_metrics), GFP_KERNEL); 80 if (unlikely(!fib_metrics)) 81 return ERR_PTR(-ENOMEM); 82 83 err = ip_metrics_convert(net, fc_mx, fc_mx_len, fib_metrics->metrics, 84 extack); 85 if (!err) { 86 refcount_set(&fib_metrics->refcnt, 1); 87 } else { 88 kfree(fib_metrics); 89 fib_metrics = ERR_PTR(err); 90 } 91 92 return fib_metrics; 93} 94EXPORT_SYMBOL_GPL(ip_fib_metrics_init); 95