diff --git a/doc/netplan-yaml.md b/doc/netplan-yaml.md index b22755755..41a520a6f 100644 --- a/doc/netplan-yaml.md +++ b/doc/netplan-yaml.md @@ -865,6 +865,10 @@ network: > Match on traffic going to the specified destination. + - **`iif`** (scalar) + + > Set an incoming interface to match traffic for this policy rule. + - **`table`** (scalar) > The table number to match for the route. In some scenarios, it may be diff --git a/src/netplan.c b/src/netplan.c index d6ab84902..0ab204345 100644 --- a/src/netplan.c +++ b/src/netplan.c @@ -633,9 +633,13 @@ write_routes(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefin for (unsigned i = 0; i < def->ip_rules->len; ++i) { NetplanIPRule *r = g_array_index(def->ip_rules, NetplanIPRule*, i); YAML_MAPPING_OPEN(event, emitter); - /* VRF devices use the VRF routing table implicitly */ - if (def->type != NETPLAN_DEF_TYPE_VRF) + + if (def->type != NETPLAN_DEF_TYPE_VRF) { + /* VRF devices use the VRF routing table implicitly */ YAML_UINT_DEFAULT(def, event, emitter, "table", r->table, NETPLAN_ROUTE_TABLE_UNSPEC); + /* VRF devices are always the incoming interfaces of the relative ip rules */ + YAML_STRING(def, event, emitter, "iif", r->iif); + } YAML_UINT_DEFAULT(def, event, emitter, "priority", r->priority, NETPLAN_IP_RULE_PRIO_UNSPEC); YAML_UINT_DEFAULT(def, event, emitter, "type-of-service", r->tos, NETPLAN_IP_RULE_TOS_UNSPEC); YAML_UINT_DEFAULT(def, event, emitter, "mark", r->fwmark, NETPLAN_IP_RULE_FW_MARK_UNSPEC); diff --git a/src/networkd.c b/src/networkd.c index 982d18cb9..71683e6f2 100644 --- a/src/networkd.c +++ b/src/networkd.c @@ -749,6 +749,9 @@ write_ip_rule(NetplanIPRule* r, GString* s) g_string_append_printf(s, "From=%s\n", r->from); if (r->to) g_string_append_printf(s, "To=%s\n", r->to); + if (r->iif) + g_string_append_printf(s, "IncomingInterface=%s\n", r->iif); + if (r->table != NETPLAN_ROUTE_TABLE_UNSPEC) g_string_append_printf(s, "Table=%d\n", r->table); diff --git a/src/nm.c b/src/nm.c index 583fd2d62..8751f0b53 100644 --- a/src/nm.c +++ b/src/nm.c @@ -297,6 +297,8 @@ write_ip_rules_nm(const NetplanNetDefinition* def, GKeyFile *kf, gint family, GE g_string_append_printf(tmp_val, " from %s", cur_rule->from); if (cur_rule->to) g_string_append_printf(tmp_val, " to %s", cur_rule->to); + if (cur_rule->iif) + g_string_append_printf(tmp_val, " iif %s", cur_rule->iif); if (cur_rule->tos != NETPLAN_IP_RULE_TOS_UNSPEC) g_string_append_printf(tmp_val, " tos %u", cur_rule->tos); if (cur_rule->fwmark != NETPLAN_IP_RULE_FW_MARK_UNSPEC) diff --git a/src/parse.c b/src/parse.c index 64a9a809f..ba51500b2 100644 --- a/src/parse.c +++ b/src/parse.c @@ -386,6 +386,21 @@ handle_generic_str(NetplanParser* npp, yaml_node_t* node, void* entryptr, const return TRUE; } +/** + * Handler for setting a string ID field from a scalar node, inside a given struct + * @entryptr: pointer to the beginning of the to-be-modified data structure + * @data: offset into entryptr struct where the const char* field to write is + * located + */ +STATIC gboolean +handle_generic_id(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error) +{ + if (!assert_valid_id(npp, node, error)) + return FALSE; + + return handle_generic_str(npp, node, entryptr, data, error); +} + STATIC gboolean handle_special_macaddress_option(NetplanParser* npp, yaml_node_t* node, void* entryptr, const void* data, GError** error) { @@ -2048,6 +2063,12 @@ handle_ip_rule_ip(NetplanParser* npp, yaml_node_t* node, const void* data, GErro return TRUE; } +STATIC gboolean +handle_ip_rule_iif(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error) +{ + return handle_generic_id(npp, node, npp->current.ip_rule, (void *) data, error); +} + STATIC gboolean handle_ip_rule_guint(NetplanParser* npp, yaml_node_t* node, const void* data, GError** error) { @@ -2295,6 +2316,7 @@ static const mapping_entry_handler ip_rules_handlers[] = { {"table", YAML_SCALAR_NODE, {.generic=handle_ip_rule_guint}, ip_rule_offset(table)}, {"to", YAML_SCALAR_NODE, {.generic=handle_ip_rule_ip}, ip_rule_offset(to)}, {"type-of-service", YAML_SCALAR_NODE, {.generic=handle_ip_rule_tos}, ip_rule_offset(tos)}, + {"iif", YAML_SCALAR_NODE, {.generic=handle_ip_rule_iif}, ip_rule_offset(iif)}, {NULL} }; @@ -2309,11 +2331,12 @@ handle_ip_rules(NetplanParser* npp, yaml_node_t* node, __unused const void* _, G reset_ip_rule(ip_rule); npp->current.ip_rule = ip_rule; + ret = process_mapping(npp, entry, NULL, ip_rules_handlers, NULL, error); npp->current.ip_rule = NULL; - if (ret && !ip_rule->from && !ip_rule->to) - ret = yaml_error(npp, node, error, "IP routing policy must include either a 'from' or 'to' IP"); + if (ret && !ip_rule->from && !ip_rule->to && !ip_rule->iif) + ret = yaml_error(npp, node, error, "IP routing policy must include at least one of the following fields: 'from', 'to', 'iif'"); if (!ret) { ip_rule_clear(&ip_rule); diff --git a/src/types-internal.h b/src/types-internal.h index f8c1df3df..f4db42c9b 100644 --- a/src/types-internal.h +++ b/src/types-internal.h @@ -168,6 +168,9 @@ typedef struct { guint fwmark; /* type-of-service: between 0 and 255 */ guint tos; + + /* incoming interface */ + char* iif; } NetplanIPRule; struct netplan_vxlan { diff --git a/src/types.c b/src/types.c index 7a1c20ed2..b69e13629 100644 --- a/src/types.c +++ b/src/types.c @@ -87,6 +87,7 @@ free_ip_rules(void* ptr) NetplanIPRule* rule = ptr; g_free(rule->to); g_free(rule->from); + g_free(rule->iif); g_free(rule); } diff --git a/src/util.c b/src/util.c index 1b2852dce..423c1e40a 100644 --- a/src/util.c +++ b/src/util.c @@ -1210,7 +1210,8 @@ is_route_rule_present(const NetplanNetDefinition* netdef, const NetplanIPRule* r entry->table == rule->table && entry->priority == rule->priority && entry->fwmark == rule->fwmark && - entry->tos == rule->tos + entry->tos == rule->tos && + g_strcmp0(entry->iif, rule->iif) == 0 ) return TRUE; } diff --git a/tests/generator/test_errors.py b/tests/generator/test_errors.py index d447c4920..1b8446da4 100644 --- a/tests/generator/test_errors.py +++ b/tests/generator/test_errors.py @@ -916,6 +916,18 @@ def test_device_ip_rule_invalid_address(self): - 192.168.14.2/24 - 2001:FFfe::1/64''', expect_fail=True) + def test_device_ip_rule_invalid_iif(self): + self.generate('''network: + version: 2 + ethernets: + engreen: + routing-policy: + - from: 10.10.10.0/24 + iif: not valid iface name + addresses: + - 192.168.14.2/24 + - 2001:FFfe::1/64''', expect_fail=True) + def test_invalid_dhcp_identifier(self): self.generate('''network: version: 2 diff --git a/tests/generator/test_routing.py b/tests/generator/test_routing.py index 2612d2e96..215abb563 100644 --- a/tests/generator/test_routing.py +++ b/tests/generator/test_routing.py @@ -654,6 +654,32 @@ def test_ip_rule_tos(self): [RoutingPolicyRule] To=10.10.10.0/24 TypeOfService=250 +'''}) + + def test_ip_rule_iif(self): + self.generate('''network: + version: 2 + ethernets: + if0: {} + engreen: + addresses: ["192.168.14.2/24"] + routing-policy: + - to: 10.10.10.0/24 + table: 100 + iif: if0 + ''') + + self.assert_networkd({'engreen.network': '''[Match] +Name=engreen + +[Network] +LinkLocalAddressing=ipv6 +Address=192.168.14.2/24 + +[RoutingPolicyRule] +To=10.10.10.0/24 +Table=100 +IncomingInterface=if0 '''}) def test_use_routes(self): @@ -1185,6 +1211,37 @@ def test_ip_rule_tos(self): address1=192.168.14.2/24 routing-rule1=priority 99 to 10.10.10.0/24 tos 250 +[ipv6] +method=ignore +'''}) + + def test_ip_rule_iif(self): + self.generate('''network: + version: 2 + renderer: NetworkManager + ethernets: + engreen: + addresses: ["192.168.14.2/24"] + routing-policy: + - to: 10.10.10.0/24 + table: 100 + priority: 99 + iif: if0 + ''') + + self.assert_nm({'engreen': '''[connection] +id=netplan-engreen +type=ethernet +interface-name=engreen + +[ethernet] +wake-on-lan=0 + +[ipv4] +method=manual +address1=192.168.14.2/24 +routing-rule1=priority 99 to 10.10.10.0/24 iif if0 table 100 + [ipv6] method=ignore '''})