From ce2c2765706a65e08804ab3005b4e9ce4d534b3c Mon Sep 17 00:00:00 2001 From: Jeremy Lakeman Date: Tue, 7 Nov 2017 16:04:57 +1030 Subject: [PATCH] Detect Wifi and cabled ethernet on linux systems, allow matching by type in config --- conf.h | 1 + conf_schema.c | 41 +++++++++++++++++++---- conf_schema.h | 3 +- constants.h | 8 +++-- overlay_interface.c | 79 +++++++++++++++++++++++++++------------------ overlay_interface.h | 6 ++-- overlay_mdp.c | 2 +- tests/config | 32 ++++++++++++++++++ 8 files changed, 127 insertions(+), 45 deletions(-) diff --git a/conf.h b/conf.h index ea27c9a2..887e6dc8 100644 --- a/conf.h +++ b/conf.h @@ -682,6 +682,7 @@ int cf_fmt_sid(const char **, const sid_t *sidp); int cf_opt_rhizome_bk(rhizome_bk_t *bkp, const char *text); int cf_fmt_rhizome_bk(const char **, const rhizome_bk_t *bkp); +const char * interface_type_name(short type); int cf_opt_interface_type(short *typep, const char *text); int cf_fmt_interface_type(const char **, const short *typep); diff --git a/conf_schema.c b/conf_schema.c index e0c62f18..8c949cc6 100644 --- a/conf_schema.c +++ b/conf_schema.c @@ -688,22 +688,36 @@ int cf_opt_interface_type(short *typep, const char *text) *typep = OVERLAY_INTERFACE_PACKETRADIO; return CFOK; } + if (strcasecmp(text, "any") == 0) { + *typep = OVERLAY_INTERFACE_ANY; + return CFOK; + } if (strcasecmp(text, "other") == 0) { + *typep = OVERLAY_INTERFACE_OTHER; + return CFOK; + } + if (strcasecmp(text, "unknown") == 0) { *typep = OVERLAY_INTERFACE_UNKNOWN; return CFOK; } return CFINVALID; } +const char * interface_type_name(short type){ + switch (type) { + case OVERLAY_INTERFACE_ETHERNET: return "ethernet"; + case OVERLAY_INTERFACE_WIFI: return "wifi"; + case OVERLAY_INTERFACE_PACKETRADIO: return "catear"; + case OVERLAY_INTERFACE_ANY: return "any"; + case OVERLAY_INTERFACE_OTHER: return "other"; + case OVERLAY_INTERFACE_UNKNOWN: return "unknown"; + } + return NULL; +} + int cf_fmt_interface_type(const char **textp, const short *typep) { - const char *t = NULL; - switch (*typep) { - case OVERLAY_INTERFACE_ETHERNET: t = "ethernet"; break; - case OVERLAY_INTERFACE_WIFI: t = "wifi"; break; - case OVERLAY_INTERFACE_PACKETRADIO: t = "catear"; break; - case OVERLAY_INTERFACE_UNKNOWN: t = "other"; break; - } + const char *t = interface_type_name(*typep); if (!t) return CFINVALID; *textp = str_edup(t); @@ -1004,6 +1018,19 @@ int cf_cmp_network_interface(const struct config_network_interface *a, const str int vld_network_interface(const struct cf_om_node *parent, struct config_network_interface *nifp, int result) { + if (nifp->match_type == OVERLAY_INTERFACE_UNKNOWN){ + int nodei_match_type = cf_om_get_child(parent, "match_type", NULL); + assert(nodei_match_type != -1); + cf_warn_node_value(parent->nodv[nodei_match_type], CFINVALID); + return result | CFSUB(CFINVALID); + } + if (nifp->type == OVERLAY_INTERFACE_ANY + || nifp->type == OVERLAY_INTERFACE_UNKNOWN){ + int nodei_type = cf_om_get_child(parent, "type", NULL); + assert(nodei_type != -1); + cf_warn_node_value(parent->nodv[nodei_type], CFINVALID); + return result | CFSUB(CFINVALID); + } if (nifp->match.patc != 0 && nifp->file[0]) { int nodei_match = cf_om_get_child(parent, "match", NULL); int nodei_file = cf_om_get_child(parent, "file", NULL); diff --git a/conf_schema.h b/conf_schema.h index bc0ffbaa..e70daaae 100644 --- a/conf_schema.h +++ b/conf_schema.h @@ -461,7 +461,8 @@ ATOM(struct in_addr, dummy_address, hton_in_addr(INADDR_LOOPBACK), in_a ATOM(struct in_addr, dummy_netmask, hton_in_addr(0xFFFFFF00), in_addr,, "Dummy interface netmask") ATOM(uint16_t, port, PORT_DNA, uint16_nonzero,, "Port number for network interface") ATOM(uint16_t, drop_packets, 0, uint16_nonzero,, "Percentage of incoming packets that should be dropped for testing purposes") -ATOM(short, type, OVERLAY_INTERFACE_WIFI, interface_type,, "Type of network interface") +ATOM(short, match_type, OVERLAY_INTERFACE_ANY, interface_type,, "Type of network interface to match against") +ATOM(short, type, OVERLAY_INTERFACE_WIFI, interface_type,, "Type of network interface for default configuration") ATOM(short, radiotype, RADIO_TYPE_RFD900, radio_type,, "Type of packet radio interface") SUB_STRUCT(mdp_iftype, broadcast,) SUB_STRUCT(mdp_iftype, unicast,) diff --git a/constants.h b/constants.h index 4fc05d13..8f5132a5 100644 --- a/constants.h +++ b/constants.h @@ -28,10 +28,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #define CRYPT_SIGNED 2 #define CRYPT_PUBLIC 4 -#define OVERLAY_INTERFACE_UNKNOWN 0 +// Interface types for matching or default config +// Note that the numeric value is also used to choose the best link #define OVERLAY_INTERFACE_ETHERNET 1 #define OVERLAY_INTERFACE_WIFI 2 -#define OVERLAY_INTERFACE_PACKETRADIO 3 +#define OVERLAY_INTERFACE_OTHER 3 +#define OVERLAY_INTERFACE_UNKNOWN 4 +#define OVERLAY_INTERFACE_ANY 5 +#define OVERLAY_INTERFACE_PACKETRADIO 6 #define RADIO_TYPE_RFD900 0 #define RADIO_TYPE_RFM69 1 diff --git a/overlay_interface.c b/overlay_interface.c index 97dd6c34..a6da3748 100644 --- a/overlay_interface.c +++ b/overlay_interface.c @@ -334,30 +334,15 @@ overlay_interface * overlay_interface_find_name_type(const char *name, int socke return NULL; } -static int interface_type_priority(int type) -{ - switch(type){ - case OVERLAY_INTERFACE_ETHERNET: - return 1; - case OVERLAY_INTERFACE_WIFI: - return 2; - case OVERLAY_INTERFACE_PACKETRADIO: - return 4; - } - return 3; -} - // Which interface is better for routing packets? // returns -1 to indicate the first is better, 0 for equal, 1 for the second int overlay_interface_compare(overlay_interface *one, overlay_interface *two) { if (one==two) return 0; - int p1 = interface_type_priority(one->ifconfig.type); - int p2 = interface_type_priority(two->ifconfig.type); - if (p1ifconfig.typeifconfig.type) return -1; - if (p2ifconfig.typeifconfig.type) return 1; return 0; } @@ -493,13 +478,10 @@ int overlay_destination_configure(struct network_destination *dest, const struct packet_interval = 100; break; case OVERLAY_INTERFACE_WIFI: + case OVERLAY_INTERFACE_OTHER: tick_ms = 500; packet_interval = 800; break; - case OVERLAY_INTERFACE_UNKNOWN: - tick_ms = 500; - packet_interval = 100; - break; } if (dest->ifconfig.tick_ms<0) @@ -555,7 +537,8 @@ int overlay_interface_configure(struct overlay_interface *interface, const struc * Returns -1 in case of error (misconfiguration or system error). */ int -overlay_interface_init(const char *name, +overlay_interface_init(const char *name, + short detected_type, const struct socket_address *addr, const struct socket_address *netmask, const struct socket_address *broadcast, @@ -580,7 +563,8 @@ overlay_interface_init(const char *name, interface->state=INTERFACE_STATE_DOWN; buf_strncpy_nul(interface->name, name); - + + interface->detected_type = detected_type; interface->destination = new_destination(interface); interface->alarm.poll.fd=-1; @@ -648,7 +632,10 @@ overlay_interface_init(const char *name, return -1; interface->state=INTERFACE_STATE_UP; - INFOF("Interface %s addr %s, is up",interface->name, alloca_socket_address(addr)); + INFOF("Interface %s type %s addr %s, is up", + interface->name, + interface_type_name(interface->detected_type), + alloca_socket_address(addr)); INFOF("Allowing a maximum of %d packets every %"PRId64"ms", interface->destination->transfer_limit.burst_size, interface->destination->transfer_limit.burst_length); @@ -1085,13 +1072,17 @@ int overlay_broadcast_ensemble(struct network_destination *destination, struct o } } -static const struct config_network_interface *find_interface_config(const char *name, int socket_type) +static const struct config_network_interface *find_interface_config(const char *name, short detected_type, int socket_type) { // Find a matching non-dummy interface rule. unsigned i; for (i = 0; i < config.interfaces.ac; ++i) { const struct config_network_interface *ifconfig = &config.interfaces.av[i].value; - if (ifconfig->socket_type==socket_type) { + if (ifconfig->socket_type==socket_type && ( + detected_type == ifconfig->match_type + || detected_type == OVERLAY_INTERFACE_UNKNOWN + || ifconfig->match_type == OVERLAY_INTERFACE_ANY + )) { unsigned j; for (j = 0; j < ifconfig->match.patc; ++j){ if (fnmatch(ifconfig->match.patv[j], name, 0) == 0) @@ -1109,8 +1100,31 @@ overlay_interface_register(const char *name, const struct socket_address *netmask, struct socket_address *broadcast) { + short detected_type = OVERLAY_INTERFACE_UNKNOWN; +#ifdef linux + detected_type = OVERLAY_INTERFACE_OTHER; + // Try to determine the interface type from /sys/class/net// + char path[256]; + struct stat file_stat; + strbuf sb = strbuf_local_buf(path); + strbuf_sprintf(sb, "/sys/class/net/%s/phy80211", name); + if (stat(path, &file_stat)==0){ + // This interface has a symlink to a physical wifi device + detected_type = OVERLAY_INTERFACE_WIFI; + }else{ + strbuf_reset(sb); + strbuf_sprintf(sb, "/sys/class/net/%s/speed", name); + int fd = open(path, O_RDONLY); + if (fd >= 0){ + // this interface implements the ethtool get_settings method + // we *could* read this file to set config based on link speed + detected_type = OVERLAY_INTERFACE_ETHERNET; + close(fd); + } + } +#endif // Find the matching non-dummy interface rule. - const struct config_network_interface *ifconfig = find_interface_config(name, SOCK_DGRAM); + const struct config_network_interface *ifconfig = find_interface_config(name, detected_type, SOCK_DGRAM); if (!ifconfig) { DEBUGF(overlayinterfaces, "Interface %s does not match any rule", name); return 0; @@ -1131,7 +1145,7 @@ overlay_interface_register(const char *name, return 0; /* New interface, so register it */ - if (overlay_interface_init(name, addr, netmask, broadcast, ifconfig)) + if (overlay_interface_init(name, detected_type, addr, netmask, broadcast, ifconfig)) return WHYF("Could not initialise newly seen interface %s", name); overlay_interface_init_any(ifconfig->port); @@ -1147,7 +1161,7 @@ static int interface_unregister(const char *name, ) { // Find the matching non-dummy interface rule. - const struct config_network_interface *ifconfig = find_interface_config(name, SOCK_DGRAM); + const struct config_network_interface *ifconfig = find_interface_config(name, OVERLAY_INTERFACE_UNKNOWN, SOCK_DGRAM); if (!ifconfig) return 0; @@ -1268,7 +1282,7 @@ void netlink_poll(struct sched_ent *alarm) } if (nlh->nlmsg_type==RTM_NEWADDR){ - DEBUGF(overlayinterfaces, "New addr %s, %s, %s, %s", + DEBUGF(overlayinterfaces, "New addr %s, %s, %s, %s", name, alloca_socket_address(&addr), alloca_socket_address(&broadcast), @@ -1428,7 +1442,7 @@ static void file_interface_init(const struct config_network_interface *ifconfig) return; } - overlay_interface_init(ifconfig->file, &addr, &netmask, &broadcast, ifconfig); + overlay_interface_init(ifconfig->file, OVERLAY_INTERFACE_UNKNOWN, &addr, &netmask, &broadcast, ifconfig); } static void rescan_soon(time_ms_t run_at){ @@ -1461,7 +1475,8 @@ void overlay_interface_config_change() continue; const struct config_network_interface *ifconfig = find_interface_config( - overlay_interfaces[i].name, + overlay_interfaces[i].name, + OVERLAY_INTERFACE_UNKNOWN, overlay_interfaces[i].ifconfig.socket_type ); diff --git a/overlay_interface.h b/overlay_interface.h index 21af386b..b8940823 100644 --- a/overlay_interface.h +++ b/overlay_interface.h @@ -68,7 +68,8 @@ typedef struct overlay_interface { struct sched_ent alarm; char name[256]; - + + short detected_type; off_t recv_offset; /* file offset */ int recv_count; @@ -115,7 +116,8 @@ struct config_network_interface; int overlay_interface_configure(struct overlay_interface *interface, const struct config_network_interface *ifconfig); int -overlay_interface_init(const char *name, +overlay_interface_init(const char *name, + short detected_type, const struct socket_address *addr, const struct socket_address *netmask, const struct socket_address *broadcast, diff --git a/overlay_mdp.c b/overlay_mdp.c index 184d3522..9d71e423 100644 --- a/overlay_mdp.c +++ b/overlay_mdp.c @@ -1408,7 +1408,7 @@ static void mdp_interface_packet(struct socket_address *client, struct mdp_heade if (result == CFOK || result == CFEMPTY){ struct overlay_interface *interface=overlay_interface_find_name_addr(NULL, client); if (!interface){ - overlay_interface_init(ifconfig.match.patv[0], client, NULL, NULL, &ifconfig); + overlay_interface_init(ifconfig.match.patv[0], OVERLAY_INTERFACE_UNKNOWN, client, NULL, NULL, &ifconfig); }else{ // reconfigure the interface with new / current settings if (overlay_interface_configure(interface, &ifconfig)==-1) diff --git a/tests/config b/tests/config index fa186529..749c34c9 100755 --- a/tests/config +++ b/tests/config @@ -255,6 +255,38 @@ test_InterfacesModernDummy() { executeOk_servald config set interfaces.0.file dummyname } +doc_InterfacesMatchType="Config options 'interfaces.N.[type|match_type]'" +test_InterfacesMatchType() { + executeOk_servald config set interfaces.0.match '*' + execute --stderr --core-backtrace --executable=$servald \ + config set interfaces.0.type 'any' + assertExitStatus '==' 2 + assert_stderr_log \ + --warn-pattern='"interfaces".*invalid' \ + --error-pattern='config file.*loaded despite defects.*invalid' + execute --stderr --core-backtrace --executable=$servald \ + config set interfaces.0.type 'unknown' + assertExitStatus '==' 2 + assert_stderr_log \ + --warn-pattern='"interfaces".*invalid' \ + --error-pattern='config file.*loaded despite defects.*invalid' + execute --stderr --core-backtrace --executable=$servald \ + config del interfaces.0.type + executeOk_servald config set interfaces.0.type wifi + executeOk_servald config set interfaces.0.type ethernet + execute --stderr --core-backtrace --executable=$servald \ + config set interfaces.0.match_type 'unknown' + assertExitStatus '==' 2 + assert_stderr_log \ + --warn-pattern='"interfaces".*invalid' \ + --error-pattern='config file.*loaded despite defects.*invalid' + execute --stderr --core-backtrace --executable=$servald \ + config del interfaces.0.match_type + executeOk_servald config set interfaces.0.match_type wifi + executeOk_servald config set interfaces.0.match_type ethernet + executeOk_servald config set interfaces.0.match_type any +} + doc_InterfacesModernIncompatible="Config options 'interfaces.match' and 'interfaces.file' are incompatible" test_InterfacesModernIncompatible() { executeOk_servald config set interfaces.0.match eth