/* * Copyright (C) 2015 Gilles Chanteperdrix * * SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smokey_net.h" #include "smokey_net_server.h" struct module { int option; const char *name; }; #define TIMEOUT 10 static struct rtnet_core_cmd cmd; static int fd; static pthread_t loopback_server_tid; static bool loopback_thread_created, ifup; static struct module modules[] = { { .name = "rtnet", }, { .name = "rtipv4", }, { .name = "rtcfg", }, { .option = _CC_COBALT_NET_UDP, .name = "rtudp", }, { .option = _CC_COBALT_NET_AF_PACKET, .name = "rtpacket", }, { .name = NULL, /* driver */ }, }; #define MODID_RTNET 0 #define MODID_IPV4 1 #define MODID_CFG 2 #define MODID_UDP 3 #define MODID_PACKET 4 #define MODID_DRIVER 5 static int option_to_modid(int option) { unsigned i; for (i = 0; i < sizeof(modules)/sizeof(modules[0]); i++) { if (modules[i].option != option) continue; return i; } return -1; } static int get_info(const char *intf) { int err; err = smokey_check_errno( snprintf(cmd.head.if_name, sizeof(cmd.head.if_name), "%s", intf)); if (err < 0) return err; cmd.args.info.ifindex = 0; err = smokey_check_errno(ioctl(fd, IOC_RT_IFINFO, &cmd)); if (err < 0) return err; return 0; } static int do_up(const char *intf) { int err; snprintf(cmd.head.if_name, sizeof(cmd.head.if_name), "%s", intf); cmd.args.info.ifindex = 0; if (strcmp(intf, "rtlo")) { cmd.args.up.ip_addr = 0xffffffff; cmd.args.up.broadcast_ip = cmd.args.up.ip_addr; } else { cmd.args.up.ip_addr = htonl(0x7f000001); /* 127.0.0.1 */ cmd.args.up.broadcast_ip = cmd.args.up.ip_addr | ~0x000000ff; } cmd.args.up.set_dev_flags = 0; cmd.args.up.clear_dev_flags = 0; cmd.args.up.dev_addr_type = 0xffff; err = smokey_check_errno(ioctl(fd, IOC_RT_IFUP, &cmd)); if (err < 0) return err; return 0; } static int do_down(const char *intf) { int err; snprintf(cmd.head.if_name, sizeof(cmd.head.if_name), "%s", intf); cmd.args.info.ifindex = 0; err = smokey_check_errno(ioctl(fd, IOC_RT_IFDOWN, &cmd)); if (err < 0) return err; return 0; } static int smokey_net_modprobe(int modid, bool silent) { struct module *m = modules + modid; if (modid < 0) return -EINVAL; return smokey_modprobe(m->name, silent); } static int smokey_net_rmmod(int modid) { struct module *m = modules + modid; return smokey_rmmod(m->name); } static int smokey_net_setup_rtcfg_client(const char *intf, int net_config) { struct rtcfg_cmd cmd; int err; if ((net_config & _CC_COBALT_NET_CFG) == 0) return -ENOSYS; err = smokey_net_modprobe(MODID_CFG, false); if (err < 0) return err; memset(&cmd, 0, sizeof(cmd)); err = smokey_check_errno( snprintf(cmd.head.if_name, sizeof(cmd.head.if_name), intf, IFNAMSIZ)); if (err < 0) return err; cmd.args.client.timeout = 10000; cmd.args.client.max_stations = 32; cmd.args.client.buffer_size = 0; err = smokey_check_errno(ioctl(fd, RTCFG_IOC_CLIENT, &cmd)); if (err < 0) return err; cmd.args.announce.timeout = 5000; cmd.args.announce.buffer_size = 0; cmd.args.announce.flags = 0; cmd.args.announce.burstrate = 4; err = smokey_check_errno(ioctl(fd, RTCFG_IOC_ANNOUNCE, &cmd)); if (err < 0) return err; return 0; } static int smokey_net_teardown_rtcfg(const char *intf) { struct rtcfg_cmd cmd; int err; memset(&cmd, 0, sizeof(cmd)); err = smokey_check_errno( snprintf(cmd.head.if_name, sizeof(cmd.head.if_name), intf, IFNAMSIZ)); if (err < 0) return err; /* * We may or may not be acting as a server; don't check the * status. */ ioctl(fd, RTCFG_IOC_DETACH, &cmd); return smokey_net_rmmod(MODID_CFG); } static int find_peer(const char *intf, void *vpeer) { struct sockaddr_in *in_peer = vpeer; struct sockaddr_ll *ll_peer = vpeer; struct sockaddr *peer = vpeer; char buf[4096]; char hash[3]; char dest[16]; char mac[18]; char dev[16]; FILE *f; int err; f = fopen("/proc/rtnet/ipv4/host_route", "r"); if (!f) { err = -errno; smokey_warning("open(/proc/rtnet/ipv4/host_route): %s", strerror(-err)); return err; } /* Skip first line */ if (!fgets(buf, sizeof(buf), f)) { err = -errno; smokey_warning("fgets(/proc/rtnet/ipv4/host_route): %s", strerror(-err)); goto err; } for(;;) { err = fscanf(f, "%s\t%s\t%s\t%s\n", hash, dest, mac, dev); if (err == EOF) { smokey_warning("No peer found\n"); err = -ENOENT; goto err; } if (err < 4) { smokey_warning("Error parsing" " /proc/rtnet/ipv4/host_route\n"); err = -EINVAL; goto err; } if (strcmp(dev, intf)) continue; if (strcmp(mac, "FF:FF:FF:FF:FF:FF") == 0) continue; if (strcmp(dest, "255.255.255.255") == 0) continue; if (strcmp(dest, "0.0.0.0") == 0) continue; break; } switch(peer->sa_family) { case AF_INET: err = smokey_check_errno( inet_pton(AF_INET, dest, &in_peer->sin_addr)); if (err < 0) goto err; break; case AF_PACKET: { const unsigned eth_alen = 6; struct ether_addr eth; struct ifreq ifr; int sock; ll_peer->sll_halen = eth_alen; if (ether_aton_r(mac, ð) == 0) { err = -errno; smokey_warning("ether_aton_r(%s): %m", mac); goto err; } memcpy(&ll_peer->sll_addr[0], eth.ether_addr_octet, eth_alen); snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", dev); err = smokey_check_errno( __RT(socket(PF_PACKET, SOCK_DGRAM, 0))); if (err < 0) goto err; sock = err; err = smokey_check_errno(__RT(ioctl(sock, SIOCGIFINDEX, &ifr))); sock = smokey_check_errno(__RT(close(sock))); if (err < 0) goto err; if (sock < 0) { err = sock; goto err; } ll_peer->sll_ifindex = ifr.ifr_ifindex; } } err = 0; err: fclose(f); return err; } int smokey_net_server_check_inner(const char *file, int line, const char *msg, int status) { if (status >= 0) return status; __smokey_warning(file, line, "%s: %s", msg, strerror(-status)); pthread_exit((void *)(long)status); } static void *loopback_server(void *cookie) { int net_config = (long)cookie; smokey_net_server_loop(net_config); return NULL; } int smokey_net_setup(const char *driver, const char *intf, int tested_config, void *vpeer) { int net_config, err, i, err_teardown; struct sockaddr_in *in_peer = vpeer; struct sockaddr *peer = vpeer; /* * Main module needs to be loaded in order to use * _CC_COBALT_GET_NET_CONFIG. */ smokey_net_modprobe(MODID_RTNET, true); err = cobalt_corectl(_CC_COBALT_GET_NET_CONFIG, &net_config, sizeof(net_config)); if (err == -EINVAL) return -ENOSYS; if (err < 0) return err; if ((net_config & (_CC_COBALT_NET | _CC_COBALT_NET_IPV4)) != (_CC_COBALT_NET | _CC_COBALT_NET_IPV4)) return -ENOSYS; if ((net_config & tested_config) == 0) return -ENOSYS; modules[MODID_DRIVER].name = driver; err = smokey_net_modprobe(MODID_DRIVER, false); if (err < 0) return err; err = smokey_net_modprobe(MODID_IPV4, false); if (err < 0) return err; err = smokey_net_modprobe(option_to_modid(tested_config), false); if (err < 0) return err; fd = smokey_check_errno(open("/dev/rtnet", O_RDWR)); if (fd < 0) return fd; err = get_info(intf); if (err < 0) goto err; if ((cmd.args.info.flags & IFF_UP) == 0) { err = do_up(intf); if (err < 0) goto err; ifup = true; } smokey_trace("Waiting for interface %s to be running", intf); for (i = 0; i < 30; i++) { err = get_info(intf); if (err < 0) goto err; if ((cmd.args.info.flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) goto running; sleep(1); } smokey_warning("Interface is not running," " giving up (cable unplugged?)"); err = -ETIMEDOUT; goto err; running: err = get_info(intf); if (err < 0) goto err; if (cmd.args.info.ip_addr == 0) { err = smokey_net_setup_rtcfg_client(intf, net_config); if (err < 0) goto err; } if (strcmp(driver, "rt_loopback") == 0) { err = smokey_check_status( __RT(pthread_create(&loopback_server_tid, NULL, loopback_server, (void *)(long)tested_config))); if (err < 0) goto err; loopback_thread_created = true; } switch (peer->sa_family) { case AF_INET: if (in_peer->sin_addr.s_addr == htonl(INADDR_ANY) && strcmp(driver, "rt_loopback") == 0) { in_peer->sin_addr.s_addr = cmd.args.info.ip_addr; break; } /* Fallthrough wanted */ case AF_PACKET: err = find_peer(intf, vpeer); if (err < 0) goto err; } close(fd); return 0; err: close(fd); err_teardown = smokey_net_teardown(driver, intf, tested_config); if (err == 0) err = err_teardown; return err; } int smokey_net_teardown(const char *driver, const char *intf, int tested_config) { int err = 0, tmp; if (loopback_thread_created) { void *status; pthread_cancel(loopback_server_tid); /* May fail */ tmp = smokey_check_errno( pthread_join(loopback_server_tid, &status)); if (err == 0) err = tmp; if (err == 0 && status != PTHREAD_CANCELED) err = (long)status; } tmp = smokey_check_errno(open("/dev/rtnet", O_RDWR)); if (tmp >= 0) { fd = tmp; if (strcmp(driver, "rt_loopback")) { tmp = smokey_net_teardown_rtcfg(intf); if (err == 0) err = tmp; } if (ifup) { tmp = do_down(intf); if (err == 0) err = tmp; } close(fd); } else err = tmp; tmp = smokey_net_rmmod(option_to_modid(tested_config)); if (err == 0) err = tmp; tmp = smokey_net_rmmod(MODID_DRIVER); if (err == 0) err = tmp; tmp = smokey_net_rmmod(MODID_IPV4); if (err == 0) err = tmp; tmp = smokey_net_rmmod(MODID_RTNET); if (err == 0) err = tmp; return err; }