/* * Copyright (c) 2016, 2017, 2018 Jonas 'Sortie' Termansen. * Copyright (c) 2021 Juhani 'nortti' Krekelä. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * ifconfig.c * Configure network interface. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct if_all { struct if_info info; struct if_status status; struct if_config config; }; static void link_id_print(const struct if_all* all, const void* ptr) { (void) all; unsigned int id = *(const unsigned int*) ptr; printf("%u", id); } static void link_type_print(const struct if_all* all, const void* ptr) { (void) all; int type = *(const int*) ptr; const char* type_string; switch ( type ) { case IF_TYPE_ETHERNET: type_string = "ether"; break; case IF_TYPE_LOOPBACK: type_string = "loopback"; break; default: type_string = "unknown"; break; } fputs(type_string, stdout); } static void link_up_print(const struct if_all* all, const void* ptr) { (void) all; int flags = *(const int*) ptr; fputs(flags & IF_STATUS_FLAGS_UP ? "yes" : "no", stdout); } static void link_name_print(const struct if_all* all, const void* ptr) { (void) all; const char* name = (const char*) ptr; fputs(name, stdout); } static void ether_address_print(const struct if_all* all, const void* ptr) { (void) all; struct ether_addr* addr = (struct ether_addr*) ptr; printf("%02x:%02x:%02x:%02x:%02x:%02x", addr->ether_addr_octet[0], addr->ether_addr_octet[1], addr->ether_addr_octet[2], addr->ether_addr_octet[3], addr->ether_addr_octet[4], addr->ether_addr_octet[5]); } static void ether_hwaddress_print(const struct if_all* all, const void* ptr) { struct ether_addr hwaddr; memcpy(&hwaddr, ptr, sizeof(hwaddr)); ether_address_print(all, &hwaddr); } static bool mac_parse(struct ether_addr* addr, const char* string) { for ( size_t i = 0; i < 6; i++ ) { int upper; if ( '0' <= string[i*3 + 0] && string[i*3 + 0] <= '9' ) upper = string[i*3 + 0] - '0'; else if ( 'a' <= string[i*3 + 0] && string[i*3 + 0] <= 'f' ) upper = string[i*3 + 0] - 'a' + 10; else if ( 'A' <= string[i*3 + 0] && string[i*3 + 0] <= 'F' ) upper = string[i*3 + 0] - 'A' + 10; else return false; int lower; if ( '0' <= string[i*3 + 1] && string[i*3 + 1] <= '9' ) lower = string[i*3 + 1] - '0'; else if ( 'a' <= string[i*3 + 1] && string[i*3 + 1] <= 'f' ) lower = string[i*3 + 1] - 'a' + 10; else if ( 'A' <= string[i*3 + 1] && string[i*3 + 1] <= 'F' ) lower = string[i*3 + 1] - 'A' + 10; else return false; if ( string[i*3 + 2] != (i + 1 != 6 ? ':' : '\0') ) return false; addr->ether_addr_octet[i] = upper << 4 | lower; } return true; } static bool ether_address_parse(const struct if_all* all, void* ptr, const char* string) { struct ether_addr* addr = (struct ether_addr*) ptr; if ( !strcmp(string, "default") ) { memcpy(addr, all->info.addr, sizeof(*addr)); return true; } return mac_parse(addr, string); } static void inet_address_print(const struct if_all* all, const void* ptr) { (void) all; char addr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, ptr, addr, sizeof(addr)); fputs(addr, stdout); } static bool inet_address_parse(const struct if_all* all, void* ptr, const char* string) { (void) all; return inet_pton(AF_INET, string, ptr) == 1; } #define ARRAY_LENGTH(array) (sizeof(array) / sizeof((array)[0])) struct configuration { const char* name; size_t offset; void (*print)(const struct if_all*, const void*); bool (*parse)(const struct if_all*, void*, const char*); bool hidden; }; #define INFOOFFSET(parameter) \ (offsetof(struct if_all, info) + \ offsetof(struct if_info, parameter)) #define STATUSOFFSET(parameter) \ (offsetof(struct if_all, status) + \ offsetof(struct if_status, parameter)) #define CONFIGOFFSET(protocol, parameter) \ (offsetof(struct if_all, config) + \ offsetof(struct if_config, protocol) + \ offsetof(struct if_config_##protocol, parameter)) struct configuration link_configurations[] = { { "up", STATUSOFFSET(flags), link_up_print, NULL, false }, { "type", INFOOFFSET(type), link_type_print, NULL, false }, { "id", INFOOFFSET(linkid), link_id_print, NULL, false }, { "name", INFOOFFSET(name), link_name_print, NULL, true }, }; struct configuration ether_configurations[] = { { "address", CONFIGOFFSET(ether, address), ether_address_print, ether_address_parse, false }, { "hwaddress", INFOOFFSET(addr), ether_hwaddress_print, NULL, false }, }; struct configuration loopback_configurations[] = { }; struct configuration inet_configurations[] = { { "address", CONFIGOFFSET(inet, address), inet_address_print, inet_address_parse, false }, { "router", CONFIGOFFSET(inet, router), inet_address_print, inet_address_parse, false }, { "subnet", CONFIGOFFSET(inet, subnet), inet_address_print, inet_address_parse, false }, }; struct protocol { const char* name; int link_type_value; struct configuration* configurations; size_t configurations_count; }; struct protocol protocols[] = { { "link", 0, link_configurations, ARRAY_LENGTH(link_configurations) }, { "ether", IF_TYPE_ETHERNET, ether_configurations, ARRAY_LENGTH(ether_configurations) }, { "loopback", IF_TYPE_LOOPBACK, loopback_configurations, ARRAY_LENGTH(loopback_configurations) }, { "inet", 0, inet_configurations, ARRAY_LENGTH(inet_configurations) }, }; static int filter_dev_netif(const struct dirent* entry) { char* path; if ( asprintf(&path, "/dev/%s", entry->d_name) < 0 ) err(1, "malloc"); // TODO: Open with O_STAT or some future extension that lets us properly // test whether this is a network interface before complaining we // couldn't open it. Otherwise it's annoying for non-root users to get // warnings about non-network-interfaces in /dev they aren't supposed // to be able to open. int fd = open(path, O_RDONLY | O_NOFOLLOW); if ( fd < 0 ) { struct stat st; if ( lstat(path, &st) < 0 ) { warn("stat: %s", path); free(path); return 0; } // TODO: Determine whether this is a network interface without having // access to the device. Otherwise non-root users will be warned // about non-network interfaces in /dev they're not supposed to be // able to access. if ( S_ISCHR(st.st_mode) ) warn("%s", path); free(path); return 0; } free(path); int type = ioctl(fd, IOCGETTYPE); close(fd); return IOC_TYPE(type) == IOC_TYPE_NETWORK_INTERFACE; } enum specifier_type { ETHER, ETHERHW, INET, ID }; struct if_specifier { enum specifier_type type; union { struct ether_addr mac_addr; struct in_addr ipv4_addr; unsigned int id; }; }; static bool parse_specifier(struct if_specifier* specifier, const char* string) { if ( !strncmp(string, "ether:", strlen("ether:")) ) { specifier->type = ETHER; return mac_parse(&specifier->mac_addr, string + strlen("ether:")); } else if ( !strncmp(string, "etherhw:", strlen("etherhw:")) ) { specifier->type = ETHERHW; return mac_parse(&specifier->mac_addr, string + strlen("etherhw:")); } else if ( !strncmp(string, "inet:", strlen("inet:")) ) { specifier->type = INET; return inet_pton(AF_INET, string + strlen("inet:"), &specifier->ipv4_addr) == 1; } else if ( !strncmp(string, "id:", strlen("id:")) ) { specifier->type = ID; const char* idstr = string + strlen("id:"); char* end; errno = 0; unsigned long ulong = strtoul(idstr, &end, 10); if ( errno || !*idstr || *end || ulong > UINT_MAX ) return false; specifier->id = ulong; return true; } return false; } static int find_interface(const char* specifier_string, char if_name[IF_NAMESIZE]) { struct if_specifier specifier; if ( !parse_specifier(&specifier, specifier_string) ) errx(1, "Invalid interface specifier: %s", specifier_string); struct if_nameindex* ifs = if_nameindex(); if ( !ifs ) err(1, "if_nameindex"); int if_fd = -1; for ( size_t i = 0; ifs[i].if_index || ifs[i].if_name; i++ ) { const char* name = ifs[i].if_name; char* path; if ( asprintf(&path, "/dev/%s", name) < 0 ) err(1, "malloc"); int fd = open(path, O_RDONLY); if ( fd < 0 ) err(1, "%s", path); free(path); struct if_info ifinfo; struct if_config ifconfig; if ( ioctl(fd, NIOC_GETINFO, &ifinfo) < 0 ) err(1, "%s: ioctl: NIOC_GETINFO", name); if ( ioctl(fd, NIOC_GETCONFIG, &ifconfig) < 0 ) err(1, "%s: ioctl: NIOC_GETCONFIG", name); bool match = false; switch ( specifier.type ) { case ETHER: match = ifinfo.type == IF_TYPE_ETHERNET && !memcmp(&specifier.mac_addr, &ifconfig.ether.address, sizeof(struct ether_addr)); break; case ETHERHW: match = ifinfo.type == IF_TYPE_ETHERNET && !memcmp(&specifier.mac_addr, &ifinfo.addr, sizeof(struct ether_addr)); break; case INET: match = !memcmp(&specifier.ipv4_addr, &ifconfig.inet.address, sizeof(struct in_addr)); break; case ID: match = ifinfo.linkid == specifier.id; break; } // Ensure the specifier unambiguously matches an interface. if ( match && if_fd != -1 ) errx(1, "Ambiguous specifier; matches at least %s and %s: %s", if_name, name, specifier_string); if ( match ) { if_fd = fd; strlcpy(if_name, name, IF_NAMESIZE); } else close(fd); } if_freenameindex(ifs); if ( if_fd == -1 ) errx(1, "Specifier does not match any interfaces: %s", specifier_string); return if_fd; } static void compact_arguments(int* argc, char*** argv) { for ( int i = 0; i < *argc; i++ ) { while ( i < *argc && !(*argv)[i] ) { for ( int n = i; n < *argc; n++ ) (*argv)[n] = (*argv)[n+1]; (*argc)--; } } } int main(int argc, char* argv[]) { bool list = false; for ( int i = 1; i < argc; i++ ) { const char* arg = argv[i]; if ( arg[0] != '-' || !arg[1] ) continue; argv[i] = NULL; if ( !strcmp(arg, "--") ) break; if ( arg[1] != '-' ) { char c; while ( (c = *++arg) ) switch ( c ) { case 'l': list = true; break; default: errx(1, "unknown option -- '%c'", c); } } else errx(1, "unknown option: %s", arg); } compact_arguments(&argc, &argv); int devices_count = 1; struct dirent** devices = NULL; if ( argc <= 1 ) { devices_count = scandir("/dev", &devices, filter_dev_netif, alphasort); if ( devices_count < 0 ) err(1, "scandir: /dev"); } int result = 0; for ( int d = 0; d < devices_count; d++ ) { char if_name[IF_NAMESIZE]; const char* name = devices ? devices[d]->d_name : argv[1]; int fd; if ( strchr(name, '/') ) fd = open(name, O_RDONLY); else if ( strchr(name, ':') ) { fd = find_interface(name, if_name); name = if_name; } else { char* path; if ( asprintf(&path, "/dev/%s", name) < 0 ) err(1, "malloc"); fd = open(path, O_RDONLY); free(path); } if ( fd < 0 ) err(1, "%s", name); struct if_all all; if ( ioctl(fd, NIOC_GETINFO, &all.info) < 0 ) err(1, "%s: ioctl: NIOC_GETINFO", name); if ( ioctl(fd, NIOC_GETSTATUS, &all.status) < 0 ) err(1, "%s: ioctl: NIOC_GETSTATUS", name); if ( ioctl(fd, NIOC_GETCONFIG, &all.config) < 0 ) err(1, "%s: ioctl: NIOC_GETCONFIG", name); if ( list && argc == 1 ) { puts(name); continue; } else if ( list && (argc == 2 || argc == 3) ) { bool found = false; for ( size_t i = 0; i < ARRAY_LENGTH(protocols); i++ ) { struct protocol* protocol = &protocols[i]; if ( 3 <= argc && strcmp(protocol->name, argv[2]) != 0 ) continue; if ( protocol->link_type_value && all.info.type != protocol->link_type_value ) { if ( 3 <= argc ) errx(1, "%s: %s: Interface does not support protocol", name, argv[2]); continue; } if ( argc < 3 ) { puts(protocol->name); continue; } found = true; for ( size_t j = 0; j < protocol->configurations_count; j++ ) puts(protocol->configurations[j].name); } if ( 3 <= argc && !found ) errx(1, "%s: %s: No such protocol", name, argv[2]); continue; } else if ( argc <= 2 ) { printf("%s:\n", name); for ( size_t i = 0; i < ARRAY_LENGTH(protocols); i++ ) { struct protocol* protocol = &protocols[i]; if ( protocol->link_type_value && all.info.type != protocol->link_type_value ) continue; putchar('\t'); fputs(protocol->name, stdout); for ( size_t j = 0; j < protocol->configurations_count; j++ ) { struct configuration* configuration = &protocol->configurations[j]; if ( configuration->hidden ) continue; putchar(' '); fputs(configuration->name, stdout); putchar(' '); void* ptr = ((char*) &all + configuration->offset); configuration->print(&all, ptr); } putchar('\n'); } continue; } struct protocol* protocol = NULL; for ( int i = 2; i < argc; ) { const char* operand = argv[i++]; bool found = false; for ( size_t n = 0; protocol && n < protocol->configurations_count; n++ ) { struct configuration* configuration = &protocol->configurations[n]; if ( strcmp(operand, configuration->name) != 0 ) continue; found = true; void* ptr = ((char*) &all + configuration->offset); if ( list ) { configuration->print(&all, ptr); putchar('\n'); } else { if ( !configuration->parse ) errx(1, "%s: %s: %s: Configuration is read-only", name, protocol->name, operand); if ( i == argc ) errx(1, "%s: %s: %s: Expected parameter", name, protocol->name, operand); const char* parameter = argv[i++]; if ( !configuration->parse(&all, ptr, parameter) ) errx(1, "%s: %s: %s: Invalid value: %s", name, protocol->name, operand, parameter); } } for ( size_t n = 0; !found && n < ARRAY_LENGTH(protocols); n++ ) { struct protocol* new_protocol = &protocols[n]; if ( strcmp(operand, new_protocol->name) != 0 ) continue; if ( new_protocol->link_type_value && all.info.type != new_protocol->link_type_value ) errx(1, "%s: %s: Interface does not support protocol", name, operand); found = true; protocol = new_protocol; } if ( !found ) { if ( !protocol ) errx(1, "%s: %s: No such protocol", name, operand); errx(1, "%s: %s: No such protocol or configuration of protocol " "%s", name, operand, protocol->name); } } if ( !list && ioctl(fd, NIOC_SETCONFIG, &all.config) < 0 ) err(1, "%s: ioctl: NIOC_SETCONFIG", name); close(fd); } if ( ferror(stdout) || fflush(stdout) == EOF ) err(1, "stdout"); return result; }