diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 96ccf5f637..d7bcf5a067 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1753,6 +1753,39 @@ + + + [CAN] Section Options + The [CAN] section manages the Controller Area Network (CAN bus) and accepts the + following keys. + + + BitRate= + + The bitrate of CAN device in bits per second. The usual SI prefixes (K, M) with the base of 1000 can + be used here. + + + + SamplePoint= + + Optional sample point in percent with one decimal (e.g. 75%, + 87.5%) or permille (e.g. 875‰). + + + + RestartSec= + + Automatic restart delay time. If set to a non-zero value, a restart of the CAN controller will be + triggered automatically in case of a bus-off condition after the specified delay time. Subsecond delays can + be specified using decimals (e.g. 0.1s) or a ms or + us postfix. Using infinity or 0 will turn the + automatic restart off. By default automatic restart is disabled. + + + + + [BridgeVLAN] Section Options The [BridgeVLAN] section manages the VLAN ID configuration of a bridge port and accepts diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 07f43b94e5..b25bc6e215 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -632,6 +632,58 @@ int parse_percent(const char *p) { return v; } +int parse_permille_unbounded(const char *p) { + const char *pc, *pm, *dot, *n; + int r, q, v; + + pm = endswith(p, "‰"); + if (pm) { + n = strndupa(p, pm - p); + r = safe_atoi(n, &v); + if (r < 0) + return r; + } else { + pc = endswith(p, "%"); + if (!pc) + return -EINVAL; + + dot = memchr(p, '.', pc - p); + if (dot) { + if (dot + 2 != pc) + return -EINVAL; + if (dot[1] < '0' || dot[1] > '9') + return -EINVAL; + q = dot[1] - '0'; + n = strndupa(p, dot - p); + } else { + q = 0; + n = strndupa(p, pc - p); + } + r = safe_atoi(n, &v); + if (r < 0) + return r; + if (v > ((INT_MAX - q) / 10)) + return -ERANGE; + + v = v * 10 + q; + } + + if (v < 0) + return -ERANGE; + + return v; +} + +int parse_permille(const char *p) { + int v; + + v = parse_permille_unbounded(p); + if (v > 1000) + return -ERANGE; + + return v; +} + int parse_nice(const char *p, int *ret) { int n, r; diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 2b75b938c7..b3edfbaecc 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -115,6 +115,9 @@ int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); int parse_percent_unbounded(const char *p); int parse_percent(const char *p); +int parse_permille_unbounded(const char *p); +int parse_permille(const char *p); + int parse_nice(const char *p, int *ret); int parse_ip_port(const char *s, uint16_t *ret); diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index ddb4d90eaf..a82b1e1a89 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -300,6 +300,11 @@ static const NLType rtnl_link_info_data_geneve_types[] = { [IFLA_GENEVE_LABEL] = { .type = NETLINK_TYPE_U32 }, }; +static const NLType rtnl_link_info_data_can_types[] = { + [IFLA_CAN_BITTIMING] = { .size = sizeof(struct can_bittiming) }, + [IFLA_CAN_RESTART_MS] = { .type = NETLINK_TYPE_U32 }, +}; + /* these strings must match the .kind entries in the kernel */ static const char* const nl_union_link_info_data_table[] = { [NL_UNION_LINK_INFO_DATA_BOND] = "bond", @@ -326,6 +331,7 @@ static const char* const nl_union_link_info_data_table[] = { [NL_UNION_LINK_INFO_DATA_VXCAN] = "vxcan", [NL_UNION_LINK_INFO_DATA_WIREGUARD] = "wireguard", [NL_UNION_LINK_INFO_DATA_NETDEVSIM] = "netdevsim", + [NL_UNION_LINK_INFO_DATA_CAN] = "can", }; DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); @@ -371,6 +377,8 @@ static const NLTypeSystem rtnl_link_info_data_type_systems[] = { .types = rtnl_link_info_data_geneve_types }, [NL_UNION_LINK_INFO_DATA_VXCAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxcan_types), .types = rtnl_link_info_data_vxcan_types }, + [NL_UNION_LINK_INFO_DATA_CAN] = { .count = ELEMENTSOF(rtnl_link_info_data_can_types), + .types = rtnl_link_info_data_can_types }, }; static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h index a7542eb33d..559976c24c 100644 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ b/src/libsystemd/sd-netlink/netlink-types.h @@ -83,6 +83,7 @@ typedef enum NLUnionLinkInfoData { NL_UNION_LINK_INFO_DATA_VXCAN, NL_UNION_LINK_INFO_DATA_WIREGUARD, NL_UNION_LINK_INFO_DATA_NETDEVSIM, + NL_UNION_LINK_INFO_DATA_CAN, _NL_UNION_LINK_INFO_DATA_MAX, _NL_UNION_LINK_INFO_DATA_INVALID = -1 } NLUnionLinkInfoData; diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index c714cd4fc7..c0496407ab 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -1847,6 +1848,130 @@ int link_up(Link *link) { return 0; } +static int link_up_can(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + assert(link); + + log_link_debug(link, "Bringing CAN link up"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); + + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + if (r < 0) + return log_link_error_errno(link, r, "Could not set link flags: %m"); + + r = sd_netlink_call_async(link->manager->rtnl, req, link_up_handler, link, 0, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + return 0; +} + +static int link_set_can(Link *link) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(link); + assert(link->network); + assert(link->manager); + assert(link->manager->rtnl); + + log_link_debug(link, "link_set_can"); + + r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->ifindex); + if (r < 0) + return log_link_error_errno(link, r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_set_flags(m, NLM_F_REQUEST | NLM_F_ACK); + if (r < 0) + return log_link_error_errno(link, r, "Could not set netlink flags: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_link_error_errno(link, r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, link->kind); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m"); + + if (link->network->can_bitrate > 0 || link->network->can_sample_point > 0) { + struct can_bittiming bt = { + .bitrate = link->network->can_bitrate, + .sample_point = link->network->can_sample_point, + }; + + if (link->network->can_bitrate > UINT32_MAX) { + log_link_error(link, "bitrate (%zu) too big.", link->network->can_bitrate); + return -ERANGE; + } + + log_link_debug(link, "Setting bitrate = %d bit/s", bt.bitrate); + if (link->network->can_sample_point > 0) + log_link_debug(link, "Setting sample point = %d.%d%%", bt.sample_point / 10, bt.sample_point % 10); + else + log_link_debug(link, "Using default sample point"); + + r = sd_netlink_message_append_data(m, IFLA_CAN_BITTIMING, &bt, sizeof(bt)); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_BITTIMING attribute: %m"); + } + + if (link->network->can_restart_us > 0) { + char time_string[FORMAT_TIMESPAN_MAX]; + uint64_t restart_ms; + + if (link->network->can_restart_us == USEC_INFINITY) + restart_ms = 0; + else + restart_ms = DIV_ROUND_UP(link->network->can_restart_us, USEC_PER_MSEC); + + format_timespan(time_string, FORMAT_TIMESPAN_MAX, restart_ms * 1000, MSEC_PER_SEC); + + if (restart_ms > UINT32_MAX) { + log_link_error(link, "restart timeout (%s) too big.", time_string); + return -ERANGE; + } + + log_link_debug(link, "Setting restart = %s", time_string); + + r = sd_netlink_message_append_u32(m, IFLA_CAN_RESTART_MS, restart_ms); + if (r < 0) + return log_link_error_errno(link, r, "Could not append IFLA_CAN_RESTART_MS attribute: %m"); + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_link_error_errno(link, r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_link_error_errno(link, r, "Failed to close netlink container: %m"); + + r = sd_netlink_call_async(link->manager->rtnl, m, link_set_handler, link, 0, NULL); + if (r < 0) + return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); + + link_ref(link); + + if (!(link->flags & IFF_UP)) { + r = link_up_can(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + log_link_debug(link, "link_set_can done"); + + return r; +} + static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { _cleanup_(link_unrefp) Link *link = userdata; int r; @@ -1860,6 +1985,11 @@ static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, void *user if (r < 0) log_link_warning_errno(link, r, "Could not bring down interface: %m"); + if (streq_ptr(link->kind, "can")) { + link_ref(link); + link_set_can(link); + } + return 1; } @@ -1891,31 +2021,6 @@ int link_down(Link *link) { return 0; } -static int link_up_can(Link *link) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - int r; - - assert(link); - - log_link_debug(link, "Bringing CAN link up"); - - r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex); - if (r < 0) - return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m"); - - r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); - if (r < 0) - return log_link_error_errno(link, r, "Could not set link flags: %m"); - - r = sd_netlink_call_async(link->manager->rtnl, req, link_up_handler, link, 0, NULL); - if (r < 0) - return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); - - return 0; -} - static int link_handle_bound_to_list(Link *link) { Link *l; Iterator i; @@ -2599,6 +2704,35 @@ static int link_update_lldp(Link *link) { return r; } +static int link_configure_can(Link *link) { + int r; + + if (streq_ptr(link->kind, "can")) { + /* The CAN interface must be down to configure bitrate, etc... */ + if ((link->flags & IFF_UP)) { + r = link_down(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + + return 0; + } + + return link_set_can(link); + } + + if (!(link->flags & IFF_UP)) { + r = link_up_can(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + + return 0; +} + static int link_configure(Link *link) { int r; @@ -2606,18 +2740,8 @@ static int link_configure(Link *link) { assert(link->network); assert(link->state == LINK_STATE_PENDING); - if (streq_ptr(link->kind, "vcan")) { - - if (!(link->flags & IFF_UP)) { - r = link_up_can(link); - if (r < 0) { - link_enter_failed(link); - return r; - } - } - - return 0; - } + if (STRPTR_IN_SET(link->kind, "can", "vcan")) + return link_configure_can(link); /* Drop foreign config, but ignore loopback or critical devices. * We do not want to remove loopback address or addresses used for root NFS. */ diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 7e625e48fa..e6ca6631ed 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -178,6 +178,9 @@ IPv6Prefix.OnLink, config_parse_prefix_flags, IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0 IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0 IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0 +CAN.BitRate, config_parse_si_size, 0, offsetof(Network, can_bitrate) +CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point) +CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us) /* backwards compatibility: do not add new entries to this section */ Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local) DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index c3a11ddca0..b2a75c7e98 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -272,7 +272,8 @@ static int network_load_one(Manager *manager, const char *filename) { "BridgeFDB\0" "BridgeVLAN\0" "IPv6PrefixDelegation\0" - "IPv6Prefix\0", + "IPv6Prefix\0" + "CAN\0", config_item_perf_lookup, network_network_gperf_lookup, CONFIG_PARSE_WARN, network); if (r < 0) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index b8e2c523a3..5b6b40d5da 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -191,6 +191,11 @@ struct Network { uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN]; uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + /* CAN support */ + size_t can_bitrate; + unsigned can_sample_point; + usec_t can_restart_us; + AddressFamilyBoolean ip_forward; bool ip_masquerade; diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index b5ee07c9bd..29dd1ddc88 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -1203,3 +1203,34 @@ int config_parse_rlimit( return 0; } + +int config_parse_permille(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) { + + unsigned *permille = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(permille); + + r = parse_permille(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse permille value, ignoring: %s", rvalue); + return 0; + } + + *permille = (unsigned) r; + + return 0; +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index 70d313d4eb..8546d5ce66 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -140,6 +140,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_log_facility); CONFIG_PARSER_PROTOTYPE(config_parse_log_level); CONFIG_PARSER_PROTOTYPE(config_parse_signal); CONFIG_PARSER_PROTOTYPE(config_parse_personality); +CONFIG_PARSER_PROTOTYPE(config_parse_permille); CONFIG_PARSER_PROTOTYPE(config_parse_ifname); CONFIG_PARSER_PROTOTYPE(config_parse_ip_port); CONFIG_PARSER_PROTOTYPE(config_parse_join_controllers); diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c index a04ab9c9aa..429eaa479d 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -648,6 +648,54 @@ static void test_parse_percent_unbounded(void) { assert_se(parse_percent_unbounded("400%") == 400); } +static void test_parse_permille(void) { + assert_se(parse_permille("") == -EINVAL); + assert_se(parse_permille("foo") == -EINVAL); + assert_se(parse_permille("0") == -EINVAL); + assert_se(parse_permille("50") == -EINVAL); + assert_se(parse_permille("100") == -EINVAL); + assert_se(parse_permille("-1") == -EINVAL); + + assert_se(parse_permille("0‰") == 0); + assert_se(parse_permille("555‰") == 555); + assert_se(parse_permille("1000‰") == 1000); + assert_se(parse_permille("-7‰") == -ERANGE); + assert_se(parse_permille("1007‰") == -ERANGE); + assert_se(parse_permille("‰") == -EINVAL); + assert_se(parse_permille("‰‰") == -EINVAL); + assert_se(parse_permille("‰1") == -EINVAL); + assert_se(parse_permille("1‰‰") == -EINVAL); + assert_se(parse_permille("3.2‰") == -EINVAL); + + assert_se(parse_permille("0%") == 0); + assert_se(parse_permille("55%") == 550); + assert_se(parse_permille("55.5%") == 555); + assert_se(parse_permille("100%") == 1000); + assert_se(parse_permille("-7%") == -ERANGE); + assert_se(parse_permille("107%") == -ERANGE); + assert_se(parse_permille("%") == -EINVAL); + assert_se(parse_permille("%%") == -EINVAL); + assert_se(parse_permille("%1") == -EINVAL); + assert_se(parse_permille("1%%") == -EINVAL); + assert_se(parse_permille("3.21%") == -EINVAL); +} + +static void test_parse_permille_unbounded(void) { + assert_se(parse_permille_unbounded("1001‰") == 1001); + assert_se(parse_permille_unbounded("4000‰") == 4000); + assert_se(parse_permille_unbounded("2147483647‰") == 2147483647); + assert_se(parse_permille_unbounded("2147483648‰") == -ERANGE); + assert_se(parse_permille_unbounded("4294967295‰") == -ERANGE); + assert_se(parse_permille_unbounded("4294967296‰") == -ERANGE); + + assert_se(parse_permille_unbounded("101%") == 1010); + assert_se(parse_permille_unbounded("400%") == 4000); + assert_se(parse_permille_unbounded("214748364.7%") == 2147483647); + assert_se(parse_permille_unbounded("214748364.8%") == -ERANGE); + assert_se(parse_permille_unbounded("429496729.5%") == -ERANGE); + assert_se(parse_permille_unbounded("429496729.6%") == -ERANGE); +} + static void test_parse_nice(void) { int n; @@ -797,6 +845,8 @@ int main(int argc, char *argv[]) { test_safe_atod(); test_parse_percent(); test_parse_percent_unbounded(); + test_parse_permille(); + test_parse_permille_unbounded(); test_parse_nice(); test_parse_dev(); test_parse_errno();