[PATCH can-next v2 3/3] can: add can diag interface
Filippo Storniolo <[email protected]>
| Newsgroups | org.kernel.vger.linux-can |
|---|---|
| Message-ID | <[email protected]> |
Add the can_diag interface for querying sockets from userspace. ss(8) tool can use this interface to list open sockets. The userspace ABI is defined in <linux/can_diag.h> and includes netlink request and response structs. The request queries open can sockets and the response contains socket information fields including the interface index for bound sockets, inode number, transport protocol etc. Support can be added later by extending can_diag_dump(). Suggested-by: Davide Caratti <[email protected]> Signed-off-by: Filippo Storniolo <[email protected]> --- MAINTAINERS | 1 + include/uapi/linux/can/diag.h | 55 +++++++++++++ net/can/Kconfig | 10 +++ net/can/Makefile | 3 + net/can/diag.c | 185 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 254 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 932ea1db048e..178caa25a6d2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5827,6 +5827,7 @@ F: include/net/can.h F: include/net/netns/can.h F: include/uapi/linux/can.h F: include/uapi/linux/can/bcm.h +F: include/uapi/linux/can/diag.h F: include/uapi/linux/can/gw.h F: include/uapi/linux/can/isotp.h F: include/uapi/linux/can/raw.h diff --git a/include/uapi/linux/can/diag.h b/include/uapi/linux/can/diag.h new file mode 100644 index 000000000000..7f99f85c4242 --- /dev/null +++ b/include/uapi/linux/can/diag.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _UAPI_CAN_DIAG_H_ +#define _UAPI_CAN_DIAG_H_ + +#include <linux/types.h> +#include <linux/can.h> + +/* Request */ +struct can_diag_req { + __u8 sdiag_family; /* must be AF_CAN */ + __u8 sdiag_protocol; /* for future filtering of transport protocols */ + __u16 pad; + __u32 cdiag_states; + __u32 cdiag_ino; + __u32 cdiag_show; + __u32 cdiag_cookie[2]; +}; + +enum { + CAN_DIAG_UNSPEC, + CAN_DIAG_UID, + CAN_DIAG_ISOTP_ADDR, + CAN_DIAG_J1939_ADDR, + + __CAN_DIAG_MAX, +}; + +#define CAN_DIAG_MAX (__CAN_DIAG_MAX - 1) + +/* Response */ +struct can_diag_msg { + __u8 cdiag_family; /* AF_CAN */ + __u8 cdiag_state; + __u16 cdiag_protocol; + __u16 cdiag_type; + __u16 pad16; + __u32 cdiag_cookie[2]; + __s32 cdiag_ifindex; + __u32 pad32; + __u64 cdiag_ino; +}; + +struct can_diag_isotp_addr { + canid_t tx_id; + canid_t rx_id; +}; + +struct can_diag_j1939_addr { + __u64 name; + __u32 pgn; + __u8 addr; +}; + +#endif /* _UAPI_CAN_DIAG_H_ */ diff --git a/net/can/Kconfig b/net/can/Kconfig index abbb4be7ad21..ed210d62da7c 100644 --- a/net/can/Kconfig +++ b/net/can/Kconfig @@ -70,4 +70,14 @@ config CAN_ISOTP as needed e.g. for vehicle diagnosis (UDS, ISO 14229) or IP-over-CAN traffic. +config CAN_DIAG + tristate "CAN socket monitoring interface" + depends on CAN + default m + help + Support for CAN socket monitoring interface used by the ss tool. + If unsure, say Y. + + Enable this module so userspace applications can query open sockets. + endif diff --git a/net/can/Makefile b/net/can/Makefile index 58f2c31c1ef3..c0ddeb9a012c 100644 --- a/net/can/Makefile +++ b/net/can/Makefile @@ -20,3 +20,6 @@ obj-$(CONFIG_CAN_J1939) += j1939/ obj-$(CONFIG_CAN_ISOTP) += can-isotp.o can-isotp-y := isotp.o + +obj-$(CONFIG_CAN_DIAG) += can-diag.o +can-diag-y := diag.o diff --git a/net/can/diag.c b/net/can/diag.c new file mode 100644 index 000000000000..e9132ba3f8cc --- /dev/null +++ b/net/can/diag.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2026 Red Hat + * Author: Filippo Storniolo <[email protected]> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/net.h> +#include <net/netlink.h> +#include <linux/sock_diag.h> +#include <linux/can.h> +#include <linux/can/diag.h> +#include <net/net_namespace.h> +#include <net/sock.h> +#include <linux/netdevice.h> +#include <linux/user_namespace.h> +#include <linux/can/core.h> + +static int sk_diag_fill(struct sock *sk, struct sk_buff *skb, + struct can_diag_req *req, /* will be used for filtering */ + struct user_namespace *user_ns, + u32 portid, u32 seq, u32 flags, u64 sk_ino) +{ + struct sockaddr_can can_addr; + struct can_diag_msg *rep; + struct nlmsghdr *nlh; + uid_t uid; + int err; + + nlh = nlmsg_put(skb, portid, seq, SOCK_DIAG_BY_FAMILY, sizeof(*rep), flags); + if (!nlh) + return -EMSGSIZE; + + rep = nlmsg_data(nlh); + memset(rep, 0, sizeof(struct can_diag_msg)); + + rep->cdiag_family = AF_CAN; + rep->cdiag_type = sk->sk_type; + rep->cdiag_ino = sk_ino; + rep->cdiag_protocol = sk->sk_protocol; + rep->cdiag_state = READ_ONCE(sk->sk_state); + sock_diag_save_cookie(sk, rep->cdiag_cookie); + + uid = from_kuid_munged(user_ns, sk_uid(sk)); + err = nla_put(skb, CAN_DIAG_UID, sizeof(uid_t), &uid); + if (err < 0) + goto cancel_nlmsg_err; + + memset(&can_addr, 0, sizeof(can_addr)); + + err = kernel_getsockname(sk->sk_socket, (struct sockaddr *)&can_addr); + if (err < 0) { + /* Some protocols (e.g. CAN_BCM) do not implement kernel_getsockname(). + * No error returned because the netlink message is still valid. + */ + if (err == -EOPNOTSUPP) + goto exit_no_err; + + goto cancel_nlmsg_err; + } + + rep->cdiag_ifindex = can_addr.can_ifindex; + + switch (sk->sk_protocol) { + case CAN_ISOTP: + { + struct can_diag_isotp_addr isotp_addr; + + memset(&isotp_addr, 0, sizeof(isotp_addr)); + + isotp_addr.tx_id = can_addr.can_addr.tp.tx_id; + isotp_addr.rx_id = can_addr.can_addr.tp.rx_id; + + err = nla_put(skb, CAN_DIAG_ISOTP_ADDR, sizeof(struct can_diag_isotp_addr), + &isotp_addr); + } + break; + case CAN_J1939: + { + struct can_diag_j1939_addr j1939_addr; + + memset(&j1939_addr, 0, sizeof(j1939_addr)); + + j1939_addr.name = can_addr.can_addr.j1939.name; + j1939_addr.pgn = can_addr.can_addr.j1939.pgn; + j1939_addr.addr = can_addr.can_addr.j1939.addr; + + err = nla_put(skb, CAN_DIAG_J1939_ADDR, sizeof(struct can_diag_j1939_addr), + &j1939_addr); + } + break; + default: + break; + } + + if (err < 0) + goto cancel_nlmsg_err; + +exit_no_err: + nlmsg_end(skb, nlh); + return 0; + +cancel_nlmsg_err: + nlmsg_cancel(skb, nlh); + return err; +} + +static int can_diag_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + int num = 0, s_num = cb->args[0]; + struct can_diag_req *req; + struct net *net; + struct sock *sk; + + net = sock_net(skb->sk); + req = nlmsg_data(cb->nlh); + + lock_can_diag_mutex(net); + sk_for_each(sk, &net->can.sklist) { + if (num < s_num) + goto next; + + if (sk_diag_fill(sk, skb, req, + sk_user_ns(NETLINK_CB(cb->skb).sk), + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + sock_i_ino(sk)) < 0) + goto done; +next: + num++; + } +done: + unlock_can_diag_mutex(net); + cb->args[0] = num; + + return skb->len; +} + +static int can_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h) +{ + int hdrlen = sizeof(struct can_diag_req); + struct net *net = sock_net(skb->sk); + struct can_diag_req *req; + + if (nlmsg_len(h) < hdrlen) + return -EINVAL; + + req = nlmsg_data(h); + if (req->sdiag_protocol) + return -EINVAL; + + if (h->nlmsg_flags & NLM_F_DUMP) { + struct netlink_dump_control c = { + .dump = can_diag_dump + }; + return netlink_dump_start(net->diag_nlsk, skb, h, &c); + } + + return -EOPNOTSUPP; +} + +static const struct sock_diag_handler can_diag_handler = { + .owner = THIS_MODULE, + .family = AF_CAN, + .dump = can_diag_handler_dump, +}; + +static int __init can_diag_init(void) +{ + pr_info("can: diagnostic module\n"); + return sock_diag_register(&can_diag_handler); +} + +static void __exit can_diag_exit(void) +{ + sock_diag_unregister(&can_diag_handler); +} + +module_init(can_diag_init); +module_exit(can_diag_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("CAN socket monitoring via SOCK_DIAG"); +MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, AF_CAN); -- 2.55.0