summaryrefslogtreecommitdiffstats
path: root/src/network/networkd-route-metric.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/networkd-route-metric.c')
-rw-r--r--src/network/networkd-route-metric.c483
1 files changed, 483 insertions, 0 deletions
diff --git a/src/network/networkd-route-metric.c b/src/network/networkd-route-metric.c
new file mode 100644
index 0000000..31a2bde
--- /dev/null
+++ b/src/network/networkd-route-metric.c
@@ -0,0 +1,483 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "netlink-util.h"
+#include "networkd-route.h"
+#include "networkd-route-metric.h"
+#include "parse-util.h"
+#include "string-util.h"
+
+void route_metric_done(RouteMetric *metric) {
+ assert(metric);
+
+ free(metric->metrics);
+ free(metric->metrics_set);
+ free(metric->tcp_congestion_control_algo);
+}
+
+int route_metric_copy(const RouteMetric *src, RouteMetric *dest) {
+ assert(src);
+ assert(dest);
+
+ dest->n_metrics = src->n_metrics;
+ if (src->n_metrics > 0) {
+ assert(src->n_metrics != 1);
+
+ dest->metrics = newdup(uint32_t, src->metrics, src->n_metrics);
+ if (!dest->metrics)
+ return -ENOMEM;
+ } else
+ dest->metrics = NULL;
+
+ dest->n_metrics_set = src->n_metrics_set;
+ if (src->n_metrics_set > 0) {
+ assert(src->n_metrics_set != 1);
+
+ dest->metrics_set = newdup(bool, src->metrics_set, src->n_metrics_set);
+ if (!dest->metrics_set)
+ return -ENOMEM;
+ } else
+ dest->metrics_set = NULL;
+
+ return strdup_to(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo);
+}
+
+void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) {
+ assert(metric);
+
+ siphash24_compress_typesafe(metric->n_metrics, state);
+ siphash24_compress_safe(metric->metrics, sizeof(uint32_t) * metric->n_metrics, state);
+ siphash24_compress_string(metric->tcp_congestion_control_algo, state);
+}
+
+int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b) {
+ int r;
+
+ assert(a);
+ assert(b);
+
+ r = memcmp_nn(a->metrics, a->n_metrics * sizeof(uint32_t), b->metrics, b->n_metrics * sizeof(uint32_t));
+ if (r != 0)
+ return r;
+
+ return strcmp_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo);
+}
+
+bool route_metric_can_update(const RouteMetric *a, const RouteMetric *b, bool expiration_by_kernel) {
+ assert(a);
+ assert(b);
+
+ /* If the kernel has expiration timer for the route, then only MTU can be updated. */
+
+ if (!expiration_by_kernel)
+ return route_metric_compare_func(a, b) == 0;
+
+ if (a->n_metrics != b->n_metrics)
+ return false;
+
+ for (size_t i = 1; i < a->n_metrics; i++) {
+ if (i != RTAX_MTU)
+ continue;
+ if (a->metrics[i] != b->metrics[i])
+ return false;
+ }
+
+ return streq_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo);
+}
+
+int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force) {
+ assert(metric);
+
+ if (force) {
+ if (!GREEDY_REALLOC0(metric->metrics_set, attr + 1))
+ return -ENOMEM;
+
+ metric->metrics_set[attr] = true;
+ metric->n_metrics_set = MAX(metric->n_metrics_set, (size_t) (attr + 1));
+ } else {
+ /* Do not override the values specified in conf parsers. */
+ if (metric->n_metrics_set > attr && metric->metrics_set[attr])
+ return 0;
+ }
+
+ if (value != 0) {
+ if (!GREEDY_REALLOC0(metric->metrics, attr + 1))
+ return -ENOMEM;
+
+ metric->metrics[attr] = value;
+ metric->n_metrics = MAX(metric->n_metrics, (size_t) (attr + 1));
+ return 0;
+ }
+
+ if (metric->n_metrics <= attr)
+ return 0;
+
+ metric->metrics[attr] = 0;
+
+ for (size_t i = metric->n_metrics; i > 0; i--)
+ if (metric->metrics[i-1] != 0) {
+ metric->n_metrics = i;
+ return 0;
+ }
+
+ metric->n_metrics = 0;
+ return 0;
+}
+
+static void route_metric_unset(RouteMetric *metric, uint16_t attr) {
+ assert(metric);
+
+ if (metric->n_metrics_set > attr)
+ metric->metrics_set[attr] = false;
+
+ assert_se(route_metric_set_full(metric, attr, 0, /* force = */ false) >= 0);
+}
+
+uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr) {
+ assert(metric);
+
+ if (metric->n_metrics <= attr)
+ return 0;
+
+ return metric->metrics[attr];
+}
+
+int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m) {
+ int r;
+
+ assert(metric);
+ assert(m);
+
+ if (metric->n_metrics <= 0 && isempty(metric->tcp_congestion_control_algo))
+ return 0;
+
+ r = sd_netlink_message_open_container(m, RTA_METRICS);
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < metric->n_metrics; i++) {
+ if (i == RTAX_CC_ALGO)
+ continue;
+
+ if (metric->metrics[i] == 0)
+ continue;
+
+ r = sd_netlink_message_append_u32(m, i, metric->metrics[i]);
+ if (r < 0)
+ return r;
+ }
+
+ if (!isempty(metric->tcp_congestion_control_algo)) {
+ r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, metric->tcp_congestion_control_algo);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *m) {
+ _cleanup_free_ void *data = NULL;
+ size_t len;
+ int r;
+
+ assert(metric);
+ assert(m);
+
+ r = sd_netlink_message_read_data(m, RTA_METRICS, &len, &data);
+ if (r == -ENODATA)
+ return 0;
+ if (r < 0)
+ return log_warning_errno(r, "rtnl: Could not read RTA_METRICS attribute, ignoring: %m");
+
+ for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+ size_t rta_type = RTA_TYPE(rta);
+
+ if (rta_type == RTAX_CC_ALGO) {
+ char *p = memdup_suffix0(RTA_DATA(rta), RTA_PAYLOAD(rta));
+ if (!p)
+ return log_oom();
+
+ free_and_replace(metric->tcp_congestion_control_algo, p);
+
+ } else {
+ if (RTA_PAYLOAD(rta) != sizeof(uint32_t))
+ continue;
+
+ r = route_metric_set(metric, rta_type, *(uint32_t*) RTA_DATA(rta));
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ return 0;
+}
+
+static int config_parse_route_metric_advmss_impl(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ uint64_t u;
+ int r;
+
+ assert(rvalue);
+
+ r = parse_size(rvalue, 1024, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (u == 0 || u > UINT32_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = (uint32_t) u;
+ return 1;
+}
+
+static int config_parse_route_metric_hop_limit_impl(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint32_t k, *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (k == 0 || k > 255) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = k;
+ return 1;
+}
+
+int config_parse_tcp_window(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint32_t k, *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+ if (k == 0 || k >= 1024) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = k;
+ return 1;
+}
+
+static int config_parse_route_metric_tcp_rto_impl(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ usec_t usec;
+ int r;
+
+ assert(rvalue);
+
+ r = parse_sec(rvalue, &usec);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (!timestamp_is_set(usec) ||
+ DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = (uint32_t) DIV_ROUND_UP(usec, USEC_PER_MSEC);
+ return 1;
+}
+
+static int config_parse_route_metric_boolean_impl(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ uint32_t *val = ASSERT_PTR(data);
+ int r;
+
+ assert(rvalue);
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ *val = r;
+ return 1;
+}
+
+#define DEFINE_CONFIG_PARSE_ROUTE_METRIC(name, parser) \
+ int config_parse_route_metric_##name( \
+ const char *unit, \
+ const char *filename, \
+ unsigned line, \
+ const char *section, \
+ unsigned section_line, \
+ const char *lvalue, \
+ int ltype, \
+ const char *rvalue, \
+ void *data, \
+ void *userdata) { \
+ \
+ Network *network = ASSERT_PTR(userdata); \
+ _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; \
+ uint16_t attr_type = ltype; \
+ int r; \
+ \
+ assert(filename); \
+ assert(section); \
+ assert(lvalue); \
+ assert(rvalue); \
+ \
+ r = route_new_static(network, filename, section_line, &route); \
+ if (r == -ENOMEM) \
+ return log_oom(); \
+ if (r < 0) { \
+ log_syntax(unit, LOG_WARNING, filename, line, r, \
+ "Failed to allocate route, ignoring assignment: %m"); \
+ return 0; \
+ } \
+ \
+ if (isempty(rvalue)) { \
+ route_metric_unset(&route->metric, attr_type); \
+ TAKE_PTR(route); \
+ return 0; \
+ } \
+ \
+ uint32_t k; \
+ r = parser(unit, filename, line, section, section_line, \
+ lvalue, /* ltype = */ 0, rvalue, \
+ &k, userdata); \
+ if (r <= 0) \
+ return r; \
+ \
+ if (route_metric_set_full( \
+ &route->metric, \
+ attr_type, \
+ k, \
+ /* force = */ true) < 0) \
+ return log_oom(); \
+ \
+ TAKE_PTR(route); \
+ return 0; \
+ }
+
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(mtu, config_parse_mtu);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(advmss, config_parse_route_metric_advmss_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(hop_limit, config_parse_route_metric_hop_limit_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_window, config_parse_tcp_window);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_rto, config_parse_route_metric_tcp_rto_impl);
+DEFINE_CONFIG_PARSE_ROUTE_METRIC(boolean, config_parse_route_metric_boolean_impl);
+
+int config_parse_route_metric_tcp_congestion(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = ASSERT_PTR(userdata);
+ _cleanup_(route_unref_or_set_invalidp) Route *route = NULL;
+ int r;
+
+ assert(filename);
+ assert(rvalue);
+
+ r = route_new_static(network, filename, section_line, &route);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to allocate route, ignoring assignment: %m");
+ return 0;
+ }
+
+ r = config_parse_string(unit, filename, line, section, section_line, lvalue, 0,
+ rvalue, &route->metric.tcp_congestion_control_algo, userdata);
+ if (r <= 0)
+ return r;
+
+ TAKE_PTR(route);
+ return 0;
+}