CAN XL data frames contain an 8-bit virtual CAN network identifier (VCID). A VCID value of zero represents an 'untagged' CAN XL frame. To receive and send these optional VCIDs via CAN_RAW sockets a new socket option CAN_RAW_XL_VCID_OPTS is introduced to define/access VCID content: - tx: set the outgoing VCID value by the kernel (one fixed 8-bit value) - tx: pass through VCID values from the user space (e.g. for traffic replay) - rx: apply VCID receive filter (value/mask) to be passed to the user space With the 'tx pass through' option CAN_RAW_XL_VCID_TX_PASS all valid VCID values can be sent, e.g. to replay full qualified CAN XL traffic. The VCID value provided for the CAN_RAW_XL_VCID_TX_SET option will override the VCID value in the struct canxl_frame.vcid element defined for CAN_RAW_XL_VCID_TX_PASS when both flags are set. With a rx_vcid_mask of zero all possible VCID values (0x00 - 0xFF) are passed to the user space when the CAN_RAW_XL_VCID_RX_FILTER flag is set. Without this flag only untagged CAN XL frames (VCID = 0x00) are delivered to the user space (default). Signed-off-by: Oliver Hartkopp <socketcan@xxxxxxxxxxxx> Reviewed-by: Vincent Mailhol <mailhol.vincent@xxxxxxxxxx> --- include/uapi/linux/can.h | 41 +++++++++++------ include/uapi/linux/can/raw.h | 14 ++++++ net/can/af_can.c | 2 + net/can/raw.c | 86 ++++++++++++++++++++++++++++++++---- 4 files changed, 121 insertions(+), 22 deletions(-) diff --git a/include/uapi/linux/can.h b/include/uapi/linux/can.h index 939db2388208..e55e98ab58dd 100644 --- a/include/uapi/linux/can.h +++ b/include/uapi/linux/can.h @@ -44,10 +44,11 @@ */ #ifndef _UAPI_CAN_H #define _UAPI_CAN_H +#include <asm/byteorder.h> #include <linux/types.h> #include <linux/socket.h> #include <linux/stddef.h> /* for offsetof */ /* controller area network (CAN) kernel definitions */ @@ -193,26 +194,38 @@ struct canfd_frame { #define CANXL_XLF 0x80 /* mandatory CAN XL frame flag (must always be set!) */ #define CANXL_SEC 0x01 /* Simple Extended Content (security/segmentation) */ /** * struct canxl_frame - CAN with e'X'tended frame 'L'ength frame structure - * @prio: 11 bit arbitration priority with zero'ed CAN_*_FLAG flags - * @flags: additional flags for CAN XL - * @sdt: SDU (service data unit) type - * @len: frame payload length in byte (CANXL_MIN_DLEN .. CANXL_MAX_DLEN) - * @af: acceptance field - * @data: CAN XL frame payload (CANXL_MIN_DLEN .. CANXL_MAX_DLEN byte) - * - * @prio shares the same position as @can_id from struct can[fd]_frame. + * @prio: 11 bit arbitration priority (unused 5 bits must be set to zero) + * Shares the lower 16 bits of @can_id from struct can[fd]_frame + * @vcid: CAN XL virtual CAN network identifier + * @__res0: reserved / padding must be set to zero to maintain the CAN filter + * functionality as it holds the @can_id flags CAN_xxx_FLAG + * @flags: additional flags for CAN XL + * @sdt: SDU (service data unit) type + * @len: frame payload length in byte (CANXL_MIN_DLEN .. CANXL_MAX_DLEN) + * @af: acceptance field + * @data: CAN XL frame payload (CANXL_MIN_DLEN .. CANXL_MAX_DLEN byte) */ struct canxl_frame { - canid_t prio; /* 11 bit priority for arbitration (canid_t) */ - __u8 flags; /* additional flags for CAN XL */ - __u8 sdt; /* SDU (service data unit) type */ - __u16 len; /* frame payload length in byte */ - __u32 af; /* acceptance field */ - __u8 data[CANXL_MAX_DLEN]; +#if defined(__LITTLE_ENDIAN) + __u16 prio; /* 11 bit priority for arbitration */ + __u8 vcid; /* virtual CAN network identifier */ + __u8 __res0; /* reserved / padding must be set to zero */ +#elif defined(__BIG_ENDIAN) + __u8 __res0; /* reserved / padding must be set to zero */ + __u8 vcid; /* virtual CAN network identifier */ + __u16 prio; /* 11 bit priority for arbitration */ +#else +#error "Unknown endianness" +#endif + __u8 flags; /* additional flags for CAN XL */ + __u8 sdt; /* SDU (service data unit) type */ + __u16 len; /* frame payload length in byte */ + __u32 af; /* acceptance field */ + __u8 data[CANXL_MAX_DLEN]; }; #define CAN_MTU (sizeof(struct can_frame)) #define CANFD_MTU (sizeof(struct canfd_frame)) #define CANXL_MTU (sizeof(struct canxl_frame)) diff --git a/include/uapi/linux/can/raw.h b/include/uapi/linux/can/raw.h index 31622c9b7988..88e4e8176b89 100644 --- a/include/uapi/linux/can/raw.h +++ b/include/uapi/linux/can/raw.h @@ -63,8 +63,22 @@ enum { CAN_RAW_LOOPBACK, /* local loopback (default:on) */ CAN_RAW_RECV_OWN_MSGS, /* receive my own msgs (default:off) */ CAN_RAW_FD_FRAMES, /* allow CAN FD frames (default:off) */ CAN_RAW_JOIN_FILTERS, /* all filters must match to trigger */ CAN_RAW_XL_FRAMES, /* allow CAN XL frames (default:off) */ + CAN_RAW_XL_VCID_OPTS, /* CAN XL VCID configuration options */ }; +/* configuration for CAN XL virtual CAN identifier (VCID) handling */ +struct can_raw_vcid_options { + __u8 flags; /* flags for vcid (filter) behaviour */ + __u8 tx_vcid; /* VCID value set into canxl_frame.vcid */ + __u8 rx_vcid; /* VCID value for VCID filter */ + __u8 rx_vcid_mask; /* VCID mask for VCID filter */ +}; + +/* can_raw_vcid_options.flags for CAN XL virtual CAN identifier handling */ +#define CAN_RAW_XL_VCID_TX_SET 0x01 +#define CAN_RAW_XL_VCID_TX_PASS 0x02 +#define CAN_RAW_XL_VCID_RX_FILTER 0x04 + #endif /* !_UAPI_CAN_RAW_H */ diff --git a/net/can/af_can.c b/net/can/af_can.c index 7343fd487dbe..707576eeeb58 100644 --- a/net/can/af_can.c +++ b/net/can/af_can.c @@ -863,10 +863,12 @@ static __init int can_init(void) int err; /* check for correct padding to be able to use the structs similarly */ BUILD_BUG_ON(offsetof(struct can_frame, len) != offsetof(struct canfd_frame, len) || + offsetof(struct can_frame, len) != + offsetof(struct canxl_frame, flags) || offsetof(struct can_frame, data) != offsetof(struct canfd_frame, data)); pr_info("can: controller area network core\n"); diff --git a/net/can/raw.c b/net/can/raw.c index e6b822624ba2..d379835d741d 100644 --- a/net/can/raw.c +++ b/net/can/raw.c @@ -89,10 +89,11 @@ struct raw_sock { struct list_head notifier; int loopback; int recv_own_msgs; int fd_frames; int xl_frames; + struct can_raw_vcid_options raw_vcid_opts; int join_filters; int count; /* number of active filters */ struct can_filter dfilter; /* default/single filter */ struct can_filter *filter; /* pointer to filter(s) */ can_err_mask_t err_mask; @@ -132,14 +133,34 @@ static void raw_rcv(struct sk_buff *oskb, void *data) /* check the received tx sock reference */ if (!ro->recv_own_msgs && oskb->sk == sk) return; /* make sure to not pass oversized frames to the socket */ - if ((!ro->fd_frames && can_is_canfd_skb(oskb)) || - (!ro->xl_frames && can_is_canxl_skb(oskb))) + if (!ro->fd_frames && can_is_canfd_skb(oskb)) return; + if (can_is_canxl_skb(oskb)) { + struct canxl_frame *cxl = (struct canxl_frame *)oskb->data; + struct can_raw_vcid_options *ropts = &ro->raw_vcid_opts; + + /* make sure to not pass oversized frames to the socket */ + if (!ro->xl_frames) + return; + + /* filter CAN XL VCID content */ + if (ropts->flags & CAN_RAW_XL_VCID_RX_FILTER) { + /* apply VCID filter if user enabled the filter */ + if ((cxl->vcid & ropts->rx_vcid_mask) != + (ropts->rx_vcid & ropts->rx_vcid_mask)) + return; + } else { + /* no filter => do not forward VCID tagged frames */ + if (cxl->vcid) + return; + } + } + /* eliminate multiple filter matches for the same skb */ if (this_cpu_ptr(ro->uniq)->skb == oskb && this_cpu_ptr(ro->uniq)->skbcnt == can_skb_prv(oskb)->skbcnt) { if (!ro->join_filters) return; @@ -696,10 +717,19 @@ static int raw_setsockopt(struct socket *sock, int level, int optname, /* Enabling CAN XL includes CAN FD */ if (ro->xl_frames) ro->fd_frames = ro->xl_frames; break; + case CAN_RAW_XL_VCID_OPTS: + if (optlen != sizeof(ro->raw_vcid_opts)) + return -EINVAL; + + if (copy_from_sockptr(&ro->raw_vcid_opts, optval, optlen)) + return -EFAULT; + + break; + case CAN_RAW_JOIN_FILTERS: if (optlen != sizeof(ro->join_filters)) return -EINVAL; if (copy_from_sockptr(&ro->join_filters, optval, optlen)) @@ -784,10 +814,25 @@ static int raw_getsockopt(struct socket *sock, int level, int optname, if (len > sizeof(int)) len = sizeof(int); val = &ro->xl_frames; break; + case CAN_RAW_XL_VCID_OPTS: + /* user space buffer to small for VCID opts? */ + if (len < sizeof(ro->raw_vcid_opts)) { + /* return -ERANGE and needed space in optlen */ + err = -ERANGE; + if (put_user(sizeof(ro->raw_vcid_opts), optlen)) + err = -EFAULT; + } else { + if (len > sizeof(ro->raw_vcid_opts)) + len = sizeof(ro->raw_vcid_opts); + if (copy_to_user(optval, &ro->raw_vcid_opts, len)) + err = -EFAULT; + } + break; + case CAN_RAW_JOIN_FILTERS: if (len > sizeof(int)) len = sizeof(int); val = &ro->join_filters; break; @@ -801,36 +846,54 @@ static int raw_getsockopt(struct socket *sock, int level, int optname, if (copy_to_user(optval, val, len)) return -EFAULT; return 0; } -static bool raw_bad_txframe(struct raw_sock *ro, struct sk_buff *skb, int mtu) +static void raw_put_canxl_vcid(struct raw_sock *ro, struct sk_buff *skb) +{ + struct canxl_frame *cxl = (struct canxl_frame *)skb->data; + struct can_raw_vcid_options *ropts = &ro->raw_vcid_opts; + + /* sanitize non CAN XL bits */ + cxl->__res0 = 0; + + /* clear VCID in CAN XL frame if pass through is disabled */ + if (!(ropts->flags & CAN_RAW_XL_VCID_TX_PASS)) + cxl->vcid = 0; + + /* set VCID in CAN XL frame if enabled */ + if (ropts->flags & CAN_RAW_XL_VCID_TX_SET) + cxl->vcid = ropts->tx_vcid; +} + +static unsigned int raw_check_txframe(struct raw_sock *ro, struct sk_buff *skb, int mtu) { /* Classical CAN -> no checks for flags and device capabilities */ if (can_is_can_skb(skb)) - return false; + return CAN_MTU; /* CAN FD -> needs to be enabled and a CAN FD or CAN XL device */ if (ro->fd_frames && can_is_canfd_skb(skb) && (mtu == CANFD_MTU || can_is_canxl_dev_mtu(mtu))) - return false; + return CANFD_MTU; /* CAN XL -> needs to be enabled and a CAN XL device */ if (ro->xl_frames && can_is_canxl_skb(skb) && can_is_canxl_dev_mtu(mtu)) - return false; + return CANXL_MTU; - return true; + return 0; } static int raw_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) { struct sock *sk = sock->sk; struct raw_sock *ro = raw_sk(sk); struct sockcm_cookie sockc; struct sk_buff *skb; struct net_device *dev; + unsigned int txmtu; int ifindex; int err = -EINVAL; /* check for valid CAN frame sizes */ if (size < CANXL_HDR_SIZE + CANXL_MIN_DLEN || size > CANXL_MTU) @@ -867,13 +930,20 @@ static int raw_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) err = memcpy_from_msg(skb_put(skb, size), msg, size); if (err < 0) goto free_skb; err = -EINVAL; - if (raw_bad_txframe(ro, skb, dev->mtu)) + + /* check for valid CAN (CC/FD/XL) frame content */ + txmtu = raw_check_txframe(ro, skb, dev->mtu); + if (!txmtu) goto free_skb; + /* only CANXL: clear/forward/set VCID value */ + if (txmtu == CANXL_MTU) + raw_put_canxl_vcid(ro, skb); + sockcm_init(&sockc, sk); if (msg->msg_controllen) { err = sock_cmsg_send(sk, msg, &sockc); if (unlikely(err)) goto free_skb; -- 2.39.2