From: Susant Sahani Date: Thu, 14 Sep 2017 19:51:39 +0000 (+0000) Subject: networkd: add support to configure IP Rule (#5725) X-Git-Tag: v235~102 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bce67bbee359eec19e6778619b6651100a1c1477;hp=f1e24a259ca182b6cd8a723a56da43435ce48aac;p=thirdparty%2Fsystemd.git networkd: add support to configure IP Rule (#5725) Routing Policy rule manipulates rules in the routing policy database control the route selection algorithm. This work supports to configure Rule ``` [RoutingPolicyRule] TypeOfService=0x08 Table=7 From= 192.168.100.18 ``` ``` ip rule show 0: from all lookup local 0: from 192.168.100.18 tos 0x08 lookup 7 ``` V2 changes: 1. Added logic to handle duplicate rules. 2. If rules are changed or deleted and networkd restarted then those are deleted when networkd restarts next time V3: 1. Add parse_fwmark_fwmask --- diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 96d38113721..ea3bf71eea4 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -878,6 +878,55 @@ + + [RoutingPolicyRule] Section Options + + An [RoutingPolicyRule] section accepts the + following keys. Specify several [RoutingPolicyRule] + sections to configure several rules. + + + + TypeOfService= + + Specifies the type of service to match a number between 0 to 255. + + + + From= + + Specifies the source address prefix to match. Possibly followed by a slash and the prefix length. + + + + To= + + Specifies the destination address prefix to match. Possibly followed by a slash and the prefix length. + + + + FirewallMark= + + Specifies the iptables firewall mark value to match (a number between 1 and 4294967295). + + + + Table= + + Specifies the routing table identifier to lookup if the rule + selector matches. The table identifier for a route (a number between 1 and 4294967295). + + + + Priority= + + Specifies the priority of this rule. Priority= is an unsigned + integer. Higher number means lower priority, and rules get processed in order of increasing number. + + + + + [Route] Section Options The [Route] section accepts the diff --git a/meson.build b/meson.build index 5fb90ddea33..af170355866 100644 --- a/meson.build +++ b/meson.build @@ -374,6 +374,7 @@ conf.set('SIZEOF_RLIM_T', cc.sizeof('rlim_t', prefix : '#include #include +#include ''' # FIXME: key_serial_t is only defined in keyutils.h, this is bound to fail @@ -381,6 +382,7 @@ foreach decl : ['char16_t', 'char32_t', 'key_serial_t', 'struct ethtool_link_settings', + 'struct fib_rule_uid_range', ] # We get -1 if the size cannot be determined @@ -409,6 +411,7 @@ foreach decl : [['IFLA_INET6_ADDR_GEN_MODE', 'linux/if_link.h'], ['IFLA_BR_VLAN_DEFAULT_PVID', 'linux/if_link.h'], ['NDA_IFINDEX', 'linux/neighbour.h'], ['IFA_FLAGS', 'linux/if_addr.h'], + ['FRA_UID_RANGE', 'linux/fib_rules.h'], ['LO_FLAGS_PARTSCAN', 'linux/loop.h'], ] prefix = decl.length() > 2 ? decl[2] : '' diff --git a/src/basic/missing.h b/src/basic/missing.h index 0269066427c..653c5b7766e 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -920,6 +920,33 @@ struct input_mask { #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) #endif +#if !HAVE_DECL_FRA_UID_RANGE +#define FRA_UNSPEC 0 +#define FRA_DST 1 +#define FRA_SRC 2 +#define FRA_IIFNAME 3 +#define FRA_GOTO 4 +#define FRA_UNUSED2 5 +#define FRA_PRIORITY 6 +#define FRA_UNUSED3 7 +#define FRA_UNUSED4 8 +#define FRA_UNUSED5 9 +#define FRA_FWMARK 10 +#define FRA_FLOW 11 +#define FRA_TUN_ID 12 +#define FRA_SUPPRESS_IFGROUP 13 +#define FRA_SUPPRESS_PREFIXLEN 14 +#define FRA_TABLE 15 +#define FRA_FWMASK 16 +#define FRA_OIFNAME 17 +#define FRA_PAD 18 +#define FRA_L3MDEV 19 +#define FRA_UID_RANGE 20 +#define __FRA_MAX 12 + +#define FRA_MAX (__FRA_MAX - 1) +#endif + #if !HAVE_DECL_IFLA_BRPORT_PROXYARP #define IFLA_BRPORT_PROXYARP 10 #endif @@ -1216,6 +1243,15 @@ struct ethtool_link_settings { #endif +#ifndef HAVE_STRUCT_FIB_RULE_UID_RANGE + +struct fib_rule_uid_range { + __u32 start; + __u32 end; +}; + +#endif + #endif #ifndef SOL_ALG diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index e8c8abac2ae..ac0427f9493 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -101,12 +101,8 @@ int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type) { int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { assert_return(m, -EINVAL); assert_return(m->hdr, -EINVAL); - assert_return(m->hdr->nlmsg_type == RTM_GETLINK || - m->hdr->nlmsg_type == RTM_GETADDR || - m->hdr->nlmsg_type == RTM_GETROUTE || - m->hdr->nlmsg_type == RTM_GETNEIGH || - m->hdr->nlmsg_type == RTM_GETADDRLABEL , - -EINVAL); + + assert_return(IN_SET(m->hdr->nlmsg_type, RTM_GETLINK, RTM_GETADDR, RTM_GETROUTE, RTM_GETNEIGH, RTM_GETRULE, RTM_GETADDRLABEL), -EINVAL); SET_FLAG(m->hdr->nlmsg_flags, NLM_F_DUMP, dump); diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 923f7dd10c9..979dc6824f3 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -29,8 +30,11 @@ #include #include #include +#include +#include #include #include +#include #include "macro.h" #include "missing.h" @@ -597,6 +601,31 @@ static const NLTypeSystem rtnl_addrlabel_type_system = { .types = rtnl_addrlabel_types, }; +static const NLType rtnl_routing_policy_rule_types[] = { + [FRA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, + [FRA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, + [FRA_IIFNAME] = { .type = NETLINK_TYPE_STRING }, + [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, + [FRA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, + [FRA_FWMARK] = { .type = NETLINK_TYPE_U32 }, + [FRA_FLOW] = { .type = NETLINK_TYPE_U32 }, + [FRA_TUN_ID] = { .type = NETLINK_TYPE_U32 }, + [FRA_SUPPRESS_IFGROUP] = { .type = NETLINK_TYPE_U32 }, + [FRA_SUPPRESS_PREFIXLEN] = { .type = NETLINK_TYPE_U32 }, + [FRA_TABLE] = { .type = NETLINK_TYPE_U32 }, + [FRA_FWMASK] = { .type = NETLINK_TYPE_U32 }, + [FRA_OIFNAME] = { .type = NETLINK_TYPE_STRING }, + [FRA_PAD] = { .type = NETLINK_TYPE_U32 }, + [FRA_L3MDEV] = { .type = NETLINK_TYPE_U64 }, + [FRA_UID_RANGE] = { .size = sizeof(struct fib_rule_uid_range) }, +}; + +static const NLTypeSystem rtnl_routing_policy_rule_type_system = { + .count = ELEMENTSOF(rtnl_routing_policy_rule_types), + .types = rtnl_routing_policy_rule_types, +}; + static const NLType rtnl_types[] = { [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, @@ -616,6 +645,9 @@ static const NLType rtnl_types[] = { [RTM_NEWADDRLABEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_addrlabel_type_system, .size = sizeof(struct ifaddrlblmsg) }, [RTM_DELADDRLABEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_addrlabel_type_system, .size = sizeof(struct ifaddrlblmsg) }, [RTM_GETADDRLABEL] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_addrlabel_type_system, .size = sizeof(struct ifaddrlblmsg) }, + [RTM_NEWRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, + [RTM_DELRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, + [RTM_GETRULE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_routing_policy_rule_type_system, .size = sizeof(struct rtmsg) }, }; const NLTypeSystem type_system_root = { diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h index 215af124063..d2fb6512043 100644 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -47,6 +47,10 @@ static inline bool rtnl_message_type_is_addrlabel(uint16_t type) { return IN_SET(type, RTM_NEWADDRLABEL, RTM_DELADDRLABEL, RTM_GETADDRLABEL); } +static inline bool rtnl_message_type_is_routing_policy_rule(uint16_t type) { + return IN_SET(type, RTM_NEWRULE, RTM_DELRULE, RTM_GETRULE); +} + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, unsigned mtu); diff --git a/src/libsystemd/sd-netlink/rtnl-message.c b/src/libsystemd/sd-netlink/rtnl-message.c index 12c51ffe2e6..586d94f624a 100644 --- a/src/libsystemd/sd-netlink/rtnl-message.c +++ b/src/libsystemd/sd-netlink/rtnl-message.c @@ -696,6 +696,14 @@ int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { *family = ifa->ifa_family; + return 0; + } else if (rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type)) { + struct rtmsg *rtm; + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + return 0; } @@ -754,3 +762,166 @@ int sd_rtnl_message_addrlabel_get_prefixlen(sd_netlink_message *m, unsigned char return 0; } + +int sd_rtnl_message_new_routing_policy_rule(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int ifal_family) { + struct rtmsg *rtm; + int r; + + assert_return(rtnl_message_type_is_routing_policy_rule(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWRULE) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + rtm = NLMSG_DATA((*ret)->hdr); + rtm->rtm_family = ifal_family; + rtm->rtm_protocol = RTPROT_BOOT; + rtm->rtm_scope = RT_SCOPE_UNIVERSE; + rtm->rtm_type = RTN_UNICAST; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_tos(sd_netlink_message *m, unsigned char tos) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_tos = tos; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_tos(sd_netlink_message *m, unsigned char *tos) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *tos = routing_policy_rule->rtm_tos; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_table(sd_netlink_message *m, unsigned char table) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_table = table; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_table(sd_netlink_message *m, unsigned char *table) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *table = routing_policy_rule->rtm_table; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_rtm_type(sd_netlink_message *m, unsigned char type) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_type = type; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_rtm_type(sd_netlink_message *m, unsigned char *type) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *type = routing_policy_rule->rtm_type; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(sd_netlink_message *m, unsigned char len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_dst_len = len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(sd_netlink_message *m, unsigned char *len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *len = routing_policy_rule->rtm_dst_len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(sd_netlink_message *m, unsigned char len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + routing_policy_rule->rtm_src_len = len; + + return 0; +} + +int sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(sd_netlink_message *m, unsigned char *len) { + struct rtmsg *routing_policy_rule; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_routing_policy_rule(m->hdr->nlmsg_type), -EINVAL); + + routing_policy_rule = NLMSG_DATA(m->hdr); + + *len = routing_policy_rule->rtm_src_len; + + return 0; +} diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index d67244676c2..77f4d5b6357 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -894,6 +894,16 @@ int sd_netlink_add_match(sd_netlink *rtnl, if (r < 0) return r; break; + case RTM_NEWRULE: + case RTM_DELRULE: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_RULE); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_RULE); + if (r < 0) + return r; + break; default: return -EOPNOTSUPP; } diff --git a/src/network/meson.build b/src/network/meson.build index 35ecd86379b..83a837d6c9c 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -61,6 +61,8 @@ sources = files(''' networkd-network.h networkd-route.c networkd-route.h + networkd-routing-policy-rule.c + networkd-routing-policy-rule.h networkd-util.c networkd-util.h '''.split()) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 2fb1dd67abe..6b591271a02 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -33,6 +33,7 @@ #include "networkd-manager.h" #include "networkd-ndisc.h" #include "networkd-radv.h" +#include "networkd-routing-policy-rule.h" #include "set.h" #include "socket-util.h" #include "stdio-util.h" @@ -497,8 +498,8 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { static void link_free(Link *link) { Address *address; - Iterator i; Link *carrier; + Iterator i; if (!link) return; @@ -1014,6 +1015,7 @@ static int link_set_bridge_fdb(Link *link) { } static int link_enter_set_addresses(Link *link) { + RoutingPolicyRule *rule, *rrule = NULL; AddressLabel *label; Address *ad; int r; @@ -1050,6 +1052,26 @@ static int link_enter_set_addresses(Link *link) { link->link_messages++; } + LIST_FOREACH(rules, rule, link->network->rules) { + r = routing_policy_rule_get(link->manager, rule->family, &rule->from, rule->from_prefixlen, &rule->to, + rule->to_prefixlen, rule->tos, rule->fwmark, rule->table, &rrule); + if (r == 1) { + (void) routing_policy_rule_make_local(link->manager, rrule); + continue; + } + + r = routing_policy_rule_configure(rule, link, link_routing_policy_rule_handler, false); + if (r < 0) { + log_link_warning_errno(link, r, "Could not set routing policy rules: %m"); + link_enter_failed(link); + return r; + } + + link->link_messages++; + } + + routing_policy_rule_purge(link->manager, link); + /* now that we can figure out a default address for the dhcp server, start it */ if (link_dhcp4_server_enabled(link)) { diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 45f9b3d359b..82b9663019a 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -19,6 +19,7 @@ #include #include +#include #include "sd-daemon.h" #include "sd-netlink.h" @@ -718,6 +719,113 @@ static int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *messa return 1; } +int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) { + uint8_t tos = 0, to_prefixlen = 0, from_prefixlen = 0; + RoutingPolicyRule *rule = NULL; + union in_addr_union to, from; + uint32_t fwmark = 0, table = 0; + Manager *m = userdata; + uint16_t type; + int family; + int r; + + assert(rtnl); + assert(message); + assert(m); + + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_warning_errno(r, "rtnl: failed to receive rule: %m"); + + return 0; + } + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWRULE, RTM_DELRULE)) { + log_warning("rtnl: received unexpected message type '%u' when processing rule.", type); + return 0; + } + + r = sd_rtnl_message_get_family(message, &family); + if (r < 0 || !IN_SET(family, AF_INET, AF_INET6)) { + log_warning_errno(r, "rtnl: received address with invalid family type %u, ignoring.", type); + return 0; + } + + switch (family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(message, FRA_SRC, &from.in); + if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(message, &from_prefixlen); + if (r < 0) + log_warning_errno(r, "rtnl: failed to retrive rule from prefix length: %m"); + } + + r = sd_netlink_message_read_in_addr(message, FRA_DST, &to.in); + if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(message, &to_prefixlen); + if (r < 0) + log_warning_errno(r, "rtnl: failed to retrive rule to prefix length: %m"); + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(message, FRA_SRC, &from.in6); + if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(message, &from_prefixlen); + if (r < 0) + log_warning_errno(r, "rtnl: failed to retrive rule from prefix length: %m"); + } + + r = sd_netlink_message_read_in6_addr(message, FRA_DST, &to.in6); + if (r >= 0) { + r = sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(message, &to_prefixlen); + if (r < 0) + log_warning_errno(r, "rtnl: failed to retrive rule to prefix length: %m"); + } + + break; + + default: + log_debug("rtnl: ignoring unsupported rule family: %d", family); + } + + if (from_prefixlen == 0 && to_prefixlen == 0) + return 0; + + (void) sd_netlink_message_read_u32(message, FRA_FWMARK, &fwmark); + (void) sd_netlink_message_read_u32(message, FRA_TABLE, &table); + (void) sd_rtnl_message_routing_policy_rule_get_tos(message, &tos); + + (void) routing_policy_rule_get(m, family, &from, from_prefixlen, &to, to_prefixlen, tos, fwmark, table, &rule); + + switch (type) { + case RTM_NEWRULE: + if(!rule) { + r = routing_policy_rule_add_foreign(m, family, &from, from_prefixlen, &to, to_prefixlen, tos, fwmark, table, &rule); + if (r < 0) { + log_warning_errno(r, "Could not add rule: %m"); + return 0; + } + } + break; + case RTM_DELRULE: + routing_policy_rule_free(rule); + + break; + + default: + assert_not_reached("Received invalid RTNL message type"); + } + + return 1; +} + static int systemd_netlink_fd(void) { int n, fd, rtnl_fd = -EINVAL; @@ -782,6 +890,14 @@ static int manager_connect_rtnl(Manager *m) { if (r < 0) return r; + r = sd_netlink_add_match(m->rtnl, RTM_NEWRULE, &manager_rtnl_process_rule, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELRULE, &manager_rtnl_process_rule, m); + if (r < 0) + return r; + return 0; } @@ -875,6 +991,8 @@ static void print_string_set(FILE *f, const char *field, OrderedSet *s) { static int manager_save(Manager *m) { _cleanup_ordered_set_free_free_ OrderedSet *dns = NULL, *ntp = NULL, *search_domains = NULL, *route_domains = NULL; + RoutingPolicyRule *rule = NULL; + bool space = false; Link *link; Iterator i; _cleanup_free_ char *temp_path = NULL; @@ -999,6 +1117,28 @@ static int manager_save(Manager *m) { print_string_set(f, "DOMAINS=", search_domains); print_string_set(f, "ROUTE_DOMAINS=", route_domains); + SET_FOREACH(rule, m->rules, i) { + _cleanup_free_ char *from_str = NULL, *to_str = NULL; + fputs("RULE=", f); + + if (!in_addr_is_null(rule->family, &rule->from)) { + r = in_addr_to_string(rule->family, &rule->from, &from_str); + if (r < 0) + goto fail; + } + + if (!in_addr_is_null(rule->family, &rule->to)) { + r = in_addr_to_string(rule->family, &rule->to, &to_str); + if (r < 0) + goto fail; + } + + fprintf(f, "from=%s%s/%hhu to=%s%s/%hhu tos=%hhu fwmark=%"PRIu32"/%"PRIu32" table=%hhu", space ? " " : "", from_str, + rule->from_prefixlen, space ? " " : "", to_str, rule->to_prefixlen, rule->tos, rule->fwmark, rule->fwmask, rule->table); + + fputc('\n', f); + } + r = fflush_and_check(f); if (r < 0) goto fail; @@ -1084,6 +1224,8 @@ int manager_new(Manager **ret, sd_event *event) { m->duid.type = DUID_TYPE_EN; + (void) routing_policy_rule_load(m); + *ret = m; m = NULL; @@ -1091,6 +1233,7 @@ int manager_new(Manager **ret, sd_event *event) { } void manager_free(Manager *m) { + RoutingPolicyRule *rule; Network *network; NetDev *netdev; Link *link; @@ -1101,13 +1244,13 @@ void manager_free(Manager *m) { free(m->state_file); + while ((network = m->networks)) + network_free(network); + while ((link = hashmap_first(m->links))) link_unref(link); hashmap_free(m->links); - while ((network = m->networks)) - network_free(network); - hashmap_free(m->networks_by_name); while ((netdev = hashmap_first(m->netdevs))) @@ -1117,6 +1260,14 @@ void manager_free(Manager *m) { while ((pool = m->address_pools)) address_pool_free(pool); + set_free(m->rules); + set_free(m->rules_foreign); + + while ((rule = set_steal_first(m->rules_saved))) + free(rule); + + set_free(m->rules_saved); + sd_netlink_unref(m->rtnl); sd_event_unref(m->event); @@ -1277,6 +1428,41 @@ int manager_rtnl_enumerate_routes(Manager *m) { return r; } +int manager_rtnl_enumerate_rules(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *rule; + int r; + + assert(m); + assert(m->rtnl); + + r = sd_rtnl_message_new_routing_policy_rule(m->rtnl, &req, RTM_GETRULE, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (rule = reply; rule; rule = sd_netlink_message_next(rule)) { + int k; + + m->enumerating = true; + + k = manager_rtnl_process_rule(m->rtnl, rule, m); + if (k < 0) + r = k; + + m->enumerating = false; + } + + return r; +} + int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) { AddressPool *p; int r; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index e2447c2230e..254aab84527 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -65,6 +65,10 @@ struct Manager { DUID duid; char* dynamic_hostname; char* dynamic_timezone; + + Set *rules; + Set *rules_foreign; + Set *rules_saved; }; static inline const DUID* link_duid(const Link *link) { @@ -88,9 +92,11 @@ bool manager_should_reload(Manager *m); int manager_rtnl_enumerate_links(Manager *m); int manager_rtnl_enumerate_addresses(Manager *m); int manager_rtnl_enumerate_routes(Manager *m); +int manager_rtnl_enumerate_rules(Manager *m); int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata); int manager_rtnl_process_route(sd_netlink *nl, sd_netlink_message *message, void *userdata); +int manager_rtnl_process_rule(sd_netlink *nl, sd_netlink_message *message, void *userdata); int manager_send_changed(Manager *m, const char *property, ...) _sentinel_; void manager_dirty(Manager *m); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 112efd2dfb8..f01e78a3564 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -84,6 +84,12 @@ Address.AutoJoin, config_parse_address_flags, Address.Scope, config_parse_address_scope, 0, 0 IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0 IPv6AddressLabel.Label, config_parse_address_label, 0, 0 +RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0 +RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0 +RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0 +RoutingPolicyRule.FirewallMark, config_parse_routing_policy_rule_fwmark_mask, 0, 0 +RoutingPolicyRule.From, config_parse_routing_policy_rule_prefix, 0, 0 +RoutingPolicyRule.To, config_parse_routing_policy_rule_prefix, 0, 0 Route.Gateway, config_parse_gateway, 0, 0 Route.Destination, config_parse_destination, 0, 0 Route.Source, config_parse_destination, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3b52a8a5b96..1694b1e5eb5 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -77,7 +77,7 @@ int network_config_section_new(const char *filename, unsigned line, NetworkConfi } void network_config_section_free(NetworkConfigSection *cs) { - free(cs); + free(cs); } /* Set defaults following RFC7844 */ @@ -157,6 +157,7 @@ static int network_load_one(Manager *manager, const char *filename) { LIST_HEAD_INIT(network->ipv6_proxy_ndp_addresses); LIST_HEAD_INIT(network->address_labels); LIST_HEAD_INIT(network->static_prefixes); + LIST_HEAD_INIT(network->rules); network->stacked_netdevs = hashmap_new(&string_hash_ops); if (!network->stacked_netdevs) @@ -182,6 +183,10 @@ static int network_load_one(Manager *manager, const char *filename) { if (!network->prefixes_by_section) return log_oom(); + network->rules_by_section = hashmap_new(&network_config_hash_ops); + if (!network->rules_by_section) + return log_oom(); + network->filename = strdup(filename); if (!network->filename) return log_oom(); @@ -258,6 +263,7 @@ static int network_load_one(Manager *manager, const char *filename) { "Network\0" "Address\0" "IPv6AddressLabel\0" + "RoutingPolicyRule\0" "Route\0" "DHCP\0" "DHCPv4\0" /* compat */ @@ -336,13 +342,14 @@ int network_load(Manager *manager) { } void network_free(Network *network) { - NetDev *netdev; - Route *route; - Address *address; - FdbEntry *fdb_entry; IPv6ProxyNDPAddress *ipv6_proxy_ndp_address; + RoutingPolicyRule *rule; + FdbEntry *fdb_entry; AddressLabel *label; Prefix *prefix; + Address *address; + NetDev *netdev; + Route *route; Iterator i; if (!network) @@ -396,11 +403,15 @@ void network_free(Network *network) { while ((prefix = network->static_prefixes)) prefix_free(prefix); + while ((rule = network->rules)) + routing_policy_rule_free(rule); + hashmap_free(network->addresses_by_section); hashmap_free(network->routes_by_section); hashmap_free(network->fdb_entries_by_section); hashmap_free(network->address_labels_by_section); hashmap_free(network->prefixes_by_section); + hashmap_free(network->rules_by_section); if (network->manager) { if (network->manager->networks) @@ -746,7 +757,7 @@ int config_parse_tunnel(const char *unit, netdev->kind != NETDEV_KIND_VTI && netdev->kind != NETDEV_KIND_VTI6 && netdev->kind != NETDEV_KIND_IP6TNL - ) { + ) { log_syntax(unit, LOG_ERR, filename, line, 0, "NetDev is not a tunnel, ignoring assignment: %s", rvalue); return 0; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 035b57d5f26..7af3171ef9a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -34,6 +34,7 @@ #include "networkd-lldp-tx.h" #include "networkd-ipv6-proxy-ndp.h" #include "networkd-route.h" +#include "networkd-routing-policy-rule.h" #include "networkd-util.h" #include "netdev/netdev.h" @@ -218,6 +219,7 @@ struct Network { LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses); LIST_HEAD(AddressLabel, address_labels); LIST_HEAD(Prefix, static_prefixes); + LIST_HEAD(RoutingPolicyRule, rules); unsigned n_static_addresses; unsigned n_static_routes; @@ -225,12 +227,14 @@ struct Network { unsigned n_ipv6_proxy_ndp_addresses; unsigned n_address_labels; unsigned n_static_prefixes; + unsigned n_rules; Hashmap *addresses_by_section; Hashmap *routes_by_section; Hashmap *fdb_entries_by_section; Hashmap *address_labels_by_section; Hashmap *prefixes_by_section; + Hashmap *rules_by_section; struct in_addr_data *dns; unsigned n_dns; diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c new file mode 100644 index 00000000000..6850135fc11 --- /dev/null +++ b/src/network/networkd-routing-policy-rule.c @@ -0,0 +1,900 @@ +/*** + This file is part of systemd. + + Copyright 2017 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "fileio.h" +#include "networkd-routing-policy-rule.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-util.h" + +int routing_policy_rule_new(RoutingPolicyRule **ret) { + RoutingPolicyRule *rule; + + rule = new0(RoutingPolicyRule, 1); + if (!rule) + return -ENOMEM; + + rule->family = AF_INET; + rule->table = RT_TABLE_MAIN; + + *ret = rule; + return 0; +} + +void routing_policy_rule_free(RoutingPolicyRule *rule) { + + if (!rule) + return; + + if (rule->network) { + LIST_REMOVE(rules, rule->network->rules, rule); + assert(rule->network->n_rules > 0); + rule->network->n_rules--; + + if (rule->section) { + hashmap_remove(rule->network->rules_by_section, rule->section); + network_config_section_free(rule->section); + } + + if (rule->network->manager) { + set_remove(rule->network->manager->rules, rule); + set_remove(rule->network->manager->rules_foreign, rule); + } + } + + free(rule); +} + +static void routing_policy_rule_hash_func(const void *b, struct siphash *state) { + const RoutingPolicyRule *rule = b; + + assert(rule); + + siphash24_compress(&rule->family, sizeof(rule->family), state); + + switch (rule->family) { + case AF_INET: + case AF_INET6: + + siphash24_compress(&rule->from, FAMILY_ADDRESS_SIZE(rule->family), state); + siphash24_compress(&rule->from_prefixlen, sizeof(rule->from_prefixlen), state); + + siphash24_compress(&rule->to, FAMILY_ADDRESS_SIZE(rule->family), state); + siphash24_compress(&rule->to_prefixlen, sizeof(rule->to_prefixlen), state); + + siphash24_compress(&rule->tos, sizeof(rule->tos), state); + siphash24_compress(&rule->fwmark, sizeof(rule->fwmark), state); + siphash24_compress(&rule->table, sizeof(rule->table), state); + + break; + default: + /* treat any other address family as AF_UNSPEC */ + break; + } +} + +static int routing_policy_rule_compare_func(const void *_a, const void *_b) { + const RoutingPolicyRule *a = _a, *b = _b; + int r; + + if (a->family < b->family) + return -1; + if (a->family > b->family) + return 1; + + switch (a->family) { + case AF_INET: + case AF_INET6: + if (a->from_prefixlen < b->from_prefixlen) + return -1; + if (a->from_prefixlen > b->from_prefixlen) + return 1; + + if (a->to_prefixlen < b->to_prefixlen) + return -1; + if (a->to_prefixlen > b->to_prefixlen) + return 1; + + if (a->tos < b->tos) + return -1; + if (a->tos > b->tos) + return 1; + + if (a->fwmask < b->fwmark) + return -1; + if (a->fwmask > b->fwmark) + return 1; + + if (a->table < b->table) + return -1; + if (a->table > b->table) + return 1; + + r = memcmp(&a->from, &b->from, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + return memcmp(&a->to, &b->to, FAMILY_ADDRESS_SIZE(a->family)); + + default: + /* treat any other address family as AF_UNSPEC */ + return 0; + } +} + +const struct hash_ops routing_policy_rule_hash_ops = { + .hash = routing_policy_rule_hash_func, + .compare = routing_policy_rule_compare_func +}; + +int routing_policy_rule_get(Manager *m, + int family, + const union in_addr_union *from, + uint8_t from_prefixlen, + const union in_addr_union *to, + uint8_t to_prefixlen, + uint8_t tos, + uint32_t fwmark, + uint32_t table, + RoutingPolicyRule **ret) { + + RoutingPolicyRule rule, *existing; + + assert_return(m, -1); + + rule = (RoutingPolicyRule) { + .family = family, + .from = *from, + .from_prefixlen = from_prefixlen, + .to = *to, + .to_prefixlen = to_prefixlen, + .tos = tos, + .fwmark = fwmark, + .table = table, + }; + + if (m->rules) { + existing = set_get(m->rules, &rule); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + } + + if (m->rules_foreign) { + existing = set_get(m->rules_foreign, &rule); + if (existing) { + if (ret) + *ret = existing; + return 1; + } + } + + return -ENOENT; +} + +int routing_policy_rule_make_local(Manager *m, RoutingPolicyRule *rule) { + int r; + + assert(m); + + if (set_contains(m->rules_foreign, rule)) { + set_remove(m->rules_foreign, rule); + + r = set_ensure_allocated(&m->rules, &routing_policy_rule_hash_ops); + if (r < 0) + return r; + + return set_put(m->rules, rule); + } + + return -ENOENT; +} + +static int routing_policy_rule_add_internal(Set **rules, + int family, + const union in_addr_union *from, + uint8_t from_prefixlen, + const union in_addr_union *to, + uint8_t to_prefixlen, + uint8_t tos, + uint32_t fwmark, + uint32_t table, + RoutingPolicyRule **ret) { + + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *rule = NULL; + int r; + + assert_return(rules, -EINVAL); + + r = routing_policy_rule_new(&rule); + if (r < 0) + return r; + + rule->family = family; + rule->from = *from; + rule->from_prefixlen = from_prefixlen; + rule->to = *to; + rule->to_prefixlen = to_prefixlen; + rule->tos = tos; + rule->fwmark = fwmark; + rule->table = table; + + r = set_ensure_allocated(rules, &routing_policy_rule_hash_ops); + if (r < 0) + return r; + + r = set_put(*rules, rule); + if (r < 0) + return r; + + if (ret) + *ret = rule; + + rule = NULL; + + return 0; +} + +int routing_policy_rule_add(Manager *m, + int family, + const union in_addr_union *from, + uint8_t from_prefixlen, + const union in_addr_union *to, + uint8_t to_prefixlen, + uint8_t tos, + uint32_t fwmark, + uint32_t table, + RoutingPolicyRule **ret) { + + return routing_policy_rule_add_internal(&m->rules, family, from, from_prefixlen, to, to_prefixlen, tos, fwmark, table, ret); +} + +int routing_policy_rule_add_foreign(Manager *m, + int family, + const union in_addr_union *from, + uint8_t from_prefixlen, + const union in_addr_union *to, + uint8_t to_prefixlen, + uint8_t tos, + uint32_t fwmark, + uint32_t table, + RoutingPolicyRule **ret) { + return routing_policy_rule_add_internal(&m->rules_foreign, family, from, from_prefixlen, to, to_prefixlen, tos, fwmark, table, ret); +} + +static int routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + _cleanup_link_unref_ Link *link = userdata; + int r; + + assert(m); + assert(link); + assert(link->ifname); + + link->link_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + log_link_warning_errno(link, r, "Could not drop routing policy rule: %m"); + + return 1; +} + +int routing_policy_rule_remove(RoutingPolicyRule *routing_policy_rule, Link *link, sd_netlink_message_handler_t callback) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(routing_policy_rule); + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(link->ifindex > 0); + assert(IN_SET(routing_policy_rule->family, AF_INET, AF_INET6)); + + r = sd_rtnl_message_new_routing_policy_rule(link->manager->rtnl, &m, RTM_DELRULE, routing_policy_rule->family); + if (r < 0) + return log_error_errno(r, "Could not allocate RTM_DELRULE message: %m"); + + if (!in_addr_is_null(routing_policy_rule->family, &routing_policy_rule->from)) { + if (routing_policy_rule->family == AF_INET) + r = sd_netlink_message_append_in_addr(m, FRA_SRC, &routing_policy_rule->from.in); + else + r = sd_netlink_message_append_in6_addr(m, FRA_SRC, &routing_policy_rule->from.in6); + + if (r < 0) + return log_error_errno(r, "Could not append FRA_SRC attribute: %m"); + + r = sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(m, routing_policy_rule->from_prefixlen); + if (r < 0) + return log_error_errno(r, "Could not set source prefix length: %m"); + } + + if (!in_addr_is_null(routing_policy_rule->family, &routing_policy_rule->to)) { + if (routing_policy_rule->family == AF_INET) + r = sd_netlink_message_append_in_addr(m, FRA_DST, &routing_policy_rule->to.in); + else + r = sd_netlink_message_append_in6_addr(m, FRA_DST, &routing_policy_rule->to.in6); + + if (r < 0) + return log_error_errno(r, "Could not append FRA_DST attribute: %m"); + + r = sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(m, routing_policy_rule->to_prefixlen); + if (r < 0) + return log_error_errno(r, "Could not set destination prefix length: %m"); + } + + r = sd_netlink_call_async(link->manager->rtnl, m, callback, link, 0, NULL); + if (r < 0) + return log_error_errno(r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int routing_policy_rule_new_static(Network *network, const char *filename, unsigned section_line, RoutingPolicyRule **ret) { + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *rule = NULL; + _cleanup_network_config_section_free_ NetworkConfigSection *n = NULL; + int r; + + assert(network); + assert(ret); + assert(!!filename == (section_line > 0)); + + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + rule = hashmap_get(network->rules_by_section, n); + if (rule) { + *ret = rule; + rule = NULL; + + return 0; + } + + r = routing_policy_rule_new(&rule); + if (r < 0) + return r; + + rule->section = n; + rule->network = network; + n = NULL; + + r = hashmap_put(network->rules_by_section, rule->section, rule); + if (r < 0) + return r; + + LIST_APPEND(rules, network->rules, rule); + network->n_rules++; + + *ret = rule; + rule = NULL; + + return 0; +} + +int link_routing_policy_rule_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + _cleanup_link_unref_ Link *link = userdata; + int r; + + assert(rtnl); + assert(m); + assert(link); + assert(link->ifname); + assert(link->link_messages > 0); + + link->link_messages--; + + if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + return 1; + + r = sd_netlink_message_get_errno(m); + if (r < 0 && r != -EEXIST) + log_link_warning_errno(link, r, "Could not add routing policy rule: %m"); + + if (link->link_messages == 0) + log_link_debug(link, "Routing policy rule configured"); + + return 1; +} + +int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, sd_netlink_message_handler_t callback, bool update) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rule); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); + + r = sd_rtnl_message_new_routing_policy_rule(link->manager->rtnl, &m, RTM_NEWRULE, rule->family); + if (r < 0) + return log_error_errno(r, "Could not allocate RTM_NEWRULE message: %m"); + + if (!in_addr_is_null(rule->family, &rule->from)) { + if (rule->family == AF_INET) + r = sd_netlink_message_append_in_addr(m, FRA_SRC, &rule->from.in); + else + r = sd_netlink_message_append_in6_addr(m, FRA_SRC, &rule->from.in6); + + if (r < 0) + return log_error_errno(r, "Could not append FRA_SRC attribute: %m"); + + r = sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(m, rule->from_prefixlen); + if (r < 0) + return log_error_errno(r, "Could not set source prefix length: %m"); + } + + if (!in_addr_is_null(rule->family, &rule->to)) { + if (rule->family == AF_INET) + r = sd_netlink_message_append_in_addr(m, FRA_DST, &rule->to.in); + else + r = sd_netlink_message_append_in6_addr(m, FRA_DST, &rule->to.in6); + + if (r < 0) + return log_error_errno(r, "Could not append FRA_DST attribute: %m"); + + r = sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(m, rule->to_prefixlen); + if (r < 0) + return log_error_errno(r, "Could not set destination prefix length: %m"); + } + + r = sd_netlink_message_append_u32(m, FRA_PRIORITY, rule->priority); + if (r < 0) + return log_error_errno(r, "Could not append FRA_PRIORITY attribute: %m"); + + if (rule->tos > 0) { + r = sd_rtnl_message_routing_policy_rule_set_tos(m, rule->tos); + if (r < 0) + return log_error_errno(r, "Could not set ip rule tos: %m"); + } + + if (rule->table < 256) { + r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table); + if (r < 0) + return log_error_errno(r, "Could not set ip rule table: %m"); + } else { + r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); + if (r < 0) + return log_error_errno(r, "Could not set ip rule table: %m"); + + r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table); + if (r < 0) + return log_error_errno(r, "Could not append FRA_TABLE attribute: %m"); + } + + if (rule->fwmark > 0) { + r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark); + if (r < 0) + return log_error_errno(r, "Could not append FRA_FWMARK attribute: %m"); + } + + if (rule->fwmask > 0) { + r = sd_netlink_message_append_u32(m, FRA_FWMASK, rule->fwmask); + if (r < 0) + return log_error_errno(r, "Could not append FRA_FWMASK attribute: %m"); + } + + rule->link = link; + + r = sd_netlink_call_async(link->manager->rtnl, m, callback, link, 0, NULL); + if (r < 0) + return log_error_errno(r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + r = routing_policy_rule_add(link->manager, rule->family, &rule->from, rule->from_prefixlen, &rule->to, + rule->to_prefixlen, rule->tos, rule->fwmark, rule->table, NULL); + if (r < 0) + return log_error_errno(r, "Could not add rule : %m"); + + return 0; +} + +static int parse_fwmark_fwmask(const char *s, uint32_t *fwmark, uint32_t *fwmask) { + _cleanup_free_ char *f = NULL; + char *p; + int r; + + assert(s); + + f = strdup(s); + if (!f) + return -ENOMEM; + + p = strchr(f, '/'); + if (p) + *p++ = '\0'; + + r = safe_atou32(f, fwmark); + if (r < 0) + return log_error_errno(r, "Failed to parse RPDB rule firewall mark, ignoring: %s", f); + + if (p) { + r = safe_atou32(p, fwmask); + if (r < 0) + return log_error_errno(r, "Failed to parse RPDB rule mask, ignoring: %s", f); + } + + return 0; +} + +int config_parse_routing_policy_rule_tos( + 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) { + + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = safe_atou8(rvalue, &n->tos); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule tos, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_priority( + 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) { + + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = safe_atou32(rvalue, &n->priority); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule priority, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_table( + 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) { + + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = safe_atou32(rvalue, &n->table); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule table, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_fwmark_mask( + 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) { + + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *n = NULL; + _cleanup_free_ char *fwmark = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = parse_fwmark_fwmask(rvalue, &n->fwmark, &n->fwmask); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", rvalue); + return 0; + } + + n = NULL; + + return 0; +} + +int config_parse_routing_policy_rule_prefix( + 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) { + + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *n = NULL; + Network *network = userdata; + union in_addr_union buffer; + uint8_t prefixlen; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return r; + + r = in_addr_prefix_from_string(rvalue, AF_INET, &buffer, &prefixlen); + if (r < 0) { + r = in_addr_prefix_from_string(rvalue, AF_INET6, &buffer, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "RPDB rule prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + n->family = AF_INET6; + } else + n->family = AF_INET; + + if (streq(lvalue, "To")) { + n->to = buffer; + n->to_prefixlen = prefixlen; + } else { + n->from = buffer; + n->from_prefixlen = prefixlen; + } + + n = NULL; + + return 0; +} + +static int routing_policy_rule_read_full_file(char *state_file, char **ret) { + _cleanup_free_ char *s = NULL, *p = NULL; + size_t size; + int r; + + assert(state_file); + + r = read_full_file(state_file, &s, &size); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (size <= 0) + return -ENODATA; + + *ret = s; + s = NULL; + + return size; +} + +int routing_policy_rule_load(Manager *m) { + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *data = NULL; + const char *p; + char **i; + int r; + + assert(m); + + r = routing_policy_rule_read_full_file(m->state_file, &data); + if (r <= 0) + return r; + + l = strv_split_newlines(data); + if (!l) + return -ENOMEM; + + r = set_ensure_allocated(&m->rules_saved, &routing_policy_rule_hash_ops); + if (r < 0) + return r; + + STRV_FOREACH(i, l) { + _cleanup_routing_policy_rule_free_ RoutingPolicyRule *rule = NULL; + + p = startswith(*i, "RULE="); + if (!p) + continue; + + p = strchr(*i, '='); + p++; + + r = routing_policy_rule_new(&rule); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *word = NULL, *a = NULL, *b = NULL; + union in_addr_union buffer; + uint8_t prefixlen; + + r = extract_first_word(&p, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = split_pair(word, "=", &a, &b); + if (r < 0) + continue; + + if (STR_IN_SET(a, "from", "to")) { + + r = in_addr_prefix_from_string(b, AF_INET, &buffer, &prefixlen); + if (r < 0) { + r = in_addr_prefix_from_string(b, AF_INET6, &buffer, &prefixlen); + if (r < 0) { + log_error_errno(r, "RPDB rule prefix is invalid, ignoring assignment: %s", b); + continue; + } + + rule->family = AF_INET6; + } else + rule->family = AF_INET; + + if (streq(a, "to")) { + rule->to = buffer; + rule->to_prefixlen = prefixlen; + } else { + rule->from = buffer; + rule->from_prefixlen = prefixlen; + } + } else if (streq(a, "tos")) { + r = safe_atou8(b, &rule->tos); + if (r < 0) { + log_error_errno(r, "Failed to parse RPDB rule tos, ignoring: %s", b); + continue; + } + } else if (streq(a, "table")) { + r = safe_atou32(b, &rule->table); + if (r < 0) { + log_error_errno(r, "Failed to parse RPDB rule table, ignoring: %s", b); + continue; + } + } else if (streq(a, "fwmark")) { + + r = parse_fwmark_fwmask(a, &rule->fwmark, &rule->fwmask); + if (r < 0) { + log_error_errno(r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", a); + continue; + } + } + } + + r = set_put(m->rules_saved, rule); + if (r < 0) { + log_warning_errno(r, "Failed to add RPDB rule to saved DB, ignoring: %s", p); + continue; + } + + rule = NULL; + } + + return 0; +} + +void routing_policy_rule_purge(Manager *m, Link *link) { + RoutingPolicyRule *rule, *existing; + Iterator i; + int r; + + assert(m); + assert(link); + + SET_FOREACH(rule, m->rules_saved, i) { + existing = set_get(m->rules_foreign, rule); + if (existing) { + + r = routing_policy_rule_remove(rule, link, routing_policy_rule_remove_handler); + if (r < 0) { + log_warning_errno(r, "Could not remove routing policy rules: %m"); + continue; + } + + link->link_messages++; + } + } +} diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h new file mode 100644 index 00000000000..d9fd93b9bff --- /dev/null +++ b/src/network/networkd-routing-policy-rule.h @@ -0,0 +1,83 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2017 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "in-addr-util.h" + +typedef struct RoutingPolicyRule RoutingPolicyRule; + +#include "networkd-link.h" +#include "networkd-network.h" + +typedef struct Network Network; +typedef struct Link Link; +typedef struct NetworkConfigSection NetworkConfigSection; + +struct RoutingPolicyRule { + Network *network; + Link *link; + NetworkConfigSection *section; + + uint8_t tos; + + uint32_t table; + uint32_t fwmark; + uint32_t fwmask; + uint32_t priority; + + int family; + unsigned char to_prefixlen; + unsigned char from_prefixlen; + + union in_addr_union to; + union in_addr_union from; + + LIST_FIELDS(RoutingPolicyRule, rules); +}; + +int routing_policy_rule_new(RoutingPolicyRule **ret); +void routing_policy_rule_free(RoutingPolicyRule *rule); + +DEFINE_TRIVIAL_CLEANUP_FUNC(RoutingPolicyRule*, routing_policy_rule_free); +#define _cleanup_routing_policy_rule_free_ _cleanup_(routing_policy_rule_freep) + +int routing_policy_rule_configure(RoutingPolicyRule *address, Link *link, sd_netlink_message_handler_t callback, bool update); +int routing_policy_rule_remove(RoutingPolicyRule *routing_policy_rule, Link *link, sd_netlink_message_handler_t callback); +int link_routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata); +int link_routing_policy_rule_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata); + +int routing_policy_rule_add(Manager *m, int family, const union in_addr_union *from, uint8_t from_prefixlen, const union in_addr_union *to, uint8_t to_prefixlen, + uint8_t tos, uint32_t fwmark, uint32_t table, RoutingPolicyRule **ret); +int routing_policy_rule_add_foreign(Manager *m, int family, const union in_addr_union *from, uint8_t from_prefixlen, const union in_addr_union *to, uint8_t to_prefixlen, + uint8_t tos, uint32_t fwmark, uint32_t table, RoutingPolicyRule **ret); +int routing_policy_rule_get(Manager *m, int family, const union in_addr_union *from, uint8_t from_prefixlen, const union in_addr_union *to, uint8_t to_prefixlen, uint8_t tos, + uint32_t fwmark, uint32_t table, RoutingPolicyRule **ret); +int routing_policy_rule_make_local(Manager *m, RoutingPolicyRule *rule); +int routing_policy_rule_load(Manager *m); +void routing_policy_rule_purge(Manager *m, Link *link); + +int config_parse_routing_policy_rule_tos(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); +int config_parse_routing_policy_rule_table(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); +int config_parse_routing_policy_rule_fwmark_mask(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); +int config_parse_routing_policy_rule_prefix(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); +int config_parse_routing_policy_rule_priority(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); diff --git a/src/network/networkd.c b/src/network/networkd.c index 8efd160aaaa..d5ba6893e33 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -132,6 +132,12 @@ int main(int argc, char *argv[]) { goto out; } + r = manager_rtnl_enumerate_rules(m); + if (r < 0) { + log_error_errno(r, "Could not enumerate rules: %m"); + goto out; + } + r = manager_start(m); if (r < 0) { log_error_errno(r, "Could not start manager: %m"); diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 3f5a6673cf5..2289269eef9 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -159,6 +159,18 @@ int sd_rtnl_message_new_addrlabel(sd_netlink *rtnl, sd_netlink_message **ret, ui int sd_rtnl_message_addrlabel_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen); int sd_rtnl_message_addrlabel_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen); +int sd_rtnl_message_new_routing_policy_rule(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int ifal_family); +int sd_rtnl_message_routing_policy_rule_set_tos(sd_netlink_message *m, unsigned char tos); +int sd_rtnl_message_routing_policy_rule_get_tos(sd_netlink_message *m, unsigned char *tos); +int sd_rtnl_message_routing_policy_rule_set_table(sd_netlink_message *m, unsigned char table); +int sd_rtnl_message_routing_policy_rule_get_table(sd_netlink_message *m, unsigned char *table); +int sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(sd_netlink_message *m, unsigned char len); +int sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(sd_netlink_message *m, unsigned char *len); +int sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(sd_netlink_message *m, unsigned char len); +int sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(sd_netlink_message *m, unsigned char *len); +int sd_rtnl_message_routing_policy_rule_set_rtm_type(sd_netlink_message *m, unsigned char type); +int sd_rtnl_message_routing_policy_rule_get_rtm_type(sd_netlink_message *m, unsigned char *type); + _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink, sd_netlink_unref); _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink_message, sd_netlink_message_unref);