ip link: support for the CAN netlink
No binary (build failed)
—
(log yok)
| @@ -703,6 +703,7 @@ CONFIG_FEATURE_INETD_RPC=y | |
| CONFIG_IP=y | |
| CONFIG_FEATURE_IP_ADDRESS=y | |
| CONFIG_FEATURE_IP_LINK=y | |
| + | CONFIG_FEATURE_IP_LINK_CAN=y |
| CONFIG_FEATURE_IP_ROUTE=y | |
| CONFIG_FEATURE_IP_TUNNEL=y | |
| CONFIG_FEATURE_IP_RULE=y | |
| @@ -74,6 +74,12 @@ | |
| //config: help | |
| //config: Configure network devices with "ip". | |
| //config: | |
| + | //config:config FEATURE_IP_LINK_CAN |
| + | //config: bool "ip link set type can" |
| + | //config: default y |
| + | //config: help |
| + | //config: Configure CAN devices with "ip". |
| + | //config: |
| //config:config FEATURE_IP_ROUTE | |
| //config: bool "ip route" | |
| //config: default y | |
| @@ -152,16 +158,61 @@ | |
| //usage:#define iplink_trivial_usage | |
| //usage: /*Usage:iplink*/"set IFACE [up|down] [arp on|off] [multicast on|off]\n" | |
| //usage: " [promisc on|off] [mtu NUM] [name NAME] [qlen NUM] [address MAC]\n" | |
| - | //usage: " [master IFACE | nomaster] [netns PID]" |
| + | //usage: " [master IFACE | nomaster] [netns PID]"IF_FEATURE_IP_LINK_CAN(" [type TYPE ARGS]") |
| // * short help shows only "set" command, long help continues (with just one "\n") | |
| // * and shows all other commands: | |
| //usage:#define iplink_full_usage "\n" | |
| //usage: "iplink add [link IFACE] IFACE [address MAC] type TYPE [ARGS]\n" | |
| //usage: "iplink delete IFACE type TYPE [ARGS]\n" | |
| - | //usage: " TYPE ARGS := vlan VLANARGS | vrf table NUM\n" |
| + | //usage: " TYPE ARGS := vlan VLANARGS | vrf table NUM"IF_FEATURE_IP_LINK_CAN(" | can CANARGS")"\n" |
| //usage: " VLANARGS := id VLANID [protocol 802.1q|802.1ad] [reorder_hdr on|off]\n" | |
| //usage: " [gvrp on|off] [mvrp on|off] [loose_binding on|off]\n" | |
| + | //usage: IF_FEATURE_IP_LINK_CAN( |
| + | //usage: " CANARGS := [bitrate BITRATE [sample-point SAMPLE-POINT]] |\n" |
| + | //usage: " [tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1\n" |
| + | //usage: " phase-seg2 PHASE-SEG2 [sjw SJW]]\n" |
| + | //usage: " [dbitrate BITRATE [dsample-point SAMPLE-POINT]] |\n" |
| + | //usage: " [dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1\n" |
| + | //usage: " dphase-seg2 PHASE-SEG2 [dsjw SJW]]\n" |
| + | //usage: " [loopback on|off] [listen-only on|off] [triple-sampling on|off]\n" |
| + | //usage: " [one-shot on|off] [berr-reporting on|off]\n" |
| + | //usage: " [fd on|off] [fd-non-iso on|off] [presume-ack on|off]\n" |
| + | //usage: " [restart-ms TIME-MS] [restart]\n" |
| + | //usage: " [termination 0..65535]\n" |
| + | //usage: ) |
| //usage: "iplink show [IFACE]" | |
| + | //upstream man ip-link-can: |
| + | //Usage: ip link set DEVICE type can |
| + | // [ bitrate BITRATE [ sample-point SAMPLE-POINT] ] | |
| + | // [ tq TQ prop-seg PROP_SEG phase-seg1 PHASE-SEG1 |
| + | // phase-seg2 PHASE-SEG2 [ sjw SJW ] ] |
| + | // |
| + | // [ dbitrate BITRATE [ dsample-point SAMPLE-POINT] ] | |
| + | // [ dtq TQ dprop-seg PROP_SEG dphase-seg1 PHASE-SEG1 |
| + | // dphase-seg2 PHASE-SEG2 [ dsjw SJW ] ] |
| + | // |
| + | // [ loopback { on | off } ] |
| + | // [ listen-only { on | off } ] |
| + | // [ triple-sampling { on | off } ] |
| + | // [ one-shot { on | off } ] |
| + | // [ berr-reporting { on | off } ] |
| + | // [ fd { on | off } ] |
| + | // [ fd-non-iso { on | off } ] |
| + | // [ presume-ack { on | off } ] |
| + | // |
| + | // [ restart-ms TIME-MS ] |
| + | // [ restart ] |
| + | // |
| + | // [ termination { 0..65535 } ] |
| + | // |
| + | // Where: BITRATE := { 1..1000000 } |
| + | // SAMPLE-POINT := { 0.000..0.999 } |
| + | // TQ := { NUMBER } |
| + | // PROP-SEG := { 1..8 } |
| + | // PHASE-SEG1 := { 1..8 } |
| + | // PHASE-SEG2 := { 1..8 } |
| + | // SJW := { 1..4 } |
| + | // RESTART-MS := { 0 | NUMBER } |
| //upstream man ip-link: | |
| //===================== | |
| //ip link add [link DEV] [ name ] NAME | |
| @@ -11,6 +11,9 @@ | |
| #include <netinet/if_ether.h> | |
| #include <linux/if_vlan.h> | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | # include <linux/can/netlink.h> |
| + | #endif |
| #include "ip_common.h" /* #include "libbb.h" is inside */ | |
| #include "rt_names.h" | |
| #include "utils.h" | |
| @@ -28,6 +31,11 @@ | |
| #undef IFLA_VLAN_PROTOCOL | |
| #define IFLA_VLAN_PROTOCOL 5 | |
| + | #ifndef NLMSG_TAIL |
| + | #define NLMSG_TAIL(nmsg) \ |
| + | ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) |
| + | #endif |
| + | |
| #ifndef IFLA_LINKINFO | |
| # define IFLA_LINKINFO 18 | |
| # define IFLA_INFO_KIND 1 | |
| @@ -55,6 +63,11 @@ struct ifla_vlan_flags { | |
| #define str_on_off "on\0""off\0" | |
| + | enum { |
| + | PARM_on = 0, |
| + | PARM_off |
| + | }; |
| + | |
| /* Exits on error */ | |
| static int get_ctl_fd(void) | |
| { | |
| @@ -241,10 +254,257 @@ static void die_must_be_on_off(const char *msg) | |
| bb_error_msg_and_die("argument of \"%s\" must be \"on\" or \"off\"", msg); | |
| } | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | static uint32_t get_float_1000(char *arg, const char *errmsg) |
| + | { |
| + | uint32_t ret; |
| + | double d; |
| + | char *ptr; |
| + | |
| + | errno = 0; |
| + | //TODO: needs setlocale(LC_NUMERIC, "C")? |
| + | d = strtod(arg, &ptr); |
| + | if (errno || ptr == arg || *ptr |
| + | || d > (0xFFFFFFFFU / 1000) || d < 0 |
| + | ) { |
| + | invarg_1_to_2(arg, errmsg); /* does not return */ |
| + | } |
| + | ret = d * 1000; |
| + | |
| + | return ret; |
| + | } |
| + | |
| + | static void do_set_can(char *dev, char **argv) |
| + | { |
| + | struct can_bittiming bt = {}, dbt = {}; |
| + | struct can_ctrlmode cm = {}; |
| + | char *keyword; |
| + | static const char keywords[] ALIGN1 = |
| + | "bitrate\0""sample-point\0""tq\0" |
| + | "prop-seg\0""phase-seg1\0""phase-seg2\0""sjw\0" |
| + | "dbitrate\0""dsample-point\0""dtq\0" |
| + | "dprop-seg\0""dphase-seg1\0""dphase-seg2\0""dsjw\0" |
| + | "loopback\0""listen-only\0""triple-sampling\0" |
| + | "one-shot\0""berr-reporting\0" |
| + | "fd\0""fd-non-iso\0""presume-ack\0" |
| + | "cc-len8-dlc\0""restart\0""restart-ms\0" |
| + | "termination\0"; |
| + | enum { ARG_bitrate = 0, ARG_sample_point, ARG_tq, |
| + | ARG_prop_seg, ARG_phase_seg1, ARG_phase_seg2, ARG_sjw, |
| + | ARG_dbitrate, ARG_dsample_point, ARG_dtq, |
| + | ARG_dprop_seg, ARG_dphase_seg1, ARG_dphase_seg2, ARG_dsjw, |
| + | ARG_loopback, ARG_listen_only, ARG_triple_sampling, |
| + | ARG_one_shot, ARG_berr_reporting, |
| + | ARG_fd, ARG_fd_non_iso, ARG_presume_ack, |
| + | ARG_cc_len8_dlc, ARG_restart, ARG_restart_ms, |
| + | ARG_termination }; |
| + | struct rtnl_handle rth; |
| + | struct { |
| + | struct nlmsghdr n; |
| + | struct ifinfomsg i; |
| + | char buf[1024]; |
| + | } req; |
| + | size_t dev_len; |
| + | struct rtattr *linkinfo, *data; |
| + | smalluint key, param; |
| + | |
| + | memset(&req, 0, sizeof(req)); |
| + | |
| + | req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); |
| + | req.n.nlmsg_flags = NLM_F_REQUEST; |
| + | req.n.nlmsg_type = RTM_NEWLINK; |
| + | req.i.ifi_family = preferred_family; |
| + | xrtnl_open(&rth); |
| + | req.i.ifi_index = xll_name_to_index(dev); |
| + | dev_len = strlen(dev); |
| + | if (dev_len < 2 || dev_len > IFNAMSIZ) |
| + | invarg_1_to_2(dev, "dev"); |
| + | |
| + | addattr_l(&req.n, sizeof(req), IFLA_IFNAME, dev, dev_len); |
| + | linkinfo = NLMSG_TAIL(&req.n); |
| + | addattr_l(&req.n, sizeof(req), IFLA_LINKINFO, NULL, 0); |
| + | addattr_l(&req.n, sizeof(req), IFLA_INFO_KIND, (void *)"can", |
| + | strlen("can")); |
| + | data = NLMSG_TAIL(&req.n); |
| + | addattr_l(&req.n, sizeof(req), IFLA_INFO_DATA, NULL, 0); |
| + | while (*argv) { |
| + | key = index_in_substrings(keywords, *argv); |
| + | keyword = *argv; |
| + | //printf("%s: key: %d, *argv: %s\n", __func__, key, *argv); |
| + | switch (key) { |
| + | case ARG_bitrate: |
| + | case ARG_tq: |
| + | case ARG_prop_seg: |
| + | case ARG_phase_seg1: |
| + | case ARG_phase_seg2: |
| + | case ARG_sjw: { |
| + | uint32_t *val; |
| + | NEXT_ARG(); |
| + | if (key == ARG_bitrate) |
| + | val = &bt.bitrate; |
| + | else if (key == ARG_tq) |
| + | val = &bt.tq; |
| + | else if (key == ARG_prop_seg) |
| + | val = &bt.prop_seg; |
| + | else if (key == ARG_phase_seg1) |
| + | val = &bt.phase_seg1; |
| + | else if (key == ARG_phase_seg2) |
| + | val = &bt.phase_seg2; |
| + | else |
| + | val = &bt.sjw; |
| + | |
| + | *val = get_u32(*argv, keyword); |
| + | break; |
| + | } |
| + | case ARG_sample_point: { |
| + | NEXT_ARG(); |
| + | bt.sample_point = get_float_1000(*argv, keyword); |
| + | break; |
| + | } |
| + | case ARG_dbitrate: |
| + | case ARG_dtq: |
| + | case ARG_dprop_seg: |
| + | case ARG_dphase_seg1: |
| + | case ARG_dphase_seg2: |
| + | case ARG_dsjw: { |
| + | uint32_t *val; |
| + | NEXT_ARG(); |
| + | if (key == ARG_dbitrate) |
| + | val = &dbt.bitrate; |
| + | else if (key == ARG_dtq) |
| + | val = &dbt.tq; |
| + | else if (key == ARG_dprop_seg) |
| + | val = &dbt.prop_seg; |
| + | else if (key == ARG_dphase_seg1) |
| + | val = &dbt.phase_seg1; |
| + | else if (key == ARG_dphase_seg2) |
| + | val = &dbt.phase_seg2; |
| + | else |
| + | val = &dbt.sjw; |
| + | |
| + | *val = get_u32(*argv, keyword); |
| + | break; |
| + | } |
| + | case ARG_dsample_point: { |
| + | NEXT_ARG(); |
| + | dbt.sample_point = get_float_1000(*argv, keyword); |
| + | break; |
| + | } |
| + | case ARG_loopback: |
| + | case ARG_listen_only: |
| + | case ARG_triple_sampling: |
| + | case ARG_one_shot: |
| + | case ARG_berr_reporting: |
| + | case ARG_fd: |
| + | case ARG_fd_non_iso: |
| + | case ARG_presume_ack: |
| + | case ARG_cc_len8_dlc: { |
| + | uint32_t flag = 0; |
| + | |
| + | NEXT_ARG(); |
| + | param = index_in_strings(str_on_off, *argv); |
| + | if (param < 0) |
| + | die_must_be_on_off(keyword); |
| + | |
| + | if (key == ARG_loopback) |
| + | flag = CAN_CTRLMODE_LOOPBACK; |
| + | else if (key == ARG_listen_only) |
| + | flag = CAN_CTRLMODE_LISTENONLY; |
| + | else if (key == ARG_triple_sampling) |
| + | flag = CAN_CTRLMODE_3_SAMPLES; |
| + | else if (key == ARG_one_shot) |
| + | flag = CAN_CTRLMODE_ONE_SHOT; |
| + | else if (key == ARG_berr_reporting) |
| + | flag = CAN_CTRLMODE_BERR_REPORTING; |
| + | else if (key == ARG_fd) |
| + | flag = CAN_CTRLMODE_FD; |
| + | else if (key == ARG_fd_non_iso) |
| + | flag = CAN_CTRLMODE_FD_NON_ISO; |
| + | else if (key == ARG_presume_ack) |
| + | flag = CAN_CTRLMODE_PRESUME_ACK; |
| + | else |
| + | #if defined(CAN_CTRLMODE_CC_LEN8_DLC) |
| + | flag = CAN_CTRLMODE_CC_LEN8_DLC; |
| + | #else |
| + | die_must_be_on_off(keyword); |
| + | #endif |
| + | cm.mask |= flag; |
| + | if (param == PARM_on) |
| + | cm.flags |= flag; |
| + | |
| + | break; |
| + | } |
| + | case ARG_restart: { |
| + | uint32_t val = 1; |
| + | /*NEXT_ARG(); - WRONG? */ |
| + | addattr_l(&req.n, sizeof(req), IFLA_CAN_RESTART, &val, sizeof(val)); |
| + | break; |
| + | } |
| + | case ARG_restart_ms: { |
| + | uint32_t val; |
| + | NEXT_ARG(); |
| + | val = get_u32(*argv, keyword); |
| + | addattr_l(&req.n, sizeof(req), IFLA_CAN_RESTART_MS, &val, sizeof(val)); |
| + | break; |
| + | } |
| + | case ARG_termination: { |
| + | uint16_t val; |
| + | NEXT_ARG(); |
| + | val = get_u16(*argv, keyword); |
| + | addattr_l(&req.n, sizeof(req), IFLA_CAN_TERMINATION, &val, sizeof(val)); |
| + | break; |
| + | } |
| + | default: |
| + | break; |
| + | } |
| + | argv++; |
| + | } |
| + | |
| + | if (bt.bitrate || bt.tq) |
| + | addattr_l(&req.n, sizeof(req), IFLA_CAN_BITTIMING, &bt, sizeof(bt)); |
| + | |
| + | if (cm.mask) |
| + | addattr_l(&req.n, sizeof(req), IFLA_CAN_CTRLMODE, &cm, sizeof(cm)); |
| + | |
| + | data->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)data; |
| + | linkinfo->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)linkinfo; |
| + | |
| + | if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) |
| + | xfunc_die(); |
| + | } |
| + | |
| + | static void set_type(char *type, char *dev, char **argv) |
| + | { |
| + | /* When we have more than just "type can ARGS" supported, maybe: |
| + | static const char keywords[] ALIGN1 = "" |
| + | IF_FEATURE_IP_LINK_CAN("can\0") |
| + | ; |
| + | typedef void FAST_FUNC(*ip_type_set_func_ptr_t)(char*, char**); |
| + | static const ip_type_set_func_ptr_t funcs[] ALIGN_PTR = { |
| + | IF_FEATURE_IP_LINK_CAN(do_set_can,) |
| + | }; |
| + | ip_type_set_func_ptr_t func; |
| + | int key; |
| + | |
| + | key = index_in_substrings(keywords, type); |
| + | if (key < 0) |
| + | invarg_1_to_2(type, "type"); |
| + | func = funcs[key]; |
| + | func(dev, argv); |
| + | */ |
| + | if (strcmp(type, "can") != 0) |
| + | invarg_1_to_2(type, "type"); |
| + | do_set_can(dev, argv); |
| + | } |
| + | #endif |
| + | |
| /* Return value becomes exitcode. It's okay to not return at all */ | |
| static int do_set(char **argv) | |
| { | |
| char *dev = NULL; | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | char *type = NULL; |
| + | #endif |
| uint32_t mask = 0; | |
| uint32_t flags = 0; | |
| int qlen = -1; | |
| @@ -261,18 +521,24 @@ static int do_set(char **argv) | |
| "up\0""down\0""name\0""mtu\0""qlen\0""multicast\0" | |
| "arp\0""promisc\0""address\0""netns\0" | |
| "master\0""nomaster\0" | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | "type\0" |
| + | #endif |
| "dev\0" /* must be last */; | |
| enum { ARG_up = 0, ARG_down, ARG_name, ARG_mtu, ARG_qlen, ARG_multicast, | |
| ARG_arp, ARG_promisc, ARG_addr, ARG_netns, | |
| ARG_master, ARG_nomaster, | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | ARG_type, |
| + | #endif |
| ARG_dev }; | |
| - | enum { PARM_on = 0, PARM_off }; |
| smalluint key; | |
| while (*argv) { | |
| /* substring search ensures that e.g. "addr" and "address" | |
| * are both accepted */ | |
| key = index_in_substrings(keywords, *argv); | |
| + | //printf("%s: key: %d, *argv: %s\n", __func__, key, *argv); |
| if (key == ARG_up) { | |
| mask |= IFF_UP; | |
| flags |= IFF_UP; | |
| @@ -304,6 +570,13 @@ static int do_set(char **argv) | |
| } else if (key == ARG_netns) { | |
| NEXT_ARG(); | |
| netns = get_unsigned(*argv, "netns"); | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | } else if (key == ARG_type) { |
| + | NEXT_ARG(); |
| + | type = *argv; |
| + | argv++; |
| + | break; |
| + | #endif |
| } else if (key >= ARG_dev) { | |
| /* ^^^^^^ ">=" here results in "dev IFACE" treated as default */ | |
| if (key == ARG_dev) { | |
| @@ -311,6 +584,7 @@ static int do_set(char **argv) | |
| } | |
| if (dev) | |
| duparg2("dev", *argv); | |
| + | |
| dev = *argv; | |
| } else { | |
| /* "on|off" options */ | |
| @@ -496,6 +770,10 @@ static int do_set(char **argv) | |
| } | |
| if (mask) | |
| do_chflags(dev, flags, mask); | |
| + | #if ENABLE_FEATURE_IP_LINK_CAN |
| + | if (type) |
| + | set_type(type, dev, argv); |
| + | #endif |
| return 0; | |
| } | |
| @@ -531,10 +809,6 @@ static void vlan_parse_opt(char **argv, struct nlmsghdr *n, unsigned int size) | |
| PROTO_8021Q = 0, | |
| PROTO_8021AD, | |
| }; | |
| - | enum { |
| - | PARM_on = 0, |
| - | PARM_off |
| - | }; |
| int arg; | |
| uint16_t id, proto; | |
| struct ifla_vlan_flags flags = {}; | |
| @@ -610,10 +884,6 @@ static void vrf_parse_opt(char **argv, struct nlmsghdr *n, unsigned int size) | |
| addattr_l(n, size, IFLA_VRF_TABLE, &table, sizeof(table)); | |
| } | |
| - | #ifndef NLMSG_TAIL |
| - | #define NLMSG_TAIL(nmsg) \ |
| - | ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) |
| - | #endif |
| /* Return value becomes exitcode. It's okay to not return at all */ | |
| static int do_add_or_delete(char **argv, const unsigned rtm) | |
| { | |