Merge pull request #9185 from marckleinebudde/can

networkd: add support to configure CAN devices
This commit is contained in:
Lennart Poettering
2018-06-11 12:58:55 +02:00
committed by GitHub
12 changed files with 350 additions and 38 deletions
+33
View File
@@ -1753,6 +1753,39 @@
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[CAN] Section Options</title>
<para>The <literal>[CAN]</literal> section manages the Controller Area Network (CAN bus) and accepts the
following keys.</para>
<variablelist class='network-directives'>
<varlistentry>
<term><varname>BitRate=</varname></term>
<listitem>
<para>The bitrate of CAN device in bits per second. The usual SI prefixes (K, M) with the base of 1000 can
be used here.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>SamplePoint=</varname></term>
<listitem>
<para>Optional sample point in percent with one decimal (e.g. <literal>75%</literal>,
<literal>87.5%</literal>) or permille (e.g. <literal>875‰</literal>).</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RestartSec=</varname></term>
<listitem>
<para>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. <literal>0.1s</literal>) or a <literal>ms</literal> or
<literal>us</literal> postfix. Using <literal>infinity</literal> or <literal>0</literal> will turn the
automatic restart off. By default automatic restart is disabled.</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>[BridgeVLAN] Section Options</title>
<para>The <literal>[BridgeVLAN]</literal> section manages the VLAN ID configuration of a bridge port and accepts
+52
View File
@@ -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;
+3
View File
@@ -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);
@@ -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 = {
@@ -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;
+161 -37
View File
@@ -7,6 +7,7 @@
#include <netinet/ether.h>
#include <linux/if.h>
#include <linux/can/netlink.h>
#include <unistd.h>
#include <stdio_ext.h>
@@ -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. */
+3
View File
@@ -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)
+2 -1
View File
@@ -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)
+5
View File
@@ -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;
+31
View File
@@ -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;
}
+1
View File
@@ -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);
+50
View File
@@ -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();