From 7814d59e38cc4839c167a179742342ab4ef030bf Mon Sep 17 00:00:00 2001 From: I-HSIN Cheng <57989092+vax-r@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:15:52 +0800 Subject: [PATCH] Introduce a userspace tool (#53) This commit introduces a userspace tool, vwifi-tool, which can display the status of the driver and allows users to specify a blocklist. This tool will show the status of the vwifi driver by reading from /sys/module/vwifi/initstate. Users can only set a blocklist for the vwifi driver when it is loaded. Users can run vwifi-tool with command-line options, parsed by getopt(), to set or unset a user-specific blocklist pair. The blocklist will be copied and sent to the vwifi driver via a netlink socket. Close #48 --- Makefile | 10 ++- README.md | 63 +++++++++++++++++ vwifi-tool.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++ vwifi.c | 102 ++++++++++++++++++++++++++- 4 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 vwifi-tool.c diff --git a/Makefile b/Makefile index f7e4693..1546ba5 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,17 @@ ccflags-y := -std=gnu99 -Wno-declaration-after-statement KDIR ?= /lib/modules/$(shell uname -r)/build GIT_HOOKS := .git/hooks/applied -all: +all: kmod vwifi-tool + +kmod: $(MAKE) -C $(KDIR) M=$(shell pwd) modules +vwifi-tool: vwifi-tool.c + $(CC) $(ccflags-y) -o $@ $< + clean: $(MAKE) -C $(KDIR) M=$(shell pwd) clean + $(RM) vwifi-tool check: all - @scripts/verify.sh + @scripts/verify.sh \ No newline at end of file diff --git a/README.md b/README.md index 6a0ff90..b03b35c 100644 --- a/README.md +++ b/README.md @@ -344,7 +344,70 @@ PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. 4 packets transmitted, 4 received, 0% packet loss, time 3058ms rtt min/avg/max/mdev = 0.054/0.141/0.342/0.117 ms ``` +### vwifi-tool +A userspace tool which supports more user-specific utilization for vwifi. +Aiming to provide more flexibility and customization for users of vwifi. +Currently supporting feature: +* display the status of vwifi driver +* Use netlink socket to communicate with vwifi driver allowing user to configure user-specific block list +#### Status checking +We can use `vwifi-tool` to check the status of vwifi driver by executing the following command: +``` +$ ./vwifi-tool +``` +If vwifi is loaded into kernel, you should see the following output: +``` +vwifi status : live +``` +Otherwise, vwifi isn't loaded into kernel yet, the output will be: +``` +vwifi status : not loaded +``` + +#### Blocklist test +vwifi also supports blocklist ability to allow some interfaces to block packets from certain interfaces. +We can use `vwifi-tool` to set or unset blocklist for vwifi, multiple options are explained as below +* `-d` : specify the destination interface for a blocklist pair +* `-s` : specify the source interface for a blocklist pair +* `-c` : `1` means to unset the blocklist in vwifi, default as `0` + +Set the blocklist pair using vwifi-tool like the following +``` +$ ./vwifi-tool -d owl2 -s owl1 +``` +You should see the following output, including your blocklist which will be sent to vwifi +``` +vwifi status : live +blocklist: +owl2 blocks owl1 +Configuring blocklist for vwifi... +Message from vwifi: vwifi has received your blocklist +``` +Then you can try to do the ping test again +``` +$ sudo ip netns exec ns1 ping -c 4 10.0.0.3 +``` +You should see the following output: +``` +PING 10.0.0.3 (10.0.0.3) 56(84) bytes of data. + +--- 10.0.0.3 ping statistics --- +4 packets transmitted, 0 received, 100% packet loss, time 3053ms +``` +You can adjust the content of your blacklist and load it into vwifi anytime. + +If you want to unset the blocklist in vwifi, simply add the option `-c` with vwifi-tool +``` +$ ./vwifi-tool -c +``` +You'll see the following output +``` +vwifi status : live +Unset blocklist for vwifi... +Configuring blocklist for vwifi... +Message from vwifi: vwifi has received your blocklist +``` ## Testing environment (virtio) Below is our testing environment with virtio feature: diff --git a/vwifi-tool.c b/vwifi-tool.c new file mode 100644 index 0000000..e4dfa12 --- /dev/null +++ b/vwifi-tool.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PAYLOAD 1024 +#define LINE_LENGTH 20 +#define MAX_BLOCKLIST_PAIR 5 +#define VWIFI_STATUS_FILE "/sys/module/vwifi/initstate" + + +/* The function aims to check the status of vwifi kernel module */ +bool vwifi_status_check() +{ + FILE *fp = fopen(VWIFI_STATUS_FILE, "r"); + if (!fp) { + printf("vwifi status : not loaded\n"); + return false; + } + + char read_buf[LINE_LENGTH]; + fgets(read_buf, LINE_LENGTH, fp); + read_buf[strcspn(read_buf, "\n")] = + 0; /* Remove newline character from string */ + if (!strcmp("live", read_buf)) + printf("vwifi status : live\n"); + else { + printf("vwifi status : %s\n", read_buf); + return false; + } + return true; +} + +/* Check if command line options are specified */ +bool opt_set(int d, int s, int c) +{ + return d || s || c; +} + +/* Check whether the number of source interfaces matches with the number of + * destination interfaces */ +bool blocklist_pair_check(int src_len, int dest_len) +{ + return src_len == dest_len; +} + +/* Copy destination and source interface pair into blocklist buffer */ +bool blocklist_make(char *blocklist, + char *dest[], + char *src[], + int blocklist_len) +{ + for (int i = 0; i < blocklist_len; i++) { + char tmp[LINE_LENGTH] = {'\0'}; + snprintf(tmp, LINE_LENGTH, "%s %s %s\n", dest[i], "blocks", src[i]); + if (strlen(tmp) + strlen(blocklist) < NLMSG_SPACE(MAX_PAYLOAD)) + strcat(blocklist, tmp); + else { + printf( + "Error: Blocklist size exceeds the maximum size of buffer\n"); + return false; + } + } + return true; +} + +/* Send blocklist to kernel using netlink socket */ +bool blocklist_send(char *blocklist) +{ + int sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USERSOCK); + if (sock_fd < 0) { + printf("Error: Can't open socket\n"); + return false; + } + + struct sockaddr_nl src_addr = { + .nl_family = AF_NETLINK, + .nl_pid = getpid(), + }; + + bind(sock_fd, (struct sockaddr *) &src_addr, sizeof(src_addr)); + + struct sockaddr_nl dest_addr = { + .nl_family = AF_NETLINK, + .nl_pid = 0, + .nl_groups = 0, + }; + + struct nlmsghdr *nlh = + (struct nlmsghdr *) calloc(1, NLMSG_SPACE(MAX_PAYLOAD)); + nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); + nlh->nlmsg_pid = getpid(); + nlh->nlmsg_flags = 0; + + strncpy(NLMSG_DATA(nlh), blocklist, NLMSG_SPACE(MAX_PAYLOAD)); + + struct iovec iov = { + .iov_base = (void *) nlh, + .iov_len = nlh->nlmsg_len, + }; + + struct msghdr msg = { + .msg_name = (void *) &dest_addr, + .msg_namelen = sizeof(dest_addr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + printf("Configuring blocklist for vwifi...\n"); + sendmsg(sock_fd, &msg, 0); + + recvmsg(sock_fd, &msg, 0); + printf("Message from vwifi: %s\n", (char *) NLMSG_DATA(nlh)); + + close(sock_fd); + + return true; +} + +int main(int argc, char *argv[]) +{ + /* Get opt arguments from command line to configure blocklist */ + char *dest[MAX_BLOCKLIST_PAIR], *src[MAX_BLOCKLIST_PAIR], + blocklist_pair[MAX_BLOCKLIST_PAIR][LINE_LENGTH]; + int blocklist_len = 0, dest_len = 0, src_len = 0, clear = 0; + int c; + + while ((c = getopt(argc, argv, "d:s:ch")) != -1) { + switch (c) { + case 'd': + dest[dest_len++] = optarg; + break; + case 's': + src[src_len++] = optarg; + break; + case 'c': + clear = 1; + break; + case 'h': + printf( + "vwifi-tool: A userspace tool which supports more " + "user-specific utilization for vwifi\n\n"); + printf("Usage:\n\n"); + printf("\tvwifi-tool [arguments]\n\n"); + printf("The arguments are:\n\n"); + printf("\t-d Destination interface name\n"); + printf("\t-s Source interface name\n"); + printf("\t-c Clear blocklist\n"); + return 0; + default: + printf("Invalid arguments\n"); + break; + } + } + + if (!vwifi_status_check()) + exit(1); + + /* When no options are specified, simply display the status of vwifi */ + if (!opt_set(dest_len, src_len, clear)) + return 0; + + if (!clear && !blocklist_pair_check(src_len, dest_len)) { + printf("Destination number doesn't match with Source number\n"); + exit(1); + } + + blocklist_len = + clear ? 0 + : (dest_len < MAX_BLOCKLIST_PAIR ? dest_len : MAX_BLOCKLIST_PAIR); + + /* Copy blocklist pair into message buffer */ + char buffer[NLMSG_SPACE(MAX_PAYLOAD)]; + memset(buffer, '\0', sizeof(buffer)); + + if (!blocklist_make(buffer, dest, src, blocklist_len)) + exit(1); + + if (!clear) + printf("blocklist:\n%s", buffer); + + /* Send blocklist buffer to kernel */ + if (!blocklist_send(buffer)) + exit(1); + + return 0; +} diff --git a/vwifi.c b/vwifi.c index 49f9a89..98aff62 100644 --- a/vwifi.c +++ b/vwifi.c @@ -14,6 +14,9 @@ #include #include +#include +#include + MODULE_LICENSE("Dual MIT/GPL"); MODULE_AUTHOR("National Cheng Kung University, Taiwan"); MODULE_DESCRIPTION("virtual cfg80211 driver"); @@ -60,6 +63,7 @@ struct owl_context { enum vwifi_state state; /**< indicate the program state */ struct list_head vif_list; /**< maintaining all interfaces */ struct list_head ap_list; /**< maintaining multiple AP */ + char *blocklist; /**< maintaining the blocklist */ }; static DEFINE_SPINLOCK(vif_list_lock); @@ -152,6 +156,80 @@ MODULE_PARM_DESC(station, "Number of virtual interfaces running in STA mode."); /* Global context */ static struct owl_context *owl = NULL; +/* Blocklist content */ +#define MAX_BLACKLIST_SIZE 1024 + +static struct sock *nl_sk = NULL; + +static int blocklist_check(char *dest, char *source) +{ + if (!owl->blocklist || !*(owl->blocklist)) + return 0; + + char *user_input = + kmalloc(sizeof(char) * (strlen(owl->blocklist) + 1), GFP_KERNEL); + strncpy(user_input, owl->blocklist, strlen(owl->blocklist)); + + char *token = strsep(&user_input, "\n"); + while (token) { + char *blacklist_dest = strsep(&token, " "); + strsep(&token, " "); + char *blacklist_source = token; + if (!strcmp(dest, blacklist_dest) && + !strcmp(source, blacklist_source)) { + kfree(user_input); + return 1; + } + token = strsep(&user_input, "\n"); + } + kfree(user_input); + + return 0; +} + +static void blocklist_load(char *blist) +{ + if (!owl->blocklist) { + pr_info("owl->blocklist have to be kmalloc first\n"); + return; + } + memset(owl->blocklist, '\0', MAX_BLACKLIST_SIZE); /* clear the blocklist */ + strncpy(owl->blocklist, blist, strlen(blist)); +} + +static void blocklist_nl_recv(struct sk_buff *skb) +{ + struct nlmsghdr *nlh; /* netlink message header */ + int pid; + struct sk_buff *skb_out; + char *msg = "vwifi has received your blocklist"; + int msg_size = strlen(msg); + + nlh = (struct nlmsghdr *) skb->data; + + blocklist_load((char *) nlmsg_data(nlh)); + + /* pid of sending process */ + pid = nlh->nlmsg_pid; + + skb_out = nlmsg_new(msg_size, 0); + if (!skb_out) { + pr_info("netlink: Failed to allocate new skb\n"); + return; + } + + nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0); + NETLINK_CB(skb_out).dst_group = 0; /* unicast group */ + strncpy(nlmsg_data(nlh), msg, msg_size); + + if (nlmsg_unicast(nl_sk, skb_out, pid) < 0) + pr_info("netlink: Error while sending back to user\n"); +} + +static struct netlink_kernel_cfg nl_config = { + .input = blocklist_nl_recv, +}; + /** * enum virtio_vqs - queues for virtio frame transmission and receivement * @@ -717,6 +795,13 @@ static netdev_tx_t owl_ndo_start_xmit(struct sk_buff *skb, } /* TX by interface of AP mode */ else if (vif->wdev.iftype == NL80211_IFTYPE_AP) { + /* Find the source interface */ + struct owl_vif *src_vif; + list_for_each_entry (src_vif, &vif->bss_list, bss_list) { + if (ether_addr_equal(eth_hdr->h_source, src_vif->ndev->dev_addr)) + break; + } + /* Check if the packet is broadcasting */ if (is_broadcast_ether_addr(eth_hdr->h_dest)) { list_for_each_entry (dest_vif, &vif->bss_list, bss_list) { @@ -726,6 +811,10 @@ static netdev_tx_t owl_ndo_start_xmit(struct sk_buff *skb, dest_vif->ndev->dev_addr)) continue; + /* Don't send packet from dest_vif's blocklist */ + if (blocklist_check(dest_vif->ndev->name, src_vif->ndev->name)) + continue; + if (__owl_ndo_start_xmit(vif, dest_vif, skb)) count++; } @@ -735,7 +824,9 @@ static netdev_tx_t owl_ndo_start_xmit(struct sk_buff *skb, list_for_each_entry (dest_vif, &vif->bss_list, bss_list) { if (ether_addr_equal(eth_hdr->h_dest, dest_vif->ndev->dev_addr)) { - if (__owl_ndo_start_xmit(vif, dest_vif, skb)) + if (!blocklist_check(dest_vif->ndev->name, + src_vif->ndev->name) && + __owl_ndo_start_xmit(vif, dest_vif, skb)) count++; break; } @@ -1788,6 +1879,7 @@ static void owl_free(void) } spin_unlock_bh(&vif_list_lock); + kfree(owl->blocklist); kfree(owl); } @@ -2838,6 +2930,7 @@ static int __init vwifi_init(void) mutex_init(&owl->lock); INIT_LIST_HEAD(&owl->vif_list); INIT_LIST_HEAD(&owl->ap_list); + owl->blocklist = kmalloc(sizeof(char) * MAX_BLACKLIST_SIZE, GFP_KERNEL); for (int i = 0; i < station; i++) { struct wiphy *wiphy = owcfg80211_add(); @@ -2847,6 +2940,12 @@ static int __init vwifi_init(void) goto interface_add; } + nl_sk = netlink_kernel_create(&init_net, NETLINK_USERSOCK, &nl_config); + if (!nl_sk) { + pr_info("Error creating netlink socket\n"); + goto cfg80211_add; + } + err = register_virtio_driver(&virtio_vwifi); if (err) goto err_register_virtio_driver; @@ -2870,6 +2969,7 @@ static void __exit vwifi_exit(void) unregister_virtio_driver(&virtio_vwifi); owl_free(); + netlink_kernel_release(nl_sk); } module_init(vwifi_init);