From 2b5e82bd8c0ab0d9705ebbe5284d6cdd5f5e11d2 Mon Sep 17 00:00:00 2001 From: jbnadal Date: Tue, 6 Dec 2016 17:42:55 +0100 Subject: [PATCH] Bump netifd-2016-06-06-99e6dc68bbac5a57a0ebca810a9dc36e38667821 --- src/3P/netifd/CMakeLists.txt | 51 + src/3P/netifd/DESIGN | 140 + src/3P/netifd/alias.c | 199 ++ src/3P/netifd/bridge.c | 726 +++++ src/3P/netifd/config.c | 434 +++ src/3P/netifd/config.h | 24 + src/3P/netifd/config/network | 74 + src/3P/netifd/config/wireless | 20 + src/3P/netifd/device.c | 1011 +++++++ src/3P/netifd/device.h | 282 ++ src/3P/netifd/examples/hotplug-cmd | 1 + src/3P/netifd/examples/proto/ppp.sh | 71 + src/3P/netifd/examples/proto/pptp.sh | 44 + src/3P/netifd/examples/wireless/mac80211.sh | 66 + src/3P/netifd/handler.c | 212 ++ src/3P/netifd/handler.h | 46 + src/3P/netifd/interface-event.c | 200 ++ src/3P/netifd/interface-ip.c | 1422 ++++++++++ src/3P/netifd/interface-ip.h | 175 ++ src/3P/netifd/interface.c | 1247 +++++++++ src/3P/netifd/interface.h | 204 ++ src/3P/netifd/iprule.c | 256 ++ src/3P/netifd/iprule.h | 101 + src/3P/netifd/macvlan.c | 263 ++ src/3P/netifd/main.c | 346 +++ src/3P/netifd/netifd.h | 103 + src/3P/netifd/proto-shell.c | 924 +++++++ src/3P/netifd/proto-static.c | 113 + src/3P/netifd/proto.c | 642 +++++ src/3P/netifd/proto.h | 83 + src/3P/netifd/scripts/netifd-proto.sh | 401 +++ src/3P/netifd/scripts/netifd-wireless.sh | 337 +++ src/3P/netifd/scripts/utils.sh | 50 + src/3P/netifd/system-dummy.c | 286 ++ src/3P/netifd/system-linux.c | 2687 +++++++++++++++++++ src/3P/netifd/system.c | 43 + src/3P/netifd/system.h | 168 ++ src/3P/netifd/tunnel.c | 99 + src/3P/netifd/ubus.c | 1216 +++++++++ src/3P/netifd/ubus.h | 26 + src/3P/netifd/utils.c | 234 ++ src/3P/netifd/utils.h | 122 + src/3P/netifd/vlan.c | 192 ++ src/3P/netifd/vlandev.c | 251 ++ src/3P/netifd/wireless.c | 996 +++++++ src/3P/netifd/wireless.h | 105 + 46 files changed, 16693 insertions(+) create mode 100644 src/3P/netifd/CMakeLists.txt create mode 100644 src/3P/netifd/DESIGN create mode 100644 src/3P/netifd/alias.c create mode 100644 src/3P/netifd/bridge.c create mode 100644 src/3P/netifd/config.c create mode 100644 src/3P/netifd/config.h create mode 100644 src/3P/netifd/config/network create mode 100644 src/3P/netifd/config/wireless create mode 100644 src/3P/netifd/device.c create mode 100644 src/3P/netifd/device.h create mode 100755 src/3P/netifd/examples/hotplug-cmd create mode 100755 src/3P/netifd/examples/proto/ppp.sh create mode 100755 src/3P/netifd/examples/proto/pptp.sh create mode 100755 src/3P/netifd/examples/wireless/mac80211.sh create mode 100644 src/3P/netifd/handler.c create mode 100644 src/3P/netifd/handler.h create mode 100644 src/3P/netifd/interface-event.c create mode 100644 src/3P/netifd/interface-ip.c create mode 100644 src/3P/netifd/interface-ip.h create mode 100644 src/3P/netifd/interface.c create mode 100644 src/3P/netifd/interface.h create mode 100644 src/3P/netifd/iprule.c create mode 100644 src/3P/netifd/iprule.h create mode 100644 src/3P/netifd/macvlan.c create mode 100644 src/3P/netifd/main.c create mode 100644 src/3P/netifd/netifd.h create mode 100644 src/3P/netifd/proto-shell.c create mode 100644 src/3P/netifd/proto-static.c create mode 100644 src/3P/netifd/proto.c create mode 100644 src/3P/netifd/proto.h create mode 100644 src/3P/netifd/scripts/netifd-proto.sh create mode 100644 src/3P/netifd/scripts/netifd-wireless.sh create mode 100644 src/3P/netifd/scripts/utils.sh create mode 100644 src/3P/netifd/system-dummy.c create mode 100644 src/3P/netifd/system-linux.c create mode 100644 src/3P/netifd/system.c create mode 100644 src/3P/netifd/system.h create mode 100644 src/3P/netifd/tunnel.c create mode 100644 src/3P/netifd/ubus.c create mode 100644 src/3P/netifd/ubus.h create mode 100644 src/3P/netifd/utils.c create mode 100644 src/3P/netifd/utils.h create mode 100644 src/3P/netifd/vlan.c create mode 100644 src/3P/netifd/vlandev.c create mode 100644 src/3P/netifd/wireless.c create mode 100644 src/3P/netifd/wireless.h diff --git a/src/3P/netifd/CMakeLists.txt b/src/3P/netifd/CMakeLists.txt new file mode 100644 index 00000000..85527bec --- /dev/null +++ b/src/3P/netifd/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(netifd C) +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(APPLE) + INCLUDE_DIRECTORIES(/opt/local/include) + LINK_DIRECTORIES(/opt/local/lib) +ENDIF() + +SET(SOURCES + main.c utils.c system.c tunnel.c handler.c + interface.c interface-ip.c interface-event.c + iprule.c proto.c proto-static.c proto-shell.c + config.c device.c bridge.c vlan.c alias.c + macvlan.c ubus.c vlandev.c wireless.c) + + +SET(LIBS + ubox ubus uci json-c blobmsg_json) + +IF (NOT DEFINED LIBNL_LIBS) + FIND_LIBRARY(libnl NAMES libnl-3 libnl nl-3 nl) + SET(LIBNL_LIBS ${libnl}) +ENDIF() + +IF("${CMAKE_SYSTEM_NAME}" MATCHES "Linux" AND NOT DUMMY_MODE) + SET(SOURCES ${SOURCES} system-linux.c) + SET(LIBS ${LIBS} ${LIBNL_LIBS}) +ELSE() + ADD_DEFINITIONS(-DDUMMY_MODE=1) + SET(SOURCES ${SOURCES} system-dummy.c) +ENDIF() + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -g3) + IF(NO_OPTIMIZE) + ADD_DEFINITIONS(-O0) + ENDIF() +ENDIF() + + +ADD_EXECUTABLE(netifd ${SOURCES}) + +TARGET_LINK_LIBRARIES(netifd ${LIBS}) + +INSTALL(TARGETS netifd + RUNTIME DESTINATION sbin +) diff --git a/src/3P/netifd/DESIGN b/src/3P/netifd/DESIGN new file mode 100644 index 00000000..4d58bcff --- /dev/null +++ b/src/3P/netifd/DESIGN @@ -0,0 +1,140 @@ +Design of the the network interface daemon (netifd) +---------------------------------------------------- + +The primary elements of netifd's state are devices and interfaces. + +Devices +------- + +A device represents either a physical Linux interface (e.g. eth0), or a +virtual link, bound to a static route, a socket or some other external trigger +(e.g. for VPN links or tunnels). + +Anything that needs to be aware of device state registers a device_user, which +is bound to the device and contains a callback for receiving events on device +changes. As soon as the last device_user is removed, the device itself is freed +immediately. + +Devices can also internally reference other devices, this is used to manage +specific kinds of devices, such as bridges or vlans, which do not require +special treatment from interfaces or other high level data structures, with +the exception of adding more member interfaces via hotplug, which is useful +for bridges or bonding interfaces. + +The device up/down state is managed through refcounting. A device can be +brought up using claim_device(), and its reference released again with +release_device(). As soon as the refcount goes to zero, the device is +immediately brought down. +If the device cannot be brought up, claim_device() will return a non-zero +status and the caller will not have increased the refcount. + +A registered device may not be available immediately, an interface or another +device could also be attached to it, waiting for it to appear in the system, +to support triggering interfaces via hotplug. + +All device state transitions are announced via events to all the device_user +callbacks. The following event types are supported: + +DEV_EVENT_ADD: + The device is now present in the system. When a device_user is being added + to a device and the device was already present, this event is generated + immediately. + +DEV_EVENT_REMOVE: + The device is no longer available. Either it is currently being removed, + or it has already disappeared. All users should drop their references + immediately and clean up their state for this device. + +DEV_EVENT_SETUP: + The device is about to be brought up. This allows device users to apply + some low level configuration parameters if necessary, however this event + may not be emitted in all cases. Externally managed devices added via + hotplug may be already up, and in that case this notification is skipped. + +DEV_EVENT_UP: + The device has been successfully brought up. + +DEV_EVENT_TEARDOWN: + The device is about to be brought down + +DEV_EVENT_DOWN: + The device has been brought down + +The following optional events are supported on some devices: + +DEV_EVENT_LINK_UP: a link has been established on this device +DEV_EVENT_LINK_DOWN: the link has been lost + + + +Interfaces +---------- + +An interface represents a high level configuration applied to one or more +devices. An active interface must always be bound to one main device and +to a layer 3 device. By default, the layer 3 device points at the reference +to the main device, based on how simple protocols like static, dhcp, etc. +are set up. More complex protcol handlers such as ppp/pptp or VPN software +can remap the layer 3 interface to something else, and external modules +such as the firewall can take care of both interfaces if necessary. + +An interface always has the following state information: + +active: + The interface can be brought up (its main device is available) + +autostart: + If the interface switches from inactive to active, netifd will attempt + to bring it up immediately. Manually setting an interface to up (regardless + of whether that was successful or not) will set this flag. + +state: + IFS_SETUP: + The interface is currently being configured by the protocol handler + IFS_UP: + The interface is fully configured + IFS_TEARDOWN: + The interface is being deconfigured + IFS_DOWN: + The interface is down + +An interface references only one protocol handler state, modular protocol +handlers such as PPP are expected to be written in a way that allows them +to be set up as slave to another protocol handler if necessary (useful for +PPPoE or PPTP). + + + +Protocol handlers +----------------- + +A protocol handler can be attached to anything that provides a callback +for state changes. For the simple case it is usually attached to an interface +directly. + +The state of a protocol handler is tracked in a struct interface_proto_state, +and it depends on the state of the entity that's controlling it. + +It responds to PROTO_CMD_SETUP and PROTO_SETUP_TEARDOWN commands, which +should not block at any point in time. Completion is signalled back to the +master by sending IFPEV_UP and IFPEV_DOWN events. + +If the setup can be done fast without blocking for a noticeable amount of +time, the callback can do it and send back those events immediately. +If the setup can take longer, it should use uloop to schedule its actions +asynchronously and (if necessary) fork. + +The protocol handler must be able to abort a setup request that's being +processed when it encounters a teardown command. + +When a PROTO_SETUP_TEARDOWN call is issued and the 'force' parameter is +set, the protocol handler needs to clean up immediately as good as possible, +without waiting for its pending actions to complete. If it has spawned +any child processes, it needs to kill them and clean up their mess. + +Simple protocol handlers can set the PROTO_FLAG_IMMEDIATE flag if they +can perform all required actions immediately without waiting and thus +do not need to schedule IFPEV_UP and IFPEV_DOWN transitions. This will +cause those events to be generated by core code instead. + +## TODO: Configuration management, ubus callbacks diff --git a/src/3P/netifd/alias.c b/src/3P/netifd/alias.c new file mode 100644 index 00000000..6b938ca0 --- /dev/null +++ b/src/3P/netifd/alias.c @@ -0,0 +1,199 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include "netifd.h" +#include "device.h" +#include "interface.h" + +static struct avl_tree aliases; + +struct alias_device { + struct avl_node avl; + struct device dev; + struct device_user dep; + struct device_user new_dep; + bool update; + char name[]; +}; + +static const struct device_type alias_device_type; + +static void alias_set_device(struct alias_device *alias, struct device *dev) +{ + if (dev == alias->dep.dev) { + if (alias->update) { + device_remove_user(&alias->new_dep); + alias->update = false; + if (dev) + device_set_present(&alias->dev, true); + } + return; + } + + device_set_present(&alias->dev, false); + device_remove_user(&alias->new_dep); + if (alias->dev.active) { + if (dev) + device_add_user(&alias->new_dep, dev); + + alias->update = true; + return; + } + + alias->update = false; + device_remove_user(&alias->dep); + alias->dev.hidden = !dev; + if (dev) { + device_set_ifindex(&alias->dev, dev->ifindex); + device_set_ifname(&alias->dev, dev->ifname); + device_add_user(&alias->dep, dev); + } else + device_set_ifname(&alias->dev, ""); +} + +static int +alias_device_set_state(struct device *dev, bool state) +{ + struct alias_device *alias; + + alias = container_of(dev, struct alias_device, dev); + if (!alias->dep.dev) + return -1; + + if (state) + return device_claim(&alias->dep); + + device_release(&alias->dep); + if (alias->update) + alias_set_device(alias, alias->new_dep.dev); + + return 0; +} + +static void alias_device_cb(struct device_user *dep, enum device_event ev) +{ + struct alias_device *alias; + bool new_state = false; + + alias = container_of(dep, struct alias_device, dep); + switch (ev) { + case DEV_EVENT_ADD: + new_state = true; + case DEV_EVENT_REMOVE: + device_set_present(&alias->dev, new_state); + break; + case DEV_EVENT_LINK_UP: + new_state = true; + case DEV_EVENT_LINK_DOWN: + device_set_link(&alias->dev, new_state); + break; + case DEV_EVENT_UPDATE_IFINDEX: + device_set_ifindex(&alias->dev, dep->dev->ifindex); + break; + default: + device_broadcast_event(&alias->dev, ev); + break; + } +} + +static struct device * +alias_device_create(const char *name, struct blob_attr *attr) +{ + struct alias_device *alias; + + alias = calloc(1, sizeof(*alias) + strlen(name) + 1); + if (!alias) + return NULL; + + strcpy(alias->name, name); + alias->dev.set_state = alias_device_set_state; + alias->dev.hidden = true; + device_init_virtual(&alias->dev, &alias_device_type, NULL); + alias->avl.key = alias->name; + avl_insert(&aliases, &alias->avl); + alias->dep.alias = true; + alias->dep.cb = alias_device_cb; + device_check_state(&alias->dev); + + return &alias->dev; +} + +static void alias_device_free(struct device *dev) +{ + struct alias_device *alias; + + alias = container_of(dev, struct alias_device, dev); + device_remove_user(&alias->new_dep); + device_remove_user(&alias->dep); + avl_delete(&aliases, &alias->avl); + free(alias); +} + +static int alias_check_state(struct device *dev) +{ + struct alias_device *alias; + struct interface *iface; + struct device *ndev = NULL; + + alias = container_of(dev, struct alias_device, dev); + + iface = vlist_find(&interfaces, alias->name, iface, node); + if (iface && iface->state == IFS_UP) + ndev = iface->l3_dev.dev; + + alias_set_device(alias, ndev); + + return 0; +} + +static const struct device_type alias_device_type = { + .name = "Network alias", + .create = alias_device_create, + .free = alias_device_free, + .check_state = alias_check_state, +}; + +void +alias_notify_device(const char *name, struct device *dev) +{ + struct alias_device *alias; + + device_lock(); + + alias = avl_find_element(&aliases, name, alias, avl); + if (alias) + alias_set_device(alias, dev); + + device_unlock(); +} + +struct device * +device_alias_get(const char *name) +{ + struct alias_device *alias; + + alias = avl_find_element(&aliases, name, alias, avl); + if (alias) + return &alias->dev; + + return alias_device_create(name, NULL); +} + +static void __init alias_init(void) +{ + avl_init(&aliases, avl_strcmp, false, NULL); +} diff --git a/src/3P/netifd/bridge.c b/src/3P/netifd/bridge.c new file mode 100644 index 00000000..1c163cd9 --- /dev/null +++ b/src/3P/netifd/bridge.c @@ -0,0 +1,726 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "system.h" + +enum { + BRIDGE_ATTR_IFNAME, + BRIDGE_ATTR_STP, + BRIDGE_ATTR_FORWARD_DELAY, + BRIDGE_ATTR_PRIORITY, + BRIDGE_ATTR_IGMP_SNOOP, + BRIDGE_ATTR_AGEING_TIME, + BRIDGE_ATTR_HELLO_TIME, + BRIDGE_ATTR_MAX_AGE, + BRIDGE_ATTR_BRIDGE_EMPTY, + BRIDGE_ATTR_MULTICAST_QUERIER, + BRIDGE_ATTR_HASH_MAX, + BRIDGE_ATTR_ROBUSTNESS, + BRIDGE_ATTR_QUERY_INTERVAL, + BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL, + BRIDGE_ATTR_LAST_MEMBER_INTERVAL, + __BRIDGE_ATTR_MAX +}; + +static const struct blobmsg_policy bridge_attrs[__BRIDGE_ATTR_MAX] = { + [BRIDGE_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_ARRAY }, + [BRIDGE_ATTR_STP] = { "stp", BLOBMSG_TYPE_BOOL }, + [BRIDGE_ATTR_FORWARD_DELAY] = { "forward_delay", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_PRIORITY] = { "priority", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_AGEING_TIME] = { "ageing_time", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_HELLO_TIME] = { "hello_time", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_MAX_AGE] = { "max_age", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_IGMP_SNOOP] = { "igmp_snooping", BLOBMSG_TYPE_BOOL }, + [BRIDGE_ATTR_BRIDGE_EMPTY] = { "bridge_empty", BLOBMSG_TYPE_BOOL }, + [BRIDGE_ATTR_MULTICAST_QUERIER] = { "multicast_querier", BLOBMSG_TYPE_BOOL }, + [BRIDGE_ATTR_HASH_MAX] = { "hash_max", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_ROBUSTNESS] = { "robustness", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_QUERY_INTERVAL] = { "query_interval", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL] = { "query_response_interval", BLOBMSG_TYPE_INT32 }, + [BRIDGE_ATTR_LAST_MEMBER_INTERVAL] = { "last_member_interval", BLOBMSG_TYPE_INT32 }, +}; + +static const struct uci_blob_param_info bridge_attr_info[__BRIDGE_ATTR_MAX] = { + [BRIDGE_ATTR_IFNAME] = { .type = BLOBMSG_TYPE_STRING }, +}; + +static const struct uci_blob_param_list bridge_attr_list = { + .n_params = __BRIDGE_ATTR_MAX, + .params = bridge_attrs, + .info = bridge_attr_info, + + .n_next = 1, + .next = { &device_attr_list }, +}; + +static struct device *bridge_create(const char *name, struct blob_attr *attr); +static void bridge_config_init(struct device *dev); +static void bridge_free(struct device *dev); +static void bridge_dump_info(struct device *dev, struct blob_buf *b); +enum dev_change_type +bridge_reload(struct device *dev, struct blob_attr *attr); + +const struct device_type bridge_device_type = { + .name = "Bridge", + .config_params = &bridge_attr_list, + + .create = bridge_create, + .config_init = bridge_config_init, + .reload = bridge_reload, + .free = bridge_free, + .dump_info = bridge_dump_info, +}; + +struct bridge_state { + struct device dev; + device_state_cb set_state; + + struct blob_attr *config_data; + struct bridge_config config; + struct blob_attr *ifnames; + bool active; + bool force_active; + + struct uloop_timeout retry; + struct bridge_member *primary_port; + struct vlist_tree members; + int n_present; + int n_failed; +}; + +struct bridge_member { + struct vlist_node node; + struct bridge_state *bst; + struct device_user dev; + bool present; + char name[]; +}; + +static void +bridge_reset_primary(struct bridge_state *bst) +{ + struct bridge_member *bm; + + if (!bst->primary_port && + (bst->dev.settings.flags & DEV_OPT_MACADDR)) + return; + + bst->primary_port = NULL; + bst->dev.settings.flags &= ~DEV_OPT_MACADDR; + vlist_for_each_element(&bst->members, bm, node) { + uint8_t *macaddr; + + if (!bm->present) + continue; + + bst->primary_port = bm; + if (bm->dev.dev->settings.flags & DEV_OPT_MACADDR) + macaddr = bm->dev.dev->settings.macaddr; + else + macaddr = bm->dev.dev->orig_settings.macaddr; + memcpy(bst->dev.settings.macaddr, macaddr, 6); + bst->dev.settings.flags |= DEV_OPT_MACADDR; + return; + } +} + +static int +bridge_disable_member(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + + if (!bm->present) + return 0; + + system_bridge_delif(&bst->dev, bm->dev.dev); + device_release(&bm->dev); + + device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE); + + return 0; +} + +static int +bridge_enable_interface(struct bridge_state *bst) +{ + int ret; + + if (bst->active) + return 0; + + ret = system_bridge_addbr(&bst->dev, &bst->config); + if (ret < 0) + return ret; + + bst->active = true; + return 0; +} + +static void +bridge_disable_interface(struct bridge_state *bst) +{ + if (!bst->active) + return; + + system_bridge_delbr(&bst->dev); + bst->active = false; +} + +static int +bridge_enable_member(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + int ret; + + if (!bm->present) + return 0; + + ret = bridge_enable_interface(bst); + if (ret) + goto error; + + /* Disable IPv6 for bridge members */ + if (!(bm->dev.dev->settings.flags & DEV_OPT_IPV6)) { + bm->dev.dev->settings.ipv6 = 0; + bm->dev.dev->settings.flags |= DEV_OPT_IPV6; + } + + ret = device_claim(&bm->dev); + if (ret < 0) + goto error; + + ret = system_bridge_addif(&bst->dev, bm->dev.dev); + if (ret < 0) { + D(DEVICE, "Bridge device %s could not be added\n", bm->dev.dev->ifname); + goto error; + } + + device_set_present(&bst->dev, true); + device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE); + + return 0; + +error: + bst->n_failed++; + bm->present = false; + bst->n_present--; + device_release(&bm->dev); + + return ret; +} + +static void +bridge_remove_member(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + + if (!bm->present) + return; + + if (bm == bst->primary_port) + bridge_reset_primary(bst); + + if (bst->dev.active) + bridge_disable_member(bm); + + bm->present = false; + bm->bst->n_present--; + + if (bst->config.bridge_empty) + return; + + bst->force_active = false; + if (bst->n_present == 0) + device_set_present(&bst->dev, false); +} + +static void +bridge_free_member(struct bridge_member *bm) +{ + struct device *dev = bm->dev.dev; + + bridge_remove_member(bm); + device_remove_user(&bm->dev); + + /* + * When reloading the config and moving a device from one bridge to + * another, the other bridge may have tried to claim this device + * before it was removed here. + * Ensure that claiming the device is retried by toggling its present + * state + */ + if (dev->present) { + device_set_present(dev, false); + device_set_present(dev, true); + } + + free(bm); +} + +static void +bridge_check_retry(struct bridge_state *bst) +{ + if (!bst->n_failed) + return; + + uloop_timeout_set(&bst->retry, 100); +} + +static void +bridge_member_cb(struct device_user *dev, enum device_event ev) +{ + struct bridge_member *bm = container_of(dev, struct bridge_member, dev); + struct bridge_state *bst = bm->bst; + + switch (ev) { + case DEV_EVENT_ADD: + assert(!bm->present); + + bm->present = true; + bst->n_present++; + + if (bst->n_present == 1) + device_set_present(&bst->dev, true); + if (bst->dev.active && !bridge_enable_member(bm)) { + /* + * Adding a bridge member can overwrite the bridge mtu + * in the kernel, apply the bridge settings in case the + * bridge mtu is set + */ + system_if_apply_settings(&bst->dev, &bst->dev.settings, + DEV_OPT_MTU | DEV_OPT_MTU6); + } + + break; + case DEV_EVENT_REMOVE: + if (dev->hotplug) { + vlist_delete(&bst->members, &bm->node); + return; + } + + if (bm->present) + bridge_remove_member(bm); + + break; + default: + return; + } +} + +static int +bridge_set_down(struct bridge_state *bst) +{ + struct bridge_member *bm; + + bst->set_state(&bst->dev, false); + + vlist_for_each_element(&bst->members, bm, node) + bridge_disable_member(bm); + + bridge_disable_interface(bst); + + return 0; +} + +static int +bridge_set_up(struct bridge_state *bst) +{ + struct bridge_member *bm; + int ret; + + if (!bst->n_present) { + if (!bst->force_active) + return -ENOENT; + + ret = bridge_enable_interface(bst); + if (ret) + return ret; + } + + bst->n_failed = 0; + vlist_for_each_element(&bst->members, bm, node) + bridge_enable_member(bm); + bridge_check_retry(bst); + + if (!bst->force_active && !bst->n_present) { + /* initialization of all member interfaces failed */ + bridge_disable_interface(bst); + device_set_present(&bst->dev, false); + return -ENOENT; + } + + bridge_reset_primary(bst); + ret = bst->set_state(&bst->dev, true); + if (ret < 0) + bridge_set_down(bst); + + return ret; +} + +static int +bridge_set_state(struct device *dev, bool up) +{ + struct bridge_state *bst; + + bst = container_of(dev, struct bridge_state, dev); + + if (up) + return bridge_set_up(bst); + else + return bridge_set_down(bst); +} + +static struct bridge_member * +bridge_create_member(struct bridge_state *bst, struct device *dev, bool hotplug) +{ + struct bridge_member *bm; + + bm = calloc(1, sizeof(*bm) + strlen(dev->ifname) + 1); + if (!bm) + return NULL; + + bm->bst = bst; + bm->dev.cb = bridge_member_cb; + bm->dev.hotplug = hotplug; + strcpy(bm->name, dev->ifname); + bm->dev.dev = dev; + vlist_add(&bst->members, &bm->node, bm->name); + // Need to look up the bridge member again as the above + // created pointer will be freed in case the bridge member + // already existed + bm = vlist_find(&bst->members, dev->ifname, bm, node); + if (hotplug && bm) + bm->node.version = -1; + + return bm; +} + +static void +bridge_member_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct bridge_member *bm; + struct device *dev; + + if (node_new) { + bm = container_of(node_new, struct bridge_member, node); + + if (node_old) { + free(bm); + return; + } + + dev = bm->dev.dev; + bm->dev.dev = NULL; + device_add_user(&bm->dev, dev); + } + + + if (node_old) { + bm = container_of(node_old, struct bridge_member, node); + bridge_free_member(bm); + } +} + + +static void +bridge_add_member(struct bridge_state *bst, const char *name) +{ + struct device *dev; + + dev = device_get(name, true); + if (!dev) + return; + + bridge_create_member(bst, dev, false); +} + +static int +bridge_hotplug_add(struct device *dev, struct device *member) +{ + struct bridge_state *bst = container_of(dev, struct bridge_state, dev); + + bridge_create_member(bst, member, true); + + return 0; +} + +static int +bridge_hotplug_del(struct device *dev, struct device *member) +{ + struct bridge_state *bst = container_of(dev, struct bridge_state, dev); + struct bridge_member *bm; + + bm = vlist_find(&bst->members, member->ifname, bm, node); + if (!bm) + return UBUS_STATUS_NOT_FOUND; + + vlist_delete(&bst->members, &bm->node); + return 0; +} + +static int +bridge_hotplug_prepare(struct device *dev) +{ + struct bridge_state *bst; + + bst = container_of(dev, struct bridge_state, dev); + bst->force_active = true; + device_set_present(&bst->dev, true); + + return 0; +} + +static const struct device_hotplug_ops bridge_ops = { + .prepare = bridge_hotplug_prepare, + .add = bridge_hotplug_add, + .del = bridge_hotplug_del +}; + +static void +bridge_free(struct device *dev) +{ + struct bridge_state *bst; + + bst = container_of(dev, struct bridge_state, dev); + vlist_flush_all(&bst->members); + free(bst->config_data); + free(bst); +} + +static void +bridge_dump_info(struct device *dev, struct blob_buf *b) +{ + struct bridge_state *bst; + struct bridge_member *bm; + void *list; + + bst = container_of(dev, struct bridge_state, dev); + + system_if_dump_info(dev, b); + list = blobmsg_open_array(b, "bridge-members"); + + vlist_for_each_element(&bst->members, bm, node) + blobmsg_add_string(b, NULL, bm->dev.dev->ifname); + + blobmsg_close_array(b, list); +} + +static void +bridge_config_init(struct device *dev) +{ + struct bridge_state *bst; + struct blob_attr *cur; + int rem; + + bst = container_of(dev, struct bridge_state, dev); + + if (bst->config.bridge_empty) { + bst->force_active = true; + device_set_present(&bst->dev, true); + } + + bst->n_failed = 0; + vlist_update(&bst->members); + if (bst->ifnames) { + blobmsg_for_each_attr(cur, bst->ifnames, rem) { + bridge_add_member(bst, blobmsg_data(cur)); + } + } + vlist_flush(&bst->members); + bridge_check_retry(bst); +} + +static void +bridge_apply_settings(struct bridge_state *bst, struct blob_attr **tb) +{ + struct bridge_config *cfg = &bst->config; + struct blob_attr *cur; + + /* defaults */ + cfg->stp = false; + cfg->forward_delay = 2; + cfg->igmp_snoop = true; + cfg->multicast_querier = true; + cfg->robustness = 2; + cfg->query_interval = 12500; + cfg->query_response_interval = 1000; + cfg->last_member_interval = 100; + cfg->hash_max = 512; + cfg->bridge_empty = false; + cfg->priority = 0x7FFF; + + if ((cur = tb[BRIDGE_ATTR_STP])) + cfg->stp = blobmsg_get_bool(cur); + + if ((cur = tb[BRIDGE_ATTR_FORWARD_DELAY])) + cfg->forward_delay = blobmsg_get_u32(cur); + + if ((cur = tb[BRIDGE_ATTR_PRIORITY])) + cfg->priority = blobmsg_get_u32(cur); + + if ((cur = tb[BRIDGE_ATTR_IGMP_SNOOP])) + cfg->multicast_querier = cfg->igmp_snoop = blobmsg_get_bool(cur); + + if ((cur = tb[BRIDGE_ATTR_MULTICAST_QUERIER])) + cfg->multicast_querier = blobmsg_get_bool(cur); + + if ((cur = tb[BRIDGE_ATTR_HASH_MAX])) + cfg->hash_max = blobmsg_get_u32(cur); + + if ((cur = tb[BRIDGE_ATTR_ROBUSTNESS])) { + cfg->robustness = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_ROBUSTNESS; + } + + if ((cur = tb[BRIDGE_ATTR_QUERY_INTERVAL])) { + cfg->query_interval = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_QUERY_INTERVAL; + } + + if ((cur = tb[BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL])) { + cfg->query_response_interval = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_QUERY_RESPONSE_INTERVAL; + } + + if ((cur = tb[BRIDGE_ATTR_LAST_MEMBER_INTERVAL])) { + cfg->last_member_interval = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_LAST_MEMBER_INTERVAL; + } + + if ((cur = tb[BRIDGE_ATTR_AGEING_TIME])) { + cfg->ageing_time = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_AGEING_TIME; + } + + if ((cur = tb[BRIDGE_ATTR_HELLO_TIME])) { + cfg->hello_time = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_HELLO_TIME; + } + + if ((cur = tb[BRIDGE_ATTR_MAX_AGE])) { + cfg->max_age = blobmsg_get_u32(cur); + cfg->flags |= BRIDGE_OPT_MAX_AGE; + } + + if ((cur = tb[BRIDGE_ATTR_BRIDGE_EMPTY])) + cfg->bridge_empty = blobmsg_get_bool(cur); +} + +enum dev_change_type +bridge_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + struct blob_attr *tb_br[__BRIDGE_ATTR_MAX]; + enum dev_change_type ret = DEV_CONFIG_APPLIED; + unsigned long diff; + struct bridge_state *bst; + + BUILD_BUG_ON(sizeof(diff) < __BRIDGE_ATTR_MAX / 8); + BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / 8); + + bst = container_of(dev, struct bridge_state, dev); + attr = blob_memdup(attr); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + blobmsg_parse(bridge_attrs, __BRIDGE_ATTR_MAX, tb_br, + blob_data(attr), blob_len(attr)); + + bst->ifnames = tb_br[BRIDGE_ATTR_IFNAME]; + device_init_settings(dev, tb_dev); + bridge_apply_settings(bst, tb_br); + + if (bst->config_data) { + struct blob_attr *otb_dev[__DEV_ATTR_MAX]; + struct blob_attr *otb_br[__BRIDGE_ATTR_MAX]; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, + blob_data(bst->config_data), blob_len(bst->config_data)); + + diff = 0; + uci_blob_diff(tb_dev, otb_dev, &device_attr_list, &diff); + if (diff) + ret = DEV_CONFIG_RESTART; + + blobmsg_parse(bridge_attrs, __BRIDGE_ATTR_MAX, otb_br, + blob_data(bst->config_data), blob_len(bst->config_data)); + + diff = 0; + uci_blob_diff(tb_br, otb_br, &bridge_attr_list, &diff); + if (diff & ~(1 << BRIDGE_ATTR_IFNAME)) + ret = DEV_CONFIG_RESTART; + + bridge_config_init(dev); + } + + free(bst->config_data); + bst->config_data = attr; + return ret; +} + +static void +bridge_retry_members(struct uloop_timeout *timeout) +{ + struct bridge_state *bst = container_of(timeout, struct bridge_state, retry); + struct bridge_member *bm; + + bst->n_failed = 0; + vlist_for_each_element(&bst->members, bm, node) { + if (bm->present) + continue; + + if (!bm->dev.dev->present) + continue; + + bm->present = true; + bst->n_present++; + bridge_enable_member(bm); + } +} + +static struct device * +bridge_create(const char *name, struct blob_attr *attr) +{ + struct bridge_state *bst; + struct device *dev = NULL; + + bst = calloc(1, sizeof(*bst)); + if (!bst) + return NULL; + + dev = &bst->dev; + device_init(dev, &bridge_device_type, name); + dev->config_pending = true; + bst->retry.cb = bridge_retry_members; + + bst->set_state = dev->set_state; + dev->set_state = bridge_set_state; + + dev->hotplug_ops = &bridge_ops; + + vlist_init(&bst->members, avl_strcmp, bridge_member_update); + bst->members.keep_old = true; + bridge_reload(dev, attr); + + return dev; +} diff --git a/src/3P/netifd/config.c b/src/3P/netifd/config.c new file mode 100644 index 00000000..5d3db9f0 --- /dev/null +++ b/src/3P/netifd/config.c @@ -0,0 +1,434 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include "netifd.h" +#include "interface.h" +#include "interface-ip.h" +#include "iprule.h" +#include "proto.h" +#include "wireless.h" +#include "config.h" + +bool config_init = false; + +static struct uci_context *uci_ctx; +static struct uci_package *uci_network; +static struct uci_package *uci_wireless; +static struct blob_buf b; + +static int +config_section_idx(struct uci_section *s) +{ + struct uci_element *e; + int idx = 0; + + uci_foreach_element(&uci_wireless->sections, e) { + struct uci_section *cur = uci_to_section(e); + + if (s == cur) + return idx; + + if (!strcmp(cur->type, s->type)) + idx++; + } + + return -1; +} + +static int +config_parse_bridge_interface(struct uci_section *s) +{ + char *name; + + name = alloca(strlen(s->e.name) + 4); + sprintf(name, "br-%s", s->e.name); + blobmsg_add_string(&b, "name", name); + + uci_to_blob(&b, s, bridge_device_type.config_params); + if (!device_create(name, &bridge_device_type, b.head)) { + D(INTERFACE, "Failed to create bridge for interface '%s'\n", s->e.name); + return -EINVAL; + } + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "ifname", name); + return 0; +} + +static void +config_parse_interface(struct uci_section *s, bool alias) +{ + struct interface *iface; + const char *type = NULL, *disabled; + struct blob_attr *config; + bool bridge = false; + + disabled = uci_lookup_option_string(uci_ctx, s, "disabled"); + if (disabled && !strcmp(disabled, "1")) + return; + + blob_buf_init(&b, 0); + + if (!alias) + type = uci_lookup_option_string(uci_ctx, s, "type"); + if (type && !strcmp(type, "bridge")) { + if (config_parse_bridge_interface(s)) + return; + + bridge = true; + } + + uci_to_blob(&b, s, &interface_attr_list); + + iface = interface_alloc(s->e.name, b.head); + if (!iface) + return; + + if (iface->proto_handler && iface->proto_handler->config_params) + uci_to_blob(&b, s, iface->proto_handler->config_params); + + if (!bridge && uci_to_blob(&b, s, simple_device_type.config_params)) + iface->device_config = true; + + config = blob_memdup(b.head); + if (!config) + goto error; + + if (alias) { + if (!interface_add_alias(iface, config)) + goto error_free_config; + } else { + interface_add(iface, config); + } + return; + +error_free_config: + free(config); +error: + free(iface); +} + +static void +config_parse_route(struct uci_section *s, bool v6) +{ + void *route; + + blob_buf_init(&b, 0); + route = blobmsg_open_array(&b, "route"); + uci_to_blob(&b, s, &route_attr_list); + blobmsg_close_array(&b, route); + interface_ip_add_route(NULL, blob_data(b.head), v6); +} + +static void +config_parse_rule(struct uci_section *s, bool v6) +{ + void *rule; + + blob_buf_init(&b, 0); + rule = blobmsg_open_array(&b, "rule"); + uci_to_blob(&b, s, &rule_attr_list); + blobmsg_close_array(&b, rule); + iprule_add(blob_data(b.head), v6); +} + +static void +config_init_devices(void) +{ + struct uci_element *e; + + uci_foreach_element(&uci_network->sections, e) { + const struct uci_blob_param_list *params = NULL; + struct uci_section *s = uci_to_section(e); + const struct device_type *devtype = NULL; + struct device *dev; + const char *type, *name; + + if (strcmp(s->type, "device") != 0) + continue; + + name = uci_lookup_option_string(uci_ctx, s, "name"); + if (!name) + continue; + + type = uci_lookup_option_string(uci_ctx, s, "type"); + if (type) { + if (!strcmp(type, "8021ad")) + devtype = &vlandev_device_type; + else if (!strcmp(type, "8021q")) + devtype = &vlandev_device_type; + else if (!strcmp(type, "bridge")) + devtype = &bridge_device_type; + else if (!strcmp(type, "macvlan")) + devtype = &macvlan_device_type; + else if (!strcmp(type, "tunnel")) + devtype = &tunnel_device_type; + } + + if (devtype) + params = devtype->config_params; + if (!params) + params = simple_device_type.config_params; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, params); + if (devtype) { + dev = device_create(name, devtype, b.head); + if (!dev) + continue; + } else { + dev = device_get(name, 1); + if (!dev) + continue; + + dev->current_config = true; + device_apply_config(dev, dev->type, b.head); + } + dev->default_config = false; + } +} + +static struct uci_package * +config_init_package(const char *config) +{ + struct uci_context *ctx = uci_ctx; + struct uci_package *p = NULL; + + if (!ctx) { + ctx = uci_alloc_context(); + uci_ctx = ctx; + + ctx->flags &= ~UCI_FLAG_STRICT; + if (config_path) + uci_set_confdir(ctx, config_path); + +#ifdef DUMMY_MODE + uci_set_savedir(ctx, "./tmp"); +#endif + } else { + p = uci_lookup_package(ctx, config); + if (p) + uci_unload(ctx, p); + } + + if (uci_load(ctx, config, &p)) + return NULL; + + return p; +} + +static void +config_init_interfaces(void) +{ + struct uci_element *e; + + uci_foreach_element(&uci_network->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (!strcmp(s->type, "interface")) + config_parse_interface(s, false); + } + + uci_foreach_element(&uci_network->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (!strcmp(s->type, "alias")) + config_parse_interface(s, true); + } +} + +static void +config_init_routes(void) +{ + struct interface *iface; + struct uci_element *e; + + vlist_for_each_element(&interfaces, iface, node) + interface_ip_update_start(&iface->config_ip); + + uci_foreach_element(&uci_network->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (!strcmp(s->type, "route")) + config_parse_route(s, false); + else if (!strcmp(s->type, "route6")) + config_parse_route(s, true); + } + + vlist_for_each_element(&interfaces, iface, node) + interface_ip_update_complete(&iface->config_ip); +} + +static void +config_init_rules(void) +{ + struct uci_element *e; + + iprule_update_start(); + + uci_foreach_element(&uci_network->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (!strcmp(s->type, "rule")) + config_parse_rule(s, false); + else if (!strcmp(s->type, "rule6")) + config_parse_rule(s, true); + } + + iprule_update_complete(); +} + +static void +config_init_globals(void) +{ + struct uci_section *globals = uci_lookup_section( + uci_ctx, uci_network, "globals"); + if (!globals) + return; + + const char *ula_prefix = uci_lookup_option_string( + uci_ctx, globals, "ula_prefix"); + interface_ip_set_ula_prefix(ula_prefix); + + const char *default_ps = uci_lookup_option_string( + uci_ctx, globals, "default_ps"); + + if (default_ps) + device_set_default_ps(strcmp(default_ps, "1") ? false : true); +} + +static void +config_parse_wireless_device(struct uci_section *s) +{ + struct wireless_driver *drv; + const char *driver_name; + + driver_name = uci_lookup_option_string(uci_ctx, s, "type"); + if (!driver_name) + return; + + drv = avl_find_element(&wireless_drivers, driver_name, drv, node); + if (!drv) + return; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, drv->device.config); + wireless_device_create(drv, s->e.name, b.head); +} + +static void +config_parse_wireless_interface(struct wireless_device *wdev, struct uci_section *s) +{ + char *name; + + name = alloca(strlen(s->type) + 16); + sprintf(name, "@%s[%d]", s->type, config_section_idx(s)); + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, wdev->drv->interface.config); + wireless_interface_create(wdev, b.head, s->anonymous ? name : s->e.name); +} + +static void +config_init_wireless(void) +{ + struct wireless_device *wdev; + struct uci_element *e; + const char *dev_name; + + if (!uci_wireless) { + DPRINTF("No wireless configuration found\n"); + return; + } + + vlist_update(&wireless_devices); + + uci_foreach_element(&uci_wireless->sections, e) { + struct uci_section *s = uci_to_section(e); + if (strcmp(s->type, "wifi-device") != 0) + continue; + + config_parse_wireless_device(s); + } + + vlist_flush(&wireless_devices); + + vlist_for_each_element(&wireless_devices, wdev, node) { + wdev->vif_idx = 0; + vlist_update(&wdev->interfaces); + } + + uci_foreach_element(&uci_wireless->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (strcmp(s->type, "wifi-iface") != 0) + continue; + + dev_name = uci_lookup_option_string(uci_ctx, s, "device"); + if (!dev_name) + continue; + + wdev = vlist_find(&wireless_devices, dev_name, wdev, node); + if (!wdev) { + DPRINTF("device %s not found!\n", dev_name); + continue; + } + + config_parse_wireless_interface(wdev, s); + } + + vlist_for_each_element(&wireless_devices, wdev, node) + vlist_flush(&wdev->interfaces); +} + +void +config_init_all(void) +{ + uci_network = config_init_package("network"); + if (!uci_network) { + fprintf(stderr, "Failed to load network config\n"); + return; + } + + uci_wireless = config_init_package("wireless"); + + vlist_update(&interfaces); + config_init = true; + device_lock(); + + device_reset_config(); + config_init_devices(); + config_init_interfaces(); + config_init_routes(); + config_init_rules(); + config_init_globals(); + config_init_wireless(); + + config_init = false; + device_unlock(); + + device_reset_old(); + device_init_pending(); + vlist_flush(&interfaces); + device_free_unused(NULL); + interface_refresh_assignments(false); + interface_start_pending(); + wireless_start_pending(); +} diff --git a/src/3P/netifd/config.h b/src/3P/netifd/config.h new file mode 100644 index 00000000..5adaca6d --- /dev/null +++ b/src/3P/netifd/config.h @@ -0,0 +1,24 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_CONFIG_H +#define __NETIFD_CONFIG_H + +#include +#include + +extern bool config_init; + +void config_init_all(void); + +#endif diff --git a/src/3P/netifd/config/network b/src/3P/netifd/config/network new file mode 100644 index 00000000..b2985d37 --- /dev/null +++ b/src/3P/netifd/config/network @@ -0,0 +1,74 @@ +# Copyright (C) 2006 OpenWrt.org + +config interface loopback + option ifname lo + option proto static + option ipaddr 127.0.0.1 + option netmask 255.0.0.0 + +config device + option name br-lan + option type bridge + list ifname eth0.1 + list ifname eth0.2 + option mtu 1500 + +config device + option name dummy + +config interface lan + option ifname 'br-lan' + option proto static + option ipaddr 192.168.1.1 + option netmask 255.255.255.0 + +config interface dummy + option ifname eth0.4 + option mtu 1500 + option proto none + +config interface lan2 + option ifname "eth0.3 eth0.5" + option type bridge + option proto static + option ipaddr 192.168.1.1 + option netmask 255.255.255.0 + option gateway 192.168.1.2 + option dns '192.168.1.5 192.168.1.6' + +config interface wan + option proto pppoe + option ifname br-lan2 + option username foo + option password bar + +config alias wan1 + option proto static + option interface wan + option ipaddr 192.168.99.1 + option ip6addr 2a01:4f8:131:30e2::2/59 + option netmask 255.255.255.0 + option gateway 192.168.99.3 + +config route6 + option interface wan1 + option target ::0/0 + option gateway 2a01:4f8:131:30e2::1 + +config interface wlan + option proto static + option ipaddr 192.168.2.1 + option netmask 255.255.255.0 + +config interface pptp + option proto pptp + option server 1.1.1.1 + option peerdns 0 + option dns 192.168.10.1 + +config route + option target 192.168.0.1 + option netmask 24 + option gateway 192.168.5.2 + option interface wan + diff --git a/src/3P/netifd/config/wireless b/src/3P/netifd/config/wireless new file mode 100644 index 00000000..5a146199 --- /dev/null +++ b/src/3P/netifd/config/wireless @@ -0,0 +1,20 @@ +config wifi-device radio0 + option type mac80211 + option channel 11 + option hwmode 11ng + option path 'platform/ar933x_wmac' + option htmode HT20 + list ht_capab SHORT-GI-20 + list ht_capab SHORT-GI-40 + list ht_capab RX-STBC1 + list ht_capab DSSS_CCK-40 + # REMOVE THIS LINE TO ENABLE WIFI: + # option disabled 1 + +config wifi-iface + option device radio0 + option network lan + option mode ap + option ssid OpenWrt + option encryption none + diff --git a/src/3P/netifd/device.c b/src/3P/netifd/device.c new file mode 100644 index 00000000..fa3d00fe --- /dev/null +++ b/src/3P/netifd/device.c @@ -0,0 +1,1011 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include + +#include +#include +#include + +#ifdef linux +#include +#endif + +#include "netifd.h" +#include "system.h" +#include "config.h" + +static struct avl_tree devices; +static bool default_ps = true; + +static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = { + [DEV_ATTR_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING }, + [DEV_ATTR_MTU] = { .name = "mtu", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_MTU6] = { .name = "mtu6", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_MACADDR] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING }, + [DEV_ATTR_TXQUEUELEN] = { .name = "txqueuelen", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_ENABLED] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_IPV6] = { .name = "ipv6", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_PROMISC] = { .name = "promisc", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_RPFILTER] = { .name = "rpfilter", .type = BLOBMSG_TYPE_STRING }, + [DEV_ATTR_ACCEPTLOCAL] = { .name = "acceptlocal", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_IGMPVERSION] = { .name = "igmpversion", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_MLDVERSION] = { .name = "mldversion", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_NEIGHREACHABLETIME] = { .name = "neighreachabletime", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_NEIGHGCSTALETIME] = { .name = "neighgcstaletime", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_RPS] = { .name = "rps", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_XPS] = { .name = "xps", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_DADTRANSMITS] = { .name = "dadtransmits", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_MULTICAST_TO_UNICAST] = { .name = "multicast_to_unicast", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_MULTICAST_ROUTER] = { .name = "multicast_router", .type = BLOBMSG_TYPE_INT32 }, + [DEV_ATTR_MULTICAST] = { .name ="multicast", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_LEARNING] = { .name ="learning", .type = BLOBMSG_TYPE_BOOL }, + [DEV_ATTR_UNICAST_FLOOD] = { .name ="unicast_flood", .type = BLOBMSG_TYPE_BOOL }, +}; + +const struct uci_blob_param_list device_attr_list = { + .n_params = __DEV_ATTR_MAX, + .params = dev_attrs, +}; + +static int __devlock = 0; + +void device_lock(void) +{ + __devlock++; +} + +void device_unlock(void) +{ + __devlock--; + if (!__devlock) + device_free_unused(NULL); +} + +static int set_device_state(struct device *dev, bool state) +{ + if (state) { + /* Get ifindex for all devices being enabled so a valid */ + /* ifindex is in place avoiding possible race conditions */ + device_set_ifindex(dev, system_if_resolve(dev)); + if (!dev->ifindex) + return -1; + + system_if_up(dev); + } + else + system_if_down(dev); + + return 0; +} + +static int +simple_device_set_state(struct device *dev, bool state) +{ + struct device *pdev; + int ret = 0; + + pdev = dev->parent.dev; + if (state && !pdev) { + pdev = system_if_get_parent(dev); + if (pdev) + device_add_user(&dev->parent, pdev); + } + + if (pdev) { + if (state) + ret = device_claim(&dev->parent); + else + device_release(&dev->parent); + + if (ret < 0) + return ret; + } + return set_device_state(dev, state); +} + +static struct device * +simple_device_create(const char *name, struct blob_attr *attr) +{ + struct blob_attr *tb[__DEV_ATTR_MAX]; + struct device *dev = NULL; + + blobmsg_parse(dev_attrs, __DEV_ATTR_MAX, tb, blob_data(attr), blob_len(attr)); + dev = device_get(name, true); + if (!dev) + return NULL; + + dev->set_state = simple_device_set_state; + device_init_settings(dev, tb); + + return dev; +} + +static void simple_device_free(struct device *dev) +{ + if (dev->parent.dev) + device_remove_user(&dev->parent); + free(dev); +} + +const struct device_type simple_device_type = { + .name = "Network device", + .config_params = &device_attr_list, + + .create = simple_device_create, + .check_state = system_if_check, + .free = simple_device_free, +}; + +static void +device_merge_settings(struct device *dev, struct device_settings *n) +{ + struct device_settings *os = &dev->orig_settings; + struct device_settings *s = &dev->settings; + + memset(n, 0, sizeof(*n)); + n->mtu = s->flags & DEV_OPT_MTU ? s->mtu : os->mtu; + n->mtu6 = s->flags & DEV_OPT_MTU6 ? s->mtu6 : os->mtu6; + n->txqueuelen = s->flags & DEV_OPT_TXQUEUELEN ? + s->txqueuelen : os->txqueuelen; + memcpy(n->macaddr, + (s->flags & DEV_OPT_MACADDR ? s->macaddr : os->macaddr), + sizeof(n->macaddr)); + n->ipv6 = s->flags & DEV_OPT_IPV6 ? s->ipv6 : os->ipv6; + n->promisc = s->flags & DEV_OPT_PROMISC ? s->promisc : os->promisc; + n->rpfilter = s->flags & DEV_OPT_RPFILTER ? s->rpfilter : os->rpfilter; + n->acceptlocal = s->flags & DEV_OPT_ACCEPTLOCAL ? s->acceptlocal : os->acceptlocal; + n->igmpversion = s->flags & DEV_OPT_IGMPVERSION ? s->igmpversion : os->igmpversion; + n->mldversion = s->flags & DEV_OPT_MLDVERSION ? s->mldversion : os->mldversion; + n->neigh4reachabletime = s->flags & DEV_OPT_NEIGHREACHABLETIME ? + s->neigh4reachabletime : os->neigh4reachabletime; + n->neigh6reachabletime = s->flags & DEV_OPT_NEIGHREACHABLETIME ? + s->neigh6reachabletime : os->neigh6reachabletime; + n->neigh4gcstaletime = s->flags & DEV_OPT_NEIGHGCSTALETIME ? + s->neigh4gcstaletime : os->neigh4gcstaletime; + n->neigh6gcstaletime = s->flags & DEV_OPT_NEIGHGCSTALETIME ? + s->neigh6gcstaletime : os->neigh6gcstaletime; + n->dadtransmits = s->flags & DEV_OPT_DADTRANSMITS ? + s->dadtransmits : os->dadtransmits; + n->multicast = s->flags & DEV_OPT_MULTICAST ? + s->multicast : os->multicast; + n->multicast_to_unicast = s->multicast_to_unicast; + n->multicast_router = s->multicast_router; + n->learning = s->learning; + n->unicast_flood = s->unicast_flood; + n->flags = s->flags | os->flags | os->valid_flags; +} + +void +device_init_settings(struct device *dev, struct blob_attr **tb) +{ + struct device_settings *s = &dev->settings; + struct blob_attr *cur; + struct ether_addr *ea; + bool disabled = false; + + s->flags = 0; + if ((cur = tb[DEV_ATTR_ENABLED])) + disabled = !blobmsg_get_bool(cur); + + if ((cur = tb[DEV_ATTR_MTU])) { + s->mtu = blobmsg_get_u32(cur); + s->flags |= DEV_OPT_MTU; + } + + if ((cur = tb[DEV_ATTR_MTU6])) { + s->mtu6 = blobmsg_get_u32(cur); + s->flags |= DEV_OPT_MTU6; + } + + if ((cur = tb[DEV_ATTR_TXQUEUELEN])) { + s->txqueuelen = blobmsg_get_u32(cur); + s->flags |= DEV_OPT_TXQUEUELEN; + } + + if ((cur = tb[DEV_ATTR_MACADDR])) { + ea = ether_aton(blobmsg_data(cur)); + if (ea) { + memcpy(s->macaddr, ea, 6); + s->flags |= DEV_OPT_MACADDR; + } + } + + if ((cur = tb[DEV_ATTR_IPV6])) { + s->ipv6 = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_IPV6; + } + + if ((cur = tb[DEV_ATTR_PROMISC])) { + s->promisc = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_PROMISC; + } + + if ((cur = tb[DEV_ATTR_RPFILTER])) { + if (system_resolve_rpfilter(blobmsg_data(cur), &s->rpfilter)) + s->flags |= DEV_OPT_RPFILTER; + else + DPRINTF("Failed to resolve rpfilter: %s\n", (char *) blobmsg_data(cur)); + } + + if ((cur = tb[DEV_ATTR_ACCEPTLOCAL])) { + s->acceptlocal = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_ACCEPTLOCAL; + } + + if ((cur = tb[DEV_ATTR_IGMPVERSION])) { + s->igmpversion = blobmsg_get_u32(cur); + if (s->igmpversion >= 1 && s->igmpversion <= 3) + s->flags |= DEV_OPT_IGMPVERSION; + else + DPRINTF("Failed to resolve igmpversion: %d\n", blobmsg_get_u32(cur)); + } + + if ((cur = tb[DEV_ATTR_MLDVERSION])) { + s->mldversion = blobmsg_get_u32(cur); + if (s->mldversion >= 1 && s->mldversion <= 2) + s->flags |= DEV_OPT_MLDVERSION; + else + DPRINTF("Failed to resolve mldversion: %d\n", blobmsg_get_u32(cur)); + } + + if ((cur = tb[DEV_ATTR_NEIGHREACHABLETIME])) { + s->neigh6reachabletime = s->neigh4reachabletime = blobmsg_get_u32(cur); + s->flags |= DEV_OPT_NEIGHREACHABLETIME; + } + + if ((cur = tb[DEV_ATTR_NEIGHGCSTALETIME])) { + s->neigh6gcstaletime = s->neigh4gcstaletime = blobmsg_get_u32(cur); + s->flags |= DEV_OPT_NEIGHGCSTALETIME; + } + + if ((cur = tb[DEV_ATTR_RPS])) { + s->rps = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_RPS; + } + else + s->rps = default_ps; + + if ((cur = tb[DEV_ATTR_XPS])) { + s->xps = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_XPS; + } + else + s->xps = default_ps; + + if ((cur = tb[DEV_ATTR_DADTRANSMITS])) { + s->dadtransmits = blobmsg_get_u32(cur); + s->flags |= DEV_OPT_DADTRANSMITS; + } + + if ((cur = tb[DEV_ATTR_MULTICAST_TO_UNICAST])) { + s->multicast_to_unicast = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_MULTICAST_TO_UNICAST; + } + + if ((cur = tb[DEV_ATTR_MULTICAST_ROUTER])) { + s->multicast_router = blobmsg_get_u32(cur); + if (s->multicast_router <= 2) + s->flags |= DEV_OPT_MULTICAST_ROUTER; + else + DPRINTF("Invalid value: %d - (Use 0: never, 1: learn, 2: always)\n", blobmsg_get_u32(cur)); + } + + if ((cur = tb[DEV_ATTR_MULTICAST])) { + s->multicast = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_MULTICAST; + } + + if ((cur = tb[DEV_ATTR_LEARNING])) { + s->learning = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_LEARNING; + } + + if ((cur = tb[DEV_ATTR_UNICAST_FLOOD])) { + s->unicast_flood = blobmsg_get_bool(cur); + s->flags |= DEV_OPT_UNICAST_FLOOD; + } + + device_set_disabled(dev, disabled); +} + +static void __init dev_init(void) +{ + avl_init(&devices, avl_strcmp, true, NULL); +} + +static int device_broadcast_cb(void *ctx, struct safe_list *list) +{ + struct device_user *dep = container_of(list, struct device_user, list); + int *ev = ctx; + + /* device might have been removed by an earlier callback */ + if (!dep->dev) + return 0; + + if (dep->cb) + dep->cb(dep, *ev); + return 0; +} + +void device_broadcast_event(struct device *dev, enum device_event ev) +{ + int dev_ev = ev; + + safe_list_for_each(&dev->aliases, device_broadcast_cb, &dev_ev); + safe_list_for_each(&dev->users, device_broadcast_cb, &dev_ev); +} + +int device_claim(struct device_user *dep) +{ + struct device *dev = dep->dev; + int ret = 0; + + if (dep->claimed) + return 0; + + if (!dev) + return -1; + + dep->claimed = true; + D(DEVICE, "Claim %s %s, new active count: %d\n", dev->type->name, dev->ifname, dev->active + 1); + if (++dev->active != 1) + return 0; + + device_broadcast_event(dev, DEV_EVENT_SETUP); + if (dev->external) { + /* Get ifindex for external claimed devices so a valid */ + /* ifindex is in place avoiding possible race conditions */ + device_set_ifindex(dev, system_if_resolve(dev)); + if (!dev->ifindex) + ret = -1; + + system_if_get_settings(dev, &dev->orig_settings); + } else + ret = dev->set_state(dev, true); + + if (ret == 0) + device_broadcast_event(dev, DEV_EVENT_UP); + else { + D(DEVICE, "claim %s %s failed: %d\n", dev->type->name, dev->ifname, ret); + dev->active = 0; + dep->claimed = false; + } + + return ret; +} + +void device_release(struct device_user *dep) +{ + struct device *dev = dep->dev; + + if (!dep->claimed) + return; + + dep->claimed = false; + dev->active--; + D(DEVICE, "Release %s %s, new active count: %d\n", dev->type->name, dev->ifname, dev->active); + assert(dev->active >= 0); + + if (dev->active) + return; + + device_broadcast_event(dev, DEV_EVENT_TEARDOWN); + if (!dev->external) + dev->set_state(dev, false); + device_broadcast_event(dev, DEV_EVENT_DOWN); +} + +int device_check_state(struct device *dev) +{ + if (!dev->type->check_state) + return simple_device_type.check_state(dev); + + return dev->type->check_state(dev); +} + +void device_init_virtual(struct device *dev, const struct device_type *type, const char *name) +{ + assert(dev); + assert(type); + + D(DEVICE, "Initialize device '%s'\n", name ? name : ""); + INIT_SAFE_LIST(&dev->users); + INIT_SAFE_LIST(&dev->aliases); + dev->type = type; + + if (name) + device_set_ifname(dev, name); + + if (!dev->set_state) + dev->set_state = set_device_state; +} + +int device_init(struct device *dev, const struct device_type *type, const char *ifname) +{ + int ret; + + device_init_virtual(dev, type, ifname); + + dev->avl.key = dev->ifname; + + ret = avl_insert(&devices, &dev->avl); + if (ret < 0) + return ret; + + system_if_clear_state(dev); + device_check_state(dev); + dev->settings.rps = default_ps; + dev->settings.xps = default_ps; + + return 0; +} + +static struct device * +device_create_default(const char *name, bool external) +{ + struct device *dev; + + if (!external && system_if_force_external(name)) + return NULL; + + D(DEVICE, "Create simple device '%s'\n", name); + dev = calloc(1, sizeof(*dev)); + if (!dev) + return NULL; + + dev->external = external; + dev->set_state = simple_device_set_state; + device_init(dev, &simple_device_type, name); + dev->default_config = true; + if (external) + system_if_apply_settings(dev, &dev->settings, dev->settings.flags); + return dev; +} + +struct device * +device_find(const char *name) +{ + struct device *dev; + + return avl_find_element(&devices, name, dev, avl); +} + +struct device * +device_get(const char *name, int create) +{ + struct device *dev; + + if (strchr(name, '.')) + return get_vlan_device_chain(name, create); + + if (name[0] == '@') + return device_alias_get(name + 1); + + dev = avl_find_element(&devices, name, dev, avl); + if (dev) { + if (create > 1 && !dev->external) { + system_if_apply_settings(dev, &dev->settings, dev->settings.flags); + dev->external = true; + device_set_present(dev, true); + } + return dev; + } + + if (!create) + return NULL; + + return device_create_default(name, create > 1); +} + +static void +device_delete(struct device *dev) +{ + if (!dev->avl.key) + return; + + D(DEVICE, "Delete device '%s' from list\n", dev->ifname); + avl_delete(&devices, &dev->avl); + dev->avl.key = NULL; +} + +static int device_cleanup_cb(void *ctx, struct safe_list *list) +{ + struct device_user *dep = container_of(list, struct device_user, list); + if (dep->cb) + dep->cb(dep, DEV_EVENT_REMOVE); + + device_release(dep); + return 0; +} + +void device_cleanup(struct device *dev) +{ + D(DEVICE, "Clean up device '%s'\n", dev->ifname); + safe_list_for_each(&dev->users, device_cleanup_cb, NULL); + safe_list_for_each(&dev->aliases, device_cleanup_cb, NULL); + device_delete(dev); +} + +static void __device_set_present(struct device *dev, bool state) +{ + if (dev->present == state) + return; + + dev->present = state; + device_broadcast_event(dev, state ? DEV_EVENT_ADD : DEV_EVENT_REMOVE); +} + +void +device_refresh_present(struct device *dev) +{ + bool state = dev->sys_present; + + if (dev->disabled || dev->deferred) + state = false; + + __device_set_present(dev, state); +} + +void device_set_present(struct device *dev, bool state) +{ + if (dev->sys_present == state) + return; + + D(DEVICE, "%s '%s' %s present\n", dev->type->name, dev->ifname, state ? "is now" : "is no longer" ); + dev->sys_present = state; + device_refresh_present(dev); +} + +void device_set_link(struct device *dev, bool state) +{ + if (dev->link_active == state) + return; + + netifd_log_message(L_NOTICE, "%s '%s' link is %s\n", dev->type->name, dev->ifname, state ? "up" : "down" ); + + dev->link_active = state; + device_broadcast_event(dev, state ? DEV_EVENT_LINK_UP : DEV_EVENT_LINK_DOWN); +} + +void device_set_ifindex(struct device *dev, int ifindex) +{ + if (dev->ifindex == ifindex) + return; + + dev->ifindex = ifindex; + device_broadcast_event(dev, DEV_EVENT_UPDATE_IFINDEX); +} + +int device_set_ifname(struct device *dev, const char *name) +{ + int ret = 0; + + if (!strcmp(dev->ifname, name)) + return 0; + + if (dev->avl.key) + avl_delete(&devices, &dev->avl); + + strncpy(dev->ifname, name, IFNAMSIZ); + + if (dev->avl.key) + ret = avl_insert(&devices, &dev->avl); + + if (ret == 0) + device_broadcast_event(dev, DEV_EVENT_UPDATE_IFNAME); + + return ret; +} + +static int device_refcount(struct device *dev) +{ + struct list_head *list; + int count = 0; + + list_for_each(list, &dev->users.list) + count++; + + list_for_each(list, &dev->aliases.list) + count++; + + return count; +} + +static void +__device_add_user(struct device_user *dep, struct device *dev) +{ + struct safe_list *head; + + dep->dev = dev; + + if (dep->alias) + head = &dev->aliases; + else + head = &dev->users; + + safe_list_add(&dep->list, head); + D(DEVICE, "Add user for device '%s', refcount=%d\n", dev->ifname, device_refcount(dev)); + + if (dep->cb && dev->present) { + dep->cb(dep, DEV_EVENT_ADD); + if (dev->active) + dep->cb(dep, DEV_EVENT_UP); + + if (dev->link_active) + dep->cb(dep, DEV_EVENT_LINK_UP); + } +} + +void device_add_user(struct device_user *dep, struct device *dev) +{ + if (dep->dev == dev) + return; + + if (dep->dev) + device_remove_user(dep); + + if (!dev) + return; + + __device_add_user(dep, dev); +} + +void +device_free(struct device *dev) +{ + __devlock++; + free(dev->config); + device_cleanup(dev); + dev->type->free(dev); + __devlock--; +} + +static void +__device_free_unused(struct device *dev) +{ + if (!safe_list_empty(&dev->users) || + !safe_list_empty(&dev->aliases) || + dev->current_config || __devlock) + return; + + device_free(dev); +} + +void device_remove_user(struct device_user *dep) +{ + struct device *dev = dep->dev; + + if (!dep->dev) + return; + + dep->hotplug = false; + if (dep->claimed) + device_release(dep); + + safe_list_del(&dep->list); + dep->dev = NULL; + D(DEVICE, "Remove user for device '%s', refcount=%d\n", dev->ifname, device_refcount(dev)); + __device_free_unused(dev); +} + +void +device_free_unused(struct device *dev) +{ + struct device *tmp; + + if (dev) + return __device_free_unused(dev); + + avl_for_each_element_safe(&devices, dev, avl, tmp) + __device_free_unused(dev); +} + +void +device_init_pending(void) +{ + struct device *dev, *tmp; + + avl_for_each_element_safe(&devices, dev, avl, tmp) { + if (!dev->config_pending) + continue; + + dev->type->config_init(dev); + dev->config_pending = false; + } +} + +static enum dev_change_type +device_set_config(struct device *dev, const struct device_type *type, + struct blob_attr *attr) +{ + struct blob_attr *tb[__DEV_ATTR_MAX]; + const struct uci_blob_param_list *cfg = type->config_params; + + if (type != dev->type) + return DEV_CONFIG_RECREATE; + + if (dev->type->reload) + return dev->type->reload(dev, attr); + + if (uci_blob_check_equal(dev->config, attr, cfg)) + return DEV_CONFIG_NO_CHANGE; + + if (cfg == &device_attr_list) { + memset(tb, 0, sizeof(tb)); + + if (attr) + blobmsg_parse(dev_attrs, __DEV_ATTR_MAX, tb, + blob_data(attr), blob_len(attr)); + + device_init_settings(dev, tb); + return DEV_CONFIG_RESTART; + } else + return DEV_CONFIG_RECREATE; +} + +enum dev_change_type +device_apply_config(struct device *dev, const struct device_type *type, + struct blob_attr *config) +{ + enum dev_change_type change; + + change = device_set_config(dev, type, config); + if (dev->external) { + system_if_apply_settings(dev, &dev->settings, dev->settings.flags); + change = DEV_CONFIG_APPLIED; + } + + switch (change) { + case DEV_CONFIG_RESTART: + case DEV_CONFIG_APPLIED: + D(DEVICE, "Device '%s': config applied\n", dev->ifname); + config = blob_memdup(config); + free(dev->config); + dev->config = config; + if (change == DEV_CONFIG_RESTART && dev->present) { + int ret = 0; + + device_set_present(dev, false); + if (dev->active && !dev->external) { + ret = dev->set_state(dev, false); + if (!ret) + ret = dev->set_state(dev, true); + } + if (!ret) + device_set_present(dev, true); + } + break; + case DEV_CONFIG_NO_CHANGE: + D(DEVICE, "Device '%s': no configuration change\n", dev->ifname); + break; + case DEV_CONFIG_RECREATE: + break; + } + + return change; +} + +static void +device_replace(struct device *dev, struct device *odev) +{ + struct device_user *dep, *tmp; + + __devlock++; + if (odev->present) + device_set_present(odev, false); + + list_for_each_entry_safe(dep, tmp, &odev->users.list, list.list) { + device_release(dep); + safe_list_del(&dep->list); + __device_add_user(dep, dev); + } + __devlock--; + + device_free(odev); +} + +void +device_reset_config(void) +{ + struct device *dev; + + avl_for_each_element(&devices, dev, avl) + dev->current_config = false; +} + +void +device_reset_old(void) +{ + struct device *dev, *tmp, *ndev; + + avl_for_each_element_safe(&devices, dev, avl, tmp) { + if (dev->current_config || dev->default_config) + continue; + + if (dev->type != &simple_device_type) + continue; + + ndev = device_create_default(dev->ifname, dev->external); + if (!ndev) + continue; + + device_replace(ndev, dev); + } +} + +void +device_set_default_ps(bool state) +{ + struct device *dev; + + if (state == default_ps) + return; + + default_ps = state; + + avl_for_each_element(&devices, dev, avl) { + struct device_settings *s = &dev->settings; + unsigned int apply_mask = 0; + + if (!(s->flags & DEV_OPT_RPS)) { + s->rps = default_ps; + apply_mask |= DEV_OPT_RPS; + } + + if (!(s->flags & DEV_OPT_XPS)) { + s->xps = default_ps; + apply_mask |= DEV_OPT_XPS; + } + + if (!apply_mask) + continue; + + if (!(dev->external || (dev->present && dev->active)) || + dev->config_pending) + continue; + + system_if_apply_settings(dev, s, apply_mask); + } +} + +struct device * +device_create(const char *name, const struct device_type *type, + struct blob_attr *config) +{ + struct device *odev = NULL, *dev; + enum dev_change_type change; + + odev = device_get(name, false); + if (odev) { + odev->current_config = true; + change = device_apply_config(odev, type, config); + switch (change) { + case DEV_CONFIG_RECREATE: + D(DEVICE, "Device '%s': recreate device\n", odev->ifname); + device_delete(odev); + break; + default: + return odev; + } + } else + D(DEVICE, "Create new device '%s' (%s)\n", name, type->name); + + config = blob_memdup(config); + if (!config) + return NULL; + + dev = type->create(name, config); + if (!dev) + return NULL; + + dev->current_config = true; + dev->config = config; + if (odev) + device_replace(dev, odev); + + if (!config_init && dev->config_pending) { + type->config_init(dev); + dev->config_pending = false; + } + + return dev; +} + +void +device_dump_status(struct blob_buf *b, struct device *dev) +{ + struct device_settings st; + void *c, *s; + + if (!dev) { + avl_for_each_element(&devices, dev, avl) { + if (!dev->present) + continue; + c = blobmsg_open_table(b, dev->ifname); + device_dump_status(b, dev); + blobmsg_close_table(b, c); + } + + return; + } + + blobmsg_add_u8(b, "external", dev->external); + blobmsg_add_u8(b, "present", dev->present); + blobmsg_add_string(b, "type", dev->type->name); + + if (!dev->present) + return; + + blobmsg_add_u8(b, "up", !!dev->active); + blobmsg_add_u8(b, "carrier", !!dev->link_active); + + if (dev->type->dump_info) + dev->type->dump_info(dev, b); + else + system_if_dump_info(dev, b); + + if (dev->active) { + device_merge_settings(dev, &st); + if (st.flags & DEV_OPT_MTU) + blobmsg_add_u32(b, "mtu", st.mtu); + if (st.flags & DEV_OPT_MTU6) + blobmsg_add_u32(b, "mtu6", st.mtu6); + if (st.flags & DEV_OPT_MACADDR) + blobmsg_add_string(b, "macaddr", format_macaddr(st.macaddr)); + if (st.flags & DEV_OPT_TXQUEUELEN) + blobmsg_add_u32(b, "txqueuelen", st.txqueuelen); + if (st.flags & DEV_OPT_IPV6) + blobmsg_add_u8(b, "ipv6", st.ipv6); + if (st.flags & DEV_OPT_PROMISC) + blobmsg_add_u8(b, "promisc", st.promisc); + if (st.flags & DEV_OPT_RPFILTER) + blobmsg_add_u32(b, "rpfilter", st.rpfilter); + if (st.flags & DEV_OPT_ACCEPTLOCAL) + blobmsg_add_u8(b, "acceptlocal", st.acceptlocal); + if (st.flags & DEV_OPT_IGMPVERSION) + blobmsg_add_u32(b, "igmpversion", st.igmpversion); + if (st.flags & DEV_OPT_MLDVERSION) + blobmsg_add_u32(b, "mldversion", st.mldversion); + if (st.flags & DEV_OPT_NEIGHREACHABLETIME) { + blobmsg_add_u32(b, "neigh4reachabletime", st.neigh4reachabletime); + blobmsg_add_u32(b, "neigh6reachabletime", st.neigh6reachabletime); + } + if (st.flags & DEV_OPT_NEIGHGCSTALETIME) { + blobmsg_add_u32(b, "neigh4gcstaletime", st.neigh4gcstaletime); + blobmsg_add_u32(b, "neigh6gcstaletime", st.neigh6gcstaletime); + } + if (st.flags & DEV_OPT_DADTRANSMITS) + blobmsg_add_u32(b, "dadtransmits", st.dadtransmits); + if (st.flags & DEV_OPT_MULTICAST_TO_UNICAST) + blobmsg_add_u8(b, "multicast_to_unicast", st.multicast_to_unicast); + if (st.flags & DEV_OPT_MULTICAST_ROUTER) + blobmsg_add_u32(b, "multicast_router", st.multicast_router); + if (st.flags & DEV_OPT_MULTICAST) + blobmsg_add_u8(b, "multicast", st.multicast); + if (st.flags & DEV_OPT_LEARNING) + blobmsg_add_u8(b, "learning", st.learning); + if (st.flags & DEV_OPT_UNICAST_FLOOD) + blobmsg_add_u8(b, "unicast_flood", st.unicast_flood); + } + + s = blobmsg_open_table(b, "statistics"); + if (dev->type->dump_stats) + dev->type->dump_stats(dev, b); + else + system_if_dump_stats(dev, b); + blobmsg_close_table(b, s); +} diff --git a/src/3P/netifd/device.h b/src/3P/netifd/device.h new file mode 100644 index 00000000..e13e4350 --- /dev/null +++ b/src/3P/netifd/device.h @@ -0,0 +1,282 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_DEVICE_H +#define __NETIFD_DEVICE_H + +#include +#include +#include + +struct device; +struct device_user; +struct device_hotplug_ops; +struct interface; + +typedef int (*device_state_cb)(struct device *, bool up); + +enum { + DEV_ATTR_TYPE, + DEV_ATTR_MTU, + DEV_ATTR_MTU6, + DEV_ATTR_MACADDR, + DEV_ATTR_TXQUEUELEN, + DEV_ATTR_ENABLED, + DEV_ATTR_IPV6, + DEV_ATTR_PROMISC, + DEV_ATTR_RPFILTER, + DEV_ATTR_ACCEPTLOCAL, + DEV_ATTR_IGMPVERSION, + DEV_ATTR_MLDVERSION, + DEV_ATTR_NEIGHREACHABLETIME, + DEV_ATTR_RPS, + DEV_ATTR_XPS, + DEV_ATTR_DADTRANSMITS, + DEV_ATTR_MULTICAST_TO_UNICAST, + DEV_ATTR_MULTICAST_ROUTER, + DEV_ATTR_MULTICAST, + DEV_ATTR_LEARNING, + DEV_ATTR_UNICAST_FLOOD, + DEV_ATTR_NEIGHGCSTALETIME, + __DEV_ATTR_MAX, +}; + +enum dev_change_type { + DEV_CONFIG_NO_CHANGE, + DEV_CONFIG_APPLIED, + DEV_CONFIG_RESTART, + DEV_CONFIG_RECREATE, +}; + +struct device_type { + struct list_head list; + const char *name; + + const struct uci_blob_param_list *config_params; + + struct device *(*create)(const char *name, struct blob_attr *attr); + void (*config_init)(struct device *); + enum dev_change_type (*reload)(struct device *, struct blob_attr *); + void (*dump_info)(struct device *, struct blob_buf *buf); + void (*dump_stats)(struct device *, struct blob_buf *buf); + int (*check_state)(struct device *); + void (*free)(struct device *); +}; + +enum { + DEV_OPT_MTU = (1 << 0), + DEV_OPT_MACADDR = (1 << 1), + DEV_OPT_TXQUEUELEN = (1 << 2), + DEV_OPT_IPV6 = (1 << 3), + DEV_OPT_PROMISC = (1 << 4), + DEV_OPT_RPFILTER = (1 << 5), + DEV_OPT_ACCEPTLOCAL = (1 << 6), + DEV_OPT_IGMPVERSION = (1 << 7), + DEV_OPT_MLDVERSION = (1 << 8), + DEV_OPT_NEIGHREACHABLETIME = (1 << 9), + DEV_OPT_RPS = (1 << 10), + DEV_OPT_XPS = (1 << 11), + DEV_OPT_MTU6 = (1 << 12), + DEV_OPT_DADTRANSMITS = (1 << 13), + DEV_OPT_MULTICAST_TO_UNICAST = (1 << 14), + DEV_OPT_MULTICAST_ROUTER = (1 << 15), + DEV_OPT_MULTICAST = (1 << 16), + DEV_OPT_LEARNING = (1 << 17), + DEV_OPT_UNICAST_FLOOD = (1 << 18), + DEV_OPT_NEIGHGCSTALETIME = (1 << 19), +}; + +/* events broadcasted to all users of a device */ +enum device_event { + DEV_EVENT_ADD, + DEV_EVENT_REMOVE, + + DEV_EVENT_UPDATE_IFNAME, + DEV_EVENT_UPDATE_IFINDEX, + + DEV_EVENT_SETUP, + DEV_EVENT_TEARDOWN, + DEV_EVENT_UP, + DEV_EVENT_DOWN, + + DEV_EVENT_LINK_UP, + DEV_EVENT_LINK_DOWN, + + /* Topology changed (i.e. bridge member added) */ + DEV_EVENT_TOPO_CHANGE, + + __DEV_EVENT_MAX +}; + +/* + * device dependency with callbacks + */ +struct device_user { + struct safe_list list; + + bool claimed; + bool hotplug; + bool alias; + + uint8_t ev_idx[__DEV_EVENT_MAX]; + + struct device *dev; + void (*cb)(struct device_user *, enum device_event); +}; + +struct device_settings { + unsigned int flags; + unsigned int valid_flags; + unsigned int mtu; + unsigned int mtu6; + unsigned int txqueuelen; + uint8_t macaddr[6]; + bool ipv6; + bool promisc; + unsigned int rpfilter; + bool acceptlocal; + unsigned int igmpversion; + unsigned int mldversion; + unsigned int neigh4reachabletime; + unsigned int neigh6reachabletime; + unsigned int neigh4gcstaletime; + unsigned int neigh6gcstaletime; + bool rps; + bool xps; + unsigned int dadtransmits; + bool multicast_to_unicast; + unsigned int multicast_router; + bool multicast; + bool learning; + bool unicast_flood; +}; + +/* + * link layer device. typically represents a linux network device. + * can be used to support VLANs as well + */ +struct device { + const struct device_type *type; + + struct avl_node avl; + struct safe_list users; + struct safe_list aliases; + + char ifname[IFNAMSIZ + 1]; + int ifindex; + + struct blob_attr *config; + bool config_pending; + bool sys_present; + /* DEV_EVENT_ADD */ + bool present; + /* DEV_EVENT_UP */ + int active; + /* DEV_EVENT_LINK_UP */ + bool link_active; + + bool external; + bool disabled; + bool deferred; + bool hidden; + + bool current_config; + bool iface_config; + bool default_config; + bool wireless; + bool wireless_ap; + bool wireless_isolate; + + struct interface *config_iface; + + /* set interface up or down */ + device_state_cb set_state; + + const struct device_hotplug_ops *hotplug_ops; + + struct device_user parent; + + struct device_settings orig_settings; + struct device_settings settings; +}; + +struct device_hotplug_ops { + int (*prepare)(struct device *dev); + int (*add)(struct device *main, struct device *member); + int (*del)(struct device *main, struct device *member); +}; + +extern const struct uci_blob_param_list device_attr_list; +extern const struct device_type simple_device_type; +extern const struct device_type bridge_device_type; +extern const struct device_type tunnel_device_type; +extern const struct device_type macvlan_device_type; +extern const struct device_type vlandev_device_type; + +void device_lock(void); +void device_unlock(void); + +struct device *device_create(const char *name, const struct device_type *type, + struct blob_attr *config); +void device_init_settings(struct device *dev, struct blob_attr **tb); +void device_init_pending(void); + +enum dev_change_type +device_apply_config(struct device *dev, const struct device_type *type, + struct blob_attr *config); + +void device_reset_config(void); +void device_reset_old(void); +void device_set_default_ps(bool state); + +void device_init_virtual(struct device *dev, const struct device_type *type, const char *name); +int device_init(struct device *iface, const struct device_type *type, const char *ifname); +void device_cleanup(struct device *dev); +struct device *device_find(const char *name); +struct device *device_get(const char *name, int create); +void device_add_user(struct device_user *dep, struct device *iface); +void device_remove_user(struct device_user *dep); +void device_broadcast_event(struct device *dev, enum device_event ev); + +void device_set_present(struct device *dev, bool state); +void device_set_link(struct device *dev, bool state); +void device_set_ifindex(struct device *dev, int ifindex); +int device_set_ifname(struct device *dev, const char *name); +void device_refresh_present(struct device *dev); +int device_claim(struct device_user *dep); +void device_release(struct device_user *dep); +int device_check_state(struct device *dev); +void device_dump_status(struct blob_buf *b, struct device *dev); + +void device_free(struct device *dev); +void device_free_unused(struct device *dev); + +struct device *get_vlan_device_chain(const char *ifname, bool create); +void alias_notify_device(const char *name, struct device *dev); +struct device *device_alias_get(const char *name); + +static inline void +device_set_deferred(struct device *dev, bool value) +{ + dev->deferred = value; + device_refresh_present(dev); +} + +static inline void +device_set_disabled(struct device *dev, bool value) +{ + dev->disabled = value; + device_refresh_present(dev); +} + +#endif diff --git a/src/3P/netifd/examples/hotplug-cmd b/src/3P/netifd/examples/hotplug-cmd new file mode 100755 index 00000000..2488a775 --- /dev/null +++ b/src/3P/netifd/examples/hotplug-cmd @@ -0,0 +1 @@ +echo "Action: $ACTION, Interface: $INTERFACE" diff --git a/src/3P/netifd/examples/proto/ppp.sh b/src/3P/netifd/examples/proto/ppp.sh new file mode 100755 index 00000000..4750f400 --- /dev/null +++ b/src/3P/netifd/examples/proto/ppp.sh @@ -0,0 +1,71 @@ +#!/bin/sh +NETIFD_MAIN_DIR=../../scripts +. $NETIFD_MAIN_DIR/netifd-proto.sh + +init_proto "$@" + +ppp_generic_init_config() { + proto_config_add_string "username" + proto_config_add_string "password" + proto_config_add_int "keepalive" +} + +proto_ppp_init_config() { + no_device=1 + available=1 + ppp_generic_init_config +} + +proto_ppp_setup() { + echo "ppp_setup($1): $2" +} + +proto_ppp_teardown() { + return +} + +add_protocol ppp + +proto_pppoe_init_config() { + ppp_generic_init_config +} + +proto_pppoe_setup() { + local interface="$1" + local device="$2" + + json_get_var username username + json_get_var password password + echo "pppoe_setup($interface, $device), username=$username, password=$password" + proto_init_update pppoe-$interface 1 + proto_set_keep 1 + proto_add_ipv4_address "192.168.2.1" 32 + proto_add_dns_server "192.168.2.2" + proto_add_ipv4_route "0.0.0.0" 0 192.168.2.2 + proto_add_data + json_add_string "ppp-type" "pppoe" + proto_close_data + proto_send_update "$interface" + + proto_init_update pppoe-$interface 1 + proto_set_keep 1 + proto_add_ipv6_address "fe80::2" 64 + proto_add_ipv6_route "::0" 0 "fe80::1" + proto_add_data + json_add_string "ppp-type" "pppoe" + proto_close_data + proto_send_update "$interface" + + proto_run_command "$interface" sleep 30 +} + +proto_pppoe_teardown() { + [ "$ERROR" = 9 ] && { + proto_notify_error "$interface" PROCESS_KILLED + proto_block_restart "$interface" + } + proto_kill_command "$interface" + return +} + +add_protocol pppoe diff --git a/src/3P/netifd/examples/proto/pptp.sh b/src/3P/netifd/examples/proto/pptp.sh new file mode 100755 index 00000000..7c8a13da --- /dev/null +++ b/src/3P/netifd/examples/proto/pptp.sh @@ -0,0 +1,44 @@ +#!/bin/sh +NETIFD_MAIN_DIR=../../scripts +. $NETIFD_MAIN_DIR/netifd-proto.sh + +init_proto "$@" + +proto_pptp_init_config() { + no_device=1 + available=1 + + proto_config_add_string "username" + proto_config_add_string "password" + proto_config_add_string server +} + +proto_pptp_setup() { + local interface="$1" + local device="$2" + + json_get_var server server + proto_add_host_dependency "$interface" "$server" + + json_get_var username username + json_get_var password password + echo "pptp_setup($interface), username=$username, password=$password" + proto_init_update "pptp-$interface" 1 + proto_set_keep 1 + proto_add_ipv4_address "192.168.9.1" 32 + proto_add_dns_server "192.168.9.2" + proto_add_ipv4_route "0.0.0.0" 0 192.168.9.2 + proto_add_data + json_add_string "ppp-type" "pptp" + proto_close_data + proto_send_update "$interface" + + proto_run_command "$interface" sleep 30 +} + +proto_pptp_teardown() { + return +} + +add_protocol pptp + diff --git a/src/3P/netifd/examples/wireless/mac80211.sh b/src/3P/netifd/examples/wireless/mac80211.sh new file mode 100755 index 00000000..2ee1c73e --- /dev/null +++ b/src/3P/netifd/examples/wireless/mac80211.sh @@ -0,0 +1,66 @@ +#!/bin/sh +NETIFD_MAIN_DIR=../../scripts +. $NETIFD_MAIN_DIR/netifd-wireless.sh + +init_wireless_driver "$@" + +drv_mac80211_init_device_config() { + # identifiers + config_add_string macaddr + config_add_string path + config_add_string phy + + # config + config_add_int channel + config_add_string hwmode + config_add_array ht_capab + + config_add_int chanbw +} + +drv_mac80211_init_iface_config() { + config_add_string macaddr + + config_add_boolean wds + config_add_int maxassoc + config_add_int dtim_period + + config_add_int max_listen_int + + config_add_boolean hidden + config_add_boolean wmm +} + +setup_vif() { + local name="$1" + + json_select config + json_get_var ssid ssid + json_select .. + + wireless_add_vif "$name" "dummy-$ssid" + /bin/sleep 10 & + wireless_add_process "$!" /bin/sleep 1 +} + +drv_mac80211_cleanup() { + echo "mac80211 cleanup" +} + +drv_mac80211_setup() { + echo "mac80211 setup: $1" + json_dump + for_each_interface "sta ap adhoc" setup_vif + wireless_set_data phy=phy0 + wireless_set_up +} + +drv_mac80211_teardown() { + json_select data + json_get_var phy phy + json_select .. + echo "mac80211 teardown: $1 ($phy)" + json_dump +} + +add_driver mac80211 diff --git a/src/3P/netifd/handler.c b/src/3P/netifd/handler.c new file mode 100644 index 00000000..0c4627f1 --- /dev/null +++ b/src/3P/netifd/handler.c @@ -0,0 +1,212 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012-2013 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include "netifd.h" +#include "system.h" +#include "handler.h" + +static int +netifd_dir_push(int fd) +{ + int prev_fd = open(".", O_RDONLY | O_DIRECTORY); + system_fd_set_cloexec(prev_fd); + if (fd >= 0) + if (fchdir(fd)) {} + return prev_fd; +} + +static void +netifd_dir_pop(int prev_fd) +{ + if (fchdir(prev_fd)) {} + close(prev_fd); +} + +int netifd_open_subdir(const char *name) +{ + int prev_dir; + int ret = -1; + + prev_dir = netifd_dir_push(-1); + if (chdir(main_path)) { + perror("chdir(main path)"); + goto out; + } + + ret = open(name, O_RDONLY | O_DIRECTORY); + if (ret >= 0) + system_fd_set_cloexec(ret); + +out: + netifd_dir_pop(prev_dir); + return ret; +} + +static void +netifd_init_script_handler(const char *script, json_object *obj, script_dump_cb cb) +{ + json_object *tmp; + const char *name; + + if (!json_check_type(obj, json_type_object)) + return; + + tmp = json_get_field(obj, "name", json_type_string); + if (!tmp) + return; + + name = json_object_get_string(tmp); + cb(script, name, obj); +} + +static void +netifd_parse_script_handler(const char *name, script_dump_cb cb) +{ + struct json_tokener *tok = NULL; + json_object *obj; + static char buf[512]; + char *start, *cmd; + FILE *f; + int len; + +#define DUMP_SUFFIX " '' dump" + + cmd = alloca(strlen(name) + 1 + sizeof(DUMP_SUFFIX)); + sprintf(cmd, "%s" DUMP_SUFFIX, name); + + f = popen(cmd, "r"); + if (!f) + return; + + do { + start = fgets(buf, sizeof(buf), f); + if (!start) + continue; + + len = strlen(start); + + if (!tok) + tok = json_tokener_new(); + + obj = json_tokener_parse_ex(tok, start, len); + if (!is_error(obj)) { + netifd_init_script_handler(name, obj, cb); + json_object_put(obj); + json_tokener_free(tok); + tok = NULL; + } else if (start[len - 1] == '\n') { + json_tokener_free(tok); + tok = NULL; + } + } while (!feof(f) && !ferror(f)); + + if (tok) + json_tokener_free(tok); + + pclose(f); +} + +void netifd_init_script_handlers(int dir_fd, script_dump_cb cb) +{ + glob_t g; + int i, prev_fd; + + prev_fd = netifd_dir_push(dir_fd); + if (glob("./*.sh", 0, NULL, &g)) + return; + + for (i = 0; i < g.gl_pathc; i++) + netifd_parse_script_handler(g.gl_pathv[i], cb); + netifd_dir_pop(prev_fd); + + globfree(&g); +} + +char * +netifd_handler_parse_config(struct uci_blob_param_list *config, json_object *obj) +{ + struct blobmsg_policy *attrs; + char *str_buf, *str_cur; + char const **validate; + int str_len = 0; + int i; + + config->n_params = json_object_array_length(obj); + attrs = calloc(1, sizeof(*attrs) * config->n_params); + if (!attrs) + return NULL; + + validate = calloc(1, sizeof(char*) * config->n_params); + if (!validate) + goto error; + + config->params = attrs; + config->validate = validate; + for (i = 0; i < config->n_params; i++) { + json_object *cur, *name, *type; + + cur = json_check_type(json_object_array_get_idx(obj, i), json_type_array); + if (!cur) + goto error; + + name = json_check_type(json_object_array_get_idx(cur, 0), json_type_string); + if (!name) + goto error; + + type = json_check_type(json_object_array_get_idx(cur, 1), json_type_int); + if (!type) + goto error; + + attrs[i].name = json_object_get_string(name); + attrs[i].type = json_object_get_int(type); + if (attrs[i].type > BLOBMSG_TYPE_LAST) + goto error; + + str_len += strlen(attrs[i].name) + 1; + } + + str_buf = malloc(str_len); + if (!str_buf) + goto error; + + str_cur = str_buf; + for (i = 0; i < config->n_params; i++) { + const char *name = attrs[i].name; + char *delim; + + attrs[i].name = str_cur; + str_cur += sprintf(str_cur, "%s", name) + 1; + delim = strchr(attrs[i].name, ':'); + if (delim) { + *delim = '\0'; + validate[i] = ++delim; + } else { + validate[i] = NULL; + } + } + + return str_buf; + +error: + free(attrs); + if (validate) + free(validate); + config->n_params = 0; + return NULL; +} diff --git a/src/3P/netifd/handler.h b/src/3P/netifd/handler.h new file mode 100644 index 00000000..e3e2af5e --- /dev/null +++ b/src/3P/netifd/handler.h @@ -0,0 +1,46 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012-2013 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_HANDLER_H +#define __NETIFD_HANDLER_H + +#include +#include +#include "config.h" + +typedef void (*script_dump_cb)(const char *script, const char *name, json_object *obj); + +static inline json_object * +json_check_type(json_object *obj, json_type type) +{ + if (!obj) + return NULL; + + if (json_object_get_type(obj) != type) + return NULL; + + return obj; +} + +static inline json_object * +json_get_field(json_object *obj, const char *name, json_type type) +{ + return json_object_object_get_ex(obj, name, &obj) ? + json_check_type(obj, type) : NULL; +} + +int netifd_open_subdir(const char *name); +void netifd_init_script_handlers(int dir_fd, script_dump_cb cb); +char *netifd_handler_parse_config(struct uci_blob_param_list *config, json_object *obj); + +#endif diff --git a/src/3P/netifd/interface-event.c b/src/3P/netifd/interface-event.c new file mode 100644 index 00000000..3cdfbdbd --- /dev/null +++ b/src/3P/netifd/interface-event.c @@ -0,0 +1,200 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include + +#include "netifd.h" +#include "interface.h" +#include "ubus.h" + +char *hotplug_cmd_path = DEFAULT_HOTPLUG_PATH; +static struct interface *current; +static enum interface_event current_ev; +static struct list_head pending = LIST_HEAD_INIT(pending); + +static void task_complete(struct uloop_process *proc, int ret); +static struct uloop_process task = { + .cb = task_complete, +}; +static const char * const eventnames[] = {"ifdown", "ifup", "ifupdate", "free", "reload"}; + +static void +run_cmd(const char *ifname, const char *device, enum interface_event event, + enum interface_update_flags updated) +{ + char *argv[3]; + int pid; + + pid = fork(); + if (pid < 0) + return task_complete(NULL, -1); + + if (pid > 0) { + task.pid = pid; + uloop_process_add(&task); + return; + } + + setenv("ACTION", eventnames[event], 1); + setenv("INTERFACE", ifname, 1); + if (device) + setenv("DEVICE", device, 1); + + if (event == IFEV_UPDATE) { + if (updated & IUF_ADDRESS) + setenv("IFUPDATE_ADDRESSES", "1", 1); + if (updated & IUF_ROUTE) + setenv("IFUPDATE_ROUTES", "1", 1); + if (updated & IUF_PREFIX) + setenv("IFUPDATE_PREFIXES", "1", 1); + if (updated & IUF_DATA) + setenv("IFUPDATE_DATA", "1", 1); + } + + argv[0] = hotplug_cmd_path; + argv[1] = "iface"; + argv[2] = NULL; + execvp(argv[0], argv); + exit(127); +} + +static void +call_hotplug(void) +{ + const char *device = NULL; + if (list_empty(&pending)) + return; + + current = list_first_entry(&pending, struct interface, hotplug_list); + current_ev = current->hotplug_ev; + list_del_init(¤t->hotplug_list); + + if ((current_ev == IFEV_UP || current_ev == IFEV_UPDATE) && current->l3_dev.dev) + device = current->l3_dev.dev->ifname; + + D(SYSTEM, "Call hotplug handler for interface '%s', event '%s' (%s)\n", + current->name, eventnames[current_ev], device ? device : "none"); + run_cmd(current->name, device, current_ev, current->updated); +} + +static void +task_complete(struct uloop_process *proc, int ret) +{ + if (current) + D(SYSTEM, "Complete hotplug handler for interface '%s'\n", current->name); + current = NULL; + call_hotplug(); +} + +/* + * Queue an interface for an up/down event. + * An interface can only have one event in the queue and one + * event waiting for completion. + * When queueing an event that is the same as the one waiting for + * completion, remove the interface from the queue + */ +static void +interface_queue_event(struct interface *iface, enum interface_event ev) +{ + D(SYSTEM, "Queue hotplug handler for interface '%s', event '%s'\n", + iface->name, eventnames[ev]); + if (ev == IFEV_UP || ev == IFEV_DOWN) + netifd_ubus_interface_event(iface, ev == IFEV_UP); + + netifd_ubus_interface_notify(iface, ev != IFEV_DOWN); + + if (current == iface) { + /* an event for iface is being processed */ + if (!list_empty(&iface->hotplug_list)) { + /* an additional event for iface is pending */ + /* overwrite pending event if it differs from */ + /* an update */ + if (ev != IFEV_UPDATE) + iface->hotplug_ev = ev; + } + else { + /* no additional event for iface is pending */ + if (ev != current_ev || ev == IFEV_UPDATE) { + /* only add the interface to the pending list if + * the event is different from the one being + * handled or if it is an update */ + iface->hotplug_ev = ev; + /* Handle hotplug calls FIFO */ + list_add_tail(&iface->hotplug_list, &pending); + } + } + } + else { + /* currently not handling an event or handling an event + * for another interface */ + if (!list_empty(&iface->hotplug_list)) { + /* an event for iface is pending */ + if (!(iface->hotplug_ev == IFEV_UP && + ev == IFEV_UPDATE)) { + /* overwrite pending event, unless the incoming + * event is an ifupdate while the pending one + * is an ifup */ + iface->hotplug_ev = ev; + } + } + else { + /* an event for the interface is not yet pending, + * queue it */ + iface->hotplug_ev = ev; + /* Handle hotplug calls FIFO */ + list_add_tail(&iface->hotplug_list, &pending); + } + } + + if (!task.pending && !current) + call_hotplug(); +} + +static void +interface_dequeue_event(struct interface *iface) +{ + if (iface == current) + current = NULL; + + if (!list_empty(&iface->hotplug_list)) + list_del_init(&iface->hotplug_list); +} + +static void interface_event_cb(struct interface_user *dep, struct interface *iface, + enum interface_event ev) +{ + switch (ev) { + case IFEV_UP: + case IFEV_UPDATE: + case IFEV_DOWN: + interface_queue_event(iface, ev); + break; + case IFEV_FREE: + case IFEV_RELOAD: + interface_dequeue_event(iface); + break; + } +} + +static struct interface_user event_user = { + .cb = interface_event_cb +}; + +static void __init interface_event_init(void) +{ + interface_add_user(&event_user, NULL); +} diff --git a/src/3P/netifd/interface-ip.c b/src/3P/netifd/interface-ip.c new file mode 100644 index 00000000..26a28654 --- /dev/null +++ b/src/3P/netifd/interface-ip.c @@ -0,0 +1,1422 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * Copyright (C) 2012 Steven Barth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include +#include +#include + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "interface-ip.h" +#include "proto.h" +#include "ubus.h" +#include "system.h" + +enum { + ROUTE_INTERFACE, + ROUTE_TARGET, + ROUTE_MASK, + ROUTE_GATEWAY, + ROUTE_METRIC, + ROUTE_MTU, + ROUTE_VALID, + ROUTE_TABLE, + ROUTE_SOURCE, + ROUTE_ONLINK, + ROUTE_TYPE, + __ROUTE_MAX +}; + +static const struct blobmsg_policy route_attr[__ROUTE_MAX] = { + [ROUTE_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, + [ROUTE_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, + [ROUTE_MASK] = { .name = "netmask", .type = BLOBMSG_TYPE_STRING }, + [ROUTE_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING }, + [ROUTE_METRIC] = { .name = "metric", .type = BLOBMSG_TYPE_INT32 }, + [ROUTE_MTU] = { .name = "mtu", .type = BLOBMSG_TYPE_INT32 }, + [ROUTE_TABLE] = { .name = "table", .type = BLOBMSG_TYPE_STRING }, + [ROUTE_VALID] = { .name = "valid", .type = BLOBMSG_TYPE_INT32 }, + [ROUTE_SOURCE] = { .name = "source", .type = BLOBMSG_TYPE_STRING }, + [ROUTE_ONLINK] = { .name = "onlink", .type = BLOBMSG_TYPE_BOOL }, + [ROUTE_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING } +}; + +const struct uci_blob_param_list route_attr_list = { + .n_params = __ROUTE_MAX, + .params = route_attr, +}; + + +struct list_head prefixes = LIST_HEAD_INIT(prefixes); +static struct device_prefix *ula_prefix = NULL; +static struct uloop_timeout valid_until_timeout; + + +static void +clear_if_addr(union if_addr *a, int mask) +{ + int m_bytes = (mask + 7) / 8; + uint8_t m_clear = (1 << (m_bytes * 8 - mask)) - 1; + uint8_t *p = (uint8_t *) a; + + if (m_bytes < sizeof(*a)) + memset(p + m_bytes, 0, sizeof(*a) - m_bytes); + + p[m_bytes - 1] &= ~m_clear; +} + +static bool +match_if_addr(union if_addr *a1, union if_addr *a2, int mask) +{ + union if_addr *p1, *p2; + + p1 = alloca(sizeof(*a1)); + p2 = alloca(sizeof(*a2)); + + memcpy(p1, a1, sizeof(*a1)); + clear_if_addr(p1, mask); + memcpy(p2, a2, sizeof(*a2)); + clear_if_addr(p2, mask); + + return !memcmp(p1, p2, sizeof(*p1)); +} + +static int set_ip_source_policy(bool add, bool v6, unsigned int priority, + const union if_addr *addr, uint8_t mask, unsigned int table, + struct interface *in_iface, const char *action, bool src) +{ + struct iprule rule = { + .flags = IPRULE_PRIORITY, + .priority = priority + }; + + if (addr) { + if (src) { + rule.flags |= IPRULE_SRC; + rule.src_addr = *addr; + rule.src_mask = mask; + } else { + rule.flags |= IPRULE_DEST; + rule.dest_addr = *addr; + rule.dest_mask = mask; + } + } + + if (table) { + rule.flags |= IPRULE_LOOKUP; + rule.lookup = table; + + if (!rule.lookup) + return 0; + } else if (action) { + rule.flags |= IPRULE_ACTION; + system_resolve_iprule_action(action, &rule.action); + } + + if (in_iface && in_iface->l3_dev.dev) { + rule.flags |= IPRULE_IN; + strcpy(rule.in_dev, in_iface->l3_dev.dev->ifname); + } + + rule.flags |= (v6) ? IPRULE_INET6 : IPRULE_INET4; + + return (add) ? system_add_iprule(&rule) : system_del_iprule(&rule); +} + +static int set_ip_lo_policy(bool add, bool v6, struct interface *iface) +{ + struct iprule rule = { + .flags = IPRULE_IN | IPRULE_LOOKUP | IPRULE_PRIORITY, + .priority = IPRULE_PRIORITY_NW + iface->l3_dev.dev->ifindex, + .lookup = (v6) ? iface->ip6table : iface->ip4table, + .in_dev = "lo" + }; + + if (!rule.lookup) + return 0; + + rule.flags |= (v6) ? IPRULE_INET6 : IPRULE_INET4; + + return (add) ? system_add_iprule(&rule) : system_del_iprule(&rule); +} + +static bool +__find_ip_addr_target(struct interface_ip_settings *ip, union if_addr *a, bool v6) +{ + struct device_addr *addr; + + vlist_for_each_element(&ip->addr, addr, node) { + if (!addr->enabled) + continue; + + if (v6 != ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET6)) + continue; + + // Handle offlink addresses correctly + unsigned int mask = addr->mask; + if ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET6 && + (addr->flags & DEVADDR_OFFLINK)) + mask = 128; + + if (!match_if_addr(&addr->addr, a, mask)) + continue; + + return true; + } + + return false; +} + +static void +__find_ip_route_target(struct interface_ip_settings *ip, union if_addr *a, + bool v6, struct device_route **res) +{ + struct device_route *route; + + vlist_for_each_element(&ip->route, route, node) { + if (!route->enabled) + continue; + + if (v6 != ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET6)) + continue; + + if (!match_if_addr(&route->addr, a, route->mask)) + continue; + + if (route->flags & DEVROUTE_TABLE) + continue; + + if (!*res || route->mask < (*res)->mask) + *res = route; + } +} + +static bool +interface_ip_find_addr_target(struct interface *iface, union if_addr *a, bool v6) +{ + return __find_ip_addr_target(&iface->proto_ip, a, v6) || + __find_ip_addr_target(&iface->config_ip, a, v6); +} + +static void +interface_ip_find_route_target(struct interface *iface, union if_addr *a, + bool v6, struct device_route **route) +{ + __find_ip_route_target(&iface->proto_ip, a, v6, route); + __find_ip_route_target(&iface->config_ip, a, v6, route); +} + +struct interface * +interface_ip_add_target_route(union if_addr *addr, bool v6, struct interface *iface) +{ + struct device_route *route, *r_next = NULL; + bool defaultroute_target = false; + int addrsize = v6 ? sizeof(addr->in6) : sizeof(addr->in); + + route = calloc(1, sizeof(*route)); + if (!route) + return NULL; + + route->flags = v6 ? DEVADDR_INET6 : DEVADDR_INET4; + route->mask = v6 ? 128 : 32; + if (memcmp(&route->addr, addr, addrsize) == 0) + defaultroute_target = true; + else + memcpy(&route->addr, addr, addrsize); + + if (iface) { + /* look for locally addressable target first */ + if (interface_ip_find_addr_target(iface, addr, v6)) + goto done; + + /* do not stop at the first route, let the lookup compare + * masks to find the best match */ + interface_ip_find_route_target(iface, addr, v6, &r_next); + } else { + vlist_for_each_element(&interfaces, iface, node) { + /* look for locally addressable target first */ + if (interface_ip_find_addr_target(iface, addr, v6)) + goto done; + + /* do not stop at the first route, let the lookup compare + * masks to find the best match */ + interface_ip_find_route_target(iface, addr, v6, &r_next); + } + } + + if (!r_next) { + free(route); + return NULL; + } + + iface = r_next->iface; + memcpy(&route->nexthop, &r_next->nexthop, sizeof(route->nexthop)); + route->mtu = r_next->mtu; + route->metric = r_next->metric; + route->table = r_next->table; + +done: + route->iface = iface; + if (defaultroute_target) + free(route); + else + vlist_add(&iface->host_routes, &route->node, route); + return iface; +} + +static void +interface_set_route_info(struct interface *iface, struct device_route *route) +{ + bool v6 = ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET6); + + if (!iface) + return; + + if (!(route->flags & DEVROUTE_METRIC)) + route->metric = iface->metric; + + if (!(route->flags & DEVROUTE_TABLE)) { + route->table = (v6) ? iface->ip6table : iface->ip4table; + if (route->table) + route->flags |= DEVROUTE_SRCTABLE; + } +} + +void +interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6) +{ + struct interface_ip_settings *ip; + struct blob_attr *tb[__ROUTE_MAX], *cur; + struct device_route *route; + int af = v6 ? AF_INET6 : AF_INET; + + blobmsg_parse(route_attr, __ROUTE_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); + + if (!iface) { + if ((cur = tb[ROUTE_INTERFACE]) == NULL) + return; + + iface = vlist_find(&interfaces, blobmsg_data(cur), iface, node); + if (!iface) + return; + + ip = &iface->config_ip; + } else { + ip = &iface->proto_ip; + } + + route = calloc(1, sizeof(*route)); + if (!route) + return; + + route->flags = v6 ? DEVADDR_INET6 : DEVADDR_INET4; + route->mask = v6 ? 128 : 32; + if ((cur = tb[ROUTE_MASK]) != NULL) { + route->mask = parse_netmask_string(blobmsg_data(cur), v6); + if (route->mask > (v6 ? 128 : 32)) + goto error; + } + + if ((cur = tb[ROUTE_TARGET]) != NULL) { + if (!parse_ip_and_netmask(af, blobmsg_data(cur), &route->addr, &route->mask)) { + DPRINTF("Failed to parse route target: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + } + + if ((cur = tb[ROUTE_GATEWAY]) != NULL) { + if (!inet_pton(af, blobmsg_data(cur), &route->nexthop)) { + DPRINTF("Failed to parse route gateway: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + } + + if ((cur = tb[ROUTE_METRIC]) != NULL) { + route->metric = blobmsg_get_u32(cur); + route->flags |= DEVROUTE_METRIC; + } + + if ((cur = tb[ROUTE_MTU]) != NULL) { + route->mtu = blobmsg_get_u32(cur); + route->flags |= DEVROUTE_MTU; + } + + // Use source-based routing + if ((cur = tb[ROUTE_SOURCE]) != NULL) { + char *saveptr, *source = alloca(blobmsg_data_len(cur)); + memcpy(source, blobmsg_data(cur), blobmsg_data_len(cur)); + + const char *addr = strtok_r(source, "/", &saveptr); + const char *mask = strtok_r(NULL, "/", &saveptr); + + if (!addr || inet_pton(af, addr, &route->source) < 1) { + DPRINTF("Failed to parse route source: %s\n", addr ? addr : "NULL"); + goto error; + } + + route->sourcemask = (mask) ? atoi(mask) : ((af == AF_INET6) ? 128 : 32); + } + + if ((cur = tb[ROUTE_ONLINK]) != NULL && blobmsg_get_bool(cur)) + route->flags |= DEVROUTE_ONLINK; + + if ((cur = tb[ROUTE_TABLE]) != NULL) { + if (!system_resolve_rt_table(blobmsg_data(cur), &route->table)) { + DPRINTF("Failed to resolve routing table: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + + /* only set the table flag if not using the main (default) table */ + if (system_is_default_rt_table(route->table)) + route->table = 0; + + if (route->table) + route->flags |= DEVROUTE_TABLE; + } + + if ((cur = tb[ROUTE_VALID]) != NULL) { + int64_t valid = blobmsg_get_u32(cur); + int64_t valid_until = valid + (int64_t)system_get_rtime(); + if (valid_until <= LONG_MAX && valid != 0xffffffffLL) // Catch overflow + route->valid_until = valid_until; + } + + if ((cur = tb[ROUTE_TYPE]) != NULL) { + if (!system_resolve_rt_type(blobmsg_data(cur), &route->type)) { + DPRINTF("Failed to resolve routing type: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + route->flags |= DEVROUTE_TYPE; + } + + interface_set_route_info(iface, route); + vlist_add(&ip->route, &route->node, route); + return; + +error: + free(route); +} + +static int +addr_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, sizeof(struct device_addr) - + offsetof(struct device_addr, flags)); +} + +static int +route_cmp(const void *k1, const void *k2, void *ptr) +{ + const struct device_route *r1 = k1, *r2 = k2; + + if (r1->mask != r2->mask) + return r2->mask - r1->mask; + + if (r1->metric != r2->metric) + return r1->metric - r2->metric; + + if (r1->flags != r2->flags) + return r2->flags - r1->flags; + + if (r1->sourcemask != r2->sourcemask) + return r1->sourcemask - r2->sourcemask; + + if (r1->table != r2->table) + return r1->table - r2->table; + + int maskcmp = memcmp(&r1->source, &r2->source, sizeof(r1->source)); + if (maskcmp) + return maskcmp; + + return memcmp(&r1->addr, &r2->addr, sizeof(r1->addr)); +} + +static int +prefix_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, offsetof(struct device_prefix, pclass) - + offsetof(struct device_prefix, addr)); +} + +static void +interface_handle_subnet_route(struct interface *iface, struct device_addr *addr, bool add) +{ + struct device *dev = iface->l3_dev.dev; + struct device_route *r = &addr->subnet; + + if (addr->flags & DEVADDR_OFFLINK) + return; + + if (!add) { + if (!addr->subnet.iface) + return; + + system_del_route(dev, r); + memset(r, 0, sizeof(*r)); + return; + } + + r->iface = iface; + r->flags = addr->flags; + r->mask = addr->mask; + memcpy(&r->addr, &addr->addr, sizeof(r->addr)); + clear_if_addr(&r->addr, r->mask); + + r->flags |= DEVADDR_KERNEL; + system_del_route(dev, r); + + r->flags &= ~DEVADDR_KERNEL; + interface_set_route_info(iface, r); + + system_add_route(dev, r); +} + +static void +interface_add_addr_rules(struct device_addr *addr, bool enabled) +{ + bool v6 = (addr->flags & DEVADDR_FAMILY) == DEVADDR_INET6; + + set_ip_source_policy(enabled, v6, IPRULE_PRIORITY_ADDR, &addr->addr, + (v6) ? 128 : 32, addr->policy_table, NULL, NULL, + true); + set_ip_source_policy(enabled, v6, IPRULE_PRIORITY_ADDR_MASK, + &addr->addr, addr->mask, addr->policy_table, NULL, + NULL, false); +} + +static void +interface_update_proto_addr(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct interface_ip_settings *ip; + struct interface *iface; + struct device *dev; + struct device_addr *a_new = NULL, *a_old = NULL; + bool replace = false; + bool keep = false; + bool v6 = false; + + ip = container_of(tree, struct interface_ip_settings, addr); + iface = ip->iface; + dev = iface->l3_dev.dev; + + if (!node_new || !node_old) + iface->updated |= IUF_ADDRESS; + + if (node_new) { + a_new = container_of(node_new, struct device_addr, node); + + if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4 && + !a_new->broadcast) { + + /* /31 and /32 addressing need 255.255.255.255 + * as broadcast address. */ + if (a_new->mask >= 31) { + a_new->broadcast = (uint32_t) ~0; + } else { + uint32_t mask = ~0; + uint32_t *a = (uint32_t *) &a_new->addr; + + mask >>= a_new->mask; + a_new->broadcast = *a | htonl(mask); + } + } + } + + if (node_old) + a_old = container_of(node_old, struct device_addr, node); + + if (a_new && a_old) { + keep = true; + + if (a_old->flags != a_new->flags || a_old->failed) + keep = false; + + if (a_old->valid_until != a_new->valid_until || + a_old->preferred_until != a_new->preferred_until) + replace = true; + + if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET4 && + a_new->broadcast != a_old->broadcast) + keep = false; + } + + if (node_old) { + if (a_old->enabled && !keep) { + //This is needed for source routing to work correctly. If a device + //has two connections to a network using the same subnet, adding + //only the network-rule will cause packets to be routed through the + //first matching network (source IP matches both masks). + if (a_old->policy_table) + interface_add_addr_rules(a_old, false); + + if (!(a_old->flags & DEVADDR_EXTERNAL)) { + interface_handle_subnet_route(iface, a_old, false); + system_del_address(dev, a_old); + } + } + free(a_old->pclass); + free(a_old); + } + + if (node_new) { + a_new->enabled = true; + + if ((a_new->flags & DEVADDR_FAMILY) == DEVADDR_INET6) + v6 = true; + + a_new->policy_table = (v6) ? iface->ip6table : iface->ip4table; + + if (!keep || replace) { + if (!(a_new->flags & DEVADDR_EXTERNAL)) { + if (system_add_address(dev, a_new)) + a_new->failed = true; + + if (iface->metric || a_new->policy_table) + interface_handle_subnet_route(iface, a_new, true); + } + + if (!keep) { + if (a_new->policy_table) + interface_add_addr_rules(a_new, true); + } + } + } +} + +static bool +enable_route(struct interface_ip_settings *ip, struct device_route *route) +{ + if (ip->no_defaultroute && !route->mask) + return false; + + return ip->enabled; +} + +static void +interface_update_proto_route(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct interface_ip_settings *ip; + struct interface *iface; + struct device *dev; + struct device_route *route_old, *route_new; + bool keep = false; + + ip = container_of(tree, struct interface_ip_settings, route); + iface = ip->iface; + dev = iface->l3_dev.dev; + + if (!node_new || !node_old) + iface->updated |= IUF_ROUTE; + + route_old = container_of(node_old, struct device_route, node); + route_new = container_of(node_new, struct device_route, node); + + if (node_old && node_new) + keep = !memcmp(&route_old->nexthop, &route_new->nexthop, sizeof(route_old->nexthop)) && + (route_old->mtu == route_new->mtu) && (route_old->type == route_new->type) && + !route_old->failed; + + if (node_old) { + if (!(route_old->flags & DEVADDR_EXTERNAL) && route_old->enabled && !keep) + system_del_route(dev, route_old); + + free(route_old); + } + + if (node_new) { + bool _enabled = enable_route(ip, route_new); + + if (!(route_new->flags & DEVADDR_EXTERNAL) && !keep && _enabled) + if (system_add_route(dev, route_new)) + route_new->failed = true; + + route_new->iface = iface; + route_new->enabled = _enabled; + } +} + +static void +interface_update_host_route(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct interface *iface; + struct device *dev; + struct device_route *route_old, *route_new; + + iface = container_of(tree, struct interface, host_routes); + dev = iface->l3_dev.dev; + + route_old = container_of(node_old, struct device_route, node); + route_new = container_of(node_new, struct device_route, node); + + if (node_old) { + system_del_route(dev, route_old); + free(route_old); + } + + if (node_new) { + if (system_add_route(dev, route_new)) + route_new->failed = true; + } +} + +static void +random_ifaceid(struct in6_addr *addr) +{ + static bool initialized = false; + struct timeval t; + + if (!initialized) { + long int seed = 0; + gettimeofday(&t, NULL); + seed = t.tv_sec ^ t.tv_usec ^ getpid(); + srand48(seed); + initialized = true; + } + addr->s6_addr32[2] = (uint32_t)mrand48(); + addr->s6_addr32[3] = (uint32_t)mrand48(); +} + +static void +eui64_ifaceid(struct interface *iface, struct in6_addr *addr) +{ + /* get mac address */ + uint8_t *macaddr = iface->l3_dev.dev->settings.macaddr; + uint8_t *ifaceid = addr->s6_addr + 8; + memcpy(ifaceid,macaddr,3); + memcpy(ifaceid + 5,macaddr + 3, 3); + ifaceid[3] = 0xff; + ifaceid[4] = 0xfe; + ifaceid[0] ^= 0x02; +} + +static void +generate_ifaceid(struct interface *iface, struct in6_addr *addr) +{ + /* generate new iface id */ + switch (iface->assignment_iface_id_selection) { + case IFID_FIXED: + /* fixed */ + /* copy host part from assignment_fixed_iface_id */ + memcpy(addr->s6_addr + 8, iface->assignment_fixed_iface_id.s6_addr + 8, 8); + break; + case IFID_RANDOM: + /* randomize last 64 bits */ + random_ifaceid(addr); + break; + case IFID_EUI64: + /* eui64 */ + eui64_ifaceid(iface, addr); + break; + } +} + +static void +interface_set_prefix_address(struct device_prefix_assignment *assignment, + const struct device_prefix *prefix, struct interface *iface, bool add) +{ + const struct interface *uplink = prefix->iface; + if (!iface->l3_dev.dev) + return; + + struct device *l3_downlink = iface->l3_dev.dev; + + struct device_addr addr; + struct device_route route; + memset(&addr, 0, sizeof(addr)); + memset(&route, 0, sizeof(route)); + + if (IN6_IS_ADDR_UNSPECIFIED(&assignment->addr)) { + addr.addr.in6 = prefix->addr; + addr.addr.in6.s6_addr32[1] |= htonl(assignment->assigned); + generate_ifaceid(iface, &addr.addr.in6); + assignment->addr = addr.addr.in6; + } + else + addr.addr.in6 = assignment->addr; + + addr.mask = assignment->length; + addr.flags = DEVADDR_INET6 | DEVADDR_OFFLINK; + addr.preferred_until = prefix->preferred_until; + addr.valid_until = prefix->valid_until; + + route.flags = DEVADDR_INET6; + route.mask = addr.mask < 64 ? 64 : addr.mask; + route.addr = addr.addr; + clear_if_addr(&route.addr, route.mask); + interface_set_route_info(iface, &route); + + if (!add && assignment->enabled) { + time_t now = system_get_rtime(); + addr.preferred_until = now; + if (!addr.valid_until || addr.valid_until - now > 7200) + addr.valid_until = now + 7200; + + if (prefix->iface) { + if (prefix->iface->ip6table) + set_ip_source_policy(false, true, IPRULE_PRIORITY_NW, &addr.addr, + addr.mask, prefix->iface->ip6table, iface, NULL, true); + + set_ip_source_policy(false, true, IPRULE_PRIORITY_REJECT, &addr.addr, + addr.mask, 0, iface, "unreachable", true); + } + + system_del_route(l3_downlink, &route); + system_add_address(l3_downlink, &addr); + + assignment->enabled = false; + } else if (add && (iface->state == IFS_UP || iface->state == IFS_SETUP) && + !system_add_address(l3_downlink, &addr)) { + + if (prefix->iface && !assignment->enabled) { + set_ip_source_policy(true, true, IPRULE_PRIORITY_REJECT, &addr.addr, + addr.mask, 0, iface, "unreachable", true); + + if (prefix->iface->ip6table) + set_ip_source_policy(true, true, IPRULE_PRIORITY_NW, &addr.addr, + addr.mask, prefix->iface->ip6table, iface, NULL, true); + } + + route.metric = iface->metric; + system_add_route(l3_downlink, &route); + + if (uplink && uplink->l3_dev.dev && !(l3_downlink->settings.flags & DEV_OPT_MTU6)) { + int mtu = system_update_ipv6_mtu(uplink->l3_dev.dev, 0); + int mtu_old = system_update_ipv6_mtu(l3_downlink, 0); + + if (mtu > 0 && mtu_old > mtu) + system_update_ipv6_mtu(l3_downlink, mtu); + } + + assignment->enabled = true; + } +} + +static bool interface_prefix_assign(struct list_head *list, + struct device_prefix_assignment *assign) +{ + int32_t current = 0, asize = (1 << (64 - assign->length)) - 1; + struct device_prefix_assignment *c; + list_for_each_entry(c, list, head) { + if (assign->assigned != -1) { + if (assign->assigned >= current && assign->assigned + asize < c->assigned) { + list_add_tail(&assign->head, &c->head); + return true; + } + } else if (assign->assigned == -1) { + current = (current + asize) & (~asize); + if (current + asize < c->assigned) { + assign->assigned = current; + list_add_tail(&assign->head, &c->head); + return true; + } + } + current = (c->assigned + (1 << (64 - c->length))); + } + return false; +} + +static void interface_update_prefix_assignments(struct device_prefix *prefix, bool setup) +{ + struct device_prefix_assignment *c; + struct interface *iface; + + // Delete all assignments + while (!list_empty(&prefix->assignments)) { + c = list_first_entry(&prefix->assignments, + struct device_prefix_assignment, head); + if ((iface = vlist_find(&interfaces, c->name, iface, node))) + interface_set_prefix_address(c, prefix, iface, false); + list_del(&c->head); + free(c); + } + + if (!setup) + return; + + // End-of-assignment sentinel + c = malloc(sizeof(*c) + 1); + if (!c) + return; + + c->assigned = 1 << (64 - prefix->length); + c->length = 64; + c->name[0] = 0; + c->addr = in6addr_any; + list_add(&c->head, &prefix->assignments); + + // Excluded prefix + if (prefix->excl_length > 0) { + const char name[] = "!excluded"; + c = malloc(sizeof(*c) + sizeof(name)); + if (c) { + c->assigned = ntohl(prefix->excl_addr.s6_addr32[1]) & + ((1 << (64 - prefix->length)) - 1); + c->length = prefix->excl_length; + c->addr = in6addr_any; + memcpy(c->name, name, sizeof(name)); + list_add(&c->head, &prefix->assignments); + } + } + + bool assigned_any = false; + struct list_head assign_later = LIST_HEAD_INIT(assign_later); + vlist_for_each_element(&interfaces, iface, node) { + if (iface->assignment_length < 48 || + iface->assignment_length > 64) + continue; + + // Test whether there is a matching class + if (!list_empty(&iface->assignment_classes)) { + bool found = false; + + struct interface_assignment_class *c; + list_for_each_entry(c, &iface->assignment_classes, head) { + if (!strcmp(c->name, prefix->pclass)) { + found = true; + break; + } + } + + if (!found) + continue; + } + + size_t namelen = strlen(iface->name) + 1; + c = malloc(sizeof(*c) + namelen); + if (!c) + continue; + + c->length = iface->assignment_length; + c->assigned = iface->assignment_hint; + c->addr = in6addr_any; + c->enabled = false; + memcpy(c->name, iface->name, namelen); + + // First process all custom assignments, put all others in later-list + if (c->assigned == -1 || !interface_prefix_assign(&prefix->assignments, c)) { + if (c->assigned != -1) { + c->assigned = -1; + netifd_log_message(L_WARNING, "Failed to assign requested subprefix " + "of size %hhu for %s, trying other\n", c->length, c->name); + } + + struct list_head *next = &assign_later; + struct device_prefix_assignment *n; + list_for_each_entry(n, &assign_later, head) { + if (n->length < c->length) { + next = &n->head; + break; + } + } + list_add_tail(&c->head, next); + } + + if (c->assigned != -1) + assigned_any = true; + } + + // Then try to assign all other + failed custom assignments + while (!list_empty(&assign_later)) { + c = list_first_entry(&assign_later, struct device_prefix_assignment, head); + list_del(&c->head); + + bool assigned = false; + do { + assigned = interface_prefix_assign(&prefix->assignments, c); + } while (!assigned && ++c->length <= 64); + + if (!assigned) { + netifd_log_message(L_WARNING, "Failed to assign subprefix " + "of size %hhu for %s\n", c->length, c->name); + free(c); + } else { + assigned_any = true; + } + } + + list_for_each_entry(c, &prefix->assignments, head) + if ((iface = vlist_find(&interfaces, c->name, iface, node))) + interface_set_prefix_address(c, prefix, iface, true); + + if (!assigned_any) + netifd_log_message(L_WARNING, "You have delegated IPv6-prefixes but haven't assigned them " + "to any interface. Did you forget to set option ip6assign on your lan-interfaces?"); +} + + +void interface_refresh_assignments(bool hint) +{ + static bool refresh = false; + if (!hint && refresh) { + struct device_prefix *p; + list_for_each_entry(p, &prefixes, head) + interface_update_prefix_assignments(p, true); + } + refresh = hint; +} + + +static void +interface_update_prefix(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct device_prefix *prefix_old, *prefix_new; + prefix_old = container_of(node_old, struct device_prefix, node); + prefix_new = container_of(node_new, struct device_prefix, node); + + struct interface_ip_settings *ip = container_of(tree, struct interface_ip_settings, prefix); + if (tree && (!node_new || !node_old)) + ip->iface->updated |= IUF_PREFIX; + + struct device_route route; + memset(&route, 0, sizeof(route)); + route.flags = DEVADDR_INET6; + route.metric = INT32_MAX; + route.mask = (node_new) ? prefix_new->length : prefix_old->length; + route.addr.in6 = (node_new) ? prefix_new->addr : prefix_old->addr; + + + struct device_prefix_assignment *c; + struct interface *iface; + + if (node_old && node_new) { + // Move assignments and refresh addresses to update valid times + list_splice(&prefix_old->assignments, &prefix_new->assignments); + + list_for_each_entry(c, &prefix_new->assignments, head) + if ((iface = vlist_find(&interfaces, c->name, iface, node))) + interface_set_prefix_address(c, prefix_new, iface, true); + } else if (node_new) { + // Set null-route to avoid routing loops + system_add_route(NULL, &route); + + if (!prefix_new->iface || !prefix_new->iface->proto_ip.no_delegation) + interface_update_prefix_assignments(prefix_new, true); + } else if (node_old) { + // Remove null-route + interface_update_prefix_assignments(prefix_old, false); + system_del_route(NULL, &route); + } + + if (node_old) { + if (prefix_old->head.next) + list_del(&prefix_old->head); + free(prefix_old); + } + + if (node_new && (!prefix_new->iface || !prefix_new->iface->proto_ip.no_delegation)) + list_add(&prefix_new->head, &prefixes); + +} + +struct device_prefix* +interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr, + uint8_t length, time_t valid_until, time_t preferred_until, + struct in6_addr *excl_addr, uint8_t excl_length, const char *pclass) +{ + if (!pclass) + pclass = (iface) ? iface->name : "local"; + + struct device_prefix *prefix = calloc(1, sizeof(*prefix) + strlen(pclass) + 1); + if (!prefix) + return NULL; + + prefix->length = length; + prefix->addr = *addr; + prefix->preferred_until = preferred_until; + prefix->valid_until = valid_until; + prefix->iface = iface; + INIT_LIST_HEAD(&prefix->assignments); + + if (excl_addr) { + prefix->excl_addr = *excl_addr; + prefix->excl_length = excl_length; + } + + strcpy(prefix->pclass, pclass); + + if (iface) + vlist_add(&iface->proto_ip.prefix, &prefix->node, &prefix->addr); + else + interface_update_prefix(NULL, &prefix->node, NULL); + + return prefix; +} + +void +interface_ip_set_ula_prefix(const char *prefix) +{ + char buf[INET6_ADDRSTRLEN + 4] = {0}, *saveptr; + if (prefix) + strncpy(buf, prefix, sizeof(buf) - 1); + char *prefixaddr = strtok_r(buf, "/", &saveptr); + + struct in6_addr addr; + if (!prefixaddr || inet_pton(AF_INET6, prefixaddr, &addr) < 1) { + if (ula_prefix) { + interface_update_prefix(NULL, NULL, &ula_prefix->node); + ula_prefix = NULL; + } + return; + } + + int length; + char *prefixlen = strtok_r(NULL, ",", &saveptr); + if (!prefixlen || (length = atoi(prefixlen)) < 1 || length > 64) + return; + + if (!ula_prefix || !IN6_ARE_ADDR_EQUAL(&addr, &ula_prefix->addr) || + ula_prefix->length != length) { + if (ula_prefix) + interface_update_prefix(NULL, NULL, &ula_prefix->node); + + ula_prefix = interface_ip_add_device_prefix(NULL, &addr, length, + 0, 0, NULL, 0, NULL); + } +} + +void +interface_add_dns_server(struct interface_ip_settings *ip, const char *str) +{ + struct dns_server *s; + + s = calloc(1, sizeof(*s)); + if (!s) + return; + + s->af = AF_INET; + if (inet_pton(s->af, str, &s->addr.in)) + goto add; + + s->af = AF_INET6; + if (inet_pton(s->af, str, &s->addr.in)) + goto add; + + free(s); + return; + +add: + D(INTERFACE, "Add IPv%c DNS server: %s\n", + s->af == AF_INET6 ? '6' : '4', str); + vlist_simple_add(&ip->dns_servers, &s->node); +} + +void +interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr *list) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, list, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + if (!blobmsg_check_attr(cur, NULL)) + continue; + + interface_add_dns_server(ip, blobmsg_data(cur)); + } +} + +static void +interface_add_dns_search_domain(struct interface_ip_settings *ip, const char *str) +{ + struct dns_search_domain *s; + int len = strlen(str); + + s = calloc(1, sizeof(*s) + len + 1); + if (!s) + return; + + D(INTERFACE, "Add DNS search domain: %s\n", str); + memcpy(s->name, str, len); + vlist_simple_add(&ip->dns_search, &s->node); +} + +void +interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, list, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + if (!blobmsg_check_attr(cur, NULL)) + continue; + + interface_add_dns_search_domain(ip, blobmsg_data(cur)); + } +} + +static void +write_resolv_conf_entries(FILE *f, struct interface_ip_settings *ip, const char *dev) +{ + struct dns_server *s; + struct dns_search_domain *d; + const char *str; + char buf[INET6_ADDRSTRLEN]; + + vlist_simple_for_each_element(&ip->dns_servers, s, node) { + str = inet_ntop(s->af, &s->addr, buf, sizeof(buf)); + if (!str) + continue; + + if (s->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&s->addr.in6)) + fprintf(f, "nameserver %s%%%s\n", str, dev); + else + fprintf(f, "nameserver %s\n", str); + } + + vlist_simple_for_each_element(&ip->dns_search, d, node) { + fprintf(f, "search %s\n", d->name); + } +} + +void +interface_write_resolv_conf(void) +{ + struct interface *iface; + char *path = alloca(strlen(resolv_conf) + 5); + FILE *f; + uint32_t crcold, crcnew; + + sprintf(path, "%s.tmp", resolv_conf); + unlink(path); + f = fopen(path, "w+"); + if (!f) { + D(INTERFACE, "Failed to open %s for writing\n", path); + return; + } + + vlist_for_each_element(&interfaces, iface, node) { + if (iface->state != IFS_UP) + continue; + + if (vlist_simple_empty(&iface->proto_ip.dns_search) && + vlist_simple_empty(&iface->proto_ip.dns_servers) && + vlist_simple_empty(&iface->config_ip.dns_search) && + vlist_simple_empty(&iface->config_ip.dns_servers)) + continue; + + fprintf(f, "# Interface %s\n", iface->name); + write_resolv_conf_entries(f, &iface->config_ip, iface->ifname); + if (!iface->proto_ip.no_dns) + write_resolv_conf_entries(f, &iface->proto_ip, iface->ifname); + } + fflush(f); + rewind(f); + crcnew = crc32_file(f); + fclose(f); + + crcold = crcnew + 1; + f = fopen(resolv_conf, "r"); + if (f) { + crcold = crc32_file(f); + fclose(f); + } + + if (crcold == crcnew) { + unlink(path); + } else if (rename(path, resolv_conf) < 0) { + D(INTERFACE, "Failed to replace %s\n", resolv_conf); + unlink(path); + } +} + +void interface_ip_set_enabled(struct interface_ip_settings *ip, bool enabled) +{ + struct device_addr *addr; + struct device_route *route; + struct device *dev; + struct interface *iface; + + ip->enabled = enabled; + iface = ip->iface; + dev = iface->l3_dev.dev; + if (!dev) + return; + + vlist_for_each_element(&ip->addr, addr, node) { + bool v6 = ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET6) ? true : false; + + if (addr->flags & DEVADDR_EXTERNAL) + continue; + + if (addr->enabled == enabled) + continue; + + if (enabled) { + system_add_address(dev, addr); + + addr->policy_table = (v6) ? iface->ip6table : iface->ip4table; + if (iface->metric || addr->policy_table) + interface_handle_subnet_route(iface, addr, true); + + if (addr->policy_table) + interface_add_addr_rules(addr, true); + } else { + interface_handle_subnet_route(iface, addr, false); + system_del_address(dev, addr); + + if (addr->policy_table) + interface_add_addr_rules(addr, false); + } + addr->enabled = enabled; + } + + vlist_for_each_element(&ip->route, route, node) { + bool _enabled = enabled; + + if (route->flags & DEVADDR_EXTERNAL) + continue; + + if (!enable_route(ip, route)) + _enabled = false; + + if (route->enabled == _enabled) + continue; + + if (_enabled) { + interface_set_route_info(ip->iface, route); + + if (system_add_route(dev, route)) + route->failed = true; + } else + system_del_route(dev, route); + route->enabled = _enabled; + } + + struct device_prefix *c; + struct device_prefix_assignment *a; + list_for_each_entry(c, &prefixes, head) + list_for_each_entry(a, &c->assignments, head) + if (!strcmp(a->name, ip->iface->name)) + interface_set_prefix_address(a, c, ip->iface, enabled); + + if (ip->iface && ip->iface->policy_rules_set != enabled && + ip->iface->l3_dev.dev) { + set_ip_lo_policy(enabled, true, ip->iface); + set_ip_lo_policy(enabled, false, ip->iface); + + set_ip_source_policy(enabled, true, IPRULE_PRIORITY_REJECT + ip->iface->l3_dev.dev->ifindex, + NULL, 0, 0, ip->iface, "failed_policy", true); + ip->iface->policy_rules_set = enabled; + } +} + +void +interface_ip_update_start(struct interface_ip_settings *ip) +{ + if (ip != &ip->iface->config_ip) { + vlist_simple_update(&ip->dns_servers); + vlist_simple_update(&ip->dns_search); + } + vlist_update(&ip->route); + vlist_update(&ip->addr); + vlist_update(&ip->prefix); +} + +void +interface_ip_update_complete(struct interface_ip_settings *ip) +{ + vlist_simple_flush(&ip->dns_servers); + vlist_simple_flush(&ip->dns_search); + vlist_flush(&ip->route); + vlist_flush(&ip->addr); + vlist_flush(&ip->prefix); + interface_write_resolv_conf(); +} + +void +interface_ip_flush(struct interface_ip_settings *ip) +{ + if (ip == &ip->iface->proto_ip) + vlist_flush_all(&ip->iface->host_routes); + vlist_simple_flush_all(&ip->dns_servers); + vlist_simple_flush_all(&ip->dns_search); + vlist_flush_all(&ip->route); + vlist_flush_all(&ip->addr); + vlist_flush_all(&ip->prefix); +} + +static void +__interface_ip_init(struct interface_ip_settings *ip, struct interface *iface) +{ + ip->iface = iface; + ip->enabled = true; + vlist_simple_init(&ip->dns_search, struct dns_search_domain, node); + vlist_simple_init(&ip->dns_servers, struct dns_server, node); + vlist_init(&ip->route, route_cmp, interface_update_proto_route); + vlist_init(&ip->addr, addr_cmp, interface_update_proto_addr); + vlist_init(&ip->prefix, prefix_cmp, interface_update_prefix); +} + +void +interface_ip_init(struct interface *iface) +{ + __interface_ip_init(&iface->proto_ip, iface); + __interface_ip_init(&iface->config_ip, iface); + vlist_init(&iface->host_routes, route_cmp, interface_update_host_route); +} + +static void +interface_ip_valid_until_handler(struct uloop_timeout *t) +{ + time_t now = system_get_rtime(); + struct interface *iface; + vlist_for_each_element(&interfaces, iface, node) { + if (iface->state != IFS_UP) + continue; + + struct device_addr *addr, *addrp; + struct device_route *route, *routep; + struct device_prefix *pref, *prefp; + + vlist_for_each_element_safe(&iface->proto_ip.addr, addr, node, addrp) + if (addr->valid_until && addr->valid_until < now) + vlist_delete(&iface->proto_ip.addr, &addr->node); + + vlist_for_each_element_safe(&iface->proto_ip.route, route, node, routep) + if (route->valid_until && route->valid_until < now) + vlist_delete(&iface->proto_ip.route, &route->node); + + vlist_for_each_element_safe(&iface->proto_ip.prefix, pref, node, prefp) + if (pref->valid_until && pref->valid_until < now) + vlist_delete(&iface->proto_ip.prefix, &pref->node); + + } + + uloop_timeout_set(t, 1000); +} + +static void __init +interface_ip_init_worker(void) +{ + valid_until_timeout.cb = interface_ip_valid_until_handler; + uloop_timeout_set(&valid_until_timeout, 1000); +} diff --git a/src/3P/netifd/interface-ip.h b/src/3P/netifd/interface-ip.h new file mode 100644 index 00000000..bbef62ce --- /dev/null +++ b/src/3P/netifd/interface-ip.h @@ -0,0 +1,175 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __INTERFACE_IP_H +#define __INTERFACE_IP_H + +#include "interface.h" + +enum device_addr_flags { + /* address family for routes and addresses */ + DEVADDR_INET4 = (0 << 0), + DEVADDR_INET6 = (1 << 0), + DEVADDR_FAMILY = DEVADDR_INET4 | DEVADDR_INET6, + + /* externally added address */ + DEVADDR_EXTERNAL = (1 << 2), + + /* route overrides the default interface metric */ + DEVROUTE_METRIC = (1 << 3), + + /* route overrides the default interface mtu */ + DEVROUTE_MTU = (1 << 4), + + /* route automatically added by kernel */ + DEVADDR_KERNEL = (1 << 5), + + /* address is off-link (no subnet-route) */ + DEVADDR_OFFLINK = (1 << 6), + + /* route resides in different table */ + DEVROUTE_TABLE = (1 << 7), + + /* route resides in default source-route table */ + DEVROUTE_SRCTABLE = (1 << 8), + + /* route is on-link */ + DEVROUTE_ONLINK = (1 << 9), + + /* route overrides the default route type */ + DEVROUTE_TYPE = (1 << 10), +}; + +union if_addr { + struct in_addr in; + struct in6_addr in6; +}; + +struct device_prefix_assignment { + struct list_head head; + int32_t assigned; + uint8_t length; + struct in6_addr addr; + bool enabled; + char name[]; +}; + +struct device_prefix { + struct vlist_node node; + struct list_head head; + struct list_head assignments; + struct interface *iface; + time_t valid_until; + time_t preferred_until; + + struct in6_addr excl_addr; + uint8_t excl_length; + + struct in6_addr addr; + uint8_t length; + + char pclass[]; +}; + +struct device_route { + struct vlist_node node; + struct interface *iface; + + bool enabled; + bool keep; + bool failed; + + union if_addr nexthop; + int mtu; + unsigned int type; + time_t valid_until; + + /* must be last */ + enum device_addr_flags flags; + int metric; // there can be multiple routes to the same target + unsigned int table; + unsigned int mask; + unsigned int sourcemask; + union if_addr addr; + union if_addr source; +}; + +struct device_addr { + struct vlist_node node; + bool enabled; + bool failed; + unsigned int policy_table; + + struct device_route subnet; + + /* ipv4 only */ + uint32_t broadcast; + uint32_t point_to_point; + + /* ipv6 only */ + time_t valid_until; + time_t preferred_until; + char *pclass; + + /* must be last */ + enum device_addr_flags flags; + unsigned int mask; + union if_addr addr; +}; + +struct device_source_table { + struct list_head head; + uint32_t table; + uint16_t refcount; + uint8_t v6; + uint8_t mask; + union if_addr addr; +}; + +struct dns_server { + struct vlist_simple_node node; + int af; + union if_addr addr; +}; + +struct dns_search_domain { + struct vlist_simple_node node; + char name[]; +}; + +extern const struct uci_blob_param_list route_attr_list; +extern struct list_head prefixes; + +void interface_ip_init(struct interface *iface); +void interface_add_dns_server(struct interface_ip_settings *ip, const char *str); +void interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr *list); +void interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list); +void interface_write_resolv_conf(void); + +void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6); + +void interface_ip_update_start(struct interface_ip_settings *ip); +void interface_ip_update_complete(struct interface_ip_settings *ip); +void interface_ip_flush(struct interface_ip_settings *ip); +void interface_ip_set_enabled(struct interface_ip_settings *ip, bool enabled); +void interface_ip_update_metric(struct interface_ip_settings *ip, int metric); + +struct interface *interface_ip_add_target_route(union if_addr *addr, bool v6, struct interface *iface); + +struct device_prefix* interface_ip_add_device_prefix(struct interface *iface, + struct in6_addr *addr, uint8_t length, time_t valid_until, time_t preferred_until, + struct in6_addr *excl_addr, uint8_t excl_length, const char *pclass); +void interface_ip_set_ula_prefix(const char *prefix); +void interface_refresh_assignments(bool hint); + +#endif diff --git a/src/3P/netifd/interface.c b/src/3P/netifd/interface.c new file mode 100644 index 00000000..7b18cef5 --- /dev/null +++ b/src/3P/netifd/interface.c @@ -0,0 +1,1247 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "interface-ip.h" +#include "proto.h" +#include "ubus.h" +#include "config.h" +#include "system.h" + +struct vlist_tree interfaces; +static LIST_HEAD(iface_all_users); + +enum { + IFACE_ATTR_IFNAME, + IFACE_ATTR_PROTO, + IFACE_ATTR_AUTO, + IFACE_ATTR_DEFAULTROUTE, + IFACE_ATTR_PEERDNS, + IFACE_ATTR_DNS, + IFACE_ATTR_DNS_SEARCH, + IFACE_ATTR_METRIC, + IFACE_ATTR_INTERFACE, + IFACE_ATTR_IP6ASSIGN, + IFACE_ATTR_IP6HINT, + IFACE_ATTR_IP4TABLE, + IFACE_ATTR_IP6TABLE, + IFACE_ATTR_IP6CLASS, + IFACE_ATTR_DELEGATE, + IFACE_ATTR_IP6IFACEID, + IFACE_ATTR_FORCE_LINK, + IFACE_ATTR_MAX +}; + +static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { + [IFACE_ATTR_PROTO] = { .name = "proto", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_AUTO] = { .name = "auto", .type = BLOBMSG_TYPE_BOOL }, + [IFACE_ATTR_DEFAULTROUTE] = { .name = "defaultroute", .type = BLOBMSG_TYPE_BOOL }, + [IFACE_ATTR_PEERDNS] = { .name = "peerdns", .type = BLOBMSG_TYPE_BOOL }, + [IFACE_ATTR_METRIC] = { .name = "metric", .type = BLOBMSG_TYPE_INT32 }, + [IFACE_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY }, + [IFACE_ATTR_DNS_SEARCH] = { .name = "dns_search", .type = BLOBMSG_TYPE_ARRAY }, + [IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_IP6ASSIGN] = { .name = "ip6assign", .type = BLOBMSG_TYPE_INT32 }, + [IFACE_ATTR_IP6HINT] = { .name = "ip6hint", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_IP4TABLE] = { .name = "ip4table", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_IP6TABLE] = { .name = "ip6table", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_IP6CLASS] = { .name = "ip6class", .type = BLOBMSG_TYPE_ARRAY }, + [IFACE_ATTR_DELEGATE] = { .name = "delegate", .type = BLOBMSG_TYPE_BOOL }, + [IFACE_ATTR_IP6IFACEID] = { .name = "ip6ifaceid", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_FORCE_LINK] = { .name = "force_link", .type = BLOBMSG_TYPE_BOOL }, +}; + +const struct uci_blob_param_list interface_attr_list = { + .n_params = IFACE_ATTR_MAX, + .params = iface_attrs, +}; + +static void +set_config_state(struct interface *iface, enum interface_config_state s); +static void +interface_event(struct interface *iface, enum interface_event ev); + +static void +interface_error_flush(struct interface *iface) +{ + struct interface_error *error, *tmp; + + list_for_each_entry_safe(error, tmp, &iface->errors, list) { + list_del(&error->list); + free(error); + } +} + +static void +interface_clear_errors(struct interface *iface) +{ + /* don't flush the errors in case the configured protocol handler matches the + running protocol handler and is having the last error capability */ + if (!(iface->proto && + (iface->proto->handler->flags & PROTO_FLAG_LASTERROR) && + (iface->proto->handler->name == iface->proto_handler->name))) + interface_error_flush(iface); +} + +void interface_add_error(struct interface *iface, const char *subsystem, + const char *code, const char **data, int n_data) +{ + struct interface_error *error; + int i, len = 0; + int *datalen = NULL; + char *dest, *d_subsys, *d_code; + + /* if the configured protocol handler has the last error support capability, + errors should only be added if the running protocol handler matches the + configured one */ + if (iface->proto && + (iface->proto->handler->flags & PROTO_FLAG_LASTERROR) && + (iface->proto->handler->name != iface->proto_handler->name)) + return; + + if (n_data) { + len = n_data * sizeof(char *); + datalen = alloca(len); + for (i = 0; i < n_data; i++) { + datalen[i] = strlen(data[i]) + 1; + len += datalen[i]; + } + } + + error = calloc_a(sizeof(*error) + sizeof(char *) + len, + &d_subsys, subsystem ? strlen(subsystem) + 1 : 0, + &d_code, code ? strlen(code) + 1 : 0); + if (!error) + return; + + /* Only keep the last flagged error, prevent this list grows unlimitted in case the + protocol can't be established (e.g auth failure) */ + if (iface->proto_handler->flags & PROTO_FLAG_LASTERROR) + interface_error_flush(iface); + + list_add_tail(&error->list, &iface->errors); + + dest = (char *) &error->data[n_data + 1]; + for (i = 0; i < n_data; i++) { + error->data[i] = dest; + memcpy(dest, data[i], datalen[i]); + dest += datalen[i]; + } + error->data[n_data] = NULL; + + if (subsystem) + error->subsystem = strcpy(d_subsys, subsystem); + + if (code) + error->code = strcpy(d_code, code); +} + +static void +interface_data_del(struct interface *iface, struct interface_data *data) +{ + avl_delete(&iface->data, &data->node); + free(data); +} + +static void +interface_data_flush(struct interface *iface) +{ + struct interface_data *d, *tmp; + + avl_for_each_element_safe(&iface->data, d, node, tmp) + interface_data_del(iface, d); +} + +int +interface_add_data(struct interface *iface, const struct blob_attr *data) +{ + struct interface_data *n, *o; + + if (!blobmsg_check_attr(data, true)) + return UBUS_STATUS_INVALID_ARGUMENT; + + const char *name = blobmsg_name(data); + unsigned len = blob_pad_len(data); + + o = avl_find_element(&iface->data, name, o, node); + if (o) { + if (blob_pad_len(o->data) == len && !memcmp(o->data, data, len)) + return 0; + + interface_data_del(iface, o); + } + + n = calloc(1, sizeof(*n) + len); + if (!n) + return UBUS_STATUS_UNKNOWN_ERROR; + + memcpy(n->data, data, len); + n->node.key = blobmsg_name(n->data); + avl_insert(&iface->data, &n->node); + + iface->updated |= IUF_DATA; + return 0; +} + +int interface_parse_data(struct interface *iface, const struct blob_attr *attr) +{ + struct blob_attr *cur; + int rem, ret; + + iface->updated = 0; + + blob_for_each_attr(cur, attr, rem) { + ret = interface_add_data(iface, cur); + if (ret) + return ret; + } + + if (iface->updated && iface->state == IFS_UP) + interface_event(iface, IFEV_UPDATE); + + return 0; +} + +static void +interface_event(struct interface *iface, enum interface_event ev) +{ + struct interface_user *dep, *tmp; + struct device *adev = NULL; + + list_for_each_entry_safe(dep, tmp, &iface->users, list) + dep->cb(dep, iface, ev); + + list_for_each_entry_safe(dep, tmp, &iface_all_users, list) + dep->cb(dep, iface, ev); + + switch (ev) { + case IFEV_UP: + interface_error_flush(iface); + adev = iface->l3_dev.dev; + /* fall through */ + case IFEV_DOWN: + alias_notify_device(iface->name, adev); + break; + default: + break; + } +} + +static void +interface_flush_state(struct interface *iface) +{ + if (iface->l3_dev.dev) + device_release(&iface->l3_dev); + interface_data_flush(iface); +} + +static void +mark_interface_down(struct interface *iface) +{ + enum interface_state state = iface->state; + + if (state == IFS_DOWN) + return; + + iface->state = IFS_DOWN; + if (state == IFS_UP) + interface_event(iface, IFEV_DOWN); + interface_ip_set_enabled(&iface->config_ip, false); + interface_ip_set_enabled(&iface->proto_ip, false); + interface_ip_flush(&iface->proto_ip); + interface_flush_state(iface); + system_flush_routes(); +} + +void +__interface_set_down(struct interface *iface, bool force) +{ + enum interface_state state = iface->state; + switch (state) { + case IFS_UP: + case IFS_SETUP: + iface->state = IFS_TEARDOWN; + if (state == IFS_UP) + interface_event(iface, IFEV_DOWN); + + interface_proto_event(iface->proto, PROTO_CMD_TEARDOWN, force); + if (force) + interface_flush_state(iface); + break; + + case IFS_DOWN: + if (iface->main_dev.dev) + device_release(&iface->main_dev); + case IFS_TEARDOWN: + default: + break; + } +} + +static int +__interface_set_up(struct interface *iface) +{ + int ret; + + netifd_log_message(L_NOTICE, "Interface '%s' is setting up now\n", iface->name); + + iface->state = IFS_SETUP; + ret = interface_proto_event(iface->proto, PROTO_CMD_SETUP, false); + if (ret) + mark_interface_down(iface); + + return ret; +} + +static void +interface_check_state(struct interface *iface) +{ + bool link_state = iface->link_state || iface->force_link; + + switch (iface->state) { + case IFS_UP: + case IFS_SETUP: + if (!iface->enabled || !link_state) { + interface_proto_event(iface->proto, PROTO_CMD_TEARDOWN, false); + mark_interface_down(iface); + } + break; + case IFS_DOWN: + if (!iface->available) + return; + + if (iface->autostart && iface->enabled && link_state && !config_init) + __interface_set_up(iface); + break; + default: + break; + } +} + +static void +interface_set_enabled(struct interface *iface, bool new_state) +{ + if (iface->enabled == new_state) + return; + + netifd_log_message(L_NOTICE, "Interface '%s' is %s\n", iface->name, new_state ? "enabled" : "disabled"); + iface->enabled = new_state; + interface_check_state(iface); +} + +static void +interface_set_link_state(struct interface *iface, bool new_state) +{ + if (iface->link_state == new_state) + return; + + netifd_log_message(L_NOTICE, "Interface '%s' has link connectivity %s\n", iface->name, new_state ? "" : "loss"); + iface->link_state = new_state; + interface_check_state(iface); +} + +static void +interface_ext_dev_cb(struct device_user *dep, enum device_event ev) +{ + if (ev == DEV_EVENT_REMOVE) + device_remove_user(dep); +} + +static void +interface_main_dev_cb(struct device_user *dep, enum device_event ev) +{ + struct interface *iface; + bool new_state = false; + + iface = container_of(dep, struct interface, main_dev); + switch (ev) { + case DEV_EVENT_ADD: + new_state = true; + case DEV_EVENT_REMOVE: + interface_set_available(iface, new_state); + if (!new_state && dep->dev && dep->dev->external) + interface_set_main_dev(iface, NULL); + break; + case DEV_EVENT_UP: + new_state = true; + case DEV_EVENT_DOWN: + interface_set_enabled(iface, new_state); + break; + case DEV_EVENT_LINK_UP: + new_state = true; + case DEV_EVENT_LINK_DOWN: + interface_set_link_state(iface, new_state); + break; + case DEV_EVENT_TOPO_CHANGE: + interface_proto_event(iface->proto, PROTO_CMD_RENEW, false); + return; + default: + break; + } +} + +static void +interface_l3_dev_cb(struct device_user *dep, enum device_event ev) +{ + struct interface *iface; + + iface = container_of(dep, struct interface, l3_dev); + if (iface->l3_dev.dev == iface->main_dev.dev) + return; + + switch (ev) { + case DEV_EVENT_LINK_DOWN: + interface_proto_event(iface->proto, PROTO_CMD_TEARDOWN, false); + break; + default: + break; + } +} + +void +interface_set_available(struct interface *iface, bool new_state) +{ + if (iface->available == new_state) + return; + + D(INTERFACE, "Interface '%s', available=%d\n", iface->name, new_state); + iface->available = new_state; + + if (new_state) { + if (iface->autostart && !config_init) + interface_set_up(iface); + } else + __interface_set_down(iface, true); +} + +void +interface_add_user(struct interface_user *dep, struct interface *iface) +{ + if (!iface) { + list_add(&dep->list, &iface_all_users); + return; + } + + dep->iface = iface; + list_add(&dep->list, &iface->users); + if (iface->state == IFS_UP) + dep->cb(dep, iface, IFEV_UP); +} + +void +interface_remove_user(struct interface_user *dep) +{ + list_del_init(&dep->list); + dep->iface = NULL; +} + +static void +interface_add_assignment_classes(struct interface *iface, struct blob_attr *list) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, list, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + if (!blobmsg_check_attr(cur, NULL)) + continue; + + struct interface_assignment_class *c = malloc(sizeof(*c) + blobmsg_data_len(cur)); + memcpy(c->name, blobmsg_data(cur), blobmsg_data_len(cur)); + list_add(&c->head, &iface->assignment_classes); + } +} + +static void +interface_clear_assignment_classes(struct interface *iface) +{ + while (!list_empty(&iface->assignment_classes)) { + struct interface_assignment_class *c = list_first_entry(&iface->assignment_classes, + struct interface_assignment_class, head); + list_del(&c->head); + free(c); + } +} + +static void +interface_merge_assignment_data(struct interface *old, struct interface *new) +{ + bool changed = (old->assignment_hint != new->assignment_hint || + old->assignment_length != new->assignment_length || + old->assignment_iface_id_selection != new->assignment_iface_id_selection || + (old->assignment_iface_id_selection == IFID_FIXED && + memcmp(&old->assignment_fixed_iface_id, &new->assignment_fixed_iface_id, + sizeof(old->assignment_fixed_iface_id))) || + list_empty(&old->assignment_classes) != list_empty(&new->assignment_classes)); + + struct interface_assignment_class *c; + list_for_each_entry(c, &new->assignment_classes, head) { + // Compare list entries one-by-one to see if there was a change + if (list_empty(&old->assignment_classes)) // The new list is longer + changed = true; + + if (changed) + break; + + struct interface_assignment_class *c_old = list_first_entry(&old->assignment_classes, + struct interface_assignment_class, head); + + if (strcmp(c_old->name, c->name)) // An entry didn't match + break; + + list_del(&c_old->head); + free(c_old); + } + + // The old list was longer than the new one or the last entry didn't match + if (!list_empty(&old->assignment_classes)) { + interface_clear_assignment_classes(old); + changed = true; + } + + list_splice_init(&new->assignment_classes, &old->assignment_classes); + + if (changed) { + old->assignment_hint = new->assignment_hint; + old->assignment_length = new->assignment_length; + old->assignment_iface_id_selection = new->assignment_iface_id_selection; + old->assignment_fixed_iface_id = new->assignment_fixed_iface_id; + interface_refresh_assignments(true); + } +} + +static void +interface_alias_cb(struct interface_user *dep, struct interface *iface, enum interface_event ev) +{ + struct interface *alias = container_of(dep, struct interface, parent_iface); + struct device *dev = iface->l3_dev.dev; + + switch (ev) { + case IFEV_UP: + if (!dev) + return; + + interface_set_main_dev(alias, dev); + interface_set_available(alias, true); + break; + case IFEV_DOWN: + interface_set_available(alias, false); + interface_set_main_dev(alias, NULL); + break; + case IFEV_FREE: + interface_remove_user(dep); + break; + case IFEV_RELOAD: + case IFEV_UPDATE: + break; + } +} + +static void +interface_set_device_config(struct interface *iface, struct device *dev) +{ + if (!dev || !dev->default_config) + return; + + if (!iface->device_config && + (!dev->iface_config || dev->config_iface != iface)) + return; + + dev->config_iface = iface; + dev->iface_config = iface->device_config; + device_apply_config(dev, dev->type, iface->config); +} + +static void +interface_claim_device(struct interface *iface) +{ + struct interface *parent; + struct device *dev = NULL; + + if (iface->parent_iface.iface) + interface_remove_user(&iface->parent_iface); + + if (iface->parent_ifname) { + parent = vlist_find(&interfaces, iface->parent_ifname, parent, node); + iface->parent_iface.cb = interface_alias_cb; + interface_add_user(&iface->parent_iface, parent); + } else if (iface->ifname && + !(iface->proto_handler->flags & PROTO_FLAG_NODEV)) { + dev = device_get(iface->ifname, true); + interface_set_device_config(iface, dev); + } else { + dev = iface->ext_dev.dev; + } + + if (dev) + interface_set_main_dev(iface, dev); + + if (iface->proto_handler->flags & PROTO_FLAG_INIT_AVAILABLE) + interface_set_available(iface, true); +} + +static void +interface_cleanup_state(struct interface *iface) +{ + interface_set_available(iface, false); + + interface_flush_state(iface); + interface_clear_errors(iface); + interface_set_proto_state(iface, NULL); + + interface_set_main_dev(iface, NULL); + interface_set_l3_dev(iface, NULL); +} + +static void +interface_cleanup(struct interface *iface) +{ + struct interface_user *dep, *tmp; + + uloop_timeout_cancel(&iface->remove_timer); + device_remove_user(&iface->ext_dev); + + if (iface->parent_iface.iface) + interface_remove_user(&iface->parent_iface); + + list_for_each_entry_safe(dep, tmp, &iface->users, list) + interface_remove_user(dep); + + interface_clear_assignment_classes(iface); + interface_ip_flush(&iface->config_ip); + interface_cleanup_state(iface); +} + +static void +interface_do_free(struct interface *iface) +{ + interface_event(iface, IFEV_FREE); + interface_cleanup(iface); + free(iface->config); + netifd_ubus_remove_interface(iface); + avl_delete(&interfaces.avl, &iface->node.avl); + free(iface); +} + +static void +interface_do_reload(struct interface *iface) +{ + interface_event(iface, IFEV_RELOAD); + interface_cleanup_state(iface); + proto_init_interface(iface, iface->config); + interface_claim_device(iface); +} + +static void +interface_handle_config_change(struct interface *iface) +{ + enum interface_config_state state = iface->config_state; + + iface->config_state = IFC_NORMAL; + switch(state) { + case IFC_NORMAL: + break; + case IFC_RELOAD: + interface_do_reload(iface); + break; + case IFC_REMOVE: + interface_do_free(iface); + return; + } + if (iface->autostart && iface->available) + interface_set_up(iface); + else if (iface->dynamic) + set_config_state(iface, IFC_REMOVE); +} + +static void +interface_proto_event_cb(struct interface_proto_state *state, enum interface_proto_event ev) +{ + struct interface *iface = state->iface; + + switch (ev) { + case IFPEV_UP: + if (iface->state != IFS_SETUP) { + interface_event(iface, IFEV_UPDATE); + return; + } + + if (!iface->l3_dev.dev) + interface_set_l3_dev(iface, iface->main_dev.dev); + + interface_ip_set_enabled(&iface->config_ip, true); + interface_ip_set_enabled(&iface->proto_ip, true); + system_flush_routes(); + iface->state = IFS_UP; + iface->start_time = system_get_rtime(); + interface_event(iface, IFEV_UP); + netifd_log_message(L_NOTICE, "Interface '%s' is now up\n", iface->name); + break; + case IFPEV_DOWN: + if (iface->state == IFS_DOWN) + return; + + netifd_log_message(L_NOTICE, "Interface '%s' is now down\n", iface->name); + mark_interface_down(iface); + if (iface->main_dev.dev) + device_release(&iface->main_dev); + if (iface->l3_dev.dev) + device_remove_user(&iface->l3_dev); + interface_handle_config_change(iface); + break; + case IFPEV_LINK_LOST: + if (iface->state != IFS_UP) + return; + + netifd_log_message(L_NOTICE, "Interface '%s' has lost the connection\n", iface->name); + mark_interface_down(iface); + iface->state = IFS_SETUP; + break; + default: + return; + } + + interface_write_resolv_conf(); +} + +void interface_set_proto_state(struct interface *iface, struct interface_proto_state *state) +{ + if (iface->proto) { + iface->proto->free(iface->proto); + iface->proto = NULL; + } + iface->state = IFS_DOWN; + iface->proto = state; + if (!state) + return; + + state->proto_event = interface_proto_event_cb; + state->iface = iface; +} + +struct interface * +interface_alloc(const char *name, struct blob_attr *config) +{ + struct interface *iface; + struct blob_attr *tb[IFACE_ATTR_MAX]; + struct blob_attr *cur; + const char *proto_name = NULL; + char *iface_name; + bool force_link = false; + + iface = calloc_a(sizeof(*iface), &iface_name, strlen(name) + 1); + iface->name = strcpy(iface_name, name); + INIT_LIST_HEAD(&iface->errors); + INIT_LIST_HEAD(&iface->users); + INIT_LIST_HEAD(&iface->hotplug_list); + INIT_LIST_HEAD(&iface->assignment_classes); + interface_ip_init(iface); + avl_init(&iface->data, avl_strcmp, false, NULL); + iface->config_ip.enabled = false; + + iface->main_dev.cb = interface_main_dev_cb; + iface->l3_dev.cb = interface_l3_dev_cb; + iface->ext_dev.cb = interface_ext_dev_cb; + + blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, + blob_data(config), blob_len(config)); + + if ((cur = tb[IFACE_ATTR_PROTO])) + proto_name = blobmsg_data(cur); + + proto_attach_interface(iface, proto_name); + if (iface->proto_handler->flags & PROTO_FLAG_FORCE_LINK_DEFAULT) + force_link = true; + + iface->autostart = blobmsg_get_bool_default(tb[IFACE_ATTR_AUTO], true); + iface->force_link = blobmsg_get_bool_default(tb[IFACE_ATTR_FORCE_LINK], force_link); + iface->proto_ip.no_defaultroute = + !blobmsg_get_bool_default(tb[IFACE_ATTR_DEFAULTROUTE], true); + iface->proto_ip.no_dns = + !blobmsg_get_bool_default(tb[IFACE_ATTR_PEERDNS], true); + + if ((cur = tb[IFACE_ATTR_DNS])) + interface_add_dns_server_list(&iface->config_ip, cur); + + if ((cur = tb[IFACE_ATTR_DNS_SEARCH])) + interface_add_dns_search_list(&iface->config_ip, cur); + + if ((cur = tb[IFACE_ATTR_METRIC])) + iface->metric = blobmsg_get_u32(cur); + + if ((cur = tb[IFACE_ATTR_IP6ASSIGN])) + iface->assignment_length = blobmsg_get_u32(cur); + + /* defaults */ + iface->assignment_iface_id_selection = IFID_FIXED; + iface->assignment_fixed_iface_id = in6addr_any; + iface->assignment_fixed_iface_id.s6_addr[15] = 1; + + if ((cur = tb[IFACE_ATTR_IP6IFACEID])) { + const char *ifaceid = blobmsg_data(cur); + if (!strcmp(ifaceid, "random")) { + iface->assignment_iface_id_selection = IFID_RANDOM; + } + else if (!strcmp(ifaceid, "eui64")) { + iface->assignment_iface_id_selection = IFID_EUI64; + } + else { + /* we expect an IPv6 address with network id zero here -> fixed iface id + if we cannot parse -> revert to iface id 1 */ + if (inet_pton(AF_INET6,ifaceid,&iface->assignment_fixed_iface_id) != 1 || + iface->assignment_fixed_iface_id.s6_addr32[0] != 0 || + iface->assignment_fixed_iface_id.s6_addr32[1] != 0) { + iface->assignment_fixed_iface_id = in6addr_any; + iface->assignment_fixed_iface_id.s6_addr[15] = 1; + netifd_log_message(L_WARNING, "Failed to parse ip6ifaceid for interface '%s', \ + falling back to iface id 1.\n", iface->name); + } + } + } + + iface->assignment_hint = -1; + if ((cur = tb[IFACE_ATTR_IP6HINT])) + iface->assignment_hint = strtol(blobmsg_get_string(cur), NULL, 16) & + ~((1 << (64 - iface->assignment_length)) - 1); + + if ((cur = tb[IFACE_ATTR_IP6CLASS])) + interface_add_assignment_classes(iface, cur); + + + if ((cur = tb[IFACE_ATTR_IP4TABLE])) { + if (!system_resolve_rt_table(blobmsg_data(cur), &iface->ip4table)) + DPRINTF("Failed to resolve routing table: %s\n", (char *) blobmsg_data(cur)); + } + + if ((cur = tb[IFACE_ATTR_IP6TABLE])) { + if (!system_resolve_rt_table(blobmsg_data(cur), &iface->ip6table)) + DPRINTF("Failed to resolve routing table: %s\n", (char *) blobmsg_data(cur)); + } + + iface->proto_ip.no_delegation = !blobmsg_get_bool_default(tb[IFACE_ATTR_DELEGATE], true); + + iface->config_autostart = iface->autostart; + return iface; +} + +void interface_set_dynamic(struct interface *iface) +{ + iface->dynamic = true; + iface->autostart = true; + iface->node.version = -1; // Don't delete on reload +} + +static bool __interface_add(struct interface *iface, struct blob_attr *config, bool alias) +{ + struct blob_attr *tb[IFACE_ATTR_MAX]; + struct blob_attr *cur; + + blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, + blob_data(config), blob_len(config)); + + if (alias) { + if ((cur = tb[IFACE_ATTR_INTERFACE])) + iface->parent_ifname = blobmsg_data(cur); + + if (!iface->parent_ifname) + return false; + } else { + if ((cur = tb[IFACE_ATTR_IFNAME])) + iface->ifname = blobmsg_data(cur); + } + + iface->config = config; + vlist_add(&interfaces, &iface->node, iface->name); + return true; +} + +void +interface_add(struct interface *iface, struct blob_attr *config) +{ + __interface_add(iface, config, false); +} + +bool +interface_add_alias(struct interface *iface, struct blob_attr *config) +{ + if (iface->proto_handler->flags & PROTO_FLAG_NODEV) + return false; + + return __interface_add(iface, config, true); +} + +void +interface_set_l3_dev(struct interface *iface, struct device *dev) +{ + bool enabled = iface->config_ip.enabled; + bool claimed = iface->l3_dev.claimed; + + if (iface->l3_dev.dev == dev) + return; + + interface_ip_set_enabled(&iface->config_ip, false); + interface_ip_set_enabled(&iface->proto_ip, false); + interface_ip_flush(&iface->proto_ip); + device_add_user(&iface->l3_dev, dev); + + if (dev) { + if (claimed) { + if (device_claim(&iface->l3_dev) < 0) + return; + } + interface_ip_set_enabled(&iface->config_ip, enabled); + interface_ip_set_enabled(&iface->proto_ip, enabled); + } +} + +void +interface_set_main_dev(struct interface *iface, struct device *dev) +{ + bool claimed = iface->l3_dev.claimed; + + if (iface->main_dev.dev == dev) + return; + + interface_set_available(iface, false); + device_add_user(&iface->main_dev, dev); + if (!dev) { + interface_set_link_state(iface, false); + return; + } + + if (claimed) { + if (device_claim(&iface->l3_dev) < 0) + return; + } + + if (!iface->l3_dev.dev) + interface_set_l3_dev(iface, dev); +} + +int +interface_remove_link(struct interface *iface, struct device *dev) +{ + struct device *mdev = iface->main_dev.dev; + + if (mdev && mdev->hotplug_ops) + return mdev->hotplug_ops->del(mdev, dev); + + if (dev == iface->ext_dev.dev) + device_remove_user(&iface->ext_dev); + + if (!iface->main_dev.hotplug) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (dev != iface->main_dev.dev) + return UBUS_STATUS_INVALID_ARGUMENT; + + interface_set_main_dev(iface, NULL); + return 0; +} + +static int +interface_add_link(struct interface *iface, struct device *dev, bool link_ext) +{ + struct device *mdev = iface->main_dev.dev; + + if (mdev == dev) + return 0; + + if (iface->main_dev.hotplug) + device_remove_user(&iface->main_dev); + + if (mdev) { + if (mdev->hotplug_ops) + return mdev->hotplug_ops->add(mdev, dev); + else + return UBUS_STATUS_NOT_SUPPORTED; + } + + if (link_ext) + device_add_user(&iface->ext_dev, dev); + + interface_set_main_dev(iface, dev); + iface->main_dev.hotplug = true; + return 0; +} + +int +interface_handle_link(struct interface *iface, const char *name, bool add, bool link_ext) +{ + struct device *dev; + int ret; + + device_lock(); + + dev = device_get(name, add ? (link_ext ? 2 : 1) : 0); + if (!dev) { + ret = UBUS_STATUS_NOT_FOUND; + goto out; + } + + if (add) { + interface_set_device_config(iface, dev); + device_set_present(dev, true); + + ret = interface_add_link(iface, dev, link_ext); + } else { + ret = interface_remove_link(iface, dev); + } + +out: + device_unlock(); + + return ret; +} + +int +interface_set_up(struct interface *iface) +{ + int ret; + + iface->autostart = true; + + if (iface->state != IFS_DOWN) + return 0; + + interface_clear_errors(iface); + if (!iface->available) { + interface_add_error(iface, "interface", "NO_DEVICE", NULL, 0); + return -1; + } + + if (iface->main_dev.dev) { + ret = device_claim(&iface->main_dev); + if (!ret) + interface_check_state(iface); + } + else + ret = __interface_set_up(iface); + + return ret; +} + +int +interface_set_down(struct interface *iface) +{ + if (!iface) { + vlist_for_each_element(&interfaces, iface, node) + __interface_set_down(iface, false); + } else { + iface->autostart = false; + __interface_set_down(iface, false); + } + + return 0; +} + +void +interface_start_pending(void) +{ + struct interface *iface; + + vlist_for_each_element(&interfaces, iface, node) { + if (iface->available && iface->autostart) + interface_set_up(iface); + } +} + +static void +set_config_state(struct interface *iface, enum interface_config_state s) +{ + iface->config_state = s; + if (iface->state == IFS_DOWN) + interface_handle_config_change(iface); + else + __interface_set_down(iface, false); +} + +void +interface_update_start(struct interface *iface) +{ + iface->updated = 0; + interface_ip_update_start(&iface->proto_ip); +} + +void +interface_update_complete(struct interface *iface) +{ + interface_ip_update_complete(&iface->proto_ip); +} + +static void +interface_replace_dns(struct interface_ip_settings *new, struct interface_ip_settings *old) +{ + vlist_simple_replace(&new->dns_servers, &old->dns_servers); + vlist_simple_replace(&new->dns_search, &old->dns_search); +} + +static bool +interface_device_config_changed(struct interface *if_old, struct interface *if_new) +{ + struct blob_attr *ntb[__DEV_ATTR_MAX]; + struct blob_attr *otb[__DEV_ATTR_MAX]; + struct device *dev = if_old->main_dev.dev; + unsigned long diff = 0; + + BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / 8); + + if (!dev) + return false; + + if (if_old->device_config != if_new->device_config) + return true; + + if (!if_new->device_config) + return false; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb, + blob_data(if_old->config), blob_len(if_old->config)); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, ntb, + blob_data(if_new->config), blob_len(if_new->config)); + + uci_blob_diff(ntb, otb, &device_attr_list, &diff); + return diff; +} + +static void +interface_change_config(struct interface *if_old, struct interface *if_new) +{ + struct blob_attr *old_config = if_old->config; + bool reload = false, reload_ip = false; + +#define FIELD_CHANGED_STR(field) \ + ((!!if_old->field != !!if_new->field) || \ + (if_old->field && \ + strcmp(if_old->field, if_new->field) != 0)) + + if (FIELD_CHANGED_STR(parent_ifname)) { + if (if_old->parent_iface.iface) + interface_remove_user(&if_old->parent_iface); + reload = true; + } + + if (!reload && interface_device_config_changed(if_old, if_new)) + reload = true; + + if (FIELD_CHANGED_STR(ifname) || + if_old->proto_handler != if_new->proto_handler) + reload = true; + + if (!if_old->proto_handler->config_params) + D(INTERFACE, "No config parameters for interface '%s'\n", + if_old->name); + else if (!uci_blob_check_equal(if_old->config, if_new->config, + if_old->proto_handler->config_params)) + reload = true; + +#define UPDATE(field, __var) ({ \ + bool __changed = (if_old->field != if_new->field); \ + if_old->field = if_new->field; \ + __var |= __changed; \ + }) + + if_old->config = if_new->config; + if (if_old->config_autostart != if_new->config_autostart) { + if (if_old->config_autostart) + reload = true; + + if_old->autostart = if_new->config_autostart; + } + + if_old->device_config = if_new->device_config; + if_old->config_autostart = if_new->config_autostart; + if_old->ifname = if_new->ifname; + if_old->parent_ifname = if_new->parent_ifname; + if_old->proto_handler = if_new->proto_handler; + if_old->force_link = if_new->force_link; + + if_old->proto_ip.no_dns = if_new->proto_ip.no_dns; + interface_replace_dns(&if_old->config_ip, &if_new->config_ip); + + UPDATE(metric, reload_ip); + UPDATE(proto_ip.no_defaultroute, reload_ip); + UPDATE(ip4table, reload_ip); + UPDATE(ip6table, reload_ip); + interface_merge_assignment_data(if_old, if_new); + +#undef UPDATE + + if (reload) { + D(INTERFACE, "Reload interface '%s' because of config changes\n", + if_old->name); + interface_clear_errors(if_old); + set_config_state(if_old, IFC_RELOAD); + goto out; + } + + if (reload_ip) { + bool config_ip_enabled = if_old->config_ip.enabled; + bool proto_ip_enabled = if_old->proto_ip.enabled; + + interface_ip_set_enabled(&if_old->config_ip, false); + interface_ip_set_enabled(&if_old->proto_ip, false); + interface_ip_set_enabled(&if_old->proto_ip, proto_ip_enabled); + interface_ip_set_enabled(&if_old->config_ip, config_ip_enabled); + } + + interface_write_resolv_conf(); + if (if_old->main_dev.dev) + interface_check_state(if_old); + +out: + if_new->config = NULL; + interface_cleanup(if_new); + free(old_config); + free(if_new); +} + +static void +interface_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct interface *if_old = container_of(node_old, struct interface, node); + struct interface *if_new = container_of(node_new, struct interface, node); + + if (node_old && node_new) { + D(INTERFACE, "Update interface '%s'\n", if_new->name); + interface_change_config(if_old, if_new); + } else if (node_old) { + D(INTERFACE, "Remove interface '%s'\n", if_old->name); + set_config_state(if_old, IFC_REMOVE); + } else if (node_new) { + D(INTERFACE, "Create interface '%s'\n", if_new->name); + proto_init_interface(if_new, if_new->config); + interface_claim_device(if_new); + netifd_ubus_add_interface(if_new); + } +} + + +static void __init +interface_init_list(void) +{ + vlist_init(&interfaces, avl_strcmp, interface_update); + interfaces.keep_old = true; + interfaces.no_delete = true; +} diff --git a/src/3P/netifd/interface.h b/src/3P/netifd/interface.h new file mode 100644 index 00000000..73a3b556 --- /dev/null +++ b/src/3P/netifd/interface.h @@ -0,0 +1,204 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_INTERFACE_H +#define __NETIFD_INTERFACE_H + +#include "device.h" +#include "config.h" + +struct interface; +struct interface_proto_state; + +enum interface_event { + IFEV_DOWN, + IFEV_UP, + IFEV_UPDATE, + IFEV_FREE, + IFEV_RELOAD, +}; + +enum interface_state { + IFS_SETUP, + IFS_UP, + IFS_TEARDOWN, + IFS_DOWN, +}; + +enum interface_config_state { + IFC_NORMAL, + IFC_RELOAD, + IFC_REMOVE +}; + +enum interface_id_selection_type { + IFID_FIXED, + IFID_RANDOM, + IFID_EUI64 +}; + +enum interface_update_flags { + IUF_ADDRESS = (1 << 0), + IUF_ROUTE = (1 << 1), + IUF_PREFIX = (1 << 2), + IUF_DATA = (1 << 3), +}; + +struct interface_error { + struct list_head list; + + const char *subsystem; + const char *code; + const char *data[]; +}; + +struct interface_user { + struct list_head list; + struct interface *iface; + void (*cb)(struct interface_user *dep, struct interface *iface, enum interface_event ev); +}; + +struct interface_ip_settings { + struct interface *iface; + bool enabled; + bool no_defaultroute; + bool no_dns; + bool no_delegation; + + struct vlist_tree addr; + struct vlist_tree route; + struct vlist_tree prefix; + + struct vlist_simple_tree dns_servers; + struct vlist_simple_tree dns_search; +}; + +struct interface_data { + struct avl_node node; + struct blob_attr data[]; +}; + +struct interface_assignment_class { + struct list_head head; + char name[]; +}; + +/* + * interface configuration + */ +struct interface { + struct vlist_node node; + struct list_head hotplug_list; + enum interface_event hotplug_ev; + + const char *name; + const char *ifname; + + bool available; + bool autostart; + bool config_autostart; + bool device_config; + bool enabled; + bool link_state; + bool force_link; + bool dynamic; + bool policy_rules_set; + + time_t start_time; + enum interface_state state; + enum interface_config_state config_state; + enum interface_update_flags updated; + + struct list_head users; + + /* for alias interface */ + const char *parent_ifname; + struct interface_user parent_iface; + + /* main interface that the interface is bound to */ + struct device_user main_dev; + struct device_user ext_dev; + + /* interface that layer 3 communication will go through */ + struct device_user l3_dev; + + struct blob_attr *config; + + /* primary protocol state */ + const struct proto_handler *proto_handler; + struct interface_proto_state *proto; + + struct interface_ip_settings proto_ip; + struct interface_ip_settings config_ip; + struct vlist_tree host_routes; + + int metric; + unsigned int ip4table; + unsigned int ip6table; + + /* IPv6 assignment parameters */ + enum interface_id_selection_type assignment_iface_id_selection; + struct in6_addr assignment_fixed_iface_id; + uint8_t assignment_length; + int32_t assignment_hint; + struct list_head assignment_classes; + + /* errors/warnings while trying to bring up the interface */ + struct list_head errors; + + /* extra data provided by protocol handlers or modules */ + struct avl_tree data; + + struct uloop_timeout remove_timer; + struct ubus_object ubus; +}; + + +extern struct vlist_tree interfaces; +extern const struct uci_blob_param_list interface_attr_list; + +struct interface *interface_alloc(const char *name, struct blob_attr *config); + +void interface_set_dynamic(struct interface *iface); + +void interface_add(struct interface *iface, struct blob_attr *config); +bool interface_add_alias(struct interface *iface, struct blob_attr *config); + +void interface_set_proto_state(struct interface *iface, struct interface_proto_state *state); + +void interface_set_available(struct interface *iface, bool new_state); +int interface_set_up(struct interface *iface); +int interface_set_down(struct interface *iface); +void __interface_set_down(struct interface *iface, bool force); + +void interface_set_main_dev(struct interface *iface, struct device *dev); +void interface_set_l3_dev(struct interface *iface, struct device *dev); + +void interface_add_user(struct interface_user *dep, struct interface *iface); +void interface_remove_user(struct interface_user *dep); + +int interface_remove_link(struct interface *iface, struct device *dev); +int interface_handle_link(struct interface *iface, const char *name, bool add, bool link_ext); + +void interface_add_error(struct interface *iface, const char *subsystem, + const char *code, const char **data, int n_data); + +int interface_add_data(struct interface *iface, const struct blob_attr *data); +int interface_parse_data(struct interface *iface, const struct blob_attr *attr); + +void interface_update_start(struct interface *iface); +void interface_update_complete(struct interface *iface); + +void interface_start_pending(void); + +#endif diff --git a/src/3P/netifd/iprule.c b/src/3P/netifd/iprule.c new file mode 100644 index 00000000..4e3dd155 --- /dev/null +++ b/src/3P/netifd/iprule.c @@ -0,0 +1,256 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * Copyright (C) 2013 Jo-Philipp Wich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "iprule.h" +#include "proto.h" +#include "ubus.h" +#include "system.h" + +struct vlist_tree iprules; +static bool iprules_flushed = false; +static unsigned int iprules_counter[2]; + +enum { + RULE_INTERFACE_IN, + RULE_INTERFACE_OUT, + RULE_INVERT, + RULE_SRC, + RULE_DEST, + RULE_PRIORITY, + RULE_TOS, + RULE_FWMARK, + RULE_LOOKUP, + RULE_ACTION, + RULE_GOTO, + __RULE_MAX +}; + +static const struct blobmsg_policy rule_attr[__RULE_MAX] = { + [RULE_INTERFACE_IN] = { .name = "in", .type = BLOBMSG_TYPE_STRING }, + [RULE_INTERFACE_OUT] = { .name = "out", .type = BLOBMSG_TYPE_STRING }, + [RULE_INVERT] = { .name = "invert", .type = BLOBMSG_TYPE_BOOL }, + [RULE_SRC] = { .name = "src", .type = BLOBMSG_TYPE_STRING }, + [RULE_DEST] = { .name = "dest", .type = BLOBMSG_TYPE_STRING }, + [RULE_PRIORITY] = { .name = "priority", .type = BLOBMSG_TYPE_INT32 }, + [RULE_TOS] = { .name = "tos", .type = BLOBMSG_TYPE_INT32 }, + [RULE_FWMARK] = { .name = "mark", .type = BLOBMSG_TYPE_STRING }, + [RULE_LOOKUP] = { .name = "lookup", .type = BLOBMSG_TYPE_STRING }, + [RULE_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_STRING }, + [RULE_GOTO] = { .name = "goto", .type = BLOBMSG_TYPE_INT32 }, +}; + +const struct uci_blob_param_list rule_attr_list = { + .n_params = __RULE_MAX, + .params = rule_attr, +}; + + +static bool +iprule_parse_mark(const char *mark, struct iprule *rule) +{ + char *s, *e; + unsigned int n; + + if ((s = strchr(mark, '/')) != NULL) + *s++ = 0; + + n = strtoul(mark, &e, 0); + + if (e == mark || *e) + return false; + + rule->fwmark = n; + rule->flags |= IPRULE_FWMARK; + + if (s) { + n = strtoul(s, &e, 0); + + if (e == s || *e) + return false; + + rule->fwmask = n; + rule->flags |= IPRULE_FWMASK; + } + + return true; +} + +void +iprule_add(struct blob_attr *attr, bool v6) +{ + struct interface *iif = NULL, *oif = NULL; + struct blob_attr *tb[__RULE_MAX], *cur; + struct interface *iface; + struct iprule *rule; + int af = v6 ? AF_INET6 : AF_INET; + + blobmsg_parse(rule_attr, __RULE_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); + + rule = calloc(1, sizeof(*rule)); + if (!rule) + return; + + rule->flags = v6 ? IPRULE_INET6 : IPRULE_INET4; + rule->order = iprules_counter[rule->flags]++; + + if ((cur = tb[RULE_INVERT]) != NULL) + rule->invert = blobmsg_get_bool(cur); + + if ((cur = tb[RULE_INTERFACE_IN]) != NULL) { + iif = vlist_find(&interfaces, blobmsg_data(cur), iface, node); + + if (!iif || !iif->l3_dev.dev) { + DPRINTF("Failed to resolve device of network: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + + memcpy(rule->in_dev, iif->l3_dev.dev->ifname, sizeof(rule->in_dev)); + rule->flags |= IPRULE_IN; + } + + if ((cur = tb[RULE_INTERFACE_OUT]) != NULL) { + oif = vlist_find(&interfaces, blobmsg_data(cur), iface, node); + + if (!oif || !oif->l3_dev.dev) { + DPRINTF("Failed to resolve device of network: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + + memcpy(rule->out_dev, oif->l3_dev.dev->ifname, sizeof(rule->out_dev)); + rule->flags |= IPRULE_OUT; + } + + if ((cur = tb[RULE_SRC]) != NULL) { + if (!parse_ip_and_netmask(af, blobmsg_data(cur), &rule->src_addr, &rule->src_mask)) { + DPRINTF("Failed to parse rule source: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + rule->flags |= IPRULE_SRC; + } + + if ((cur = tb[RULE_DEST]) != NULL) { + if (!parse_ip_and_netmask(af, blobmsg_data(cur), &rule->dest_addr, &rule->dest_mask)) { + DPRINTF("Failed to parse rule destination: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + rule->flags |= IPRULE_DEST; + } + + if ((cur = tb[RULE_PRIORITY]) != NULL) { + rule->priority = blobmsg_get_u32(cur); + rule->flags |= IPRULE_PRIORITY; + } + + if ((cur = tb[RULE_TOS]) != NULL) { + if ((rule->tos = blobmsg_get_u32(cur)) > 255) { + DPRINTF("Invalid TOS value: %u\n", blobmsg_get_u32(cur)); + goto error; + } + rule->flags |= IPRULE_TOS; + } + + if ((cur = tb[RULE_FWMARK]) != NULL) { + if (!iprule_parse_mark(blobmsg_data(cur), rule)) { + DPRINTF("Failed to parse rule fwmark: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + /* flags set by iprule_parse_mark() */ + } + + if ((cur = tb[RULE_LOOKUP]) != NULL) { + if (!system_resolve_rt_table(blobmsg_data(cur), &rule->lookup)) { + DPRINTF("Failed to parse rule lookup table: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + rule->flags |= IPRULE_LOOKUP; + } + + if ((cur = tb[RULE_ACTION]) != NULL) { + if (!system_resolve_iprule_action(blobmsg_data(cur), &rule->action)) { + DPRINTF("Failed to parse rule action: %s\n", (char *) blobmsg_data(cur)); + goto error; + } + rule->flags |= IPRULE_ACTION; + } + + if ((cur = tb[RULE_GOTO]) != NULL) { + rule->gotoid = blobmsg_get_u32(cur); + rule->flags |= IPRULE_GOTO; + } + + vlist_add(&iprules, &rule->node, &rule->flags); + return; + +error: + free(rule); +} + +void +iprule_update_start(void) +{ + if (!iprules_flushed) { + system_flush_iprules(); + iprules_flushed = true; + } + + iprules_counter[0] = 1; + iprules_counter[1] = 1; + vlist_update(&iprules); +} + +void +iprule_update_complete(void) +{ + vlist_flush(&iprules); +} + + +static int +rule_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, sizeof(struct iprule)-offsetof(struct iprule, flags)); +} + +static void +iprule_update_rule(struct vlist_tree *tree, + struct vlist_node *node_new, struct vlist_node *node_old) +{ + struct iprule *rule_old, *rule_new; + + rule_old = container_of(node_old, struct iprule, node); + rule_new = container_of(node_new, struct iprule, node); + + if (node_old) { + system_del_iprule(rule_old); + free(rule_old); + } + + if (node_new) + system_add_iprule(rule_new); +} + +static void __init +iprule_init_list(void) +{ + vlist_init(&iprules, rule_cmp, iprule_update_rule); +} diff --git a/src/3P/netifd/iprule.h b/src/3P/netifd/iprule.h new file mode 100644 index 00000000..e8a25558 --- /dev/null +++ b/src/3P/netifd/iprule.h @@ -0,0 +1,101 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * Copyright (C) 2013 Jo-Philipp Wich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __IPRULE_H +#define __IPRULE_H + +#include "interface-ip.h" + +#define IPRULE_PRIORITY_ADDR 10000 +#define IPRULE_PRIORITY_ADDR_MASK 20000 +#define IPRULE_PRIORITY_NW 90000 +#define IPRULE_PRIORITY_REJECT 4200000000 + +enum iprule_flags { + /* address family for rule */ + IPRULE_INET4 = (0 << 0), + IPRULE_INET6 = (1 << 0), + IPRULE_FAMILY = IPRULE_INET4 | IPRULE_INET6, + + /* rule specifies input device */ + IPRULE_IN = (1 << 2), + + /* rule specifies output device */ + IPRULE_OUT = (1 << 3), + + /* rule specifies src */ + IPRULE_SRC = (1 << 4), + + /* rule specifies dest */ + IPRULE_DEST = (1 << 5), + + /* rule specifies priority */ + IPRULE_PRIORITY = (1 << 6), + + /* rule specifies diffserv/tos */ + IPRULE_TOS = (1 << 7), + + /* rule specifies fwmark */ + IPRULE_FWMARK = (1 << 8), + + /* rule specifies fwmask */ + IPRULE_FWMASK = (1 << 9), + + /* rule performs table lookup */ + IPRULE_LOOKUP = (1 << 10), + + /* rule performs routing action */ + IPRULE_ACTION = (1 << 11), + + /* rule is a goto */ + IPRULE_GOTO = (1 << 12), +}; + +struct iprule { + struct vlist_node node; + unsigned int order; + + /* everything below is used as avl tree key */ + enum iprule_flags flags; + + bool invert; + + char in_dev[IFNAMSIZ + 1]; + char out_dev[IFNAMSIZ + 1]; + + unsigned int src_mask; + union if_addr src_addr; + + unsigned int dest_mask; + union if_addr dest_addr; + + unsigned int priority; + unsigned int tos; + + unsigned int fwmark; + unsigned int fwmask; + + unsigned int lookup; + unsigned int action; + unsigned int gotoid; +}; + +extern struct vlist_tree iprules; +extern const struct uci_blob_param_list rule_attr_list; + +void iprule_add(struct blob_attr *attr, bool v6); +void iprule_update_start(void); +void iprule_update_complete(void); + +#endif diff --git a/src/3P/netifd/macvlan.c b/src/3P/netifd/macvlan.c new file mode 100644 index 00000000..a0f11ae1 --- /dev/null +++ b/src/3P/netifd/macvlan.c @@ -0,0 +1,263 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * Copyright (C) 2013 Jo-Philipp Wich + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include + +#ifdef linux +#include +#endif + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "system.h" + +enum { + MACVLAN_ATTR_IFNAME, + MACVLAN_ATTR_MACADDR, + MACVLAN_ATTR_MODE, + __MACVLAN_ATTR_MAX +}; + +static const struct blobmsg_policy macvlan_attrs[__MACVLAN_ATTR_MAX] = { + [MACVLAN_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_STRING }, + [MACVLAN_ATTR_MACADDR] = { "macaddr", BLOBMSG_TYPE_STRING }, + [MACVLAN_ATTR_MODE] = { "mode", BLOBMSG_TYPE_STRING }, +}; + +static const struct uci_blob_param_list macvlan_attr_list = { + .n_params = __MACVLAN_ATTR_MAX, + .params = macvlan_attrs, + + .n_next = 1, + .next = { &device_attr_list }, +}; + +struct macvlan_device { + struct device dev; + struct device_user parent; + + device_state_cb set_state; + + struct blob_attr *config_data; + struct blob_attr *ifname; + struct macvlan_config config; +}; + +static void +macvlan_base_cb(struct device_user *dev, enum device_event ev) +{ + struct macvlan_device *mvdev = container_of(dev, struct macvlan_device, parent); + + switch (ev) { + case DEV_EVENT_ADD: + device_set_present(&mvdev->dev, true); + break; + case DEV_EVENT_REMOVE: + device_set_present(&mvdev->dev, false); + break; + default: + return; + } +} + +static int +macvlan_set_down(struct macvlan_device *mvdev) +{ + mvdev->set_state(&mvdev->dev, false); + system_macvlan_del(&mvdev->dev); + device_release(&mvdev->parent); + + return 0; +} + +static int +macvlan_set_up(struct macvlan_device *mvdev) +{ + int ret; + + ret = device_claim(&mvdev->parent); + if (ret < 0) + return ret; + + ret = system_macvlan_add(&mvdev->dev, mvdev->parent.dev, &mvdev->config); + if (ret < 0) + goto release; + + ret = mvdev->set_state(&mvdev->dev, true); + if (ret) + goto delete; + + return 0; + +delete: + system_macvlan_del(&mvdev->dev); +release: + device_release(&mvdev->parent); + return ret; +} + +static int +macvlan_set_state(struct device *dev, bool up) +{ + struct macvlan_device *mvdev; + + D(SYSTEM, "macvlan_set_state(%s, %u)\n", dev->ifname, up); + + mvdev = container_of(dev, struct macvlan_device, dev); + if (up) + return macvlan_set_up(mvdev); + else + return macvlan_set_down(mvdev); +} + +static void +macvlan_free(struct device *dev) +{ + struct macvlan_device *mvdev; + + mvdev = container_of(dev, struct macvlan_device, dev); + device_remove_user(&mvdev->parent); + free(mvdev->config_data); + free(mvdev); +} + +static void +macvlan_dump_info(struct device *dev, struct blob_buf *b) +{ + struct macvlan_device *mvdev; + + mvdev = container_of(dev, struct macvlan_device, dev); + blobmsg_add_string(b, "parent", mvdev->parent.dev->ifname); + system_if_dump_info(dev, b); +} + +static void +macvlan_config_init(struct device *dev) +{ + struct macvlan_device *mvdev; + struct device *basedev = NULL; + + mvdev = container_of(dev, struct macvlan_device, dev); + if (mvdev->ifname) + basedev = device_get(blobmsg_data(mvdev->ifname), true); + + device_add_user(&mvdev->parent, basedev); +} + +static void +macvlan_apply_settings(struct macvlan_device *mvdev, struct blob_attr **tb) +{ + struct macvlan_config *cfg = &mvdev->config; + struct blob_attr *cur; + struct ether_addr *ea; + + cfg->flags = 0; + cfg->mode = NULL; + + if ((cur = tb[MACVLAN_ATTR_MACADDR])) { + ea = ether_aton(blobmsg_data(cur)); + if (ea) { + memcpy(cfg->macaddr, ea, 6); + cfg->flags |= MACVLAN_OPT_MACADDR; + } + } + + if ((cur = tb[MACVLAN_ATTR_MODE])) + cfg->mode = blobmsg_data(cur); +} + +static enum dev_change_type +macvlan_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + struct blob_attr *tb_mv[__MACVLAN_ATTR_MAX]; + enum dev_change_type ret = DEV_CONFIG_APPLIED; + struct macvlan_device *mvdev; + + mvdev = container_of(dev, struct macvlan_device, dev); + attr = blob_memdup(attr); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + blobmsg_parse(macvlan_attrs, __MACVLAN_ATTR_MAX, tb_mv, + blob_data(attr), blob_len(attr)); + + device_init_settings(dev, tb_dev); + macvlan_apply_settings(mvdev, tb_mv); + mvdev->ifname = tb_mv[MACVLAN_ATTR_IFNAME]; + + if (mvdev->config_data) { + struct blob_attr *otb_dev[__DEV_ATTR_MAX]; + struct blob_attr *otb_mv[__MACVLAN_ATTR_MAX]; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_dev, otb_dev, &device_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + blobmsg_parse(macvlan_attrs, __MACVLAN_ATTR_MAX, otb_mv, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_mv, otb_mv, &macvlan_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + macvlan_config_init(dev); + } + + free(mvdev->config_data); + mvdev->config_data = attr; + return ret; +} + +static struct device * +macvlan_create(const char *name, struct blob_attr *attr) +{ + struct macvlan_device *mvdev; + struct device *dev = NULL; + + mvdev = calloc(1, sizeof(*mvdev)); + if (!mvdev) + return NULL; + + dev = &mvdev->dev; + device_init(dev, &macvlan_device_type, name); + dev->config_pending = true; + + mvdev->set_state = dev->set_state; + dev->set_state = macvlan_set_state; + + dev->hotplug_ops = NULL; + mvdev->parent.cb = macvlan_base_cb; + + macvlan_reload(dev, attr); + + return dev; +} + +const struct device_type macvlan_device_type = { + .name = "MAC VLAN", + .config_params = &macvlan_attr_list, + .create = macvlan_create, + .config_init = macvlan_config_init, + .reload = macvlan_reload, + .free = macvlan_free, + .dump_info = macvlan_dump_info, +}; diff --git a/src/3P/netifd/main.c b/src/3P/netifd/main.c new file mode 100644 index 00000000..5717b819 --- /dev/null +++ b/src/3P/netifd/main.c @@ -0,0 +1,346 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "netifd.h" +#include "ubus.h" +#include "config.h" +#include "system.h" +#include "interface.h" +#include "wireless.h" +#include "proto.h" + +unsigned int debug_mask = 0; +const char *main_path = DEFAULT_MAIN_PATH; +const char *config_path = DEFAULT_CONFIG_PATH; +const char *resolv_conf = DEFAULT_RESOLV_CONF; +static char **global_argv; + +static struct list_head process_list = LIST_HEAD_INIT(process_list); + +#define DEFAULT_LOG_LEVEL L_NOTICE + +static int log_level = DEFAULT_LOG_LEVEL; +static const int log_class[] = { + [L_CRIT] = LOG_CRIT, + [L_WARNING] = LOG_WARNING, + [L_NOTICE] = LOG_NOTICE, + [L_INFO] = LOG_INFO, + [L_DEBUG] = LOG_DEBUG +}; + +#ifdef DUMMY_MODE +#define use_syslog false +#else +static bool use_syslog = true; +#endif + + +static void +netifd_delete_process(struct netifd_process *proc) +{ + list_del(&proc->list); + ustream_free(&proc->log.stream); + close(proc->log.fd.fd); +} + +void +netifd_log_message(int priority, const char *format, ...) +{ + va_list vl; + + if (priority > log_level) + return; + + va_start(vl, format); + if (use_syslog) + vsyslog(log_class[priority], format, vl); + else + vfprintf(stderr, format, vl); + va_end(vl); +} + +static void +netifd_process_log_read_cb(struct ustream *s, int bytes) +{ + struct netifd_process *proc; + const char *log_prefix; + char *data; + int len = 0; + + proc = container_of(s, struct netifd_process, log.stream); + log_prefix = proc->log_prefix; + if (!log_prefix) + log_prefix = "process"; + + do { + char *newline; + + data = ustream_get_read_buf(s, &len); + if (!len) + break; + + newline = strchr(data, '\n'); + + if (proc->log_overflow) { + if (newline) { + len = newline + 1 - data; + proc->log_overflow = false; + } + } else if (newline) { + *newline = 0; + len = newline + 1 - data; + netifd_log_message(L_NOTICE, "%s (%d): %s\n", + log_prefix, proc->uloop.pid, data); + } else if (len == s->r.buffer_len) { + netifd_log_message(L_NOTICE, "%s (%d): %s [...]\n", + log_prefix, proc->uloop.pid, data); + proc->log_overflow = true; + } else + break; + + ustream_consume(s, len); + } while (1); +} + +static void +netifd_process_cb(struct uloop_process *proc, int ret) +{ + struct netifd_process *np; + np = container_of(proc, struct netifd_process, uloop); + + while (ustream_poll(&np->log.stream)); + netifd_delete_process(np); + return np->cb(np, ret); +} + +int +netifd_start_process(const char **argv, char **env, struct netifd_process *proc) +{ + int pfds[2]; + int pid; + + netifd_kill_process(proc); + + if (pipe(pfds) < 0) + return -1; + + if ((pid = fork()) < 0) + goto error; + + if (!pid) { + int i; + + if (env) { + while (*env) { + putenv(*env); + env++; + } + } + if (proc->dir_fd >= 0) + if (fchdir(proc->dir_fd)) {} + + close(pfds[0]); + + for (i = 0; i <= 2; i++) { + if (pfds[1] == i) + continue; + + dup2(pfds[1], i); + } + + if (pfds[1] > 2) + close(pfds[1]); + + execvp(argv[0], (char **) argv); + exit(127); + } + + close(pfds[1]); + proc->uloop.cb = netifd_process_cb; + proc->uloop.pid = pid; + uloop_process_add(&proc->uloop); + list_add_tail(&proc->list, &process_list); + + system_fd_set_cloexec(pfds[0]); + proc->log.stream.string_data = true; + proc->log.stream.notify_read = netifd_process_log_read_cb; + ustream_fd_init(&proc->log, pfds[0]); + + return 0; + +error: + close(pfds[0]); + close(pfds[1]); + return -1; +} + +void +netifd_kill_process(struct netifd_process *proc) +{ + if (!proc->uloop.pending) + return; + + kill(proc->uloop.pid, SIGKILL); + uloop_process_delete(&proc->uloop); + netifd_delete_process(proc); +} + +static void netifd_do_restart(struct uloop_timeout *timeout) +{ + execvp(global_argv[0], global_argv); +} + +void netifd_reload(void) +{ + config_init_all(); +} + +void netifd_restart(void) +{ + static struct uloop_timeout main_timer = { + .cb = netifd_do_restart + }; + + interface_set_down(NULL); + uloop_timeout_set(&main_timer, 1000); +} + +static int usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options:\n" + " -d : Mask for debug messages\n" + " -s : Path to the ubus socket\n" + " -p : Path to netifd addons (default: %s)\n" + " -c : Path to UCI configuration\n" + " -h : Path to the hotplug script\n" + " -r : Path to resolv.conf\n" + " -l : Log output level (default: %d)\n" + " -S: Use stderr instead of syslog for log messages\n" + " (default: "DEFAULT_HOTPLUG_PATH")\n" + "\n", progname, main_path, DEFAULT_LOG_LEVEL); + + return 1; +} + +static void +netifd_handle_signal(int signo) +{ + uloop_end(); +} + +static void +netifd_setup_signals(void) +{ + struct sigaction s; + + memset(&s, 0, sizeof(s)); + s.sa_handler = netifd_handle_signal; + s.sa_flags = 0; + sigaction(SIGINT, &s, NULL); + sigaction(SIGTERM, &s, NULL); + sigaction(SIGUSR1, &s, NULL); + sigaction(SIGUSR2, &s, NULL); + + s.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &s, NULL); +} + +static void +netifd_kill_processes(void) +{ + struct netifd_process *proc, *tmp; + + list_for_each_entry_safe(proc, tmp, &process_list, list) + netifd_kill_process(proc); +} + +int main(int argc, char **argv) +{ + const char *socket = NULL; + int ch; + + global_argv = argv; + + while ((ch = getopt(argc, argv, "d:s:p:c:h:r:l:S")) != -1) { + switch(ch) { + case 'd': + debug_mask = strtoul(optarg, NULL, 0); + break; + case 's': + socket = optarg; + break; + case 'p': + main_path = optarg; + break; + case 'c': + config_path = optarg; + break; + case 'h': + hotplug_cmd_path = optarg; + break; + case 'r': + resolv_conf = optarg; + break; + case 'l': + log_level = atoi(optarg); + if (log_level >= ARRAY_SIZE(log_class)) + log_level = ARRAY_SIZE(log_class) - 1; + break; +#ifndef DUMMY_MODE + case 'S': + use_syslog = false; + break; +#endif + default: + return usage(argv[0]); + } + } + + if (use_syslog) + openlog("netifd", 0, LOG_DAEMON); + + netifd_setup_signals(); + if (netifd_ubus_init(socket) < 0) { + fprintf(stderr, "Failed to connect to ubus\n"); + return 1; + } + + proto_shell_init(); + wireless_init(); + + if (system_init()) { + fprintf(stderr, "Failed to initialize system control\n"); + return 1; + } + + config_init_all(); + + uloop_run(); + netifd_kill_processes(); + + netifd_ubus_done(); + + if (use_syslog) + closelog(); + + return 0; +} diff --git a/src/3P/netifd/netifd.h b/src/3P/netifd/netifd.h new file mode 100644 index 00000000..5a908587 --- /dev/null +++ b/src/3P/netifd/netifd.h @@ -0,0 +1,103 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_H +#define __NETIFD_H + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "utils.h" + +#ifdef DUMMY_MODE +#define DEFAULT_MAIN_PATH "./examples" +#define DEFAULT_CONFIG_PATH "./config" +#define DEFAULT_HOTPLUG_PATH "./examples/hotplug-cmd" +#define DEFAULT_RESOLV_CONF "./tmp/resolv.conf" +#else +#define DEFAULT_MAIN_PATH "/lib/netifd" +#define DEFAULT_CONFIG_PATH NULL /* use the default set in libuci */ +#define DEFAULT_HOTPLUG_PATH "/sbin/hotplug-call" +#define DEFAULT_RESOLV_CONF "/tmp/resolv.conf.auto" +#endif + +extern const char *resolv_conf; +extern char *hotplug_cmd_path; +extern unsigned int debug_mask; + +enum { + L_CRIT, + L_WARNING, + L_NOTICE, + L_INFO, + L_DEBUG +}; + +enum { + DEBUG_SYSTEM = 0, + DEBUG_DEVICE = 1, + DEBUG_INTERFACE = 2, + DEBUG_WIRELESS = 3, +}; + +#ifdef DEBUG +#define DPRINTF(format, ...) fprintf(stderr, "%s(%d): " format, __func__, __LINE__, ## __VA_ARGS__) +#define D(level, format, ...) do { \ + if (debug_mask & (1 << (DEBUG_ ## level))) \ + DPRINTF(format, ##__VA_ARGS__); \ + } while (0) +#else +#define DPRINTF(format, ...) no_debug(0, format, ## __VA_ARGS__) +#define D(level, format, ...) no_debug(DEBUG_ ## level, format, ## __VA_ARGS__) +#endif + +#define LOG_BUF_SIZE 256 + +static inline void no_debug(int level, const char *fmt, ...) +{ +} + +struct netifd_process { + struct list_head list; + struct uloop_process uloop; + void (*cb)(struct netifd_process *, int ret); + int dir_fd; + + struct ustream_fd log; + const char *log_prefix; + bool log_overflow; +}; + +void netifd_log_message(int priority, const char *format, ...); + +int netifd_start_process(const char **argv, char **env, struct netifd_process *proc); +void netifd_kill_process(struct netifd_process *proc); + +struct device; +struct interface; + +extern const char *main_path; +extern const char *config_path; +void netifd_restart(void); +void netifd_reload(void); + +#endif diff --git a/src/3P/netifd/proto-shell.c b/src/3P/netifd/proto-shell.c new file mode 100644 index 00000000..25dec009 --- /dev/null +++ b/src/3P/netifd/proto-shell.c @@ -0,0 +1,924 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include +#include + + +#include "netifd.h" +#include "interface.h" +#include "interface-ip.h" +#include "proto.h" +#include "system.h" +#include "handler.h" + +static int proto_fd = -1; + +enum proto_shell_sm { + S_IDLE, + S_SETUP, + S_SETUP_ABORT, + S_TEARDOWN, +}; + +struct proto_shell_handler { + struct list_head list; + struct proto_handler proto; + char *config_buf; + char *script_name; + bool init_available; + bool no_proto_task; + + struct uci_blob_param_list config; +}; + +struct proto_shell_dependency { + struct list_head list; + + struct proto_shell_state *proto; + struct interface_user dep; + + union if_addr host; + bool v6; + bool any; + + char interface[]; +}; + +struct proto_shell_state { + struct interface_proto_state proto; + struct proto_shell_handler *handler; + struct blob_attr *config; + + struct uloop_timeout teardown_timeout; + + /* + * Teardown and setup interface again if it is still not up (IFS_UP) + * after checkup_interval seconds since previous attempt. This check + * will be disabled when the config option "checkup_interval" is + * missing or has a negative value + */ + int checkup_interval; + struct uloop_timeout checkup_timeout; + + struct netifd_process script_task; + struct netifd_process proto_task; + + enum proto_shell_sm sm; + bool proto_task_killed; + bool renew_pending; + + int last_error; + + struct list_head deps; +}; + +static void +proto_shell_check_dependencies(struct proto_shell_state *state) +{ + struct proto_shell_dependency *dep; + bool available = true; + + list_for_each_entry(dep, &state->deps, list) { + if (dep->dep.iface) + continue; + + available = false; + break; + } + + interface_set_available(state->proto.iface, available); +} + +static void +proto_shell_if_up_cb(struct interface_user *dep, struct interface *iface, + enum interface_event ev); +static void +proto_shell_if_down_cb(struct interface_user *dep, struct interface *iface, + enum interface_event ev); + +static void +proto_shell_update_host_dep(struct proto_shell_dependency *dep) +{ + struct interface *iface = NULL; + + if (dep->dep.iface) + goto out; + + if (dep->interface[0]) { + iface = vlist_find(&interfaces, dep->interface, iface, node); + + if (!iface || iface->state != IFS_UP) + goto out; + } + + if (!dep->any) + iface = interface_ip_add_target_route(&dep->host, dep->v6, iface); + + if (!iface) + goto out; + + interface_remove_user(&dep->dep); + dep->dep.cb = proto_shell_if_down_cb; + interface_add_user(&dep->dep, iface); + +out: + proto_shell_check_dependencies(dep->proto); +} + +static void +proto_shell_clear_host_dep(struct proto_shell_state *state) +{ + struct proto_shell_dependency *dep, *tmp; + + list_for_each_entry_safe(dep, tmp, &state->deps, list) { + interface_remove_user(&dep->dep); + list_del(&dep->list); + free(dep); + } +} + +static int +proto_shell_handler(struct interface_proto_state *proto, + enum interface_proto_cmd cmd, bool force) +{ + struct proto_shell_state *state; + struct proto_shell_handler *handler; + struct netifd_process *proc; + static char error_buf[32]; + const char *argv[7]; + char *envp[2]; + const char *action; + char *config; + int ret, i = 0, j = 0; + + state = container_of(proto, struct proto_shell_state, proto); + handler = state->handler; + proc = &state->script_task; + + if (cmd == PROTO_CMD_SETUP) { + switch (state->sm) { + case S_IDLE: + action = "setup"; + state->last_error = -1; + proto_shell_clear_host_dep(state); + state->sm = S_SETUP; + break; + + case S_SETUP_ABORT: + case S_TEARDOWN: + case S_SETUP: + return 0; + + default: + return -1; + } + } else if (cmd == PROTO_CMD_RENEW) { + if (!(handler->proto.flags & PROTO_FLAG_RENEW_AVAILABLE)) + return 0; + + if (state->script_task.uloop.pending) { + state->renew_pending = true; + return 0; + } + + state->renew_pending = false; + action = "renew"; + } else { + switch (state->sm) { + case S_SETUP: + if (state->script_task.uloop.pending) { + uloop_timeout_set(&state->teardown_timeout, 1000); + kill(state->script_task.uloop.pid, SIGTERM); + if (state->proto_task.uloop.pending) + kill(state->proto_task.uloop.pid, SIGTERM); + state->renew_pending = false; + state->sm = S_SETUP_ABORT; + return 0; + } + /* fall through if no script task is running */ + case S_IDLE: + action = "teardown"; + state->renew_pending = false; + state->sm = S_TEARDOWN; + if (state->last_error >= 0) { + snprintf(error_buf, sizeof(error_buf), "ERROR=%d", state->last_error); + envp[j++] = error_buf; + } + uloop_timeout_set(&state->teardown_timeout, 5000); + break; + + case S_TEARDOWN: + return 0; + + default: + return -1; + } + } + + D(INTERFACE, "run %s for interface '%s'\n", action, proto->iface->name); + config = blobmsg_format_json(state->config, true); + if (!config) + return -1; + + argv[i++] = handler->script_name; + argv[i++] = handler->proto.name; + argv[i++] = action; + argv[i++] = proto->iface->name; + argv[i++] = config; + if (proto->iface->main_dev.dev) + argv[i++] = proto->iface->main_dev.dev->ifname; + argv[i] = NULL; + envp[j] = NULL; + + ret = netifd_start_process(argv, envp, proc); + free(config); + + return ret; +} + +static void +proto_shell_if_up_cb(struct interface_user *dep, struct interface *iface, + enum interface_event ev) +{ + struct proto_shell_dependency *pdep; + + if (ev != IFEV_UP && ev != IFEV_UPDATE) + return; + + pdep = container_of(dep, struct proto_shell_dependency, dep); + proto_shell_update_host_dep(pdep); +} + +static void +proto_shell_if_down_cb(struct interface_user *dep, struct interface *iface, + enum interface_event ev) +{ + struct proto_shell_dependency *pdep; + struct proto_shell_state *state; + + if (ev == IFEV_UP || ev == IFEV_UPDATE) + return; + + pdep = container_of(dep, struct proto_shell_dependency, dep); + interface_remove_user(dep); + dep->cb = proto_shell_if_up_cb; + interface_add_user(dep, NULL); + + state = pdep->proto; + if (state->sm == S_IDLE) { + state->proto.proto_event(&state->proto, IFPEV_LINK_LOST); + proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false); + } +} + +static void +proto_shell_task_finish(struct proto_shell_state *state, + struct netifd_process *task) +{ + switch (state->sm) { + case S_IDLE: + if (task == &state->proto_task) + state->proto.proto_event(&state->proto, IFPEV_LINK_LOST); + /* fall through */ + case S_SETUP: + if (task == &state->proto_task) + proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, + false); + else if (task == &state->script_task) { + if (state->renew_pending) + proto_shell_handler(&state->proto, + PROTO_CMD_RENEW, false); + else if (!state->handler->no_proto_task && + !state->proto_task.uloop.pending && + state->sm == S_SETUP) + proto_shell_handler(&state->proto, + PROTO_CMD_TEARDOWN, + false); + + /* check up status after setup attempt by this script_task */ + if (state->sm == S_SETUP && state->checkup_interval > 0) { + uloop_timeout_set(&state->checkup_timeout, + state->checkup_interval * 1000); + } + } + break; + + case S_SETUP_ABORT: + if (state->script_task.uloop.pending || + state->proto_task.uloop.pending) + break; + + /* completed aborting all tasks, now idle */ + uloop_timeout_cancel(&state->teardown_timeout); + uloop_timeout_cancel(&state->checkup_timeout); + state->sm = S_IDLE; + proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false); + break; + + case S_TEARDOWN: + if (state->script_task.uloop.pending) + break; + + if (state->proto_task.uloop.pending) { + if (!state->proto_task_killed) + kill(state->proto_task.uloop.pid, SIGTERM); + break; + } + + /* completed tearing down all tasks, now idle */ + uloop_timeout_cancel(&state->teardown_timeout); + uloop_timeout_cancel(&state->checkup_timeout); + state->sm = S_IDLE; + state->proto.proto_event(&state->proto, IFPEV_DOWN); + break; + } +} + +static void +proto_shell_teardown_timeout_cb(struct uloop_timeout *timeout) +{ + struct proto_shell_state *state; + + state = container_of(timeout, struct proto_shell_state, teardown_timeout); + + netifd_kill_process(&state->script_task); + netifd_kill_process(&state->proto_task); + proto_shell_task_finish(state, NULL); +} + +static void +proto_shell_script_cb(struct netifd_process *p, int ret) +{ + struct proto_shell_state *state; + + state = container_of(p, struct proto_shell_state, script_task); + proto_shell_task_finish(state, p); +} + +static void +proto_shell_task_cb(struct netifd_process *p, int ret) +{ + struct proto_shell_state *state; + + state = container_of(p, struct proto_shell_state, proto_task); + + if (state->sm == S_IDLE || state->sm == S_SETUP) + state->last_error = WEXITSTATUS(ret); + + proto_shell_task_finish(state, p); +} + +static void +proto_shell_free(struct interface_proto_state *proto) +{ + struct proto_shell_state *state; + + state = container_of(proto, struct proto_shell_state, proto); + uloop_timeout_cancel(&state->teardown_timeout); + uloop_timeout_cancel(&state->checkup_timeout); + proto_shell_clear_host_dep(state); + netifd_kill_process(&state->script_task); + netifd_kill_process(&state->proto_task); + free(state->config); + free(state); +} + +static void +proto_shell_parse_route_list(struct interface *iface, struct blob_attr *attr, + bool v6) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) { + DPRINTF("Ignore wrong route type: %d\n", blobmsg_type(cur)); + continue; + } + + interface_ip_add_route(iface, cur, v6); + } +} + +static void +proto_shell_parse_data(struct interface *iface, struct blob_attr *attr) +{ + struct blob_attr *cur; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) + interface_add_data(iface, cur); +} + +static struct device * +proto_shell_create_tunnel(const char *name, struct blob_attr *attr) +{ + struct device *dev; + struct blob_buf b; + + memset(&b, 0, sizeof(b)); + blob_buf_init(&b, 0); + blob_put(&b, 0, blobmsg_data(attr), blobmsg_data_len(attr)); + dev = device_create(name, &tunnel_device_type, blob_data(b.head)); + blob_buf_free(&b); + + return dev; +} + +enum { + NOTIFY_ACTION, + NOTIFY_ERROR, + NOTIFY_COMMAND, + NOTIFY_ENV, + NOTIFY_SIGNAL, + NOTIFY_AVAILABLE, + NOTIFY_LINK_UP, + NOTIFY_IFNAME, + NOTIFY_ADDR_EXT, + NOTIFY_ROUTES, + NOTIFY_ROUTES6, + NOTIFY_TUNNEL, + NOTIFY_DATA, + NOTIFY_KEEP, + NOTIFY_HOST, + NOTIFY_DNS, + NOTIFY_DNS_SEARCH, + __NOTIFY_LAST +}; + +static const struct blobmsg_policy notify_attr[__NOTIFY_LAST] = { + [NOTIFY_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_INT32 }, + [NOTIFY_ERROR] = { .name = "error", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_COMMAND] = { .name = "command", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_ENV] = { .name = "env", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 }, + [NOTIFY_AVAILABLE] = { .name = "available", .type = BLOBMSG_TYPE_BOOL }, + [NOTIFY_LINK_UP] = { .name = "link-up", .type = BLOBMSG_TYPE_BOOL }, + [NOTIFY_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING }, + [NOTIFY_ADDR_EXT] = { .name = "address-external", .type = BLOBMSG_TYPE_BOOL }, + [NOTIFY_ROUTES] = { .name = "routes", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_ROUTES6] = { .name = "routes6", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_TUNNEL] = { .name = "tunnel", .type = BLOBMSG_TYPE_TABLE }, + [NOTIFY_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE }, + [NOTIFY_KEEP] = { .name = "keep", .type = BLOBMSG_TYPE_BOOL }, + [NOTIFY_HOST] = { .name = "host", .type = BLOBMSG_TYPE_STRING }, + [NOTIFY_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY }, + [NOTIFY_DNS_SEARCH] = { .name = "dns_search", .type = BLOBMSG_TYPE_ARRAY }, +}; + +static int +proto_shell_update_link(struct proto_shell_state *state, struct blob_attr *data, struct blob_attr **tb) +{ + struct interface *iface = state->proto.iface; + struct blob_attr *cur; + struct device *dev; + const char *devname; + int dev_create = 1; + bool addr_ext = false; + bool keep = false; + bool up; + + if (state->sm == S_TEARDOWN || state->sm == S_SETUP_ABORT) + return UBUS_STATUS_PERMISSION_DENIED; + + if (!tb[NOTIFY_LINK_UP]) + return UBUS_STATUS_INVALID_ARGUMENT; + + up = blobmsg_get_bool(tb[NOTIFY_LINK_UP]); + if (!up) { + state->proto.proto_event(&state->proto, IFPEV_LINK_LOST); + return 0; + } + + if ((cur = tb[NOTIFY_KEEP]) != NULL) + keep = blobmsg_get_bool(cur); + + if ((cur = tb[NOTIFY_ADDR_EXT]) != NULL) { + addr_ext = blobmsg_get_bool(cur); + if (addr_ext) + dev_create = 2; + } + + if (iface->state != IFS_UP || !iface->l3_dev.dev) + keep = false; + + if (!keep) { + dev = iface->main_dev.dev; + if (tb[NOTIFY_IFNAME]) { + keep = false; + devname = blobmsg_data(tb[NOTIFY_IFNAME]); + if (tb[NOTIFY_TUNNEL]) + dev = proto_shell_create_tunnel(devname, tb[NOTIFY_TUNNEL]); + else + dev = device_get(devname, dev_create); + } + + if (!dev) + return UBUS_STATUS_INVALID_ARGUMENT; + + interface_set_l3_dev(iface, dev); + if (device_claim(&iface->l3_dev) < 0) + return UBUS_STATUS_UNKNOWN_ERROR; + + device_set_present(dev, true); + + interface_update_start(iface); + } + + proto_apply_ip_settings(iface, data, addr_ext); + + if ((cur = tb[NOTIFY_ROUTES]) != NULL) + proto_shell_parse_route_list(state->proto.iface, cur, false); + + if ((cur = tb[NOTIFY_ROUTES6]) != NULL) + proto_shell_parse_route_list(state->proto.iface, cur, true); + + if ((cur = tb[NOTIFY_DNS])) + interface_add_dns_server_list(&iface->proto_ip, cur); + + if ((cur = tb[NOTIFY_DNS_SEARCH])) + interface_add_dns_search_list(&iface->proto_ip, cur); + + if ((cur = tb[NOTIFY_DATA])) + proto_shell_parse_data(state->proto.iface, cur); + + interface_update_complete(state->proto.iface); + + if ((state->sm != S_SETUP_ABORT) && (state->sm != S_TEARDOWN)) { + if (!keep) + state->proto.proto_event(&state->proto, IFPEV_UP); + state->sm = S_IDLE; + } + + return 0; +} + +static bool +fill_string_list(struct blob_attr *attr, char **argv, int max) +{ + struct blob_attr *cur; + int argc = 0; + int rem; + + if (!attr) + goto out; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + return false; + + if (!blobmsg_check_attr(cur, NULL)) + return false; + + argv[argc++] = blobmsg_data(cur); + if (argc == max - 1) + return false; + } + +out: + argv[argc] = NULL; + return true; +} + +static int +proto_shell_run_command(struct proto_shell_state *state, struct blob_attr **tb) +{ + static char *argv[64]; + static char *env[32]; + + if (state->sm == S_TEARDOWN || state->sm == S_SETUP_ABORT) + return UBUS_STATUS_PERMISSION_DENIED; + + if (!tb[NOTIFY_COMMAND]) + goto error; + + if (!fill_string_list(tb[NOTIFY_COMMAND], argv, ARRAY_SIZE(argv))) + goto error; + + if (!fill_string_list(tb[NOTIFY_ENV], env, ARRAY_SIZE(env))) + goto error; + + netifd_start_process((const char **) argv, (char **) env, &state->proto_task); + + return 0; + +error: + return UBUS_STATUS_INVALID_ARGUMENT; +} + +static int +proto_shell_kill_command(struct proto_shell_state *state, struct blob_attr **tb) +{ + unsigned int signal = ~0; + + if (tb[NOTIFY_SIGNAL]) + signal = blobmsg_get_u32(tb[NOTIFY_SIGNAL]); + + if (signal > 31) + signal = SIGTERM; + + if (state->proto_task.uloop.pending) { + if (signal == SIGTERM || signal == SIGKILL) + state->proto_task_killed = true; + kill(state->proto_task.uloop.pid, signal); + } + + return 0; +} + +static int +proto_shell_notify_error(struct proto_shell_state *state, struct blob_attr **tb) +{ + struct blob_attr *cur; + char *data[16]; + int n_data = 0; + int rem; + + if (!tb[NOTIFY_ERROR]) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_for_each_attr(cur, tb[NOTIFY_ERROR], rem) { + if (n_data + 1 == ARRAY_SIZE(data)) + goto error; + + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + goto error; + + if (!blobmsg_check_attr(cur, NULL)) + goto error; + + data[n_data++] = blobmsg_data(cur); + } + + if (!n_data) + goto error; + + interface_add_error(state->proto.iface, state->handler->proto.name, + data[0], (const char **) &data[1], n_data - 1); + + return 0; + +error: + return UBUS_STATUS_INVALID_ARGUMENT; +} + +static int +proto_shell_block_restart(struct proto_shell_state *state, struct blob_attr **tb) +{ + state->proto.iface->autostart = false; + return 0; +} + +static int +proto_shell_set_available(struct proto_shell_state *state, struct blob_attr **tb) +{ + if (!tb[NOTIFY_AVAILABLE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + interface_set_available(state->proto.iface, blobmsg_get_bool(tb[NOTIFY_AVAILABLE])); + return 0; +} + +static int +proto_shell_add_host_dependency(struct proto_shell_state *state, struct blob_attr **tb) +{ + struct proto_shell_dependency *dep; + const char *ifname = tb[NOTIFY_IFNAME] ? blobmsg_data(tb[NOTIFY_IFNAME]) : ""; + const char *host = tb[NOTIFY_HOST] ? blobmsg_data(tb[NOTIFY_HOST]) : ""; + + if (state->sm == S_TEARDOWN || state->sm == S_SETUP_ABORT) + return UBUS_STATUS_PERMISSION_DENIED; + + dep = calloc(1, sizeof(*dep) + strlen(ifname) + 1); + if (!dep) + return UBUS_STATUS_UNKNOWN_ERROR; + + if (!host[0] && ifname[0]) { + dep->any = true; + } else if (inet_pton(AF_INET, host, &dep->host) < 1) { + if (inet_pton(AF_INET6, host, &dep->host) < 1) { + free(dep); + return UBUS_STATUS_INVALID_ARGUMENT; + } else { + dep->v6 = true; + } + } + + dep->proto = state; + strcpy(dep->interface, ifname); + + dep->dep.cb = proto_shell_if_up_cb; + interface_add_user(&dep->dep, NULL); + list_add(&dep->list, &state->deps); + proto_shell_update_host_dep(dep); + if (!dep->dep.iface) + return UBUS_STATUS_NOT_FOUND; + + return 0; +} + +static int +proto_shell_setup_failed(struct proto_shell_state *state) +{ + int ret = 0; + + switch (state->sm) { + case S_IDLE: + state->proto.proto_event(&state->proto, IFPEV_LINK_LOST); + /* fall through */ + case S_SETUP: + proto_shell_handler(&state->proto, PROTO_CMD_TEARDOWN, false); + break; + case S_SETUP_ABORT: + case S_TEARDOWN: + default: + ret = UBUS_STATUS_PERMISSION_DENIED; + break; + } + return ret; +} + +static int +proto_shell_notify(struct interface_proto_state *proto, struct blob_attr *attr) +{ + struct proto_shell_state *state; + struct blob_attr *tb[__NOTIFY_LAST]; + + state = container_of(proto, struct proto_shell_state, proto); + + blobmsg_parse(notify_attr, __NOTIFY_LAST, tb, blob_data(attr), blob_len(attr)); + if (!tb[NOTIFY_ACTION]) + return UBUS_STATUS_INVALID_ARGUMENT; + + switch(blobmsg_get_u32(tb[NOTIFY_ACTION])) { + case 0: + return proto_shell_update_link(state, attr, tb); + case 1: + return proto_shell_run_command(state, tb); + case 2: + return proto_shell_kill_command(state, tb); + case 3: + return proto_shell_notify_error(state, tb); + case 4: + return proto_shell_block_restart(state, tb); + case 5: + return proto_shell_set_available(state, tb); + case 6: + return proto_shell_add_host_dependency(state, tb); + case 7: + return proto_shell_setup_failed(state); + default: + return UBUS_STATUS_INVALID_ARGUMENT; + } +} + +static void +proto_shell_checkup_timeout_cb(struct uloop_timeout *timeout) +{ + struct proto_shell_state *state = container_of(timeout, struct + proto_shell_state, checkup_timeout); + struct interface_proto_state *proto = &state->proto; + struct interface *iface = proto->iface; + + if (!iface->autostart) + return; + + if (iface->state == IFS_UP) + return; + + D(INTERFACE, "Interface '%s' is not up after %d sec\n", + iface->name, state->checkup_interval); + proto_shell_handler(proto, PROTO_CMD_TEARDOWN, false); +} + +static void +proto_shell_checkup_attach(struct proto_shell_state *state, + const struct blob_attr *attr) +{ + struct blob_attr *tb; + struct blobmsg_policy checkup_policy = { + .name = "checkup_interval", + .type = BLOBMSG_TYPE_INT32 + }; + + blobmsg_parse(&checkup_policy, 1, &tb, blob_data(attr), blob_len(attr)); + if (!tb) { + state->checkup_interval = -1; + state->checkup_timeout.cb = NULL; + } else { + state->checkup_interval = blobmsg_get_u32(tb); + state->checkup_timeout.cb = proto_shell_checkup_timeout_cb; + } +} + +static struct interface_proto_state * +proto_shell_attach(const struct proto_handler *h, struct interface *iface, + struct blob_attr *attr) +{ + struct proto_shell_state *state; + + state = calloc(1, sizeof(*state)); + if (!state) + return NULL; + + INIT_LIST_HEAD(&state->deps); + + state->config = malloc(blob_pad_len(attr)); + if (!state->config) + goto error; + + memcpy(state->config, attr, blob_pad_len(attr)); + proto_shell_checkup_attach(state, state->config); + state->proto.free = proto_shell_free; + state->proto.notify = proto_shell_notify; + state->proto.cb = proto_shell_handler; + state->teardown_timeout.cb = proto_shell_teardown_timeout_cb; + state->script_task.cb = proto_shell_script_cb; + state->script_task.dir_fd = proto_fd; + state->script_task.log_prefix = iface->name; + state->proto_task.cb = proto_shell_task_cb; + state->proto_task.dir_fd = proto_fd; + state->proto_task.log_prefix = iface->name; + state->handler = container_of(h, struct proto_shell_handler, proto); + + return &state->proto; + +error: + free(state); + return NULL; +} + +static void +proto_shell_add_handler(const char *script, const char *name, json_object *obj) +{ + struct proto_shell_handler *handler; + struct proto_handler *proto; + json_object *config, *tmp; + char *proto_name, *script_name; + + handler = calloc_a(sizeof(*handler), + &proto_name, strlen(name) + 1, + &script_name, strlen(script) + 1); + if (!handler) + return; + + handler->script_name = strcpy(script_name, script); + + proto = &handler->proto; + proto->name = strcpy(proto_name, name); + proto->config_params = &handler->config; + proto->attach = proto_shell_attach; + + tmp = json_get_field(obj, "no-device", json_type_boolean); + if (tmp && json_object_get_boolean(tmp)) + handler->proto.flags |= PROTO_FLAG_NODEV; + + tmp = json_get_field(obj, "no-proto-task", json_type_boolean); + handler->no_proto_task = tmp && json_object_get_boolean(tmp); + + tmp = json_get_field(obj, "available", json_type_boolean); + if (tmp && json_object_get_boolean(tmp)) + handler->proto.flags |= PROTO_FLAG_INIT_AVAILABLE; + + tmp = json_get_field(obj, "renew-handler", json_type_boolean); + if (tmp && json_object_get_boolean(tmp)) + handler->proto.flags |= PROTO_FLAG_RENEW_AVAILABLE; + + tmp = json_get_field(obj, "lasterror", json_type_boolean); + if (tmp && json_object_get_boolean(tmp)) + handler->proto.flags |= PROTO_FLAG_LASTERROR; + + config = json_get_field(obj, "config", json_type_array); + if (config) + handler->config_buf = netifd_handler_parse_config(&handler->config, config); + + DPRINTF("Add handler for script %s: %s\n", script, proto->name); + add_proto_handler(proto); +} + +void proto_shell_init(void) +{ + proto_fd = netifd_open_subdir("proto"); + if (proto_fd < 0) + return; + + netifd_init_script_handlers(proto_fd, proto_shell_add_handler); +} diff --git a/src/3P/netifd/proto-static.c b/src/3P/netifd/proto-static.c new file mode 100644 index 00000000..3e33c3d8 --- /dev/null +++ b/src/3P/netifd/proto-static.c @@ -0,0 +1,113 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include +#include + +#include "netifd.h" +#include "interface.h" +#include "interface-ip.h" +#include "proto.h" +#include "system.h" + +struct static_proto_state { + struct interface_proto_state proto; + + struct blob_attr *config; +}; + +static bool +static_proto_setup(struct static_proto_state *state) +{ + struct interface *iface = state->proto.iface; + struct device *dev = iface->main_dev.dev; + + interface_set_l3_dev(iface, dev); + return proto_apply_static_ip_settings(state->proto.iface, state->config) == 0; +} + +static int +static_handler(struct interface_proto_state *proto, + enum interface_proto_cmd cmd, bool force) +{ + struct static_proto_state *state; + int ret = 0; + + state = container_of(proto, struct static_proto_state, proto); + + switch (cmd) { + case PROTO_CMD_SETUP: + if (!static_proto_setup(state)) + return -1; + + break; + case PROTO_CMD_TEARDOWN: + case PROTO_CMD_RENEW: + break; + } + + return ret; +} + +static void +static_free(struct interface_proto_state *proto) +{ + struct static_proto_state *state; + + state = container_of(proto, struct static_proto_state, proto); + free(state->config); + free(state); +} + +static struct interface_proto_state * +static_attach(const struct proto_handler *h, struct interface *iface, + struct blob_attr *attr) +{ + struct static_proto_state *state; + + state = calloc(1, sizeof(*state)); + if (!state) + return NULL; + + state->config = malloc(blob_pad_len(attr)); + if (!state->config) + goto error; + + memcpy(state->config, attr, blob_pad_len(attr)); + state->proto.free = static_free; + state->proto.cb = static_handler; + + return &state->proto; + +error: + free(state); + return NULL; +} + +static struct proto_handler static_proto = { + .name = "static", + .flags = PROTO_FLAG_IMMEDIATE | + PROTO_FLAG_FORCE_LINK_DEFAULT, + .config_params = &proto_ip_attr, + .attach = static_attach, +}; + +static void __init +static_proto_init(void) +{ + add_proto_handler(&static_proto); +} diff --git a/src/3P/netifd/proto.c b/src/3P/netifd/proto.c new file mode 100644 index 00000000..45eeb4b9 --- /dev/null +++ b/src/3P/netifd/proto.c @@ -0,0 +1,642 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * Copyright (C) 2012 Steven Barth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include + +#include +#include + +#include "netifd.h" +#include "system.h" +#include "interface.h" +#include "interface-ip.h" +#include "proto.h" + +static struct avl_tree handlers; + +enum { + OPT_IPADDR, + OPT_IP6ADDR, + OPT_NETMASK, + OPT_BROADCAST, + OPT_GATEWAY, + OPT_IP6GW, + OPT_IP6PREFIX, + __OPT_MAX, +}; + +static const struct blobmsg_policy proto_ip_attributes[__OPT_MAX] = { + [OPT_IPADDR] = { .name = "ipaddr", .type = BLOBMSG_TYPE_ARRAY }, + [OPT_IP6ADDR] = { .name = "ip6addr", .type = BLOBMSG_TYPE_ARRAY }, + [OPT_NETMASK] = { .name = "netmask", .type = BLOBMSG_TYPE_STRING }, + [OPT_BROADCAST] = { .name = "broadcast", .type = BLOBMSG_TYPE_STRING }, + [OPT_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING }, + [OPT_IP6GW] = { .name = "ip6gw", .type = BLOBMSG_TYPE_STRING }, + [OPT_IP6PREFIX] = { .name = "ip6prefix", .type = BLOBMSG_TYPE_ARRAY }, +}; + +static const struct uci_blob_param_info proto_ip_attr_info[__OPT_MAX] = { + [OPT_IPADDR] = { .type = BLOBMSG_TYPE_STRING }, + [OPT_IP6ADDR] = { .type = BLOBMSG_TYPE_STRING }, + [OPT_IP6PREFIX] = { .type = BLOBMSG_TYPE_STRING }, +}; + +static const char * const proto_ip_validate[__OPT_MAX] = { + [OPT_IPADDR] = "ip4addr", + [OPT_IP6ADDR] = "ip6addr", + [OPT_NETMASK] = "netmask", + [OPT_BROADCAST] = "ipaddr", + [OPT_GATEWAY] = "ip4addr", + [OPT_IP6GW] = "ip6addr", + [OPT_IP6PREFIX] = "ip6addr", +}; + +const struct uci_blob_param_list proto_ip_attr = { + .n_params = __OPT_MAX, + .params = proto_ip_attributes, + .validate = proto_ip_validate, + .info = proto_ip_attr_info, +}; + +enum { + ADDR_IPADDR, + ADDR_MASK, + ADDR_BROADCAST, + ADDR_PTP, + ADDR_PREFERRED, + ADDR_VALID, + ADDR_OFFLINK, + ADDR_CLASS, + __ADDR_MAX +}; + +static const struct blobmsg_policy proto_ip_addr[__ADDR_MAX] = { + [ADDR_IPADDR] = { .name = "ipaddr", .type = BLOBMSG_TYPE_STRING }, + [ADDR_MASK] = { .name = "mask", .type = BLOBMSG_TYPE_STRING }, + [ADDR_BROADCAST] = { .name = "broadcast", .type = BLOBMSG_TYPE_STRING }, + [ADDR_PTP] = { .name = "ptp", .type = BLOBMSG_TYPE_STRING }, + [ADDR_PREFERRED] = { .name = "preferred", .type = BLOBMSG_TYPE_INT32 }, + [ADDR_VALID] = { .name = "valid", .type = BLOBMSG_TYPE_INT32 }, + [ADDR_OFFLINK] = { .name = "offlink", .type = BLOBMSG_TYPE_BOOL }, + [ADDR_CLASS] = { .name = "class", .type = BLOBMSG_TYPE_STRING }, +}; + +static struct device_addr * +alloc_device_addr(bool v6, bool ext) +{ + struct device_addr *addr; + + addr = calloc(1, sizeof(*addr)); + if (!addr) + return NULL; + + addr->flags = v6 ? DEVADDR_INET6 : DEVADDR_INET4; + if (ext) + addr->flags |= DEVADDR_EXTERNAL; + + return addr; +} + +static bool +parse_addr(struct interface *iface, const char *str, bool v6, int mask, + bool ext, uint32_t broadcast) +{ + struct device_addr *addr; + int af = v6 ? AF_INET6 : AF_INET; + + addr = alloc_device_addr(v6, ext); + if (!addr) + return false; + + addr->mask = mask; + if (!parse_ip_and_netmask(af, str, &addr->addr, &addr->mask)) { + interface_add_error(iface, "proto", "INVALID_ADDRESS", &str, 1); + free(addr); + return false; + } + + if (broadcast) + addr->broadcast = broadcast; + + vlist_add(&iface->proto_ip.addr, &addr->node, &addr->flags); + return true; +} + +static int +parse_static_address_option(struct interface *iface, struct blob_attr *attr, + bool v6, int netmask, bool ext, uint32_t broadcast) +{ + struct blob_attr *cur; + int n_addr = 0; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + return -1; + + n_addr++; + if (!parse_addr(iface, blobmsg_data(cur), v6, netmask, ext, + broadcast)) + return -1; + } + + return n_addr; +} + +static struct device_addr * +parse_address_item(struct blob_attr *attr, bool v6, bool ext) +{ + struct device_addr *addr; + struct blob_attr *tb[__ADDR_MAX]; + struct blob_attr *cur; + + if (blobmsg_type(attr) != BLOBMSG_TYPE_TABLE) + return NULL; + + addr = alloc_device_addr(v6, ext); + if (!addr) + return NULL; + + blobmsg_parse(proto_ip_addr, __ADDR_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); + + addr->mask = v6 ? 128 : 32; + if ((cur = tb[ADDR_MASK])) { + unsigned int new_mask; + + new_mask = parse_netmask_string(blobmsg_data(cur), v6); + if (new_mask > addr->mask) + goto error; + + addr->mask = new_mask; + } + + cur = tb[ADDR_IPADDR]; + if (!cur) + goto error; + + if (!inet_pton(v6 ? AF_INET6 : AF_INET, blobmsg_data(cur), &addr->addr)) + goto error; + + if ((cur = tb[ADDR_OFFLINK]) && blobmsg_get_bool(cur)) + addr->flags |= DEVADDR_OFFLINK; + + if (!v6) { + if ((cur = tb[ADDR_BROADCAST]) && + !inet_pton(AF_INET, blobmsg_data(cur), &addr->broadcast)) + goto error; + if ((cur = tb[ADDR_PTP]) && + !inet_pton(AF_INET, blobmsg_data(cur), &addr->point_to_point)) + goto error; + } else { + time_t now = system_get_rtime(); + if ((cur = tb[ADDR_PREFERRED])) { + int64_t preferred = blobmsg_get_u32(cur); + int64_t preferred_until = preferred + (int64_t)now; + if (preferred_until <= LONG_MAX && preferred != 0xffffffffLL) + addr->preferred_until = preferred_until; + } + + if ((cur = tb[ADDR_VALID])) { + int64_t valid = blobmsg_get_u32(cur); + int64_t valid_until = valid + (int64_t)now; + if (valid_until <= LONG_MAX && valid != 0xffffffffLL) + addr->valid_until = valid_until; + + } + + if (addr->valid_until) { + if (!addr->preferred_until) + addr->preferred_until = addr->valid_until; + else if (addr->preferred_until > addr->valid_until) + goto error; + } + + if ((cur = tb[ADDR_CLASS])) + addr->pclass = strdup(blobmsg_get_string(cur)); + } + + return addr; + +error: + free(addr); + return NULL; +} + +static int +parse_address_list(struct interface *iface, struct blob_attr *attr, bool v6, + bool ext) +{ + struct device_addr *addr; + struct blob_attr *cur; + int n_addr = 0; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) { + addr = parse_address_item(cur, v6, ext); + if (!addr) + return -1; + + n_addr++; + vlist_add(&iface->proto_ip.addr, &addr->node, &addr->flags); + } + + return n_addr; +} + +static bool +parse_gateway_option(struct interface *iface, struct blob_attr *attr, bool v6) +{ + struct device_route *route; + const char *str = blobmsg_data(attr); + int af = v6 ? AF_INET6 : AF_INET; + + route = calloc(1, sizeof(*route)); + if (!route) + return NULL; + + if (!inet_pton(af, str, &route->nexthop)) { + interface_add_error(iface, "proto", "INVALID_GATEWAY", &str, 1); + free(route); + return false; + } + + route->mask = 0; + route->flags = (v6 ? DEVADDR_INET6 : DEVADDR_INET4); + route->metric = iface->metric; + + unsigned int table = (v6) ? iface->ip6table : iface->ip4table; + if (table) { + route->table = table; + route->flags |= DEVROUTE_SRCTABLE; + } + + vlist_add(&iface->proto_ip.route, &route->node, route); + + return true; +} + +static bool +parse_prefix_option(struct interface *iface, const char *str, size_t len) +{ + char buf[128] = {0}, *saveptr; + if (len >= sizeof(buf)) + return false; + + memcpy(buf, str, len); + char *addrstr = strtok_r(buf, "/", &saveptr); + if (!addrstr) + return false; + + char *lengthstr = strtok_r(NULL, ",", &saveptr); + if (!lengthstr) + return false; + + char *prefstr = strtok_r(NULL, ",", &saveptr); + char *validstr = (!prefstr) ? NULL : strtok_r(NULL, ",", &saveptr); + char *addstr = (!validstr) ? NULL : strtok_r(NULL, ",", &saveptr); + const char *pclass = NULL; + + int64_t pref = (!prefstr) ? 0 : strtoul(prefstr, NULL, 10); + int64_t valid = (!validstr) ? 0 : strtoul(validstr, NULL, 10); + + uint8_t length = strtoul(lengthstr, NULL, 10), excl_length = 0; + if (length < 1 || length > 64) + return false; + + struct in6_addr addr, excluded, *excludedp = NULL; + if (inet_pton(AF_INET6, addrstr, &addr) < 1) + return false; + + for (; addstr; addstr = strtok_r(NULL, ",", &saveptr)) { + char *key = NULL, *val = NULL, *addsaveptr; + if (!(key = strtok_r(addstr, "=", &addsaveptr)) || + !(val = strtok_r(NULL, ",", &addsaveptr))) + continue; + + if (!strcmp(key, "excluded")) { + char *sep = strchr(val, '/'); + if (!sep) + return false; + + *sep = 0; + excl_length = atoi(sep + 1); + + if (inet_pton(AF_INET6, val, &excluded) < 1) + return false; + + excludedp = &excluded; + } else if (!strcmp(key, "class")) { + pclass = val; + } + + } + + + + + int64_t now = system_get_rtime(); + time_t preferred_until = 0; + if (prefstr && pref != 0xffffffffLL && pref + now <= LONG_MAX) + preferred_until = pref + now; + + time_t valid_until = 0; + if (validstr && valid != 0xffffffffLL && valid + now <= LONG_MAX) + valid_until = valid + now; + + interface_ip_add_device_prefix(iface, &addr, length, + valid_until, preferred_until, + excludedp, excl_length, pclass); + return true; +} + +static int +parse_prefix_list(struct interface *iface, struct blob_attr *attr) +{ + struct blob_attr *cur; + int n_addr = 0; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + return -1; + + n_addr++; + if (!parse_prefix_option(iface, blobmsg_data(cur), + blobmsg_data_len(cur))) + return -1; + } + + return n_addr; +} + +int +proto_apply_static_ip_settings(struct interface *iface, struct blob_attr *attr) +{ + struct blob_attr *tb[__OPT_MAX]; + struct blob_attr *cur; + const char *error; + unsigned int netmask = 32; + int n_v4 = 0, n_v6 = 0; + struct in_addr bcast = {}; + + blobmsg_parse(proto_ip_attributes, __OPT_MAX, tb, blob_data(attr), blob_len(attr)); + + if ((cur = tb[OPT_NETMASK])) { + netmask = parse_netmask_string(blobmsg_data(cur), false); + if (netmask > 32) { + error = "INVALID_NETMASK"; + goto error; + } + } + + if ((cur = tb[OPT_BROADCAST])) { + if (!inet_pton(AF_INET, blobmsg_data(cur), &bcast)) { + error = "INVALID_BROADCAST"; + goto error; + } + } + + if ((cur = tb[OPT_IPADDR])) + n_v4 = parse_static_address_option(iface, cur, false, + netmask, false, bcast.s_addr); + + if ((cur = tb[OPT_IP6ADDR])) + n_v6 = parse_static_address_option(iface, cur, true, + 128, false, 0); + + if ((cur = tb[OPT_IP6PREFIX])) + if (parse_prefix_list(iface, cur) < 0) + goto out; + + if (n_v4 < 0 || n_v6 < 0) + goto out; + + if ((cur = tb[OPT_GATEWAY])) { + if (n_v4 && !parse_gateway_option(iface, cur, false)) + goto out; + } + + if ((cur = tb[OPT_IP6GW])) { + if (n_v6 && !parse_gateway_option(iface, cur, true)) + goto out; + } + + return 0; + +error: + interface_add_error(iface, "proto", error, NULL, 0); +out: + return -1; +} + +int +proto_apply_ip_settings(struct interface *iface, struct blob_attr *attr, bool ext) +{ + struct blob_attr *tb[__OPT_MAX]; + struct blob_attr *cur; + int n_v4 = 0, n_v6 = 0; + + blobmsg_parse(proto_ip_attributes, __OPT_MAX, tb, blob_data(attr), blob_len(attr)); + + if ((cur = tb[OPT_IPADDR])) + n_v4 = parse_address_list(iface, cur, false, ext); + + if ((cur = tb[OPT_IP6ADDR])) + n_v6 = parse_address_list(iface, cur, true, ext); + + if ((cur = tb[OPT_IP6PREFIX])) + if (parse_prefix_list(iface, cur) < 0) + goto out; + + if (n_v4 < 0 || n_v6 < 0) + goto out; + + if ((cur = tb[OPT_GATEWAY])) { + if (n_v4 && !parse_gateway_option(iface, cur, false)) + goto out; + } + + if ((cur = tb[OPT_IP6GW])) { + if (n_v6 && !parse_gateway_option(iface, cur, true)) + goto out; + } + + return 0; + +out: + return -1; +} + +void add_proto_handler(struct proto_handler *p) +{ + if (!handlers.comp) + avl_init(&handlers, avl_strcmp, false, NULL); + + if (p->avl.key) + return; + + p->avl.key = p->name; + avl_insert(&handlers, &p->avl); +} + +static void +default_proto_free(struct interface_proto_state *proto) +{ + free(proto); +} + +static int +invalid_proto_handler(struct interface_proto_state *proto, + enum interface_proto_cmd cmd, bool force) +{ + return -1; +} + +static int +no_proto_handler(struct interface_proto_state *proto, + enum interface_proto_cmd cmd, bool force) +{ + return 0; +} + +static struct interface_proto_state * +default_proto_attach(const struct proto_handler *h, + struct interface *iface, struct blob_attr *attr) +{ + struct interface_proto_state *proto; + + proto = calloc(1, sizeof(*proto)); + if (!proto) + return NULL; + + proto->free = default_proto_free; + proto->cb = no_proto_handler; + + return proto; +} + +static const struct proto_handler no_proto = { + .name = "none", + .flags = PROTO_FLAG_IMMEDIATE, + .attach = default_proto_attach, +}; + +static const struct proto_handler * +get_proto_handler(const char *name) +{ + struct proto_handler *proto; + + if (!strcmp(name, "none")) + return &no_proto; + + if (!handlers.comp) + return NULL; + + return avl_find_element(&handlers, name, proto, avl); +} + +void +proto_dump_handlers(struct blob_buf *b) +{ + struct proto_handler *p; + void *c; + + avl_for_each_element(&handlers, p, avl) { + void *v; + + c = blobmsg_open_table(b, p->name); + if (p->config_params->validate) { + int i; + + v = blobmsg_open_table(b, "validate"); + for (i = 0; i < p->config_params->n_params; i++) + blobmsg_add_string(b, p->config_params->params[i].name, uci_get_validate_string(p->config_params, i)); + blobmsg_close_table(b, v); + } + blobmsg_add_u8(b, "no_device", !!(p->flags & PROTO_FLAG_NODEV)); + blobmsg_close_table(b, c); + } +} + +void +proto_init_interface(struct interface *iface, struct blob_attr *attr) +{ + const struct proto_handler *proto = iface->proto_handler; + struct interface_proto_state *state = NULL; + + if (!proto) + proto = &no_proto; + + state = proto->attach(proto, iface, attr); + if (!state) { + state = no_proto.attach(&no_proto, iface, attr); + state->cb = invalid_proto_handler; + } + + state->handler = proto; + interface_set_proto_state(iface, state); +} + +void +proto_attach_interface(struct interface *iface, const char *proto_name) +{ + const struct proto_handler *proto = &no_proto; + const char *error = NULL; + + if (proto_name) { + proto = get_proto_handler(proto_name); + if (!proto) { + error = "INVALID_PROTO"; + proto = &no_proto; + } + } + + iface->proto_handler = proto; + + if (error) + interface_add_error(iface, "proto", error, NULL, 0); +} + +int +interface_proto_event(struct interface_proto_state *proto, + enum interface_proto_cmd cmd, bool force) +{ + enum interface_proto_event ev; + int ret; + + ret = proto->cb(proto, cmd, force); + if (ret || !(proto->handler->flags & PROTO_FLAG_IMMEDIATE)) + goto out; + + switch(cmd) { + case PROTO_CMD_SETUP: + ev = IFPEV_UP; + break; + case PROTO_CMD_TEARDOWN: + ev = IFPEV_DOWN; + break; + case PROTO_CMD_RENEW: + ev = IFPEV_RENEW; + break; + default: + return -EINVAL; + } + proto->proto_event(proto, ev); + +out: + return ret; +} diff --git a/src/3P/netifd/proto.h b/src/3P/netifd/proto.h new file mode 100644 index 00000000..87dec4e9 --- /dev/null +++ b/src/3P/netifd/proto.h @@ -0,0 +1,83 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_PROTO_H +#define __NETIFD_PROTO_H + +struct interface; +struct interface_proto_state; +struct proto_handler; + +enum interface_proto_event { + IFPEV_UP, + IFPEV_DOWN, + IFPEV_LINK_LOST, + IFPEV_RENEW, +}; + +enum interface_proto_cmd { + PROTO_CMD_SETUP, + PROTO_CMD_TEARDOWN, + PROTO_CMD_RENEW, +}; + +enum { + PROTO_FLAG_IMMEDIATE = (1 << 0), + PROTO_FLAG_NODEV = (1 << 1), + PROTO_FLAG_INIT_AVAILABLE = (1 << 2), + PROTO_FLAG_RENEW_AVAILABLE = (1 << 3), + PROTO_FLAG_FORCE_LINK_DEFAULT = (1 << 4), + PROTO_FLAG_LASTERROR = (1 << 5), +}; + +struct interface_proto_state { + const struct proto_handler *handler; + struct interface *iface; + + /* filled in by the protocol user */ + void (*proto_event)(struct interface_proto_state *, enum interface_proto_event ev); + + /* filled in by the protocol handler */ + int (*notify)(struct interface_proto_state *, struct blob_attr *data); + int (*cb)(struct interface_proto_state *, enum interface_proto_cmd cmd, bool force); + void (*free)(struct interface_proto_state *); +}; + + +struct proto_handler { + struct avl_node avl; + + unsigned int flags; + + const char *name; + const struct uci_blob_param_list *config_params; + + struct interface_proto_state *(*attach)(const struct proto_handler *h, + struct interface *iface, struct blob_attr *attr); +}; + +extern const struct uci_blob_param_list proto_ip_attr; + +void add_proto_handler(struct proto_handler *p); +void proto_init_interface(struct interface *iface, struct blob_attr *attr); +void proto_attach_interface(struct interface *iface, const char *proto_name); +int interface_proto_event(struct interface_proto_state *proto, + enum interface_proto_cmd cmd, bool force); + +unsigned int parse_netmask_string(const char *str, bool v6); +int proto_apply_static_ip_settings(struct interface *iface, struct blob_attr *attr); +int proto_apply_ip_settings(struct interface *iface, struct blob_attr *attr, bool ext); +void proto_dump_handlers(struct blob_buf *b); +void proto_shell_init(void); + +#endif diff --git a/src/3P/netifd/scripts/netifd-proto.sh b/src/3P/netifd/scripts/netifd-proto.sh new file mode 100644 index 00000000..447f0f66 --- /dev/null +++ b/src/3P/netifd/scripts/netifd-proto.sh @@ -0,0 +1,401 @@ +NETIFD_MAIN_DIR="${NETIFD_MAIN_DIR:-/lib/netifd}" + +. /usr/share/libubox/jshn.sh +. $NETIFD_MAIN_DIR/utils.sh + +proto_config_add_int() { + config_add_int "$@" +} + +proto_config_add_string() { + config_add_string "$@" +} + +proto_config_add_boolean() { + config_add_boolean "$@" +} + +_proto_do_teardown() { + json_load "$data" + eval "proto_$1_teardown \"$interface\" \"$ifname\"" +} + +_proto_do_renew() { + json_load "$data" + eval "proto_$1_renew \"$interface\" \"$ifname\"" +} + +_proto_do_setup() { + json_load "$data" + _EXPORT_VAR=0 + _EXPORT_VARS= + eval "proto_$1_setup \"$interface\" \"$ifname\"" +} + +proto_init_update() { + local ifname="$1" + local up="$2" + local external="$3" + + PROTO_KEEP=0 + PROTO_INIT=1 + PROTO_TUNNEL_OPEN= + PROTO_IPADDR= + PROTO_IP6ADDR= + PROTO_ROUTE= + PROTO_ROUTE6= + PROTO_PREFIX6= + PROTO_DNS= + PROTO_DNS_SEARCH= + json_init + json_add_int action 0 + [ -n "$ifname" -a "*" != "$ifname" ] && json_add_string "ifname" "$ifname" + json_add_boolean "link-up" "$up" + [ -n "$3" ] && json_add_boolean "address-external" "$external" +} + +proto_set_keep() { + PROTO_KEEP="$1" +} + +proto_close_nested() { + [ -n "$PROTO_NESTED_OPEN" ] && json_close_object + PROTO_NESTED_OPEN= +} + +proto_add_nested() { + PROTO_NESTED_OPEN=1 + json_add_object "$1" +} + +proto_add_tunnel() { + proto_add_nested "tunnel" +} + +proto_close_tunnel() { + proto_close_nested +} + +proto_add_data() { + proto_add_nested "data" +} + +proto_close_data() { + proto_close_nested +} + +proto_add_dns_server() { + local address="$1" + + append PROTO_DNS "$address" +} + +proto_add_dns_search() { + local address="$1" + + append PROTO_DNS_SEARCH "$address" +} + +proto_add_ipv4_address() { + local address="$1" + local mask="$2" + local broadcast="$3" + local ptp="$4" + + append PROTO_IPADDR "$address/$mask/$broadcast/$ptp" +} + +proto_add_ipv6_address() { + local address="$1" + local mask="$2" + local preferred="$3" + local valid="$4" + local offlink="$5" + local class="$6" + + append PROTO_IP6ADDR "$address/$mask/$preferred/$valid/$offlink/$class" +} + +proto_add_ipv4_route() { + local target="$1" + local mask="$2" + local gw="$3" + local source="$4" + local metric="$5" + + append PROTO_ROUTE "$target/$mask/$gw/$metric///$source" +} + +proto_add_ipv6_route() { + local target="$1" + local mask="$2" + local gw="$3" + local metric="$4" + local valid="$5" + local source="$6" + local table="$7" + + append PROTO_ROUTE6 "$target/$mask/$gw/$metric/$valid/$table/$source" +} + +proto_add_ipv6_prefix() { + local prefix="$1" + local valid="$2" + local preferred="$3" + + if [ -z "$valid" ]; then + append PROTO_PREFIX6 "$prefix" + else + [ -z "$preferred" ] && preferred="$valid" + append PROTO_PREFIX6 "$prefix,$valid,$preferred" + fi +} + +_proto_push_ipv4_addr() { + local str="$1" + local address mask broadcast ptp + + address="${str%%/*}" + str="${str#*/}" + mask="${str%%/*}" + str="${str#*/}" + broadcast="${str%%/*}" + str="${str#*/}" + ptp="$str" + + json_add_object "" + json_add_string ipaddr "$address" + [ -n "$mask" ] && json_add_string mask "$mask" + [ -n "$broadcast" ] && json_add_string broadcast "$broadcast" + [ -n "$ptp" ] && json_add_string ptp "$ptp" + json_close_object +} + +_proto_push_ipv6_addr() { + local str="$1" + local address mask preferred valid offlink + + address="${str%%/*}" + str="${str#*/}" + mask="${str%%/*}" + str="${str#*/}" + preferred="${str%%/*}" + str="${str#*/}" + valid="${str%%/*}" + str="${str#*/}" + offlink="${str%%/*}" + str="${str#*/}" + class="${str%%/*}" + + json_add_object "" + json_add_string ipaddr "$address" + [ -n "$mask" ] && json_add_string mask "$mask" + [ -n "$preferred" ] && json_add_int preferred "$preferred" + [ -n "$valid" ] && json_add_int valid "$valid" + [ -n "$offlink" ] && json_add_boolean offlink "$offlink" + [ -n "$class" ] && json_add_string class "$class" + json_close_object +} + +_proto_push_string() { + json_add_string "" "$1" +} + +_proto_push_route() { + local str="$1"; + local target="${str%%/*}" + str="${str#*/}" + local mask="${str%%/*}" + str="${str#*/}" + local gw="${str%%/*}" + str="${str#*/}" + local metric="${str%%/*}" + str="${str#*/}" + local valid="${str%%/*}" + str="${str#*/}" + local table="${str%%/*}" + str="${str#*/}" + local source="${str}" + + json_add_object "" + json_add_string target "$target" + json_add_string netmask "$mask" + [ -n "$gw" ] && json_add_string gateway "$gw" + [ -n "$metric" ] && json_add_int metric "$metric" + [ -n "$valid" ] && json_add_int valid "$valid" + [ -n "$source" ] && json_add_string source "$source" + [ -n "$table" ] && json_add_string table "$table" + json_close_object +} + +_proto_push_array() { + local name="$1" + local val="$2" + local cb="$3" + + [ -n "$val" ] || return 0 + json_add_array "$name" + for item in $val; do + eval "$cb \"\$item\"" + done + json_close_array +} + +_proto_notify() { + local interface="$1" + local options="$2" + json_add_string "interface" "$interface" + ubus $options call network.interface notify_proto "$(json_dump)" +} + +proto_send_update() { + local interface="$1" + + proto_close_nested + json_add_boolean keep "$PROTO_KEEP" + _proto_push_array "ipaddr" "$PROTO_IPADDR" _proto_push_ipv4_addr + _proto_push_array "ip6addr" "$PROTO_IP6ADDR" _proto_push_ipv6_addr + _proto_push_array "routes" "$PROTO_ROUTE" _proto_push_route + _proto_push_array "routes6" "$PROTO_ROUTE6" _proto_push_route + _proto_push_array "ip6prefix" "$PROTO_PREFIX6" _proto_push_string + _proto_push_array "dns" "$PROTO_DNS" _proto_push_string + _proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_string + _proto_notify "$interface" +} + +proto_export() { + local var="VAR${_EXPORT_VAR}" + _EXPORT_VAR="$(($_EXPORT_VAR + 1))" + export -- "$var=$1" + append _EXPORT_VARS "$var" +} + +proto_run_command() { + local interface="$1"; shift + + json_init + json_add_int action 1 + json_add_array command + while [ $# -gt 0 ]; do + json_add_string "" "$1" + shift + done + json_close_array + [ -n "$_EXPORT_VARS" ] && { + json_add_array env + for var in $_EXPORT_VARS; do + eval "json_add_string \"\" \"\${$var}\"" + done + json_close_array + } + _proto_notify "$interface" +} + +proto_kill_command() { + local interface="$1"; shift + + json_init + json_add_int action 2 + [ -n "$1" ] && json_add_int signal "$1" + _proto_notify "$interface" +} + +proto_notify_error() { + local interface="$1"; shift + + json_init + json_add_int action 3 + json_add_array error + while [ $# -gt 0 ]; do + json_add_string "" "$1" + shift + done + json_close_array + _proto_notify "$interface" +} + +proto_block_restart() { + local interface="$1"; shift + + json_init + json_add_int action 4 + _proto_notify "$interface" +} + +proto_set_available() { + local interface="$1" + local state="$2" + json_init + json_add_int action 5 + json_add_boolean available "$state" + _proto_notify "$interface" +} + +proto_add_host_dependency() { + local interface="$1" + local host="$2" + local ifname="$3" + + # execute in subshell to not taint callers env + # see tickets #11046, #11545, #11570 + ( + json_init + json_add_int action 6 + json_add_string host "$host" + [ -n "$ifname" ] && json_add_string ifname "$ifname" + _proto_notify "$interface" -S + ) +} + +proto_setup_failed() { + local interface="$1" + json_init + json_add_int action 7 + _proto_notify "$interface" +} + +init_proto() { + proto="$1"; shift + cmd="$1"; shift + + case "$cmd" in + dump) + add_protocol() { + no_device=0 + no_proto_task=0 + available=0 + renew_handler=0 + + add_default_handler "proto_$1_init_config" + + json_init + json_add_string "name" "$1" + json_add_array "config" + eval "proto_$1_init_config" + json_close_array + json_add_boolean no-device "$no_device" + json_add_boolean no-proto-task "$no_proto_task" + json_add_boolean available "$available" + json_add_boolean renew-handler "$renew_handler" + json_add_boolean lasterror "$lasterror" + json_dump + } + ;; + setup|teardown|renew) + interface="$1"; shift + data="$1"; shift + ifname="$1"; shift + + add_protocol() { + [[ "$proto" == "$1" ]] || return 0 + + case "$cmd" in + setup) _proto_do_setup "$1";; + teardown) _proto_do_teardown "$1" ;; + renew) _proto_do_renew "$1" ;; + *) return 1 ;; + esac + } + ;; + esac +} diff --git a/src/3P/netifd/scripts/netifd-wireless.sh b/src/3P/netifd/scripts/netifd-wireless.sh new file mode 100644 index 00000000..87d13cac --- /dev/null +++ b/src/3P/netifd/scripts/netifd-wireless.sh @@ -0,0 +1,337 @@ +NETIFD_MAIN_DIR="${NETIFD_MAIN_DIR:-/lib/netifd}" + +. /usr/share/libubox/jshn.sh +. $NETIFD_MAIN_DIR/utils.sh + +CMD_UP=0 +CMD_SET_DATA=1 +CMD_PROCESS_ADD=2 +CMD_PROCESS_KILL_ALL=3 +CMD_SET_RETRY=4 + +add_driver() { + return +} + +wireless_setup_vif_failed() { + local error="$1" + echo "Interface $_w_iface setup failed: $error" +} + +wireless_setup_failed() { + local error="$1" + + echo "Device setup failed: $error" + wireless_set_retry 0 +} + +prepare_key_wep() { + local key="$1" + local hex=1 + + echo -n "$key" | grep -qE "[^a-fA-F0-9]" && hex=0 + [ "${#key}" -eq 10 -a $hex -eq 1 ] || \ + [ "${#key}" -eq 26 -a $hex -eq 1 ] || { + [ "${key:0:2}" = "s:" ] && key="${key#s:}" + key="$(echo -n "$key" | hexdump -ve '1/1 "%02x" ""')" + } + echo "$key" +} + +_wdev_prepare_channel() { + json_get_vars channel hwmode + + auto_channel=0 + enable_ht=0 + htmode= + hwmode="${hwmode##11}" + hwmode_n="${hwmode##n}" + + case "$channel" in + ""|0|auto) + channel=0 + auto_channel=1 + ;; + [0-9]*) ;; + *) + wireless_setup_failed "INVALID_CHANNEL" + ;; + esac + + [[ "$hwmode_n" = "$hwmode" ]] || { + enable_ht=1 + hwmode="$hwmode_n" + + json_get_vars htmode + case "$htmode" in + HT20|HT40+|HT40-);; + *) htmode= ;; + esac + } + + case "$hwmode" in + a|b|g) ;; + *) + if [ "$channel" -gt 14 ]; then + hwmode=a + else + hwmode=g + fi + ;; + esac +} + +_wdev_handler() { + json_load "$data" + + json_select config + _wdev_prepare_channel + json_select .. + + eval "drv_$1_$2 \"$interface\"" +} + +_wdev_msg_call() { + local old_cb + + json_set_namespace wdev old_cb + "$@" + json_set_namespace $old_cb +} + +_wdev_wrapper() { + while [ -n "$1" ]; do + eval "$1() { _wdev_msg_call _$1 \"\$@\"; }" + shift + done +} + +_wdev_notify_init() { + local command="$1" + local interface="$2" + + json_init + json_add_int "command" "$command" + json_add_string "device" "$__netifd_device" + [ -n "$interface" ] && json_add_string "interface" "$interface" + json_add_object "data" +} + +_wdev_notify() { + local options="$1" + + json_close_object + ubus $options call network.wireless notify "$(json_dump)" +} + +_wdev_add_variables() { + while [ -n "$1" ]; do + local var="${1%%=*}" + local val="$1" + shift + [[ "$var" = "$val" ]] && continue + val="${val#*=}" + json_add_string "$var" "$val" + done +} + +_wireless_add_vif() { + local name="$1"; shift + local ifname="$1"; shift + + _wdev_notify_init $CMD_SET_DATA "$name" + json_add_string "ifname" "$ifname" + _wdev_add_variables "$@" + _wdev_notify +} + +_wireless_set_up() { + _wdev_notify_init $CMD_UP + _wdev_notify +} + +_wireless_set_data() { + _wdev_notify_init $CMD_SET_DATA + _wdev_add_variables "$@" + _wdev_notify +} + +_wireless_add_process() { + _wdev_notify_init $CMD_PROCESS_ADD + local exe="$2" + [ -L "$exe" ] && exe="$(readlink -f "$exe")" + json_add_int pid "$1" + json_add_string exe "$exe" + [ -n "$3" ] && json_add_boolean required 1 + exe2="$(readlink -f /proc/$pid/exe)" + [ "$exe" = "$exe2" ] && echo "WARNING (wireless_add_process): executable path $exe does not match process $1 path ($exe2)" + _wdev_notify +} + +_wireless_process_kill_all() { + _wdev_notify_init $CMD_PROCESS_KILL_ALL + [ -n "$1" ] && json_add_int signal "$1" + _wdev_notify +} + +_wireless_set_retry() { + _wdev_notify_init $CMD_SET_RETRY + json_add_int retry "$1" + _wdev_notify +} + +_wdev_wrapper \ + wireless_add_vif \ + wireless_set_up \ + wireless_set_data \ + wireless_add_process \ + wireless_process_kill_all \ + wireless_set_retry \ + +wireless_vif_parse_encryption() { + json_get_vars encryption + set_default encryption none + + auth_mode_open=1 + auth_mode_shared=0 + auth_type=none + wpa_cipher=CCMP + case "$encryption" in + *tkip+aes|*tkip+ccmp|*aes+tkip|*ccmp+tkip) wpa_cipher="CCMP TKIP";; + *aes|*ccmp) wpa_cipher="CCMP";; + *tkip) wpa_cipher="TKIP";; + esac + + # 802.11n requires CCMP for WPA + [ "$enable_ht:$wpa_cipher" = "1:TKIP" ] && wpa_cipher="CCMP TKIP" + + # Examples: + # psk-mixed/tkip => WPA1+2 PSK, TKIP + # wpa-psk2/tkip+aes => WPA2 PSK, CCMP+TKIP + # wpa2/tkip+aes => WPA2 RADIUS, CCMP+TKIP + + case "$encryption" in + wpa2*|*psk2*) + wpa=2 + ;; + *mixed*) + wpa=3 + ;; + wpa*|*psk*) + wpa=1 + ;; + *) + wpa=0 + wpa_cipher= + ;; + esac + wpa_pairwise="$wpa_cipher" + + case "$encryption" in + *psk*) + auth_type=psk + ;; + *wpa*|*8021x*) + auth_type=eap + ;; + *wep*) + auth_type=wep + case "$encryption" in + *shared*) + auth_mode_open=0 + auth_mode_shared=1 + ;; + *mixed*) + auth_mode_shared=1 + ;; + esac + ;; + esac +} + +_wireless_set_brsnoop_isolation() { + local multicast_to_unicast="$1" + local isolate + + json_get_var isolate isolate + + [ ${isolate:-0} -gt 0 -o -z "$network_bridge" ] && return + [ ${multicast_to_unicast:-1} -gt 0 ] && json_add_boolean isolate 1 +} + +for_each_interface() { + local _w_types="$1"; shift + local _w_ifaces _w_iface + local _w_type + local _w_found + + local multicast_to_unicast + + json_get_keys _w_ifaces interfaces + json_select interfaces + for _w_iface in $_w_ifaces; do + json_select "$_w_iface" + if [ -n "$_w_types" ]; then + json_get_var network_bridge bridge + json_get_var multicast_to_unicast multicast_to_unicast + json_select config + _wireless_set_brsnoop_isolation "$multicast_to_unicast" + json_get_var _w_type mode + json_select .. + _w_types=" $_w_types " + [[ "${_w_types%$_w_type*}" = "$_w_types" ]] && { + json_select .. + continue + } + fi + "$@" "$_w_iface" + json_select .. + done + json_select .. +} + +_wdev_common_device_config() { + config_add_string channel hwmode htmode +} + +_wdev_common_iface_config() { + config_add_string mode ssid encryption 'key:wpakey' +} + +init_wireless_driver() { + name="$1"; shift + cmd="$1"; shift + + case "$cmd" in + dump) + add_driver() { + eval "drv_$1_cleanup" + + json_init + json_add_string name "$1" + + json_add_array device + _wdev_common_device_config + eval "drv_$1_init_device_config" + json_close_array + + json_add_array iface + _wdev_common_iface_config + eval "drv_$1_init_iface_config" + json_close_array + + json_dump + } + ;; + setup|teardown) + interface="$1"; shift + data="$1"; shift + export __netifd_device="$interface" + + add_driver() { + [[ "$name" == "$1" ]] || return 0 + _wdev_handler "$1" "$cmd" + } + ;; + esac +} diff --git a/src/3P/netifd/scripts/utils.sh b/src/3P/netifd/scripts/utils.sh new file mode 100644 index 00000000..60470a56 --- /dev/null +++ b/src/3P/netifd/scripts/utils.sh @@ -0,0 +1,50 @@ +N=" +" + +append() { + local var="$1" + local value="$2" + local sep="${3:- }" + + eval "export -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\"" +} + +add_default_handler() { + case "$(type $1 2>/dev/null)" in + *function*) return;; + *) eval "$1() { return; }" + esac +} + +set_default() { + local __s_var="$1" + local __s_val="$2" + eval "export -- \"$__s_var=\${$__s_var:-\$__s_val}\"" +} + +_config_add_generic() { + local type="$1"; shift + + for name in "$@"; do + json_add_array "" + json_add_string "" "$name" + json_add_int "" "$type" + json_close_array + done +} + +config_add_int() { + _config_add_generic 5 "$@" +} + +config_add_array() { + _config_add_generic 1 "$@" +} + +config_add_string() { + _config_add_generic 3 "$@" +} + +config_add_boolean() { + _config_add_generic 7 "$@" +} diff --git a/src/3P/netifd/system-dummy.c b/src/3P/netifd/system-dummy.c new file mode 100644 index 00000000..9c734ea5 --- /dev/null +++ b/src/3P/netifd/system-dummy.c @@ -0,0 +1,286 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include + +#ifndef DEBUG +#define DEBUG +#endif + +#include "netifd.h" +#include "device.h" +#include "system.h" + +int system_init(void) +{ + return 0; +} + +int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg) +{ + D(SYSTEM, "brctl addbr %s\n", bridge->ifname); + return 0; +} + +int system_bridge_delbr(struct device *bridge) +{ + D(SYSTEM, "brctl delbr %s\n", bridge->ifname); + return 0; +} + +int system_bridge_addif(struct device *bridge, struct device *dev) +{ + D(SYSTEM, "brctl addif %s %s\n", bridge->ifname, dev->ifname); + return 0; +} + +int system_bridge_delif(struct device *bridge, struct device *dev) +{ + D(SYSTEM, "brctl delif %s %s\n", bridge->ifname, dev->ifname); + return 0; +} + +int system_vlan_add(struct device *dev, int id) +{ + D(SYSTEM, "vconfig add %s %d\n", dev->ifname, id); + return 0; +} + +int system_vlan_del(struct device *dev) +{ + D(SYSTEM, "vconfig rem %s\n", dev->ifname); + return 0; +} + +bool system_if_force_external(const char *ifname) +{ + return false; +} + +int system_if_up(struct device *dev) +{ + D(SYSTEM, "ifconfig %s up\n", dev->ifname); + return 0; +} + +int system_if_down(struct device *dev) +{ + D(SYSTEM, "ifconfig %s down\n", dev->ifname); + return 0; +} + +void system_if_get_settings(struct device *dev, struct device_settings *s) +{ +} + +void system_if_clear_state(struct device *dev) +{ +} + +int system_if_check(struct device *dev) +{ + dev->ifindex = 0; + device_set_present(dev, true); + device_set_link(dev, true); + + return 0; +} + +int system_if_resolve(struct device *dev) +{ + return 0; +} + +struct device * +system_if_get_parent(struct device *dev) +{ + return NULL; +} + +int +system_if_dump_info(struct device *dev, struct blob_buf *b) +{ + blobmsg_add_u8(b, "link", dev->present); + return 0; +} + +int +system_if_dump_stats(struct device *dev, struct blob_buf *b) +{ + return 0; +} + +void +system_if_apply_settings(struct device *dev, struct device_settings *s, unsigned int apply_mask) +{ +} + +static int system_address_msg(struct device *dev, struct device_addr *addr, const char *type) +{ + char ipaddr[64]; + int af = system_get_addr_family(addr->flags); + + D(SYSTEM, "ifconfig %s %s %s/%d\n", + dev->ifname, type, inet_ntop(af, &addr->addr.in, ipaddr, sizeof(ipaddr)), + addr->mask); + + return 0; +} + +int system_add_address(struct device *dev, struct device_addr *addr) +{ + return system_address_msg(dev, addr, "add"); +} + +int system_del_address(struct device *dev, struct device_addr *addr) +{ + return system_address_msg(dev, addr, "del"); +} + +static int system_route_msg(struct device *dev, struct device_route *route, const char *type) +{ + char addr[64], gw[64] = " gw ", devstr[64] = ""; + int af = system_get_addr_family(route->flags); + int alen = system_get_addr_len(route->flags); + static uint32_t zero_addr[4]; + + if ((route->flags & DEVADDR_FAMILY) != DEVADDR_INET4) + return -1; + + if (!route->mask) + sprintf(addr, "default"); + else + inet_ntop(af, &route->addr.in, addr, sizeof(addr)); + + if (memcmp(&route->nexthop.in, (void *) zero_addr, alen) != 0) + inet_ntop(af, &route->nexthop.in, gw + 4, sizeof(gw) - 4); + else + gw[0] = 0; + + if (dev) + sprintf(devstr, " dev %s", dev->ifname); + + if (route->metric > 0) + sprintf(devstr, " metric %d", route->metric); + + D(SYSTEM, "route %s %s%s%s\n", type, addr, gw, devstr); + return 0; +} + +int system_add_route(struct device *dev, struct device_route *route) +{ + return system_route_msg(dev, route, "add"); +} + +int system_del_route(struct device *dev, struct device_route *route) +{ + return system_route_msg(dev, route, "del"); +} + +int system_flush_routes(void) +{ + return 0; +} + +bool system_resolve_rt_type(const char *type, unsigned int *id) +{ + *id = 0; + return true; +} + +bool system_resolve_rt_table(const char *name, unsigned int *id) +{ + *id = 0; + return true; +} + +bool system_is_default_rt_table(unsigned int id) +{ + return true; +} + +bool system_resolve_rpfilter(const char *filter, unsigned int *id) +{ + *id = 0; + return true; +} + +int system_add_iprule(struct iprule *rule) +{ + return 0; +} + +int system_del_iprule(struct iprule *rule) +{ + return 0; +} + +int system_flush_iprules(void) +{ + return 0; +} + +bool system_resolve_iprule_action(const char *action, unsigned int *id) +{ + *id = 0; + return true; +} + +time_t system_get_rtime(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) == 0) + return tv.tv_sec; + + return 0; +} + +int system_del_ip_tunnel(const char *name, struct blob_attr *attr) +{ + return 0; +} + +int system_add_ip_tunnel(const char *name, struct blob_attr *attr) +{ + return 0; +} + +int system_update_ipv6_mtu(struct device *dev, int mtu) +{ + return 0; +} + +int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg) +{ + return 0; +} + +int system_macvlan_del(struct device *macvlan) +{ + return 0; +} + +int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg) +{ + return 0; +} + +int system_vlandev_del(struct device *vlandev) +{ + return 0; +} diff --git a/src/3P/netifd/system-linux.c b/src/3P/netifd/system-linux.c new file mode 100644 index 00000000..d868c15d --- /dev/null +++ b/src/3P/netifd/system-linux.c @@ -0,0 +1,2687 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * Copyright (C) 2013 Jo-Philipp Wich + * Copyright (C) 2013 Steven Barth + * Copyright (C) 2014 Gioacchino Mazzurco + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef RTN_FAILED_POLICY +#define RTN_FAILED_POLICY 12 +#endif + +#ifndef IFA_F_NOPREFIXROUTE +#define IFA_F_NOPREFIXROUTE 0x200 +#endif + +#ifndef IFA_FLAGS +#define IFA_FLAGS (IFA_MULTICAST + 1) +#endif + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "netifd.h" +#include "device.h" +#include "system.h" + +struct event_socket { + struct uloop_fd uloop; + struct nl_sock *sock; + int bufsize; +}; + +static int sock_ioctl = -1; +static struct nl_sock *sock_rtnl = NULL; + +static int cb_rtnl_event(struct nl_msg *msg, void *arg); +static void handle_hotplug_event(struct uloop_fd *u, unsigned int events); + +static char dev_buf[256]; + +static void +handler_nl_event(struct uloop_fd *u, unsigned int events) +{ + struct event_socket *ev = container_of(u, struct event_socket, uloop); + int err; + socklen_t errlen = sizeof(err); + + if (!u->error) { + nl_recvmsgs_default(ev->sock); + return; + } + + if (getsockopt(u->fd, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen)) + goto abort; + + switch(err) { + case ENOBUFS: + // Increase rx buffer size on netlink socket + ev->bufsize *= 2; + if (nl_socket_set_buffer_size(ev->sock, ev->bufsize, 0)) + goto abort; + + // Request full dump since some info got dropped + struct rtgenmsg msg = { .rtgen_family = AF_UNSPEC }; + nl_send_simple(ev->sock, RTM_GETLINK, NLM_F_DUMP, &msg, sizeof(msg)); + break; + + default: + goto abort; + } + u->error = false; + return; + +abort: + uloop_fd_delete(&ev->uloop); + return; +} + +static struct nl_sock * +create_socket(int protocol, int groups) +{ + struct nl_sock *sock; + + sock = nl_socket_alloc(); + if (!sock) + return NULL; + + if (groups) + nl_join_groups(sock, groups); + + if (nl_connect(sock, protocol)) + return NULL; + + return sock; +} + +static bool +create_raw_event_socket(struct event_socket *ev, int protocol, int groups, + uloop_fd_handler cb, int flags) +{ + ev->sock = create_socket(protocol, groups); + if (!ev->sock) + return false; + + ev->uloop.fd = nl_socket_get_fd(ev->sock); + ev->uloop.cb = cb; + if (uloop_fd_add(&ev->uloop, ULOOP_READ|flags)) + return false; + + return true; +} + +static bool +create_event_socket(struct event_socket *ev, int protocol, + int (*cb)(struct nl_msg *msg, void *arg)) +{ + if (!create_raw_event_socket(ev, protocol, 0, handler_nl_event, ULOOP_ERROR_CB)) + return false; + + // Install the valid custom callback handler + nl_socket_modify_cb(ev->sock, NL_CB_VALID, NL_CB_CUSTOM, cb, NULL); + + // Disable sequence number checking on event sockets + nl_socket_disable_seq_check(ev->sock); + + // Increase rx buffer size to 65K on event sockets + ev->bufsize = 65535; + if (nl_socket_set_buffer_size(ev->sock, ev->bufsize, 0)) + return false; + + return true; +} + +static bool +system_rtn_aton(const char *src, unsigned int *dst) +{ + char *e; + unsigned int n; + + if (!strcmp(src, "local")) + n = RTN_LOCAL; + else if (!strcmp(src, "nat")) + n = RTN_NAT; + else if (!strcmp(src, "broadcast")) + n = RTN_BROADCAST; + else if (!strcmp(src, "anycast")) + n = RTN_ANYCAST; + else if (!strcmp(src, "multicast")) + n = RTN_MULTICAST; + else if (!strcmp(src, "prohibit")) + n = RTN_PROHIBIT; + else if (!strcmp(src, "unreachable")) + n = RTN_UNREACHABLE; + else if (!strcmp(src, "blackhole")) + n = RTN_BLACKHOLE; + else if (!strcmp(src, "xresolve")) + n = RTN_XRESOLVE; + else if (!strcmp(src, "unicast")) + n = RTN_UNICAST; + else if (!strcmp(src, "throw")) + n = RTN_THROW; + else if (!strcmp(src, "failed_policy")) + n = RTN_FAILED_POLICY; + else { + n = strtoul(src, &e, 0); + if (!e || *e || e == src || n > 255) + return false; + } + + *dst = n; + return true; +} + +static bool +system_tos_aton(const char *src, unsigned *dst) +{ + char *e; + + *dst = strtoul(src, &e, 16); + if (e == src || *e || *dst > 255) + return false; + + return true; +} + +int system_init(void) +{ + static struct event_socket rtnl_event; + static struct event_socket hotplug_event; + + sock_ioctl = socket(AF_LOCAL, SOCK_DGRAM, 0); + system_fd_set_cloexec(sock_ioctl); + + // Prepare socket for routing / address control + sock_rtnl = create_socket(NETLINK_ROUTE, 0); + if (!sock_rtnl) + return -1; + + if (!create_event_socket(&rtnl_event, NETLINK_ROUTE, cb_rtnl_event)) + return -1; + + if (!create_raw_event_socket(&hotplug_event, NETLINK_KOBJECT_UEVENT, 1, + handle_hotplug_event, 0)) + return -1; + + // Receive network link events form kernel + nl_socket_add_membership(rtnl_event.sock, RTNLGRP_LINK); + + return 0; +} + +static void system_set_sysctl(const char *path, const char *val) +{ + int fd; + + fd = open(path, O_WRONLY); + if (fd < 0) + return; + + if (write(fd, val, strlen(val))) {} + close(fd); +} + +static void system_set_dev_sysctl(const char *path, const char *device, const char *val) +{ + snprintf(dev_buf, sizeof(dev_buf), path, device); + system_set_sysctl(dev_buf, val); +} + +static void system_set_disable_ipv6(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/disable_ipv6", dev->ifname, val); +} + +static void system_set_rpfilter(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/rp_filter", dev->ifname, val); +} + +static void system_set_acceptlocal(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/accept_local", dev->ifname, val); +} + +static void system_set_igmpversion(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv4/conf/%s/force_igmp_version", dev->ifname, val); +} + +static void system_set_mldversion(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/force_mld_version", dev->ifname, val); +} + +static void system_set_neigh4reachabletime(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/base_reachable_time_ms", dev->ifname, val); +} + +static void system_set_neigh6reachabletime(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", dev->ifname, val); +} + +static void system_set_neigh4gcstaletime(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/gc_stale_time", dev->ifname, val); +} + +static void system_set_neigh6gcstaletime(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/gc_stale_time", dev->ifname, val); +} + +static void system_set_dadtransmits(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits", dev->ifname, val); +} + +static void system_bridge_set_multicast_to_unicast(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_to_unicast", dev->ifname, val); +} + +static void system_bridge_set_hairpin_mode(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/class/net/%s/brport/hairpin_mode", dev->ifname, val); +} + +static void system_bridge_set_multicast_router(struct device *dev, const char *val, bool bridge) +{ + system_set_dev_sysctl(bridge ? "/sys/class/net/%s/bridge/multicast_router" : + "/sys/class/net/%s/brport/multicast_router", + dev->ifname, val); +} + +static void system_bridge_set_robustness(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_startup_query_count", + dev->ifname, val); + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_last_member_count", + dev->ifname, val); +} + +static void system_bridge_set_query_interval(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_query_interval", + dev->ifname, val); +} + +static void system_bridge_set_query_response_interval(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_query_response_interval", + dev->ifname, val); +} + +static void system_bridge_set_last_member_interval(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_last_member_interval", + dev->ifname, val); +} + +static void system_bridge_set_membership_interval(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_membership_interval", + dev->ifname, val); +} + +static void system_bridge_set_other_querier_timeout(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_querier_interval", + dev->ifname, val); +} + +static void system_bridge_set_startup_query_interval(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_startup_query_interval", + dev->ifname, val); +} + +static void system_bridge_set_learning(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/class/net/%s/brport/learning", dev->ifname, val); +} + +static void system_bridge_set_unicast_flood(struct device *dev, const char *val) +{ + system_set_dev_sysctl("/sys/class/net/%s/brport/unicast_flood", dev->ifname, val); +} + +static int system_get_sysctl(const char *path, char *buf, const size_t buf_sz) +{ + int fd = -1, ret = -1; + + fd = open(path, O_RDONLY); + if (fd < 0) + goto out; + + ssize_t len = read(fd, buf, buf_sz - 1); + if (len < 0) + goto out; + + ret = buf[len] = 0; + +out: + if (fd >= 0) + close(fd); + + return ret; +} + +static int +system_get_dev_sysctl(const char *path, const char *device, char *buf, const size_t buf_sz) +{ + snprintf(dev_buf, sizeof(dev_buf), path, device); + return system_get_sysctl(dev_buf, buf, buf_sz); +} + +static int system_get_disable_ipv6(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/disable_ipv6", + dev->ifname, buf, buf_sz); +} + +static int system_get_rpfilter(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/rp_filter", + dev->ifname, buf, buf_sz); +} + +static int system_get_acceptlocal(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/accept_local", + dev->ifname, buf, buf_sz); +} + +static int system_get_igmpversion(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv4/conf/%s/force_igmp_version", + dev->ifname, buf, buf_sz); +} + +static int system_get_mldversion(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/force_mld_version", + dev->ifname, buf, buf_sz); +} + +static int system_get_neigh4reachabletime(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/base_reachable_time_ms", + dev->ifname, buf, buf_sz); +} + +static int system_get_neigh6reachabletime(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/base_reachable_time_ms", + dev->ifname, buf, buf_sz); +} + +static int system_get_neigh4gcstaletime(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/gc_stale_time", + dev->ifname, buf, buf_sz); +} + +static int system_get_neigh6gcstaletime(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/gc_stale_time", + dev->ifname, buf, buf_sz); +} + +static int system_get_dadtransmits(struct device *dev, char *buf, const size_t buf_sz) +{ + return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits", + dev->ifname, buf, buf_sz); +} + +// Evaluate netlink messages +static int cb_rtnl_event(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct nlattr *nla[__IFLA_MAX]; + int link_state = 0; + char buf[10]; + + if (nh->nlmsg_type != RTM_NEWLINK) + goto out; + + nlmsg_parse(nh, sizeof(struct ifinfomsg), nla, __IFLA_MAX - 1, NULL); + if (!nla[IFLA_IFNAME]) + goto out; + + struct device *dev = device_find(nla_data(nla[IFLA_IFNAME])); + if (!dev) + goto out; + + if (!system_get_dev_sysctl("/sys/class/net/%s/carrier", dev->ifname, buf, sizeof(buf))) + link_state = strtoul(buf, NULL, 0); + + device_set_link(dev, link_state ? true : false); + +out: + return 0; +} + +static void +handle_hotplug_msg(char *data, int size) +{ + const char *subsystem = NULL, *interface = NULL; + char *cur, *end, *sep; + struct device *dev; + int skip; + bool add; + + if (!strncmp(data, "add@", 4)) + add = true; + else if (!strncmp(data, "remove@", 7)) + add = false; + else + return; + + skip = strlen(data) + 1; + end = data + size; + + for (cur = data + skip; cur < end; cur += skip) { + skip = strlen(cur) + 1; + + sep = strchr(cur, '='); + if (!sep) + continue; + + *sep = 0; + if (!strcmp(cur, "INTERFACE")) + interface = sep + 1; + else if (!strcmp(cur, "SUBSYSTEM")) { + subsystem = sep + 1; + if (strcmp(subsystem, "net") != 0) + return; + } + if (subsystem && interface) + goto found; + } + return; + +found: + dev = device_find(interface); + if (!dev) + return; + + if (dev->type != &simple_device_type) + return; + + if (add && system_if_force_external(dev->ifname)) + return; + + device_set_present(dev, add); +} + +static void +handle_hotplug_event(struct uloop_fd *u, unsigned int events) +{ + struct event_socket *ev = container_of(u, struct event_socket, uloop); + struct sockaddr_nl nla; + unsigned char *buf = NULL; + int size; + + while ((size = nl_recv(ev->sock, &nla, &buf, NULL)) > 0) { + if (nla.nl_pid == 0) + handle_hotplug_msg((char *) buf, size); + + free(buf); + } +} + +static int system_rtnl_call(struct nl_msg *msg) +{ + int ret; + + ret = nl_send_auto_complete(sock_rtnl, msg); + nlmsg_free(msg); + + if (ret < 0) + return ret; + + return nl_wait_for_ack(sock_rtnl); +} + +int system_bridge_delbr(struct device *bridge) +{ + return ioctl(sock_ioctl, SIOCBRDELBR, bridge->ifname); +} + +static int system_bridge_if(const char *bridge, struct device *dev, int cmd, void *data) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + if (dev) + ifr.ifr_ifindex = dev->ifindex; + else + ifr.ifr_data = data; + strncpy(ifr.ifr_name, bridge, sizeof(ifr.ifr_name)); + return ioctl(sock_ioctl, cmd, &ifr); +} + +static bool system_is_bridge(const char *name, char *buf, int buflen) +{ + struct stat st; + + snprintf(buf, buflen, "/sys/devices/virtual/net/%s/bridge", name); + if (stat(buf, &st) < 0) + return false; + + return true; +} + +static char *system_get_bridge(const char *name, char *buf, int buflen) +{ + char *path; + ssize_t len = -1; + glob_t gl; + + snprintf(buf, buflen, "/sys/devices/virtual/net/*/brif/%s/bridge", name); + if (glob(buf, GLOB_NOSORT, NULL, &gl) < 0) + return NULL; + + if (gl.gl_pathc > 0) + len = readlink(gl.gl_pathv[0], buf, buflen); + + globfree(&gl); + + if (len < 0) + return NULL; + + buf[len] = 0; + path = strrchr(buf, '/'); + if (!path) + return NULL; + + return path + 1; +} + +static void +system_bridge_set_wireless(struct device *bridge, struct device *dev) +{ + bool mcast_to_ucast = dev->wireless_ap; + bool hairpin = true; + + if (bridge->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST && + !bridge->settings.multicast_to_unicast) + mcast_to_ucast = false; + + if (!mcast_to_ucast || dev->wireless_isolate) + hairpin = false; + + system_bridge_set_multicast_to_unicast(dev, mcast_to_ucast ? "1" : "0"); + system_bridge_set_hairpin_mode(dev, hairpin ? "1" : "0"); +} + +int system_bridge_addif(struct device *bridge, struct device *dev) +{ + char buf[64]; + char *oldbr; + int ret = 0; + + oldbr = system_get_bridge(dev->ifname, dev_buf, sizeof(dev_buf)); + if (!oldbr || strcmp(oldbr, bridge->ifname) != 0) + ret = system_bridge_if(bridge->ifname, dev, SIOCBRADDIF, NULL); + + if (dev->wireless) + system_bridge_set_wireless(bridge, dev); + + if (dev->settings.flags & DEV_OPT_MULTICAST_ROUTER) { + snprintf(buf, sizeof(buf), "%i", dev->settings.multicast_router); + system_bridge_set_multicast_router(dev, buf, false); + } + + if (dev->settings.flags & DEV_OPT_LEARNING && + !dev->settings.learning) + system_bridge_set_learning(dev, "0"); + + if (dev->settings.flags & DEV_OPT_UNICAST_FLOOD && + !dev->settings.unicast_flood) + system_bridge_set_unicast_flood(dev, "0"); + + return ret; +} + +int system_bridge_delif(struct device *bridge, struct device *dev) +{ + return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF, NULL); +} + +int system_if_resolve(struct device *dev) +{ + struct ifreq ifr; + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name)); + if (!ioctl(sock_ioctl, SIOCGIFINDEX, &ifr)) + return ifr.ifr_ifindex; + else + return 0; +} + +static int system_if_flags(const char *ifname, unsigned add, unsigned rem) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ioctl(sock_ioctl, SIOCGIFFLAGS, &ifr); + ifr.ifr_flags |= add; + ifr.ifr_flags &= ~rem; + return ioctl(sock_ioctl, SIOCSIFFLAGS, &ifr); +} + +struct clear_data { + struct nl_msg *msg; + struct device *dev; + int type; + int size; + int af; +}; + + +static bool check_ifaddr(struct nlmsghdr *hdr, int ifindex) +{ + struct ifaddrmsg *ifa = NLMSG_DATA(hdr); + + return ifa->ifa_index == ifindex; +} + +static bool check_route(struct nlmsghdr *hdr, int ifindex) +{ + struct rtmsg *r = NLMSG_DATA(hdr); + struct nlattr *tb[__RTA_MAX]; + + if (r->rtm_protocol == RTPROT_KERNEL && + r->rtm_family == AF_INET6) + return false; + + nlmsg_parse(hdr, sizeof(struct rtmsg), tb, __RTA_MAX - 1, NULL); + if (!tb[RTA_OIF]) + return false; + + return *(int *)RTA_DATA(tb[RTA_OIF]) == ifindex; +} + +static bool check_rule(struct nlmsghdr *hdr, int ifindex) +{ + return true; +} + +static int cb_clear_event(struct nl_msg *msg, void *arg) +{ + struct clear_data *clr = arg; + struct nlmsghdr *hdr = nlmsg_hdr(msg); + bool (*cb)(struct nlmsghdr *, int ifindex); + int type; + + switch(clr->type) { + case RTM_GETADDR: + type = RTM_DELADDR; + if (hdr->nlmsg_type != RTM_NEWADDR) + return NL_SKIP; + + cb = check_ifaddr; + break; + case RTM_GETROUTE: + type = RTM_DELROUTE; + if (hdr->nlmsg_type != RTM_NEWROUTE) + return NL_SKIP; + + cb = check_route; + break; + case RTM_GETRULE: + type = RTM_DELRULE; + if (hdr->nlmsg_type != RTM_NEWRULE) + return NL_SKIP; + + cb = check_rule; + break; + default: + return NL_SKIP; + } + + if (!cb(hdr, clr->dev ? clr->dev->ifindex : 0)) + return NL_SKIP; + + if (type == RTM_DELRULE) + D(SYSTEM, "Remove a rule\n"); + else + D(SYSTEM, "Remove %s from device %s\n", + type == RTM_DELADDR ? "an address" : "a route", + clr->dev->ifname); + memcpy(nlmsg_hdr(clr->msg), hdr, hdr->nlmsg_len); + hdr = nlmsg_hdr(clr->msg); + hdr->nlmsg_type = type; + hdr->nlmsg_flags = NLM_F_REQUEST; + + nl_socket_disable_auto_ack(sock_rtnl); + nl_send_auto_complete(sock_rtnl, clr->msg); + nl_socket_enable_auto_ack(sock_rtnl); + + return NL_SKIP; +} + +static int +cb_finish_event(struct nl_msg *msg, void *arg) +{ + int *pending = arg; + *pending = 0; + return NL_STOP; +} + +static int +error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + int *pending = arg; + *pending = err->error; + return NL_STOP; +} + +static void +system_if_clear_entries(struct device *dev, int type, int af) +{ + struct clear_data clr; + struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); + struct rtmsg rtm = { + .rtm_family = af, + .rtm_flags = RTM_F_CLONED, + }; + int flags = NLM_F_DUMP; + int pending = 1; + + clr.af = af; + clr.dev = dev; + clr.type = type; + switch (type) { + case RTM_GETADDR: + case RTM_GETRULE: + clr.size = sizeof(struct rtgenmsg); + break; + case RTM_GETROUTE: + clr.size = sizeof(struct rtmsg); + break; + default: + return; + } + + if (!cb) + return; + + clr.msg = nlmsg_alloc_simple(type, flags); + if (!clr.msg) + goto out; + + nlmsg_append(clr.msg, &rtm, clr.size, 0); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_clear_event, &clr); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_finish_event, &pending); + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &pending); + + nl_send_auto_complete(sock_rtnl, clr.msg); + while (pending > 0) + nl_recvmsgs(sock_rtnl, cb); + + nlmsg_free(clr.msg); +out: + nl_cb_put(cb); +} + +/* + * Clear bridge (membership) state and bring down device + */ +void system_if_clear_state(struct device *dev) +{ + static char buf[256]; + char *bridge; + + device_set_ifindex(dev, system_if_resolve(dev)); + if (dev->external || !dev->ifindex) + return; + + system_if_flags(dev->ifname, 0, IFF_UP); + + if (system_is_bridge(dev->ifname, buf, sizeof(buf))) { + D(SYSTEM, "Delete existing bridge named '%s'\n", dev->ifname); + system_bridge_delbr(dev); + return; + } + + bridge = system_get_bridge(dev->ifname, buf, sizeof(buf)); + if (bridge) { + D(SYSTEM, "Remove device '%s' from bridge '%s'\n", dev->ifname, bridge); + system_bridge_if(bridge, dev, SIOCBRDELIF, NULL); + } + + system_if_clear_entries(dev, RTM_GETROUTE, AF_INET); + system_if_clear_entries(dev, RTM_GETADDR, AF_INET); + system_if_clear_entries(dev, RTM_GETROUTE, AF_INET6); + system_if_clear_entries(dev, RTM_GETADDR, AF_INET6); + system_set_disable_ipv6(dev, "0"); +} + +static inline unsigned long +sec_to_jiffies(int val) +{ + return (unsigned long) val * 100; +} + +static void system_bridge_conf_multicast_deps(struct device *bridge, + struct bridge_config *cfg, + char *buf, + int buf_len) +{ + int val; + + if (cfg->flags & BRIDGE_OPT_ROBUSTNESS || + cfg->flags & BRIDGE_OPT_QUERY_INTERVAL || + cfg->flags & BRIDGE_OPT_QUERY_RESPONSE_INTERVAL) { + val = cfg->robustness * cfg->query_interval + + cfg->query_response_interval; + + snprintf(buf, buf_len, "%i", val); + system_bridge_set_membership_interval(bridge, buf); + + val = cfg->robustness * cfg->query_interval + + cfg->query_response_interval / 2; + + snprintf(buf, buf_len, "%i", val); + system_bridge_set_other_querier_timeout(bridge, buf); + } + + if (cfg->flags & BRIDGE_OPT_QUERY_INTERVAL) { + val = cfg->query_interval / 4; + + snprintf(buf, buf_len, "%i", val); + system_bridge_set_startup_query_interval(bridge, buf); + } +} + +static void system_bridge_conf_multicast(struct device *bridge, + struct bridge_config *cfg, + char *buf, + int buf_len) +{ + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_snooping", + bridge->ifname, cfg->igmp_snoop ? "1" : "0"); + + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/multicast_querier", + bridge->ifname, cfg->multicast_querier ? "1" : "0"); + + snprintf(buf, buf_len, "%i", cfg->hash_max); + system_set_dev_sysctl("/sys/devices/virtual/net/%s/bridge/hash_max", + bridge->ifname, buf); + + if (bridge->settings.flags & DEV_OPT_MULTICAST_ROUTER) { + snprintf(buf, buf_len, "%i", bridge->settings.multicast_router); + system_bridge_set_multicast_router(bridge, buf, true); + } + + if (cfg->flags & BRIDGE_OPT_ROBUSTNESS) { + snprintf(buf, buf_len, "%i", cfg->robustness); + system_bridge_set_robustness(bridge, buf); + } + + if (cfg->flags & BRIDGE_OPT_QUERY_INTERVAL) { + snprintf(buf, buf_len, "%i", cfg->query_interval); + system_bridge_set_query_interval(bridge, buf); + } + + if (cfg->flags & BRIDGE_OPT_QUERY_RESPONSE_INTERVAL) { + snprintf(buf, buf_len, "%i", cfg->query_response_interval); + system_bridge_set_query_response_interval(bridge, buf); + } + + if (cfg->flags & BRIDGE_OPT_LAST_MEMBER_INTERVAL) { + snprintf(buf, buf_len, "%i", cfg->last_member_interval); + system_bridge_set_last_member_interval(bridge, buf); + } + + system_bridge_conf_multicast_deps(bridge, cfg, buf, buf_len); +} + +int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg) +{ + char buf[64]; + unsigned long args[4] = {}; + + if (ioctl(sock_ioctl, SIOCBRADDBR, bridge->ifname) < 0) + return -1; + + args[0] = BRCTL_SET_BRIDGE_STP_STATE; + args[1] = !!cfg->stp; + system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args); + + args[0] = BRCTL_SET_BRIDGE_FORWARD_DELAY; + args[1] = sec_to_jiffies(cfg->forward_delay); + system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args); + + system_bridge_conf_multicast(bridge, cfg, buf, sizeof(buf)); + + args[0] = BRCTL_SET_BRIDGE_PRIORITY; + args[1] = cfg->priority; + system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args); + + if (cfg->flags & BRIDGE_OPT_AGEING_TIME) { + args[0] = BRCTL_SET_AGEING_TIME; + args[1] = sec_to_jiffies(cfg->ageing_time); + system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args); + } + + if (cfg->flags & BRIDGE_OPT_HELLO_TIME) { + args[0] = BRCTL_SET_BRIDGE_HELLO_TIME; + args[1] = sec_to_jiffies(cfg->hello_time); + system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args); + } + + if (cfg->flags & BRIDGE_OPT_MAX_AGE) { + args[0] = BRCTL_SET_BRIDGE_MAX_AGE; + args[1] = sec_to_jiffies(cfg->max_age); + system_bridge_if(bridge->ifname, NULL, SIOCDEVPRIVATE, &args); + } + + return 0; +} + +int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg) +{ + struct nl_msg *msg; + struct nlattr *linkinfo, *data; + struct ifinfomsg iim = { .ifi_family = AF_UNSPEC, }; + int i, rv; + static const struct { + const char *name; + enum macvlan_mode val; + } modes[] = { + { "private", MACVLAN_MODE_PRIVATE }, + { "vepa", MACVLAN_MODE_VEPA }, + { "bridge", MACVLAN_MODE_BRIDGE }, + { "passthru", MACVLAN_MODE_PASSTHRU }, + }; + + msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + + if (cfg->flags & MACVLAN_OPT_MACADDR) + nla_put(msg, IFLA_ADDRESS, sizeof(cfg->macaddr), cfg->macaddr); + nla_put_string(msg, IFLA_IFNAME, macvlan->ifname); + nla_put_u32(msg, IFLA_LINK, dev->ifindex); + + if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + nla_put_string(msg, IFLA_INFO_KIND, "macvlan"); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + if (cfg->mode) { + for (i = 0; i < ARRAY_SIZE(modes); i++) { + if (strcmp(cfg->mode, modes[i].name) != 0) + continue; + + nla_put_u32(msg, IFLA_MACVLAN_MODE, modes[i].val); + break; + } + } + + nla_nest_end(msg, data); + nla_nest_end(msg, linkinfo); + + rv = system_rtnl_call(msg); + if (rv) + D(SYSTEM, "Error adding macvlan '%s' over '%s': %d\n", macvlan->ifname, dev->ifname, rv); + + return rv; + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; +} + +static int system_link_del(const char *ifname) +{ + struct nl_msg *msg; + struct ifinfomsg iim = { + .ifi_family = AF_UNSPEC, + .ifi_index = 0, + }; + + msg = nlmsg_alloc_simple(RTM_DELLINK, NLM_F_REQUEST); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + nla_put_string(msg, IFLA_IFNAME, ifname); + return system_rtnl_call(msg); +} + +int system_macvlan_del(struct device *macvlan) +{ + return system_link_del(macvlan->ifname); +} + +static int system_vlan(struct device *dev, int id) +{ + struct vlan_ioctl_args ifr = { + .cmd = SET_VLAN_NAME_TYPE_CMD, + .u.name_type = VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, + }; + + ioctl(sock_ioctl, SIOCSIFVLAN, &ifr); + + if (id < 0) { + ifr.cmd = DEL_VLAN_CMD; + ifr.u.VID = 0; + } else { + ifr.cmd = ADD_VLAN_CMD; + ifr.u.VID = id; + } + strncpy(ifr.device1, dev->ifname, sizeof(ifr.device1)); + return ioctl(sock_ioctl, SIOCSIFVLAN, &ifr); +} + +int system_vlan_add(struct device *dev, int id) +{ + return system_vlan(dev, id); +} + +int system_vlan_del(struct device *dev) +{ + return system_vlan(dev, -1); +} + +int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg) +{ + struct nl_msg *msg; + struct nlattr *linkinfo, *data; + struct ifinfomsg iim = { .ifi_family = AF_UNSPEC }; + int rv; + + msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + nla_put_string(msg, IFLA_IFNAME, vlandev->ifname); + nla_put_u32(msg, IFLA_LINK, dev->ifindex); + + if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + nla_put_string(msg, IFLA_INFO_KIND, "vlan"); + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + nla_put_u16(msg, IFLA_VLAN_ID, cfg->vid); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) + nla_put_u16(msg, IFLA_VLAN_PROTOCOL, htons(cfg->proto)); +#else + if(cfg->proto == VLAN_PROTO_8021AD) + netifd_log_message(L_WARNING, "%s Your kernel is older than linux 3.10.0, 802.1ad is not supported defaulting to 802.1q", vlandev->type->name); +#endif + + nla_nest_end(msg, data); + nla_nest_end(msg, linkinfo); + + rv = system_rtnl_call(msg); + if (rv) + D(SYSTEM, "Error adding vlandev '%s' over '%s': %d\n", vlandev->ifname, dev->ifname, rv); + + return rv; + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; +} + +int system_vlandev_del(struct device *vlandev) +{ + return system_link_del(vlandev->ifname); +} + +void +system_if_get_settings(struct device *dev, struct device_settings *s) +{ + struct ifreq ifr; + char buf[10]; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name)); + + if (ioctl(sock_ioctl, SIOCGIFMTU, &ifr) == 0) { + s->mtu = ifr.ifr_mtu; + s->flags |= DEV_OPT_MTU; + } + + s->mtu6 = system_update_ipv6_mtu(dev, 0); + if (s->mtu6 > 0) + s->flags |= DEV_OPT_MTU6; + + if (ioctl(sock_ioctl, SIOCGIFTXQLEN, &ifr) == 0) { + s->txqueuelen = ifr.ifr_qlen; + s->flags |= DEV_OPT_TXQUEUELEN; + } + + if (ioctl(sock_ioctl, SIOCGIFHWADDR, &ifr) == 0) { + memcpy(s->macaddr, &ifr.ifr_hwaddr.sa_data, sizeof(s->macaddr)); + s->flags |= DEV_OPT_MACADDR; + } + + if (!system_get_disable_ipv6(dev, buf, sizeof(buf))) { + s->ipv6 = !strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_IPV6; + } + + if (ioctl(sock_ioctl, SIOCGIFFLAGS, &ifr) == 0) { + s->promisc = ifr.ifr_flags & IFF_PROMISC; + s->flags |= DEV_OPT_PROMISC; + + s->multicast = ifr.ifr_flags & IFF_MULTICAST; + s->flags |= DEV_OPT_MULTICAST; + } + + if (!system_get_rpfilter(dev, buf, sizeof(buf))) { + s->rpfilter = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_RPFILTER; + } + + if (!system_get_acceptlocal(dev, buf, sizeof(buf))) { + s->acceptlocal = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_ACCEPTLOCAL; + } + + if (!system_get_igmpversion(dev, buf, sizeof(buf))) { + s->igmpversion = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_IGMPVERSION; + } + + if (!system_get_mldversion(dev, buf, sizeof(buf))) { + s->mldversion = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_MLDVERSION; + } + + if (!system_get_neigh4reachabletime(dev, buf, sizeof(buf))) { + s->neigh4reachabletime = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_NEIGHREACHABLETIME; + } + + if (!system_get_neigh6reachabletime(dev, buf, sizeof(buf))) { + s->neigh6reachabletime = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_NEIGHREACHABLETIME; + } + + if (!system_get_neigh4gcstaletime(dev, buf, sizeof(buf))) { + s->neigh4gcstaletime = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_NEIGHGCSTALETIME; + } + + if (!system_get_neigh6gcstaletime(dev, buf, sizeof(buf))) { + s->neigh6gcstaletime = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_NEIGHGCSTALETIME; + } + + if (!system_get_dadtransmits(dev, buf, sizeof(buf))) { + s->dadtransmits = strtoul(buf, NULL, 0); + s->flags |= DEV_OPT_DADTRANSMITS; + } +} + +static void +system_if_set_rps_xps_val(const char *path, int val) +{ + char val_buf[8]; + glob_t gl; + int i; + + if (glob(path, 0, NULL, &gl)) + return; + + snprintf(val_buf, sizeof(val_buf), "%x", val); + for (i = 0; i < gl.gl_pathc; i++) + system_set_sysctl(gl.gl_pathv[i], val_buf); + + globfree(&gl); +} + +static void +system_if_apply_rps_xps(struct device *dev, struct device_settings *s) +{ + long n_cpus = sysconf(_SC_NPROCESSORS_ONLN); + int val; + + if (n_cpus < 2) + return; + + val = (1 << n_cpus) - 1; + snprintf(dev_buf, sizeof(dev_buf), "/sys/class/net/%s/queues/*/rps_cpus", dev->ifname); + system_if_set_rps_xps_val(dev_buf, s->rps ? val : 0); + + snprintf(dev_buf, sizeof(dev_buf), "/sys/class/net/%s/queues/*/xps_cpus", dev->ifname); + system_if_set_rps_xps_val(dev_buf, s->xps ? val : 0); +} + +void +system_if_apply_settings(struct device *dev, struct device_settings *s, unsigned int apply_mask) +{ + struct ifreq ifr; + char buf[12]; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev->ifname, sizeof(ifr.ifr_name)); + if (s->flags & DEV_OPT_MTU & apply_mask) { + ifr.ifr_mtu = s->mtu; + if (ioctl(sock_ioctl, SIOCSIFMTU, &ifr) < 0) + s->flags &= ~DEV_OPT_MTU; + } + if (s->flags & DEV_OPT_MTU6 & apply_mask) { + system_update_ipv6_mtu(dev, s->mtu6); + } + if (s->flags & DEV_OPT_TXQUEUELEN & apply_mask) { + ifr.ifr_qlen = s->txqueuelen; + if (ioctl(sock_ioctl, SIOCSIFTXQLEN, &ifr) < 0) + s->flags &= ~DEV_OPT_TXQUEUELEN; + } + if ((s->flags & DEV_OPT_MACADDR & apply_mask) && !dev->external) { + ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER; + memcpy(&ifr.ifr_hwaddr.sa_data, s->macaddr, sizeof(s->macaddr)); + if (ioctl(sock_ioctl, SIOCSIFHWADDR, &ifr) < 0) + s->flags &= ~DEV_OPT_MACADDR; + } + if (s->flags & DEV_OPT_IPV6 & apply_mask) + system_set_disable_ipv6(dev, s->ipv6 ? "0" : "1"); + if (s->flags & DEV_OPT_PROMISC & apply_mask) { + if (system_if_flags(dev->ifname, s->promisc ? IFF_PROMISC : 0, + !s->promisc ? IFF_PROMISC : 0) < 0) + s->flags &= ~DEV_OPT_PROMISC; + } + if (s->flags & DEV_OPT_RPFILTER & apply_mask) { + snprintf(buf, sizeof(buf), "%d", s->rpfilter); + system_set_rpfilter(dev, buf); + } + if (s->flags & DEV_OPT_ACCEPTLOCAL & apply_mask) + system_set_acceptlocal(dev, s->acceptlocal ? "1" : "0"); + if (s->flags & DEV_OPT_IGMPVERSION & apply_mask) { + snprintf(buf, sizeof(buf), "%d", s->igmpversion); + system_set_igmpversion(dev, buf); + } + if (s->flags & DEV_OPT_MLDVERSION & apply_mask) { + snprintf(buf, sizeof(buf), "%d", s->mldversion); + system_set_mldversion(dev, buf); + } + if (s->flags & DEV_OPT_NEIGHREACHABLETIME & apply_mask) { + snprintf(buf, sizeof(buf), "%d", s->neigh4reachabletime); + system_set_neigh4reachabletime(dev, buf); + snprintf(buf, sizeof(buf), "%d", s->neigh6reachabletime); + system_set_neigh6reachabletime(dev, buf); + } + if (s->flags & DEV_OPT_NEIGHGCSTALETIME & apply_mask) { + snprintf(buf, sizeof(buf), "%d", s->neigh4gcstaletime); + system_set_neigh4gcstaletime(dev, buf); + snprintf(buf, sizeof(buf), "%d", s->neigh6gcstaletime); + system_set_neigh6gcstaletime(dev, buf); + } + if (s->flags & DEV_OPT_DADTRANSMITS & apply_mask) { + snprintf(buf, sizeof(buf), "%d", s->dadtransmits); + system_set_dadtransmits(dev, buf); + } + if (s->flags & DEV_OPT_MULTICAST & apply_mask) { + if (system_if_flags(dev->ifname, s->multicast ? IFF_MULTICAST : 0, + !s->multicast ? IFF_MULTICAST : 0) < 0) + s->flags &= ~DEV_OPT_MULTICAST; + } + + system_if_apply_rps_xps(dev, s); +} + +int system_if_up(struct device *dev) +{ + system_if_get_settings(dev, &dev->orig_settings); + /* Only keep orig settings based on what needs to be set */ + dev->orig_settings.valid_flags = dev->orig_settings.flags; + dev->orig_settings.flags &= dev->settings.flags; + system_if_apply_settings(dev, &dev->settings, dev->settings.flags); + return system_if_flags(dev->ifname, IFF_UP, 0); +} + +int system_if_down(struct device *dev) +{ + int ret = system_if_flags(dev->ifname, 0, IFF_UP); + system_if_apply_settings(dev, &dev->orig_settings, dev->orig_settings.flags); + return ret; +} + +struct if_check_data { + struct device *dev; + int pending; + int ret; +}; + +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 +#endif + +static int cb_if_check_valid(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nh = nlmsg_hdr(msg); + struct ifinfomsg *ifi = NLMSG_DATA(nh); + struct if_check_data *chk = (struct if_check_data *)arg; + + if (nh->nlmsg_type != RTM_NEWLINK) + return NL_SKIP; + + device_set_present(chk->dev, ifi->ifi_index > 0 ? true : false); + device_set_link(chk->dev, ifi->ifi_flags & IFF_LOWER_UP ? true : false); + + return NL_OK; +} + +static int cb_if_check_ack(struct nl_msg *msg, void *arg) +{ + struct if_check_data *chk = (struct if_check_data *)arg; + chk->pending = 0; + return NL_STOP; +} + +static int cb_if_check_error(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) +{ + struct if_check_data *chk = (struct if_check_data *)arg; + + device_set_present(chk->dev, false); + device_set_link(chk->dev, false); + chk->pending = err->error; + + return NL_STOP; +} + +int system_if_check(struct device *dev) +{ + struct nl_cb *cb = nl_cb_alloc(NL_CB_DEFAULT); + struct nl_msg *msg; + struct ifinfomsg ifi = { + .ifi_family = AF_UNSPEC, + .ifi_index = 0, + }; + struct if_check_data chk = { + .dev = dev, + .pending = 1, + }; + int ret = 1; + + msg = nlmsg_alloc_simple(RTM_GETLINK, 0); + if (!msg) + goto out; + + if (nlmsg_append(msg, &ifi, sizeof(ifi), 0) || + nla_put_string(msg, IFLA_IFNAME, dev->ifname)) + goto free; + + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_if_check_valid, &chk); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, cb_if_check_ack, &chk); + nl_cb_err(cb, NL_CB_CUSTOM, cb_if_check_error, &chk); + + nl_send_auto_complete(sock_rtnl, msg); + while (chk.pending > 0) + nl_recvmsgs(sock_rtnl, cb); + + ret = chk.pending; + +free: + nlmsg_free(msg); +out: + nl_cb_put(cb); + return ret; +} + +struct device * +system_if_get_parent(struct device *dev) +{ + char buf[64], *devname; + int ifindex, iflink, len; + FILE *f; + + snprintf(buf, sizeof(buf), "/sys/class/net/%s/iflink", dev->ifname); + f = fopen(buf, "r"); + if (!f) + return NULL; + + len = fread(buf, 1, sizeof(buf) - 1, f); + fclose(f); + + if (len <= 0) + return NULL; + + buf[len] = 0; + iflink = strtoul(buf, NULL, 0); + ifindex = system_if_resolve(dev); + if (!iflink || iflink == ifindex) + return NULL; + + devname = if_indextoname(iflink, buf); + if (!devname) + return NULL; + + return device_get(devname, true); +} + +static bool +read_string_file(int dir_fd, const char *file, char *buf, int len) +{ + bool ret = false; + char *c; + int fd; + + fd = openat(dir_fd, file, O_RDONLY); + if (fd < 0) + return false; + +retry: + len = read(fd, buf, len - 1); + if (len < 0) { + if (errno == EINTR) + goto retry; + } else if (len > 0) { + buf[len] = 0; + + c = strchr(buf, '\n'); + if (c) + *c = 0; + + ret = true; + } + + close(fd); + + return ret; +} + +static bool +read_uint64_file(int dir_fd, const char *file, uint64_t *val) +{ + char buf[64]; + bool ret = false; + + ret = read_string_file(dir_fd, file, buf, sizeof(buf)); + if (ret) + *val = strtoull(buf, NULL, 0); + + return ret; +} + +/* Assume advertised flags == supported flags */ +static const struct { + uint32_t mask; + const char *name; +} ethtool_link_modes[] = { + { ADVERTISED_10baseT_Half, "10H" }, + { ADVERTISED_10baseT_Full, "10F" }, + { ADVERTISED_100baseT_Half, "100H" }, + { ADVERTISED_100baseT_Full, "100F" }, + { ADVERTISED_1000baseT_Half, "1000H" }, + { ADVERTISED_1000baseT_Full, "1000F" }, +}; + +static void system_add_link_modes(struct blob_buf *b, __u32 mask) +{ + int i; + for (i = 0; i < ARRAY_SIZE(ethtool_link_modes); i++) { + if (mask & ethtool_link_modes[i].mask) + blobmsg_add_string(b, NULL, ethtool_link_modes[i].name); + } +} + +bool +system_if_force_external(const char *ifname) +{ + char buf[64]; + struct stat s; + + snprintf(buf, sizeof(buf), "/sys/class/net/%s/phy80211", ifname); + return stat(buf, &s) == 0; +} + +int +system_if_dump_info(struct device *dev, struct blob_buf *b) +{ + struct ethtool_cmd ecmd; + struct ifreq ifr; + char buf[64], *s; + void *c; + int dir_fd; + + snprintf(buf, sizeof(buf), "/sys/class/net/%s", dev->ifname); + dir_fd = open(buf, O_DIRECTORY); + + memset(&ecmd, 0, sizeof(ecmd)); + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, dev->ifname); + ifr.ifr_data = (caddr_t) &ecmd; + ecmd.cmd = ETHTOOL_GSET; + + if (ioctl(sock_ioctl, SIOCETHTOOL, &ifr) == 0) { + c = blobmsg_open_array(b, "link-advertising"); + system_add_link_modes(b, ecmd.advertising); + blobmsg_close_array(b, c); + + c = blobmsg_open_array(b, "link-supported"); + system_add_link_modes(b, ecmd.supported); + blobmsg_close_array(b, c); + + s = blobmsg_alloc_string_buffer(b, "speed", 8); + snprintf(s, 8, "%d%c", ethtool_cmd_speed(&ecmd), + ecmd.duplex == DUPLEX_HALF ? 'H' : 'F'); + blobmsg_add_string_buffer(b); + } + + close(dir_fd); + return 0; +} + +int +system_if_dump_stats(struct device *dev, struct blob_buf *b) +{ + const char *const counters[] = { + "collisions", "rx_frame_errors", "tx_compressed", + "multicast", "rx_length_errors", "tx_dropped", + "rx_bytes", "rx_missed_errors", "tx_errors", + "rx_compressed", "rx_over_errors", "tx_fifo_errors", + "rx_crc_errors", "rx_packets", "tx_heartbeat_errors", + "rx_dropped", "tx_aborted_errors", "tx_packets", + "rx_errors", "tx_bytes", "tx_window_errors", + "rx_fifo_errors", "tx_carrier_errors", + }; + char buf[64]; + int stats_dir; + int i; + uint64_t val = 0; + + snprintf(buf, sizeof(buf), "/sys/class/net/%s/statistics", dev->ifname); + stats_dir = open(buf, O_DIRECTORY); + if (stats_dir < 0) + return -1; + + for (i = 0; i < ARRAY_SIZE(counters); i++) + if (read_uint64_file(stats_dir, counters[i], &val)) + blobmsg_add_u64(b, counters[i], val); + + close(stats_dir); + return 0; +} + +static int system_addr(struct device *dev, struct device_addr *addr, int cmd) +{ + bool v4 = ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET4); + int alen = v4 ? 4 : 16; + unsigned int flags = 0; + struct ifaddrmsg ifa = { + .ifa_family = (alen == 4) ? AF_INET : AF_INET6, + .ifa_prefixlen = addr->mask, + .ifa_index = dev->ifindex, + }; + + struct nl_msg *msg; + if (cmd == RTM_NEWADDR) + flags |= NLM_F_CREATE | NLM_F_REPLACE; + + msg = nlmsg_alloc_simple(cmd, flags); + if (!msg) + return -1; + + nlmsg_append(msg, &ifa, sizeof(ifa), 0); + nla_put(msg, IFA_LOCAL, alen, &addr->addr); + if (v4) { + if (addr->broadcast) + nla_put_u32(msg, IFA_BROADCAST, addr->broadcast); + if (addr->point_to_point) + nla_put_u32(msg, IFA_ADDRESS, addr->point_to_point); + } else { + time_t now = system_get_rtime(); + struct ifa_cacheinfo cinfo = {0xffffffffU, 0xffffffffU, 0, 0}; + + if (addr->preferred_until) { + int64_t preferred = addr->preferred_until - now; + if (preferred < 0) + preferred = 0; + else if (preferred > UINT32_MAX) + preferred = UINT32_MAX; + + cinfo.ifa_prefered = preferred; + } + + if (addr->valid_until) { + int64_t valid = addr->valid_until - now; + if (valid <= 0) { + nlmsg_free(msg); + return -1; + } + else if (valid > UINT32_MAX) + valid = UINT32_MAX; + + cinfo.ifa_valid = valid; + } + + nla_put(msg, IFA_CACHEINFO, sizeof(cinfo), &cinfo); + + if (cmd == RTM_NEWADDR && (addr->flags & DEVADDR_OFFLINK)) + nla_put_u32(msg, IFA_FLAGS, IFA_F_NOPREFIXROUTE); + } + + return system_rtnl_call(msg); +} + +int system_add_address(struct device *dev, struct device_addr *addr) +{ + return system_addr(dev, addr, RTM_NEWADDR); +} + +int system_del_address(struct device *dev, struct device_addr *addr) +{ + return system_addr(dev, addr, RTM_DELADDR); +} + +static int system_rt(struct device *dev, struct device_route *route, int cmd) +{ + int alen = ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16; + bool have_gw; + unsigned int flags = 0; + + if (alen == 4) + have_gw = !!route->nexthop.in.s_addr; + else + have_gw = route->nexthop.in6.s6_addr32[0] || + route->nexthop.in6.s6_addr32[1] || + route->nexthop.in6.s6_addr32[2] || + route->nexthop.in6.s6_addr32[3]; + + unsigned int table = (route->flags & (DEVROUTE_TABLE | DEVROUTE_SRCTABLE)) + ? route->table : RT_TABLE_MAIN; + + struct rtmsg rtm = { + .rtm_family = (alen == 4) ? AF_INET : AF_INET6, + .rtm_dst_len = route->mask, + .rtm_src_len = route->sourcemask, + .rtm_table = (table < 256) ? table : RT_TABLE_UNSPEC, + .rtm_protocol = (route->flags & DEVADDR_KERNEL) ? RTPROT_KERNEL : RTPROT_STATIC, + .rtm_scope = RT_SCOPE_NOWHERE, + .rtm_type = (cmd == RTM_DELROUTE) ? 0: RTN_UNICAST, + .rtm_flags = (route->flags & DEVROUTE_ONLINK) ? RTNH_F_ONLINK : 0, + }; + struct nl_msg *msg; + + if (cmd == RTM_NEWROUTE) { + flags |= NLM_F_CREATE | NLM_F_REPLACE; + + if (!dev) { // Add null-route + rtm.rtm_scope = RT_SCOPE_UNIVERSE; + rtm.rtm_type = RTN_UNREACHABLE; + } + else + rtm.rtm_scope = (have_gw) ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK; + } + + if (route->flags & DEVROUTE_TYPE) { + rtm.rtm_type = route->type; + if (!(route->flags & (DEVROUTE_TABLE | DEVROUTE_SRCTABLE))) { + if (rtm.rtm_type == RTN_LOCAL || rtm.rtm_type == RTN_BROADCAST || + rtm.rtm_type == RTN_NAT || rtm.rtm_type == RTN_ANYCAST) + rtm.rtm_table = RT_TABLE_LOCAL; + } + + if (rtm.rtm_type == RTN_LOCAL || rtm.rtm_type == RTN_NAT) { + rtm.rtm_scope = RT_SCOPE_HOST; + } else if (rtm.rtm_type == RTN_BROADCAST || rtm.rtm_type == RTN_MULTICAST || + rtm.rtm_type == RTN_ANYCAST) { + rtm.rtm_scope = RT_SCOPE_LINK; + } else if (rtm.rtm_type == RTN_BLACKHOLE || rtm.rtm_type == RTN_UNREACHABLE || + rtm.rtm_type == RTN_PROHIBIT || rtm.rtm_type == RTN_FAILED_POLICY) { + rtm.rtm_scope = RT_SCOPE_UNIVERSE; + dev = NULL; + } + } + + msg = nlmsg_alloc_simple(cmd, flags); + if (!msg) + return -1; + + nlmsg_append(msg, &rtm, sizeof(rtm), 0); + + if (route->mask) + nla_put(msg, RTA_DST, alen, &route->addr); + + if (route->sourcemask) { + if (rtm.rtm_family == AF_INET) + nla_put(msg, RTA_PREFSRC, alen, &route->source); + else + nla_put(msg, RTA_SRC, alen, &route->source); + } + + if (route->metric > 0) + nla_put_u32(msg, RTA_PRIORITY, route->metric); + + if (have_gw) + nla_put(msg, RTA_GATEWAY, alen, &route->nexthop); + + if (dev) + nla_put_u32(msg, RTA_OIF, dev->ifindex); + + if (table >= 256) + nla_put_u32(msg, RTA_TABLE, table); + + if (route->flags & DEVROUTE_MTU) { + struct nlattr *metrics; + + if (!(metrics = nla_nest_start(msg, RTA_METRICS))) + goto nla_put_failure; + + nla_put_u32(msg, RTAX_MTU, route->mtu); + + nla_nest_end(msg, metrics); + } + + return system_rtnl_call(msg); + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; +} + +int system_add_route(struct device *dev, struct device_route *route) +{ + return system_rt(dev, route, RTM_NEWROUTE); +} + +int system_del_route(struct device *dev, struct device_route *route) +{ + return system_rt(dev, route, RTM_DELROUTE); +} + +int system_flush_routes(void) +{ + const char *names[] = { + "/proc/sys/net/ipv4/route/flush", + "/proc/sys/net/ipv6/route/flush" + }; + int fd, i; + + for (i = 0; i < ARRAY_SIZE(names); i++) { + fd = open(names[i], O_WRONLY); + if (fd < 0) + continue; + + if (write(fd, "-1", 2)) {} + close(fd); + } + return 0; +} + +bool system_resolve_rt_type(const char *type, unsigned int *id) +{ + return system_rtn_aton(type, id); +} + +bool system_resolve_rt_table(const char *name, unsigned int *id) +{ + FILE *f; + char *e, buf[128]; + unsigned int n, table = RT_TABLE_UNSPEC; + + /* first try to parse table as number */ + if ((n = strtoul(name, &e, 0)) > 0 && !*e) + table = n; + + /* handle well known aliases */ + else if (!strcmp(name, "default")) + table = RT_TABLE_DEFAULT; + else if (!strcmp(name, "main")) + table = RT_TABLE_MAIN; + else if (!strcmp(name, "local")) + table = RT_TABLE_LOCAL; + + /* try to look up name in /etc/iproute2/rt_tables */ + else if ((f = fopen("/etc/iproute2/rt_tables", "r")) != NULL) + { + while (fgets(buf, sizeof(buf) - 1, f) != NULL) + { + if ((e = strtok(buf, " \t\n")) == NULL || *e == '#') + continue; + + n = strtoul(e, NULL, 10); + e = strtok(NULL, " \t\n"); + + if (e && !strcmp(e, name)) + { + table = n; + break; + } + } + + fclose(f); + } + + if (table == RT_TABLE_UNSPEC) + return false; + + *id = table; + return true; +} + +bool system_is_default_rt_table(unsigned int id) +{ + return (id == RT_TABLE_MAIN); +} + +bool system_resolve_rpfilter(const char *filter, unsigned int *id) +{ + char *e; + unsigned int n; + + if (!strcmp(filter, "strict")) + n = 1; + else if (!strcmp(filter, "loose")) + n = 2; + else { + n = strtoul(filter, &e, 0); + if (*e || e == filter || n > 2) + return false; + } + + *id = n; + return true; +} + +static int system_iprule(struct iprule *rule, int cmd) +{ + int alen = ((rule->flags & IPRULE_FAMILY) == IPRULE_INET4) ? 4 : 16; + + struct nl_msg *msg; + struct rtmsg rtm = { + .rtm_family = (alen == 4) ? AF_INET : AF_INET6, + .rtm_protocol = RTPROT_STATIC, + .rtm_scope = RT_SCOPE_UNIVERSE, + .rtm_table = RT_TABLE_UNSPEC, + .rtm_type = RTN_UNSPEC, + .rtm_flags = 0, + }; + + if (cmd == RTM_NEWRULE) + rtm.rtm_type = RTN_UNICAST; + + if (rule->invert) + rtm.rtm_flags |= FIB_RULE_INVERT; + + if (rule->flags & IPRULE_SRC) + rtm.rtm_src_len = rule->src_mask; + + if (rule->flags & IPRULE_DEST) + rtm.rtm_dst_len = rule->dest_mask; + + if (rule->flags & IPRULE_TOS) + rtm.rtm_tos = rule->tos; + + if (rule->flags & IPRULE_LOOKUP) { + if (rule->lookup < 256) + rtm.rtm_table = rule->lookup; + } + + if (rule->flags & IPRULE_ACTION) + rtm.rtm_type = rule->action; + else if (rule->flags & IPRULE_GOTO) + rtm.rtm_type = FR_ACT_GOTO; + else if (!(rule->flags & (IPRULE_LOOKUP | IPRULE_ACTION | IPRULE_GOTO))) + rtm.rtm_type = FR_ACT_NOP; + + msg = nlmsg_alloc_simple(cmd, NLM_F_REQUEST); + + if (!msg) + return -1; + + nlmsg_append(msg, &rtm, sizeof(rtm), 0); + + if (rule->flags & IPRULE_IN) + nla_put(msg, FRA_IFNAME, strlen(rule->in_dev) + 1, rule->in_dev); + + if (rule->flags & IPRULE_OUT) + nla_put(msg, FRA_OIFNAME, strlen(rule->out_dev) + 1, rule->out_dev); + + if (rule->flags & IPRULE_SRC) + nla_put(msg, FRA_SRC, alen, &rule->src_addr); + + if (rule->flags & IPRULE_DEST) + nla_put(msg, FRA_DST, alen, &rule->dest_addr); + + if (rule->flags & IPRULE_PRIORITY) + nla_put_u32(msg, FRA_PRIORITY, rule->priority); + else if (cmd == RTM_NEWRULE) + nla_put_u32(msg, FRA_PRIORITY, rule->order); + + if (rule->flags & IPRULE_FWMARK) + nla_put_u32(msg, FRA_FWMARK, rule->fwmark); + + if (rule->flags & IPRULE_FWMASK) + nla_put_u32(msg, FRA_FWMASK, rule->fwmask); + + if (rule->flags & IPRULE_LOOKUP) { + if (rule->lookup >= 256) + nla_put_u32(msg, FRA_TABLE, rule->lookup); + } + + if (rule->flags & IPRULE_GOTO) + nla_put_u32(msg, FRA_GOTO, rule->gotoid); + + return system_rtnl_call(msg); +} + +int system_add_iprule(struct iprule *rule) +{ + return system_iprule(rule, RTM_NEWRULE); +} + +int system_del_iprule(struct iprule *rule) +{ + return system_iprule(rule, RTM_DELRULE); +} + +int system_flush_iprules(void) +{ + int rv = 0; + struct iprule rule; + + system_if_clear_entries(NULL, RTM_GETRULE, AF_INET); + system_if_clear_entries(NULL, RTM_GETRULE, AF_INET6); + + memset(&rule, 0, sizeof(rule)); + + + rule.flags = IPRULE_INET4 | IPRULE_PRIORITY | IPRULE_LOOKUP; + + rule.priority = 0; + rule.lookup = RT_TABLE_LOCAL; + rv |= system_iprule(&rule, RTM_NEWRULE); + + rule.priority = 32766; + rule.lookup = RT_TABLE_MAIN; + rv |= system_iprule(&rule, RTM_NEWRULE); + + rule.priority = 32767; + rule.lookup = RT_TABLE_DEFAULT; + rv |= system_iprule(&rule, RTM_NEWRULE); + + + rule.flags = IPRULE_INET6 | IPRULE_PRIORITY | IPRULE_LOOKUP; + + rule.priority = 0; + rule.lookup = RT_TABLE_LOCAL; + rv |= system_iprule(&rule, RTM_NEWRULE); + + rule.priority = 32766; + rule.lookup = RT_TABLE_MAIN; + rv |= system_iprule(&rule, RTM_NEWRULE); + + return rv; +} + +bool system_resolve_iprule_action(const char *action, unsigned int *id) +{ + return system_rtn_aton(action, id); +} + +time_t system_get_rtime(void) +{ + struct timespec ts; + struct timeval tv; + + if (syscall(__NR_clock_gettime, CLOCK_MONOTONIC, &ts) == 0) + return ts.tv_sec; + + if (gettimeofday(&tv, NULL) == 0) + return tv.tv_sec; + + return 0; +} + +#ifndef IP_DF +#define IP_DF 0x4000 +#endif + +static int tunnel_ioctl(const char *name, int cmd, void *p) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + ifr.ifr_ifru.ifru_data = p; + return ioctl(sock_ioctl, cmd, &ifr); +} + +#ifdef IFLA_IPTUN_MAX +#define IP6_FLOWINFO_TCLASS htonl(0x0FF00000) +static int system_add_gre_tunnel(const char *name, const char *kind, + const unsigned int link, struct blob_attr **tb, bool v6) +{ + struct nl_msg *nlm; + struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC, }; + struct blob_attr *cur; + uint32_t ikey = 0, okey = 0, flags = 0, flowinfo = 0; + uint16_t iflags = 0, oflags = 0; + uint8_t tos = 0; + int ret = 0, ttl = 64; + + nlm = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE); + if (!nlm) + return -1; + + nlmsg_append(nlm, &ifi, sizeof(ifi), 0); + nla_put_string(nlm, IFLA_IFNAME, name); + + struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO); + if (!linkinfo) { + ret = -ENOMEM; + goto failure; + } + + nla_put_string(nlm, IFLA_INFO_KIND, kind); + struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA); + if (!infodata) { + ret = -ENOMEM; + goto failure; + } + + if (link) + nla_put_u32(nlm, IFLA_GRE_LINK, link); + + if ((cur = tb[TUNNEL_ATTR_TTL])) + ttl = blobmsg_get_u32(cur); + + nla_put_u8(nlm, IFLA_GRE_TTL, ttl); + + if ((cur = tb[TUNNEL_ATTR_TOS])) { + char *str = blobmsg_get_string(cur); + if (strcmp(str, "inherit")) { + unsigned uval; + + if (!system_tos_aton(str, &uval)) { + ret = -EINVAL; + goto failure; + } + + if (v6) + flowinfo |= htonl(uval << 20) & IP6_FLOWINFO_TCLASS; + else + tos = uval; + } else { + if (v6) + flags |= IP6_TNL_F_USE_ORIG_TCLASS; + else + tos = 1; + } + } + + if ((cur = tb[TUNNEL_ATTR_INFO]) && (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)) { + uint8_t icsum, ocsum, iseqno, oseqno; + if (sscanf(blobmsg_get_string(cur), "%u,%u,%hhu,%hhu,%hhu,%hhu", + &ikey, &okey, &icsum, &ocsum, &iseqno, &oseqno) < 6) { + ret = -EINVAL; + goto failure; + } + + if (ikey) + iflags |= GRE_KEY; + + if (okey) + oflags |= GRE_KEY; + + if (icsum) + iflags |= GRE_CSUM; + + if (ocsum) + oflags |= GRE_CSUM; + + if (iseqno) + iflags |= GRE_SEQ; + + if (oseqno) + oflags |= GRE_SEQ; + } + + if (v6) { + struct in6_addr in6buf; + if ((cur = tb[TUNNEL_ATTR_LOCAL])) { + if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_GRE_LOCAL, sizeof(in6buf), &in6buf); + } + + if ((cur = tb[TUNNEL_ATTR_REMOTE])) { + if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_GRE_REMOTE, sizeof(in6buf), &in6buf); + } + nla_put_u8(nlm, IFLA_GRE_ENCAP_LIMIT, 4); + + if (flowinfo) + nla_put_u32(nlm, IFLA_GRE_FLOWINFO, flowinfo); + + if (flags) + nla_put_u32(nlm, IFLA_GRE_FLAGS, flags); + } else { + struct in_addr inbuf; + bool set_df = true; + + if ((cur = tb[TUNNEL_ATTR_LOCAL])) { + if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_GRE_LOCAL, sizeof(inbuf), &inbuf); + } + + if ((cur = tb[TUNNEL_ATTR_REMOTE])) { + if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_GRE_REMOTE, sizeof(inbuf), &inbuf); + + if (IN_MULTICAST(ntohl(inbuf.s_addr))) { + if (!okey) { + okey = inbuf.s_addr; + oflags |= GRE_KEY; + } + + if (!ikey) { + ikey = inbuf.s_addr; + iflags |= GRE_KEY; + } + } + } + + if ((cur = tb[TUNNEL_ATTR_DF])) + set_df = blobmsg_get_bool(cur); + + /* ttl !=0 and nopmtudisc are incompatible */ + if (ttl && !set_df) { + ret = -EINVAL; + goto failure; + } + + nla_put_u8(nlm, IFLA_GRE_PMTUDISC, set_df ? 1 : 0); + + nla_put_u8(nlm, IFLA_GRE_TOS, tos); + } + + if (oflags) + nla_put_u16(nlm, IFLA_GRE_OFLAGS, oflags); + + if (iflags) + nla_put_u16(nlm, IFLA_GRE_IFLAGS, iflags); + + if (okey) + nla_put_u32(nlm, IFLA_GRE_OKEY, okey); + + if (ikey) + nla_put_u32(nlm, IFLA_GRE_IKEY, ikey); + + nla_nest_end(nlm, infodata); + nla_nest_end(nlm, linkinfo); + + return system_rtnl_call(nlm); + +failure: + nlmsg_free(nlm); + return ret; +} +#endif + +#ifdef IFLA_VTI_MAX +static int system_add_vti_tunnel(const char *name, const char *kind, + const unsigned int link, struct blob_attr **tb, bool v6) +{ + struct nl_msg *nlm; + struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC, }; + struct blob_attr *cur; + uint32_t ikey = 0, okey = 0; + int ret = 0; + + nlm = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE); + if (!nlm) + return -1; + + nlmsg_append(nlm, &ifi, sizeof(ifi), 0); + nla_put_string(nlm, IFLA_IFNAME, name); + + struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO); + if (!linkinfo) { + ret = -ENOMEM; + goto failure; + } + + nla_put_string(nlm, IFLA_INFO_KIND, kind); + struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA); + if (!infodata) { + ret = -ENOMEM; + goto failure; + } + + if (link) + nla_put_u32(nlm, IFLA_VTI_LINK, link); + + if ((cur = tb[TUNNEL_ATTR_INFO]) && (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)) { + if (sscanf(blobmsg_get_string(cur), "%u,%u", + &ikey, &okey) < 2) { + ret = -EINVAL; + goto failure; + } + } + + if (v6) { + struct in6_addr in6buf; + if ((cur = tb[TUNNEL_ATTR_LOCAL])) { + if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_VTI_LOCAL, sizeof(in6buf), &in6buf); + } + + if ((cur = tb[TUNNEL_ATTR_REMOTE])) { + if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_VTI_REMOTE, sizeof(in6buf), &in6buf); + } + + } else { + struct in_addr inbuf; + + if ((cur = tb[TUNNEL_ATTR_LOCAL])) { + if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_VTI_LOCAL, sizeof(inbuf), &inbuf); + } + + if ((cur = tb[TUNNEL_ATTR_REMOTE])) { + if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_VTI_REMOTE, sizeof(inbuf), &inbuf); + } + + } + + if (okey) + nla_put_u32(nlm, IFLA_VTI_OKEY, htonl(okey)); + + if (ikey) + nla_put_u32(nlm, IFLA_VTI_IKEY, htonl(ikey)); + + nla_nest_end(nlm, infodata); + nla_nest_end(nlm, linkinfo); + + return system_rtnl_call(nlm); + +failure: + nlmsg_free(nlm); + return ret; +} +#endif + +static int system_add_proto_tunnel(const char *name, const uint8_t proto, const unsigned int link, struct blob_attr **tb) +{ + struct blob_attr *cur; + bool set_df = true; + struct ip_tunnel_parm p = { + .link = link, + .iph = { + .version = 4, + .ihl = 5, + .protocol = proto, + } + }; + + if ((cur = tb[TUNNEL_ATTR_LOCAL]) && + inet_pton(AF_INET, blobmsg_data(cur), &p.iph.saddr) < 1) + return -EINVAL; + + if ((cur = tb[TUNNEL_ATTR_REMOTE]) && + inet_pton(AF_INET, blobmsg_data(cur), &p.iph.daddr) < 1) + return -EINVAL; + + if ((cur = tb[TUNNEL_ATTR_DF])) + set_df = blobmsg_get_bool(cur); + + if ((cur = tb[TUNNEL_ATTR_TTL])) + p.iph.ttl = blobmsg_get_u32(cur); + + if ((cur = tb[TUNNEL_ATTR_TOS])) { + char *str = blobmsg_get_string(cur); + if (strcmp(str, "inherit")) { + unsigned uval; + + if (!system_tos_aton(str, &uval)) + return -EINVAL; + + p.iph.tos = uval; + } else + p.iph.tos = 1; + } + + p.iph.frag_off = set_df ? htons(IP_DF) : 0; + /* ttl !=0 and nopmtudisc are incompatible */ + if (p.iph.ttl && p.iph.frag_off == 0) + return -EINVAL; + + strncpy(p.name, name, sizeof(p.name)); + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return tunnel_ioctl("tunl0", SIOCADDTUNNEL, &p); + case IPPROTO_IPV6: + return tunnel_ioctl("sit0", SIOCADDTUNNEL, &p); + default: + break; + } + return -1; +} + +static int __system_del_ip_tunnel(const char *name, struct blob_attr **tb) +{ + struct blob_attr *cur; + const char *str; + + if (!(cur = tb[TUNNEL_ATTR_TYPE])) + return -EINVAL; + str = blobmsg_data(cur); + + if (!strcmp(str, "greip") || !strcmp(str, "gretapip") || + !strcmp(str, "greip6") || !strcmp(str, "gretapip6") || + !strcmp(str, "vtiip") || !strcmp(str, "vtiip6")) + return system_link_del(name); + else + return tunnel_ioctl(name, SIOCDELTUNNEL, NULL); +} + +int system_del_ip_tunnel(const char *name, struct blob_attr *attr) +{ + struct blob_attr *tb[__TUNNEL_ATTR_MAX]; + + blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb, + blob_data(attr), blob_len(attr)); + + return __system_del_ip_tunnel(name, tb); +} + +int system_update_ipv6_mtu(struct device *dev, int mtu) +{ + int ret = -1; + char buf[64]; + snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/conf/%s/mtu", + dev->ifname); + + int fd = open(buf, O_RDWR); + + if (!mtu) { + ssize_t len = read(fd, buf, sizeof(buf) - 1); + if (len < 0) + goto out; + + buf[len] = 0; + ret = atoi(buf); + } else { + if (write(fd, buf, snprintf(buf, sizeof(buf), "%i", mtu)) > 0) + ret = mtu; + } + +out: + close(fd); + return ret; +} + +int system_add_ip_tunnel(const char *name, struct blob_attr *attr) +{ + struct blob_attr *tb[__TUNNEL_ATTR_MAX]; + struct blob_attr *cur; + const char *str; + + blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb, + blob_data(attr), blob_len(attr)); + + __system_del_ip_tunnel(name, tb); + + if (!(cur = tb[TUNNEL_ATTR_TYPE])) + return -EINVAL; + str = blobmsg_data(cur); + + unsigned int ttl = 0; + if ((cur = tb[TUNNEL_ATTR_TTL])) { + ttl = blobmsg_get_u32(cur); + if (ttl > 255) + return -EINVAL; + } + + unsigned int link = 0; + if ((cur = tb[TUNNEL_ATTR_LINK])) { + struct interface *iface = vlist_find(&interfaces, blobmsg_data(cur), iface, node); + if (!iface) + return -EINVAL; + + if (iface->l3_dev.dev) + link = iface->l3_dev.dev->ifindex; + } + + if (!strcmp(str, "sit")) { + if (system_add_proto_tunnel(name, IPPROTO_IPV6, link, tb) < 0) + return -1; + +#ifdef SIOCADD6RD + if ((cur = tb[TUNNEL_ATTR_6RD_PREFIX])) { + unsigned int mask; + struct ip_tunnel_6rd p6; + + memset(&p6, 0, sizeof(p6)); + + if (!parse_ip_and_netmask(AF_INET6, blobmsg_data(cur), + &p6.prefix, &mask) || mask > 128) + return -EINVAL; + p6.prefixlen = mask; + + if ((cur = tb[TUNNEL_ATTR_6RD_RELAY_PREFIX])) { + if (!parse_ip_and_netmask(AF_INET, blobmsg_data(cur), + &p6.relay_prefix, &mask) || mask > 32) + return -EINVAL; + p6.relay_prefixlen = mask; + } + + if (tunnel_ioctl(name, SIOCADD6RD, &p6) < 0) { + __system_del_ip_tunnel(name, tb); + return -1; + } + } +#endif +#ifdef IFLA_IPTUN_MAX + } else if (!strcmp(str, "ipip6")) { + struct nl_msg *nlm = nlmsg_alloc_simple(RTM_NEWLINK, + NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE); + struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC }; + int ret = 0; + + if (!nlm) + return -1; + + nlmsg_append(nlm, &ifi, sizeof(ifi), 0); + nla_put_string(nlm, IFLA_IFNAME, name); + + if (link) + nla_put_u32(nlm, IFLA_LINK, link); + + struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO); + if (!linkinfo) { + ret = -ENOMEM; + goto failure; + } + nla_put_string(nlm, IFLA_INFO_KIND, "ip6tnl"); + struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA); + if (!infodata) { + ret = -ENOMEM; + goto failure; + } + + if (link) + nla_put_u32(nlm, IFLA_IPTUN_LINK, link); + + nla_put_u8(nlm, IFLA_IPTUN_PROTO, IPPROTO_IPIP); + nla_put_u8(nlm, IFLA_IPTUN_TTL, (ttl) ? ttl : 64); + nla_put_u8(nlm, IFLA_IPTUN_ENCAP_LIMIT, 4); + + struct in6_addr in6buf; + if ((cur = tb[TUNNEL_ATTR_LOCAL])) { + if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_IPTUN_LOCAL, sizeof(in6buf), &in6buf); + } + + if ((cur = tb[TUNNEL_ATTR_REMOTE])) { + if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) { + ret = -EINVAL; + goto failure; + } + nla_put(nlm, IFLA_IPTUN_REMOTE, sizeof(in6buf), &in6buf); + } + +#ifdef IFLA_IPTUN_FMR_MAX + if ((cur = tb[TUNNEL_ATTR_FMRS])) { + struct nlattr *fmrs = nla_nest_start(nlm, IFLA_IPTUN_FMRS); + + struct blob_attr *fmr; + unsigned rem, fmrcnt = 0; + blobmsg_for_each_attr(fmr, cur, rem) { + if (blobmsg_type(fmr) != BLOBMSG_TYPE_STRING) + continue; + + unsigned ip4len, ip6len, ealen, offset = 6; + char ip6buf[48]; + char ip4buf[16]; + + if (sscanf(blobmsg_get_string(fmr), "%47[^/]/%u,%15[^/]/%u,%u,%u", + ip6buf, &ip6len, ip4buf, &ip4len, &ealen, &offset) < 5) { + ret = -EINVAL; + goto failure; + } + + struct in6_addr ip6prefix; + struct in_addr ip4prefix; + if (inet_pton(AF_INET6, ip6buf, &ip6prefix) != 1 || + inet_pton(AF_INET, ip4buf, &ip4prefix) != 1) { + ret = -EINVAL; + goto failure; + } + + struct nlattr *rule = nla_nest_start(nlm, ++fmrcnt); + + nla_put(nlm, IFLA_IPTUN_FMR_IP6_PREFIX, sizeof(ip6prefix), &ip6prefix); + nla_put(nlm, IFLA_IPTUN_FMR_IP4_PREFIX, sizeof(ip4prefix), &ip4prefix); + nla_put_u8(nlm, IFLA_IPTUN_FMR_IP6_PREFIX_LEN, ip6len); + nla_put_u8(nlm, IFLA_IPTUN_FMR_IP4_PREFIX_LEN, ip4len); + nla_put_u8(nlm, IFLA_IPTUN_FMR_EA_LEN, ealen); + nla_put_u8(nlm, IFLA_IPTUN_FMR_OFFSET, offset); + + nla_nest_end(nlm, rule); + } + + nla_nest_end(nlm, fmrs); + } +#endif + + nla_nest_end(nlm, infodata); + nla_nest_end(nlm, linkinfo); + + return system_rtnl_call(nlm); +failure: + nlmsg_free(nlm); + return ret; + } else if (!strcmp(str, "greip")) { + return system_add_gre_tunnel(name, "gre", link, tb, false); + } else if (!strcmp(str, "gretapip")) { + return system_add_gre_tunnel(name, "gretap", link, tb, false); + } else if (!strcmp(str, "greip6")) { + return system_add_gre_tunnel(name, "ip6gre", link, tb, true); + } else if (!strcmp(str, "gretapip6")) { + return system_add_gre_tunnel(name, "ip6gretap", link, tb, true); +#ifdef IFLA_VTI_MAX + } else if (!strcmp(str, "vtiip")) { + return system_add_vti_tunnel(name, "vti", link, tb, false); + } else if (!strcmp(str, "vtiip6")) { + return system_add_vti_tunnel(name, "vti6", link, tb, true); +#endif +#endif + } else if (!strcmp(str, "ipip")) { + return system_add_proto_tunnel(name, IPPROTO_IPIP, link, tb); + } + else + return -EINVAL; + + return 0; +} diff --git a/src/3P/netifd/system.c b/src/3P/netifd/system.c new file mode 100644 index 00000000..e57084f7 --- /dev/null +++ b/src/3P/netifd/system.c @@ -0,0 +1,43 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "netifd.h" +#include "system.h" +#include + +static const struct blobmsg_policy tunnel_attrs[__TUNNEL_ATTR_MAX] = { + [TUNNEL_ATTR_TYPE] = { .name = "mode", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_LOCAL] = { .name = "local", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_REMOTE] = { .name = "remote", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_MTU] = { .name = "mtu", .type = BLOBMSG_TYPE_INT32 }, + [TUNNEL_ATTR_DF] = { .name = "df", .type = BLOBMSG_TYPE_BOOL }, + [TUNNEL_ATTR_TTL] = { .name = "ttl", .type = BLOBMSG_TYPE_INT32 }, + [TUNNEL_ATTR_TOS] = { .name = "tos", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_6RD_PREFIX] = {.name = "6rd-prefix", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_6RD_RELAY_PREFIX] = { .name = "6rd-relay-prefix", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_LINK] = { .name = "link", .type = BLOBMSG_TYPE_STRING }, + [TUNNEL_ATTR_FMRS] = { .name = "fmrs", .type = BLOBMSG_TYPE_ARRAY }, + [TUNNEL_ATTR_INFO] = { .name = "info", .type = BLOBMSG_TYPE_STRING }, +}; + +const struct uci_blob_param_list tunnel_attr_list = { + .n_params = __TUNNEL_ATTR_MAX, + .params = tunnel_attrs, +}; + +void system_fd_set_cloexec(int fd) +{ +#ifdef FD_CLOEXEC + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); +#endif +} diff --git a/src/3P/netifd/system.h b/src/3P/netifd/system.h new file mode 100644 index 00000000..d5cb4e37 --- /dev/null +++ b/src/3P/netifd/system.h @@ -0,0 +1,168 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_SYSTEM_H +#define __NETIFD_SYSTEM_H + +#include +#include +#include +#include "device.h" +#include "interface-ip.h" +#include "iprule.h" + +enum tunnel_param { + TUNNEL_ATTR_TYPE, + TUNNEL_ATTR_REMOTE, + TUNNEL_ATTR_LOCAL, + TUNNEL_ATTR_MTU, + TUNNEL_ATTR_DF, + TUNNEL_ATTR_TTL, + TUNNEL_ATTR_TOS, + TUNNEL_ATTR_6RD_PREFIX, + TUNNEL_ATTR_6RD_RELAY_PREFIX, + TUNNEL_ATTR_LINK, + TUNNEL_ATTR_FMRS, + TUNNEL_ATTR_INFO, + __TUNNEL_ATTR_MAX +}; + +extern const struct uci_blob_param_list tunnel_attr_list; + +enum bridge_opt { + /* stp and forward delay always set */ + BRIDGE_OPT_AGEING_TIME = (1 << 0), + BRIDGE_OPT_HELLO_TIME = (1 << 1), + BRIDGE_OPT_MAX_AGE = (1 << 2), + BRIDGE_OPT_ROBUSTNESS = (1 << 3), + BRIDGE_OPT_QUERY_INTERVAL = (1 << 4), + BRIDGE_OPT_QUERY_RESPONSE_INTERVAL = (1 << 5), + BRIDGE_OPT_LAST_MEMBER_INTERVAL = (1 << 6), +}; + +struct bridge_config { + enum bridge_opt flags; + bool stp; + + bool igmp_snoop; + bool multicast_querier; + int robustness; + int query_interval; + int query_response_interval; + int last_member_interval; + + unsigned short priority; + int forward_delay; + bool bridge_empty; + + int ageing_time; + int hello_time; + int max_age; + int hash_max; +}; + +enum macvlan_opt { + MACVLAN_OPT_MACADDR = (1 << 0), +}; + +struct macvlan_config { + const char *mode; + + enum macvlan_opt flags; + unsigned char macaddr[6]; +}; + +enum vlan_proto { + VLAN_PROTO_8021Q = 0x8100, + VLAN_PROTO_8021AD = 0x88A8 +}; + +struct vlandev_config { + enum vlan_proto proto; + uint16_t vid; +}; + +static inline int system_get_addr_family(unsigned int flags) +{ + if ((flags & DEVADDR_FAMILY) == DEVADDR_INET6) + return AF_INET6; + else + return AF_INET; +} + +static inline int system_get_addr_len(unsigned int flags) +{ + if ((flags & DEVADDR_FAMILY) != DEVADDR_INET6) + return sizeof(struct in_addr); + else + return sizeof(struct in6_addr); +} + +int system_init(void); + +int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg); +int system_bridge_delbr(struct device *bridge); +int system_bridge_addif(struct device *bridge, struct device *dev); +int system_bridge_delif(struct device *bridge, struct device *dev); + +int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg); +int system_macvlan_del(struct device *macvlan); + +int system_vlan_add(struct device *dev, int id); +int system_vlan_del(struct device *dev); + +int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg); +int system_vlandev_del(struct device *vlandev); + +void system_if_get_settings(struct device *dev, struct device_settings *s); +void system_if_clear_state(struct device *dev); +int system_if_up(struct device *dev); +int system_if_down(struct device *dev); +int system_if_check(struct device *dev); +int system_if_resolve(struct device *dev); + +int system_if_dump_info(struct device *dev, struct blob_buf *b); +int system_if_dump_stats(struct device *dev, struct blob_buf *b); +struct device *system_if_get_parent(struct device *dev); +bool system_if_force_external(const char *ifname); +void system_if_apply_settings(struct device *dev, struct device_settings *s, + unsigned int apply_mask); + +int system_add_address(struct device *dev, struct device_addr *addr); +int system_del_address(struct device *dev, struct device_addr *addr); + +int system_add_route(struct device *dev, struct device_route *route); +int system_del_route(struct device *dev, struct device_route *route); +int system_flush_routes(void); + +bool system_resolve_rt_type(const char *type, unsigned int *id); +bool system_resolve_rt_table(const char *name, unsigned int *id); +bool system_is_default_rt_table(unsigned int id); +bool system_resolve_rpfilter(const char *filter, unsigned int *id); + +int system_del_ip_tunnel(const char *name, struct blob_attr *attr); +int system_add_ip_tunnel(const char *name, struct blob_attr *attr); + +int system_add_iprule(struct iprule *rule); +int system_del_iprule(struct iprule *rule); +int system_flush_iprules(void); + +bool system_resolve_iprule_action(const char *action, unsigned int *id); + +time_t system_get_rtime(void); + +void system_fd_set_cloexec(int fd); + +int system_update_ipv6_mtu(struct device *device, int mtu); + +#endif diff --git a/src/3P/netifd/tunnel.c b/src/3P/netifd/tunnel.c new file mode 100644 index 00000000..4a6c4093 --- /dev/null +++ b/src/3P/netifd/tunnel.c @@ -0,0 +1,99 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include "netifd.h" +#include "device.h" +#include "config.h" +#include "system.h" + +struct tunnel { + struct device dev; + device_state_cb set_state; +}; + +static int +tunnel_set_state(struct device *dev, bool up) +{ + struct tunnel *tun = container_of(dev, struct tunnel, dev); + int ret; + + if (up) { + ret = system_add_ip_tunnel(dev->ifname, dev->config); + if (ret != 0) + return ret; + } + + ret = tun->set_state(dev, up); + if (ret || !up) + system_del_ip_tunnel(dev->ifname, dev->config); + + return ret; +} + +static enum dev_change_type +tunnel_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + const struct uci_blob_param_list *cfg = dev->type->config_params; + + if (uci_blob_check_equal(dev->config, attr, cfg)) + return DEV_CONFIG_NO_CHANGE; + + memset(tb_dev, 0, sizeof(tb_dev)); + + if (attr) + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + + device_init_settings(dev, tb_dev); + + return DEV_CONFIG_RESTART; +} + +static struct device * +tunnel_create(const char *name, struct blob_attr *attr) +{ + struct tunnel *tun; + struct device *dev; + + tun = calloc(1, sizeof(*tun)); + if (!tun) + return NULL; + + dev = &tun->dev; + device_init(dev, &tunnel_device_type, name); + tun->set_state = dev->set_state; + dev->set_state = tunnel_set_state; + device_apply_config(dev, &tunnel_device_type, attr); + device_set_present(dev, true); + + return dev; +} + +static void +tunnel_free(struct device *dev) +{ + struct tunnel *tun = container_of(dev, struct tunnel, dev); + + free(tun); +} + +const struct device_type tunnel_device_type = { + .name = "IP tunnel", + .config_params = &tunnel_attr_list, + .reload = tunnel_reload, + .create = tunnel_create, + .free = tunnel_free, +}; + + diff --git a/src/3P/netifd/ubus.c b/src/3P/netifd/ubus.c new file mode 100644 index 00000000..74cdf28d --- /dev/null +++ b/src/3P/netifd/ubus.c @@ -0,0 +1,1216 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#define _GNU_SOURCE + +#include +#include +#include + +#include "netifd.h" +#include "interface.h" +#include "proto.h" +#include "ubus.h" +#include "system.h" +#include "wireless.h" + +struct ubus_context *ubus_ctx = NULL; +static struct blob_buf b; +static const char *ubus_path; + +/* global object */ + +static int +netifd_handle_restart(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + netifd_restart(); + return 0; +} + +static int +netifd_handle_reload(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + netifd_reload(); + return 0; +} + +enum { + HR_TARGET, + HR_V6, + HR_INTERFACE, + __HR_MAX +}; + +static const struct blobmsg_policy route_policy[__HR_MAX] = { + [HR_TARGET] = { .name = "target", .type = BLOBMSG_TYPE_STRING }, + [HR_V6] = { .name = "v6", .type = BLOBMSG_TYPE_BOOL }, + [HR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, +}; + +static int +netifd_add_host_route(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__HR_MAX]; + struct interface *iface = NULL; + union if_addr a; + bool v6 = false; + + blobmsg_parse(route_policy, __HR_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[HR_TARGET]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (tb[HR_V6]) + v6 = blobmsg_get_bool(tb[HR_V6]); + + if (tb[HR_INTERFACE]) + iface = vlist_find(&interfaces, blobmsg_data(tb[HR_INTERFACE]), iface, node); + + memset(&a, 0, sizeof(a)); + if (!inet_pton(v6 ? AF_INET6 : AF_INET, blobmsg_data(tb[HR_TARGET]), &a)) + return UBUS_STATUS_INVALID_ARGUMENT; + + + iface = interface_ip_add_target_route(&a, v6, iface); + if (!iface) + return UBUS_STATUS_NOT_FOUND; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "interface", iface->name); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +static int +netifd_get_proto_handlers(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + blob_buf_init(&b, 0); + proto_dump_handlers(&b); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + + +enum { + DI_NAME, + __DI_MAX +}; + +static const struct blobmsg_policy dynamic_policy[__DI_MAX] = { + [DI_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, +}; + +static int +netifd_add_dynamic(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__DI_MAX]; + struct interface *iface; + struct blob_attr *config; + struct device *dev; + + blobmsg_parse(dynamic_policy, __DI_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[DI_NAME]) + return UBUS_STATUS_INVALID_ARGUMENT; + + const char *name = blobmsg_get_string(tb[DI_NAME]); + + iface = interface_alloc(name, msg); + if (!iface) + return UBUS_STATUS_UNKNOWN_ERROR; + + config = blob_memdup(msg); + if (!config) + goto error; + + interface_add(iface, config); + + // need to look up the interface name again, in case of config update + // the pointer will have changed + iface = vlist_find(&interfaces, name, iface, node); + if (!iface) + return UBUS_STATUS_UNKNOWN_ERROR; + + // Set interface as dynamic + interface_set_dynamic(iface); + + dev = iface->main_dev.dev; + if (!dev || !dev->default_config) + return UBUS_STATUS_UNKNOWN_ERROR; + + return UBUS_STATUS_OK; + +error: + free(iface); + return UBUS_STATUS_UNKNOWN_ERROR; +} + +static struct ubus_method main_object_methods[] = { + { .name = "restart", .handler = netifd_handle_restart }, + { .name = "reload", .handler = netifd_handle_reload }, + UBUS_METHOD("add_host_route", netifd_add_host_route, route_policy), + { .name = "get_proto_handlers", .handler = netifd_get_proto_handlers }, + UBUS_METHOD("add_dynamic", netifd_add_dynamic, dynamic_policy), +}; + +static struct ubus_object_type main_object_type = + UBUS_OBJECT_TYPE("netifd", main_object_methods); + +static struct ubus_object main_object = { + .name = "network", + .type = &main_object_type, + .methods = main_object_methods, + .n_methods = ARRAY_SIZE(main_object_methods), +}; + +enum { + DEV_NAME, + __DEV_MAX, +}; + +static const struct blobmsg_policy dev_policy[__DEV_MAX] = { + [DEV_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, +}; + +static int +netifd_dev_status(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct device *dev = NULL; + struct blob_attr *tb[__DEV_MAX]; + + blobmsg_parse(dev_policy, __DEV_MAX, tb, blob_data(msg), blob_len(msg)); + + if (tb[DEV_NAME]) { + dev = device_get(blobmsg_data(tb[DEV_NAME]), false); + if (!dev) + return UBUS_STATUS_INVALID_ARGUMENT; + } + + blob_buf_init(&b, 0); + device_dump_status(&b, dev); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +enum { + ALIAS_ATTR_ALIAS, + ALIAS_ATTR_DEV, + __ALIAS_ATTR_MAX, +}; + +static const struct blobmsg_policy alias_attrs[__ALIAS_ATTR_MAX] = { + [ALIAS_ATTR_ALIAS] = { "alias", BLOBMSG_TYPE_ARRAY }, + [ALIAS_ATTR_DEV] = { "device", BLOBMSG_TYPE_STRING }, +}; + +static int +netifd_handle_alias(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct device *dev = NULL; + struct blob_attr *tb[__ALIAS_ATTR_MAX]; + struct blob_attr *cur; + int rem; + + blobmsg_parse(alias_attrs, __ALIAS_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[ALIAS_ATTR_ALIAS]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if ((cur = tb[ALIAS_ATTR_DEV]) != NULL) { + dev = device_get(blobmsg_data(cur), true); + if (!dev) + return UBUS_STATUS_NOT_FOUND; + } + + blobmsg_for_each_attr(cur, tb[ALIAS_ATTR_ALIAS], rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + goto error; + + if (!blobmsg_check_attr(cur, NULL)) + goto error; + + alias_notify_device(blobmsg_data(cur), dev); + } + return 0; + +error: + device_free_unused(dev); + return UBUS_STATUS_INVALID_ARGUMENT; +} + +enum { + DEV_STATE_NAME, + DEV_STATE_DEFER, + __DEV_STATE_MAX, +}; + +static const struct blobmsg_policy dev_state_policy[__DEV_STATE_MAX] = { + [DEV_STATE_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, + [DEV_STATE_DEFER] = { .name = "defer", .type = BLOBMSG_TYPE_BOOL }, +}; + +static int +netifd_handle_set_state(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct device *dev = NULL; + struct blob_attr *tb[__DEV_STATE_MAX]; + struct blob_attr *cur; + + blobmsg_parse(dev_state_policy, __DEV_STATE_MAX, tb, blob_data(msg), blob_len(msg)); + + cur = tb[DEV_STATE_NAME]; + if (!cur) + return UBUS_STATUS_INVALID_ARGUMENT; + + dev = device_find(blobmsg_data(cur)); + if (!dev) + return UBUS_STATUS_NOT_FOUND; + + cur = tb[DEV_STATE_DEFER]; + if (cur) + device_set_deferred(dev, !!blobmsg_get_u8(cur)); + + return 0; +} + +static struct ubus_method dev_object_methods[] = { + UBUS_METHOD("status", netifd_dev_status, dev_policy), + UBUS_METHOD("set_alias", netifd_handle_alias, alias_attrs), + UBUS_METHOD("set_state", netifd_handle_set_state, dev_state_policy), +}; + +static struct ubus_object_type dev_object_type = + UBUS_OBJECT_TYPE("device", dev_object_methods); + +static struct ubus_object dev_object = { + .name = "network.device", + .type = &dev_object_type, + .methods = dev_object_methods, + .n_methods = ARRAY_SIZE(dev_object_methods), +}; + +static void +netifd_ubus_add_fd(void) +{ + ubus_add_uloop(ubus_ctx); + system_fd_set_cloexec(ubus_ctx->sock.fd); +} + +static void +netifd_ubus_reconnect_timer(struct uloop_timeout *timeout) +{ + static struct uloop_timeout retry = { + .cb = netifd_ubus_reconnect_timer, + }; + int t = 2; + + if (ubus_reconnect(ubus_ctx, ubus_path) != 0) { + DPRINTF("failed to reconnect, trying again in %d seconds\n", t); + uloop_timeout_set(&retry, t * 1000); + return; + } + + DPRINTF("reconnected to ubus, new id: %08x\n", ubus_ctx->local_id); + netifd_ubus_add_fd(); +} + +static void +netifd_ubus_connection_lost(struct ubus_context *ctx) +{ + netifd_ubus_reconnect_timer(NULL); +} + +/* per-interface object */ + +static int +netifd_handle_up(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + interface_set_up(iface); + + return 0; +} + +static int +netifd_handle_down(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + interface_set_down(iface); + + return 0; +} + +static void +netifd_add_interface_errors(struct blob_buf *b, struct interface *iface) +{ + struct interface_error *error; + void *e, *e2, *e3; + int i; + + e = blobmsg_open_array(b, "errors"); + list_for_each_entry(error, &iface->errors, list) { + e2 = blobmsg_open_table(b, NULL); + + blobmsg_add_string(b, "subsystem", error->subsystem); + blobmsg_add_string(b, "code", error->code); + if (error->data[0]) { + e3 = blobmsg_open_array(b, "data"); + for (i = 0; error->data[i]; i++) + blobmsg_add_string(b, NULL, error->data[i]); + blobmsg_close_array(b, e3); + } + + blobmsg_close_table(b, e2); + } + blobmsg_close_array(b, e); +} + +static void +interface_ip_dump_address_list(struct interface_ip_settings *ip, bool v6, + bool enabled) +{ + struct device_addr *addr; + char *buf; + void *a; + int buflen = 128; + int af; + + time_t now = system_get_rtime(); + vlist_for_each_element(&ip->addr, addr, node) { + if (addr->enabled != enabled) + continue; + + if ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET4) + af = AF_INET; + else + af = AF_INET6; + + if (af != (v6 ? AF_INET6 : AF_INET)) + continue; + + a = blobmsg_open_table(&b, NULL); + + buf = blobmsg_alloc_string_buffer(&b, "address", buflen); + inet_ntop(af, &addr->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", addr->mask); + + if (addr->preferred_until) { + int preferred = addr->preferred_until - now; + if (preferred < 0) + preferred = 0; + blobmsg_add_u32(&b, "preferred", preferred); + } + + if (addr->valid_until) + blobmsg_add_u32(&b, "valid", addr->valid_until - now); + + if (addr->pclass) + blobmsg_add_string(&b, "class", addr->pclass); + + blobmsg_close_table(&b, a); + } +} + +static void +interface_ip_dump_route_list(struct interface_ip_settings *ip, bool enabled) +{ + struct device_route *route; + int buflen = 128; + char *buf; + void *r; + int af; + + time_t now = system_get_rtime(); + vlist_for_each_element(&ip->route, route, node) { + if (route->enabled != enabled) + continue; + + if ((ip->no_defaultroute == enabled) && !route->mask) + continue; + + if ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET4) + af = AF_INET; + else + af = AF_INET6; + + r = blobmsg_open_table(&b, NULL); + + buf = blobmsg_alloc_string_buffer(&b, "target", buflen); + inet_ntop(af, &route->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", route->mask); + + buf = blobmsg_alloc_string_buffer(&b, "nexthop", buflen); + inet_ntop(af, &route->nexthop, buf, buflen); + blobmsg_add_string_buffer(&b); + + if (route->flags & DEVROUTE_TYPE) + blobmsg_add_u32(&b, "type", route->type); + + if (route->flags & DEVROUTE_MTU) + blobmsg_add_u32(&b, "mtu", route->mtu); + + if (route->flags & DEVROUTE_METRIC) + blobmsg_add_u32(&b, "metric", route->metric); + + if (route->flags & DEVROUTE_TABLE) + blobmsg_add_u32(&b, "table", route->table); + + if (route->valid_until) + blobmsg_add_u32(&b, "valid", route->valid_until - now); + + buf = blobmsg_alloc_string_buffer(&b, "source", buflen); + inet_ntop(af, &route->source, buf, buflen); + snprintf(buf + strlen(buf), buflen - strlen(buf), "/%u", route->sourcemask); + blobmsg_add_string_buffer(&b); + + blobmsg_close_table(&b, r); + } +} + + +static void +interface_ip_dump_prefix_list(struct interface_ip_settings *ip) +{ + struct device_prefix *prefix; + char *buf; + void *a, *c; + const int buflen = INET6_ADDRSTRLEN; + + time_t now = system_get_rtime(); + vlist_for_each_element(&ip->prefix, prefix, node) { + a = blobmsg_open_table(&b, NULL); + + buf = blobmsg_alloc_string_buffer(&b, "address", buflen); + inet_ntop(AF_INET6, &prefix->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", prefix->length); + + if (prefix->preferred_until) { + int preferred = prefix->preferred_until - now; + if (preferred < 0) + preferred = 0; + blobmsg_add_u32(&b, "preferred", preferred); + } + + if (prefix->valid_until) + blobmsg_add_u32(&b, "valid", prefix->valid_until - now); + + blobmsg_add_string(&b, "class", prefix->pclass); + + c = blobmsg_open_table(&b, "assigned"); + struct device_prefix_assignment *assign; + list_for_each_entry(assign, &prefix->assignments, head) { + if (!assign->name[0]) + continue; + + struct in6_addr addr = prefix->addr; + addr.s6_addr32[1] |= htonl(assign->assigned); + + void *d = blobmsg_open_table(&b, assign->name); + + buf = blobmsg_alloc_string_buffer(&b, "address", buflen); + inet_ntop(AF_INET6, &addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", assign->length); + + blobmsg_close_table(&b, d); + } + blobmsg_close_table(&b, c); + + blobmsg_close_table(&b, a); + } +} + + +static void +interface_ip_dump_prefix_assignment_list(struct interface *iface) +{ + void *a; + char *buf; + const int buflen = INET6_ADDRSTRLEN; + time_t now = system_get_rtime(); + + struct device_prefix *prefix; + list_for_each_entry(prefix, &prefixes, head) { + struct device_prefix_assignment *assign; + list_for_each_entry(assign, &prefix->assignments, head) { + if (strcmp(assign->name, iface->name)) + continue; + + struct in6_addr addr = prefix->addr; + addr.s6_addr32[1] |= htonl(assign->assigned); + + a = blobmsg_open_table(&b, NULL); + + buf = blobmsg_alloc_string_buffer(&b, "address", buflen); + inet_ntop(AF_INET6, &addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", assign->length); + + if (prefix->preferred_until) { + int preferred = prefix->preferred_until - now; + if (preferred < 0) + preferred = 0; + blobmsg_add_u32(&b, "preferred", preferred); + } + + if (prefix->valid_until) + blobmsg_add_u32(&b, "valid", prefix->valid_until - now); + + blobmsg_close_table(&b, a); + } + } +} + + +static void +interface_ip_dump_dns_server_list(struct interface_ip_settings *ip, + bool enabled) +{ + struct dns_server *dns; + int buflen = 128; + char *buf; + + vlist_simple_for_each_element(&ip->dns_servers, dns, node) { + if (ip->no_dns == enabled) + continue; + + buf = blobmsg_alloc_string_buffer(&b, NULL, buflen); + inet_ntop(dns->af, &dns->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + } +} + +static void +interface_ip_dump_dns_search_list(struct interface_ip_settings *ip, + bool enabled) +{ + struct dns_search_domain *dns; + + vlist_simple_for_each_element(&ip->dns_search, dns, node) { + if (ip->no_dns == enabled) + continue; + + blobmsg_add_string(&b, NULL, dns->name); + } +} + +static void +netifd_dump_status(struct interface *iface) +{ + struct interface_data *data; + struct device *dev; + void *a, *inactive; + + blobmsg_add_u8(&b, "up", iface->state == IFS_UP); + blobmsg_add_u8(&b, "pending", iface->state == IFS_SETUP); + blobmsg_add_u8(&b, "available", iface->available); + blobmsg_add_u8(&b, "autostart", iface->autostart); + blobmsg_add_u8(&b, "dynamic", iface->dynamic); + + if (iface->state == IFS_UP) { + time_t cur = system_get_rtime(); + blobmsg_add_u32(&b, "uptime", cur - iface->start_time); + if (iface->l3_dev.dev) + blobmsg_add_string(&b, "l3_device", iface->l3_dev.dev->ifname); + } + + if (iface->proto_handler) + blobmsg_add_string(&b, "proto", iface->proto_handler->name); + + dev = iface->main_dev.dev; + if (dev && !dev->hidden && iface->proto_handler && + !(iface->proto_handler->flags & PROTO_FLAG_NODEV)) + blobmsg_add_string(&b, "device", dev->ifname); + + if (iface->state == IFS_UP) { + if (iface->updated) { + a = blobmsg_open_array(&b, "updated"); + + if (iface->updated & IUF_ADDRESS) + blobmsg_add_string(&b, NULL, "addresses"); + if (iface->updated & IUF_ROUTE) + blobmsg_add_string(&b, NULL, "routes"); + if (iface->updated & IUF_PREFIX) + blobmsg_add_string(&b, NULL, "prefixes"); + if (iface->updated & IUF_DATA) + blobmsg_add_string(&b, NULL, "data"); + + blobmsg_close_array(&b, a); + } + + if (iface->ip4table) + blobmsg_add_u32(&b, "ip4table", iface->ip4table); + if (iface->ip6table) + blobmsg_add_u32(&b, "ip6table", iface->ip6table); + blobmsg_add_u32(&b, "metric", iface->metric); + blobmsg_add_u8(&b, "delegation", !iface->proto_ip.no_delegation); + a = blobmsg_open_array(&b, "ipv4-address"); + interface_ip_dump_address_list(&iface->config_ip, false, true); + interface_ip_dump_address_list(&iface->proto_ip, false, true); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "ipv6-address"); + interface_ip_dump_address_list(&iface->config_ip, true, true); + interface_ip_dump_address_list(&iface->proto_ip, true, true); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "ipv6-prefix"); + interface_ip_dump_prefix_list(&iface->config_ip); + interface_ip_dump_prefix_list(&iface->proto_ip); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "ipv6-prefix-assignment"); + interface_ip_dump_prefix_assignment_list(iface); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "route"); + interface_ip_dump_route_list(&iface->config_ip, true); + interface_ip_dump_route_list(&iface->proto_ip, true); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "dns-server"); + interface_ip_dump_dns_server_list(&iface->config_ip, true); + interface_ip_dump_dns_server_list(&iface->proto_ip, true); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "dns-search"); + interface_ip_dump_dns_search_list(&iface->config_ip, true); + interface_ip_dump_dns_search_list(&iface->proto_ip, true); + blobmsg_close_array(&b, a); + + inactive = blobmsg_open_table(&b, "inactive"); + a = blobmsg_open_array(&b, "ipv4-address"); + interface_ip_dump_address_list(&iface->config_ip, false, false); + interface_ip_dump_address_list(&iface->proto_ip, false, false); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "ipv6-address"); + interface_ip_dump_address_list(&iface->config_ip, true, false); + interface_ip_dump_address_list(&iface->proto_ip, true, false); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "route"); + interface_ip_dump_route_list(&iface->config_ip, false); + interface_ip_dump_route_list(&iface->proto_ip, false); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "dns-server"); + interface_ip_dump_dns_server_list(&iface->config_ip, false); + interface_ip_dump_dns_server_list(&iface->proto_ip, false); + blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "dns-search"); + interface_ip_dump_dns_search_list(&iface->config_ip, false); + interface_ip_dump_dns_search_list(&iface->proto_ip, false); + blobmsg_close_array(&b, a); + blobmsg_close_table(&b, inactive); + } + + a = blobmsg_open_table(&b, "data"); + avl_for_each_element(&iface->data, data, node) + blobmsg_add_blob(&b, data->data); + + blobmsg_close_table(&b, a); + + if (!list_empty(&iface->errors)) + netifd_add_interface_errors(&b, iface); +} + +static int +netifd_handle_status(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface = container_of(obj, struct interface, ubus); + + blob_buf_init(&b, 0); + netifd_dump_status(iface); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + + +static int +netifd_handle_dump(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + blob_buf_init(&b, 0); + void *a = blobmsg_open_array(&b, "interface"); + + struct interface *iface; + vlist_for_each_element(&interfaces, iface, node) { + void *i = blobmsg_open_table(&b, NULL); + blobmsg_add_string(&b, "interface", iface->name); + netifd_dump_status(iface); + blobmsg_close_table(&b, i); + } + + blobmsg_close_array(&b, a); + ubus_send_reply(ctx, req, b.head); + + return 0; +} + +enum { + DEV_LINK_NAME, + DEV_LINK_EXT, + __DEV_LINK_MAX, +}; + +static const struct blobmsg_policy dev_link_policy[__DEV_LINK_MAX] = { + [DEV_LINK_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, + [DEV_LINK_EXT] = { .name = "link-ext", .type = BLOBMSG_TYPE_BOOL }, +}; + +static int +netifd_iface_handle_device(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct blob_attr *tb[__DEV_LINK_MAX]; + struct blob_attr *cur; + struct interface *iface; + bool add = !strncmp(method, "add", 3); + bool link_ext = true; + + iface = container_of(obj, struct interface, ubus); + + blobmsg_parse(dev_link_policy, __DEV_LINK_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[DEV_LINK_NAME]) + return UBUS_STATUS_INVALID_ARGUMENT; + + cur = tb[DEV_LINK_EXT]; + if (cur) + link_ext = blobmsg_get_bool(cur); + + return interface_handle_link(iface, blobmsg_data(tb[DEV_LINK_NAME]), add, link_ext); +} + + +static int +netifd_iface_notify_proto(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + + if (!iface->proto || !iface->proto->notify) + return UBUS_STATUS_NOT_SUPPORTED; + + return iface->proto->notify(iface->proto, msg); +} + +static void +netifd_iface_do_remove(struct uloop_timeout *timeout) +{ + struct interface *iface; + + iface = container_of(timeout, struct interface, remove_timer); + vlist_delete(&interfaces, &iface->node); +} + +static int +netifd_iface_remove(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + if (iface->remove_timer.cb) + return UBUS_STATUS_INVALID_ARGUMENT; + + iface->remove_timer.cb = netifd_iface_do_remove; + uloop_timeout_set(&iface->remove_timer, 100); + return 0; +} + +static int +netifd_handle_iface_prepare(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + struct device *dev; + const struct device_hotplug_ops *ops; + + iface = container_of(obj, struct interface, ubus); + dev = iface->main_dev.dev; + if (!dev) + return 0; + + ops = dev->hotplug_ops; + if (!ops) + return 0; + + return ops->prepare(dev); +} + +static int +netifd_handle_set_data(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + + return interface_parse_data(iface, msg); +} + +static struct ubus_method iface_object_methods[] = { + { .name = "up", .handler = netifd_handle_up }, + { .name = "down", .handler = netifd_handle_down }, + { .name = "status", .handler = netifd_handle_status }, + { .name = "prepare", .handler = netifd_handle_iface_prepare }, + { .name = "dump", .handler = netifd_handle_dump }, + UBUS_METHOD("add_device", netifd_iface_handle_device, dev_link_policy ), + UBUS_METHOD("remove_device", netifd_iface_handle_device, dev_link_policy ), + { .name = "notify_proto", .handler = netifd_iface_notify_proto }, + { .name = "remove", .handler = netifd_iface_remove }, + { .name = "set_data", .handler = netifd_handle_set_data }, +}; + +static struct ubus_object_type iface_object_type = + UBUS_OBJECT_TYPE("netifd_iface", iface_object_methods); + + +static struct ubus_object iface_object = { + .name = "network.interface", + .type = &iface_object_type, + .n_methods = ARRAY_SIZE(iface_object_methods), +}; + +static void netifd_add_object(struct ubus_object *obj) +{ + int ret = ubus_add_object(ubus_ctx, obj); + + if (ret != 0) + fprintf(stderr, "Failed to publish object '%s': %s\n", obj->name, ubus_strerror(ret)); +} + +static const struct blobmsg_policy iface_policy = { + .name = "interface", + .type = BLOBMSG_TYPE_STRING, +}; + +static int +netifd_handle_iface(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + struct blob_attr *tb; + int i; + + blobmsg_parse(&iface_policy, 1, &tb, blob_data(msg), blob_len(msg)); + if (!tb) + return UBUS_STATUS_INVALID_ARGUMENT; + + iface = vlist_find(&interfaces, blobmsg_data(tb), iface, node); + if (!iface) + return UBUS_STATUS_NOT_FOUND; + + for (i = 0; i < ARRAY_SIZE(iface_object_methods); i++) { + ubus_handler_t cb; + + if (strcmp(method, iface_object_methods[i].name) != 0) + continue; + + cb = iface_object_methods[i].handler; + return cb(ctx, &iface->ubus, req, method, msg); + } + + return UBUS_STATUS_INVALID_ARGUMENT; +} + +static void netifd_add_iface_object(void) +{ + struct ubus_method *methods; + int i; + + methods = calloc(1, sizeof(iface_object_methods)); + if (!methods) + return; + + memcpy(methods, iface_object_methods, sizeof(iface_object_methods)); + iface_object.methods = methods; + + for (i = 0; i < ARRAY_SIZE(iface_object_methods); i++) { + if (methods[i].handler == netifd_handle_dump) + continue; + + methods[i].handler = netifd_handle_iface; + methods[i].policy = &iface_policy; + methods[i].n_policy = 1; + } + netifd_add_object(&iface_object); +} + +static struct wireless_device * +get_wdev(struct blob_attr *msg, int *ret) +{ + struct blobmsg_policy wdev_policy = { + .name = "device", + .type = BLOBMSG_TYPE_STRING, + }; + struct blob_attr *dev_attr; + struct wireless_device *wdev = NULL; + + + blobmsg_parse(&wdev_policy, 1, &dev_attr, blob_data(msg), blob_len(msg)); + if (!dev_attr) { + *ret = UBUS_STATUS_INVALID_ARGUMENT; + return NULL; + } + + wdev = vlist_find(&wireless_devices, blobmsg_data(dev_attr), wdev, node); + if (!wdev) { + *ret = UBUS_STATUS_NOT_FOUND; + return NULL; + } + + *ret = 0; + return wdev; +} + +static int +netifd_handle_wdev_up(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct wireless_device *wdev; + int ret; + + wdev = get_wdev(msg, &ret); + if (ret == UBUS_STATUS_NOT_FOUND) + return ret; + + if (wdev) { + wireless_device_set_up(wdev); + } else { + vlist_for_each_element(&wireless_devices, wdev, node) + wireless_device_set_up(wdev); + } + + return 0; +} + +static int +netifd_handle_wdev_down(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct wireless_device *wdev; + int ret; + + wdev = get_wdev(msg, &ret); + if (ret == UBUS_STATUS_NOT_FOUND) + return ret; + + if (wdev) { + wireless_device_set_down(wdev); + } else { + vlist_for_each_element(&wireless_devices, wdev, node) + wireless_device_set_down(wdev); + } + + return 0; +} + +static int +netifd_handle_wdev_status(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct wireless_device *wdev; + int ret; + + wdev = get_wdev(msg, &ret); + if (ret == UBUS_STATUS_NOT_FOUND) + return ret; + + blob_buf_init(&b, 0); + if (wdev) { + wireless_device_status(wdev, &b); + } else { + vlist_for_each_element(&wireless_devices, wdev, node) + wireless_device_status(wdev, &b); + } + ubus_send_reply(ctx, req, b.head); + return 0; +} + +static int +netifd_handle_wdev_get_validate(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct wireless_device *wdev; + int ret; + + wdev = get_wdev(msg, &ret); + if (ret == UBUS_STATUS_NOT_FOUND) + return ret; + + blob_buf_init(&b, 0); + if (wdev) { + wireless_device_get_validate(wdev, &b); + } else { + vlist_for_each_element(&wireless_devices, wdev, node) + wireless_device_get_validate(wdev, &b); + } + ubus_send_reply(ctx, req, b.head); + return 0; +} + +static int +netifd_handle_wdev_notify(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct wireless_device *wdev; + int ret; + + wdev = get_wdev(msg, &ret); + if (!wdev) + return ret; + + return wireless_device_notify(wdev, msg, req); +} + +static struct ubus_method wireless_object_methods[] = { + { .name = "up", .handler = netifd_handle_wdev_up }, + { .name = "down", .handler = netifd_handle_wdev_down }, + { .name = "status", .handler = netifd_handle_wdev_status }, + { .name = "notify", .handler = netifd_handle_wdev_notify }, + { .name = "get_validate", .handler = netifd_handle_wdev_get_validate }, +}; + +static struct ubus_object_type wireless_object_type = + UBUS_OBJECT_TYPE("netifd_iface", wireless_object_methods); + + +static struct ubus_object wireless_object = { + .name = "network.wireless", + .type = &wireless_object_type, + .methods = wireless_object_methods, + .n_methods = ARRAY_SIZE(wireless_object_methods), +}; + +int +netifd_ubus_init(const char *path) +{ + uloop_init(); + ubus_path = path; + + ubus_ctx = ubus_connect(path); + if (!ubus_ctx) + return -EIO; + + DPRINTF("connected as %08x\n", ubus_ctx->local_id); + ubus_ctx->connection_lost = netifd_ubus_connection_lost; + netifd_ubus_add_fd(); + + netifd_add_object(&main_object); + netifd_add_object(&dev_object); + netifd_add_object(&wireless_object); + netifd_add_iface_object(); + + return 0; +} + +void +netifd_ubus_done(void) +{ + ubus_free(ubus_ctx); +} + +void +netifd_ubus_interface_event(struct interface *iface, bool up) +{ + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "action", up ? "ifup" : "ifdown"); + blobmsg_add_string(&b, "interface", iface->name); + ubus_send_event(ubus_ctx, "network.interface", b.head); +} + +void +netifd_ubus_interface_notify(struct interface *iface, bool up) +{ + const char *event = (up) ? "interface.update" : "interface.down"; + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "interface", iface->name); + netifd_dump_status(iface); + ubus_notify(ubus_ctx, &iface_object, event, b.head, -1); + ubus_notify(ubus_ctx, &iface->ubus, event, b.head, -1); +} + +void +netifd_ubus_add_interface(struct interface *iface) +{ + struct ubus_object *obj = &iface->ubus; + char *name = NULL; + + if (asprintf(&name, "%s.interface.%s", main_object.name, iface->name) == -1) + return; + + obj->name = name; + obj->type = &iface_object_type; + obj->methods = iface_object_methods; + obj->n_methods = ARRAY_SIZE(iface_object_methods); + if (ubus_add_object(ubus_ctx, &iface->ubus)) { + DPRINTF("failed to publish ubus object for interface '%s'\n", iface->name); + free(name); + obj->name = NULL; + } +} + +void +netifd_ubus_remove_interface(struct interface *iface) +{ + if (!iface->ubus.name) + return; + + ubus_remove_object(ubus_ctx, &iface->ubus); + free((void *) iface->ubus.name); +} diff --git a/src/3P/netifd/ubus.h b/src/3P/netifd/ubus.h new file mode 100644 index 00000000..54196225 --- /dev/null +++ b/src/3P/netifd/ubus.h @@ -0,0 +1,26 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_UBUS_H +#define __NETIFD_UBUS_H + +extern struct ubus_context *ubus_ctx; + +int netifd_ubus_init(const char *path); +void netifd_ubus_done(void); +void netifd_ubus_add_interface(struct interface *iface); +void netifd_ubus_remove_interface(struct interface *iface); +void netifd_ubus_interface_event(struct interface *iface, bool up); +void netifd_ubus_interface_notify(struct interface *iface, bool up); + +#endif diff --git a/src/3P/netifd/utils.c b/src/3P/netifd/utils.c new file mode 100644 index 00000000..e01b633c --- /dev/null +++ b/src/3P/netifd/utils.c @@ -0,0 +1,234 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include "utils.h" + +#include +#include +#include + +#ifdef __APPLE__ +#include +#endif + +void +__vlist_simple_init(struct vlist_simple_tree *tree, int offset) +{ + INIT_LIST_HEAD(&tree->list); + tree->version = 1; + tree->head_offset = offset; +} + +void +vlist_simple_delete(struct vlist_simple_tree *tree, struct vlist_simple_node *node) +{ + char *ptr; + + list_del(&node->list); + ptr = (char *) node - tree->head_offset; + free(ptr); +} + +void +vlist_simple_flush(struct vlist_simple_tree *tree) +{ + struct vlist_simple_node *n, *tmp; + + list_for_each_entry_safe(n, tmp, &tree->list, list) { + if ((n->version == tree->version || n->version == -1) && + tree->version != -1) + continue; + + vlist_simple_delete(tree, n); + } +} + +void +vlist_simple_replace(struct vlist_simple_tree *dest, struct vlist_simple_tree *old) +{ + struct vlist_simple_node *n, *tmp; + + vlist_simple_update(dest); + list_for_each_entry_safe(n, tmp, &old->list, list) { + list_del(&n->list); + vlist_simple_add(dest, n); + } + vlist_simple_flush(dest); +} + +void +vlist_simple_flush_all(struct vlist_simple_tree *tree) +{ + tree->version = -1; + vlist_simple_flush(tree); +} + +unsigned int +parse_netmask_string(const char *str, bool v6) +{ + struct in_addr addr; + unsigned int ret; + char *err = NULL; + + if (!strchr(str, '.')) { + ret = strtoul(str, &err, 0); + if (err && *err) + goto error; + + return ret; + } + + if (v6) + goto error; + + if (inet_aton(str, &addr) != 1) + goto error; + + return 32 - fls(~(ntohl(addr.s_addr))); + +error: + return ~0; +} + +bool +split_netmask(char *str, unsigned int *netmask, bool v6) +{ + char *delim = strchr(str, '/'); + + if (delim) { + *(delim++) = 0; + + *netmask = parse_netmask_string(delim, v6); + } + return true; +} + +int +parse_ip_and_netmask(int af, const char *str, void *addr, unsigned int *netmask) +{ + char *astr = alloca(strlen(str) + 1); + int ret = 0; + + strcpy(astr, str); + if (!split_netmask(astr, netmask, af == AF_INET6)) + return 0; + + if (af == AF_INET6) { + if (*netmask > 128) + return 0; + } else { + if (*netmask > 32) + return 0; + } + + ret = inet_pton(af, astr, addr); + if (ret > 0) { + if (af == AF_INET) { + struct in_addr *ip4_addr = (struct in_addr *)addr; + uint32_t host_addr = ntohl(ip4_addr->s_addr); + + if (IN_EXPERIMENTAL(host_addr)) { + return 0; + } + } + else if (af == AF_INET6) { + if (IN6_IS_ADDR_MULTICAST((struct in6_addr *)addr)) { + return 0; + } + } + } + return ret; +} + +char * +format_macaddr(uint8_t *mac) +{ + static char str[sizeof("ff:ff:ff:ff:ff:ff ")]; + + snprintf(str, sizeof(str), "%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + return str; +} + +uint32_t +crc32_file(FILE *fp) +{ + static uint32_t *crcvals = NULL; + if (!crcvals) { + crcvals = malloc(sizeof(*crcvals) * 256); + + for (size_t i = 0; i < 256; ++i) { + uint32_t c = i; + for (size_t j = 0; j < 8; ++j) + c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1); + crcvals[i] = c; + } + } + + uint8_t buf[1024]; + size_t len; + uint32_t c = 0xFFFFFFFF; + + do { + len = fread(buf, 1, sizeof(buf), fp); + for (size_t i = 0; i < len; ++i) + c = crcvals[(c ^ buf[i]) & 0xFF] ^ (c >> 8); + } while (len == sizeof(buf)); + + return c ^ 0xFFFFFFFF; +} + +bool check_pid_path(int pid, const char *exe) +{ + int proc_exe_len; + int exe_len = strlen(exe); + +#ifdef __APPLE__ + char proc_exe_buf[PROC_PIDPATHINFO_SIZE]; + + proc_exe_len = proc_pidpath(pid, proc_exe_buf, sizeof(proc_exe_buf)); +#else + char proc_exe[32]; + char *proc_exe_buf = alloca(exe_len); + + sprintf(proc_exe, "/proc/%d/exe", pid); + proc_exe_len = readlink(proc_exe, proc_exe_buf, exe_len); +#endif + + if (proc_exe_len != exe_len) + return false; + + return !memcmp(exe, proc_exe_buf, exe_len); +} + +static const char * const uci_validate_name[__BLOBMSG_TYPE_LAST] = { + [BLOBMSG_TYPE_STRING] = "string", + [BLOBMSG_TYPE_ARRAY] = "list(string)", + [BLOBMSG_TYPE_INT32] = "uinteger", + [BLOBMSG_TYPE_BOOL] = "bool", +}; + +const char* +uci_get_validate_string(const struct uci_blob_param_list *p, int i) +{ + if (p->validate[i]) + return p->validate[i]; + + else if (uci_validate_name[p->params[i].type]) + return uci_validate_name[p->params[i].type]; + + return p->validate[BLOBMSG_TYPE_STRING]; +} diff --git a/src/3P/netifd/utils.h b/src/3P/netifd/utils.h new file mode 100644 index 00000000..4e14bcf5 --- /dev/null +++ b/src/3P/netifd/utils.h @@ -0,0 +1,122 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_UTILS_H +#define __NETIFD_UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline bool blobmsg_get_bool_default(struct blob_attr *attr, bool val) +{ + if (!attr) + return val; + + return blobmsg_get_bool(attr); +} + +#define __init __attribute__((constructor)) + +struct vlist_simple_tree { + struct list_head list; + int head_offset; + int version; +}; + +struct vlist_simple_node { + struct list_head list; + int version; +}; + +#define vlist_for_each_element_safe(tree, element, node_member, ptr) \ + avl_for_each_element_safe(&(tree)->avl, element, node_member.avl, ptr) + +#define vlist_simple_init(tree, node, member) \ + __vlist_simple_init(tree, offsetof(node, member)) + +void __vlist_simple_init(struct vlist_simple_tree *tree, int offset); +void vlist_simple_delete(struct vlist_simple_tree *tree, struct vlist_simple_node *node); +void vlist_simple_flush(struct vlist_simple_tree *tree); +void vlist_simple_flush_all(struct vlist_simple_tree *tree); +void vlist_simple_replace(struct vlist_simple_tree *dest, struct vlist_simple_tree *old); + +static inline void vlist_simple_update(struct vlist_simple_tree *tree) +{ + tree->version++; +} + +static inline void vlist_simple_add(struct vlist_simple_tree *tree, struct vlist_simple_node *node) +{ + node->version = tree->version; + list_add_tail(&node->list, &tree->list); +} + +#define vlist_simple_for_each_element(tree, element, node_member) \ + list_for_each_entry(element, &(tree)->list, node_member.list) + +#define vlist_simple_empty(tree) \ + list_empty(&(tree)->list) + + +#ifdef __linux__ +static inline int fls(int x) +{ + int r = 32; + + if (!x) + return 0; + if (!(x & 0xffff0000u)) { + x <<= 16; + r -= 16; + } + if (!(x & 0xff000000u)) { + x <<= 8; + r -= 8; + } + if (!(x & 0xf0000000u)) { + x <<= 4; + r -= 4; + } + if (!(x & 0xc0000000u)) { + x <<= 2; + r -= 2; + } + if (!(x & 0x80000000u)) + r -= 1; + return r; +} +#endif + +unsigned int parse_netmask_string(const char *str, bool v6); +bool split_netmask(char *str, unsigned int *netmask, bool v6); +int parse_ip_and_netmask(int af, const char *str, void *addr, unsigned int *netmask); +bool check_pid_path(int pid, const char *exe); + +char * format_macaddr(uint8_t *mac); + +uint32_t crc32_file(FILE *fp); + +const char * uci_get_validate_string(const struct uci_blob_param_list *c, int i); + +#ifdef __APPLE__ +#define s6_addr32 __u6_addr.__u6_addr32 +#endif + +#endif diff --git a/src/3P/netifd/vlan.c b/src/3P/netifd/vlan.c new file mode 100644 index 00000000..7f8697b6 --- /dev/null +++ b/src/3P/netifd/vlan.c @@ -0,0 +1,192 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2012 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include + +#include "netifd.h" +#include "system.h" + +struct vlan_device { + struct device dev; + struct device_user dep; + + device_state_cb set_state; + int id; +}; + +static void free_vlan_if(struct device *iface) +{ + struct vlan_device *vldev; + + vldev = container_of(iface, struct vlan_device, dev); + device_remove_user(&vldev->dep); + device_cleanup(&vldev->dev); + free(vldev); +} + +static int vlan_set_device_state(struct device *dev, bool up) +{ + struct vlan_device *vldev; + int ret = 0; + + vldev = container_of(dev, struct vlan_device, dev); + if (!up) { + vldev->set_state(dev, false); + system_vlan_del(dev); + device_release(&vldev->dep); + return 0; + } + + ret = device_claim(&vldev->dep); + if (ret < 0) + return ret; + + system_vlan_add(vldev->dep.dev, vldev->id); + ret = vldev->set_state(dev, true); + if (ret) + device_release(&vldev->dep); + + return ret; +} + +static void vlan_dev_set_name(struct vlan_device *vldev, struct device *dev) +{ + char name[IFNAMSIZ]; + + vldev->dev.hidden = dev->hidden; + snprintf(name, IFNAMSIZ, "%s.%d", dev->ifname, vldev->id); + device_set_ifname(&vldev->dev, name); +} + +static void vlan_dev_cb(struct device_user *dep, enum device_event ev) +{ + struct vlan_device *vldev; + bool new_state = false; + + vldev = container_of(dep, struct vlan_device, dep); + switch(ev) { + case DEV_EVENT_ADD: + new_state = true; + case DEV_EVENT_REMOVE: + device_set_present(&vldev->dev, new_state); + break; + case DEV_EVENT_UPDATE_IFNAME: + vlan_dev_set_name(vldev, dep->dev); + break; + case DEV_EVENT_TOPO_CHANGE: + /* Propagate topo changes */ + device_broadcast_event(&vldev->dev, DEV_EVENT_TOPO_CHANGE); + break; + default: + break; + } +} + +static struct device *get_vlan_device(struct device *dev, int id, bool create) +{ + static const struct device_type vlan_type = { + .name = "VLAN", + .config_params = &device_attr_list, + .free = free_vlan_if, + }; + struct vlan_device *vldev; + struct device_user *dep; + + /* look for an existing interface before creating a new one */ + list_for_each_entry(dep, &dev->users.list, list.list) { + if (dep->cb != vlan_dev_cb) + continue; + + vldev = container_of(dep, struct vlan_device, dep); + if (vldev->id != id) + continue; + + return &vldev->dev; + } + + if (!create) + return NULL; + + D(DEVICE, "Create vlan device '%s.%d'\n", dev->ifname, id); + + vldev = calloc(1, sizeof(*vldev)); + if (!vldev) + return NULL; + + vldev->id = id; + + device_init(&vldev->dev, &vlan_type, NULL); + + vlan_dev_set_name(vldev, dev); + vldev->dev.default_config = true; + + vldev->set_state = vldev->dev.set_state; + vldev->dev.set_state = vlan_set_device_state; + + vldev->dep.cb = vlan_dev_cb; + device_add_user(&vldev->dep, dev); + + return &vldev->dev; +} + +static char *split_vlan(char *s) +{ + s = strchr(s, '.'); + if (!s) + goto out; + + *s = 0; + s++; + +out: + return s; +} + +struct device *get_vlan_device_chain(const char *ifname, bool create) +{ + struct device *dev = NULL; + char *buf, *s, *next, *err = NULL; + int id; + + buf = strdup(ifname); + if (!buf) + return NULL; + + s = split_vlan(buf); + dev = device_get(buf, create); + if (!dev) + goto error; + + do { + next = split_vlan(s); + id = strtoul(s, &err, 10); + if (err && *err) + goto error; + + dev = get_vlan_device(dev, id, create); + if (!dev) + goto error; + + s = next; + if (!s) + goto out; + } while (1); + +error: + dev = NULL; +out: + free(buf); + return dev; +} diff --git a/src/3P/netifd/vlandev.c b/src/3P/netifd/vlandev.c new file mode 100644 index 00000000..b93527c5 --- /dev/null +++ b/src/3P/netifd/vlandev.c @@ -0,0 +1,251 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2014 Gioacchino Mazzurco + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "system.h" + +enum { + VLANDEV_ATTR_TYPE, + VLANDEV_ATTR_IFNAME, + VLANDEV_ATTR_VID, + __VLANDEV_ATTR_MAX +}; + +static const struct blobmsg_policy vlandev_attrs[__VLANDEV_ATTR_MAX] = { + [VLANDEV_ATTR_TYPE] = { "type", BLOBMSG_TYPE_STRING }, + [VLANDEV_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_STRING }, + [VLANDEV_ATTR_VID] = { "vid", BLOBMSG_TYPE_INT32 }, +}; + +static const struct uci_blob_param_list vlandev_attr_list = { + .n_params = __VLANDEV_ATTR_MAX, + .params = vlandev_attrs, + + .n_next = 1, + .next = { &device_attr_list }, +}; + +struct vlandev_device { + struct device dev; + struct device_user parent; + + device_state_cb set_state; + + struct blob_attr *config_data; + struct blob_attr *ifname; + struct vlandev_config config; +}; + +static void +vlandev_base_cb(struct device_user *dev, enum device_event ev) +{ + struct vlandev_device *mvdev = container_of(dev, struct vlandev_device, parent); + + switch (ev) { + case DEV_EVENT_ADD: + device_set_present(&mvdev->dev, true); + break; + case DEV_EVENT_REMOVE: + device_set_present(&mvdev->dev, false); + break; + default: + return; + } +} + +static int +vlandev_set_down(struct vlandev_device *mvdev) +{ + mvdev->set_state(&mvdev->dev, false); + system_vlandev_del(&mvdev->dev); + device_release(&mvdev->parent); + + return 0; +} + +static int +vlandev_set_up(struct vlandev_device *mvdev) +{ + int ret; + + ret = device_claim(&mvdev->parent); + if (ret < 0) + return ret; + + ret = system_vlandev_add(&mvdev->dev, mvdev->parent.dev, &mvdev->config); + if (ret < 0) + goto release; + + ret = mvdev->set_state(&mvdev->dev, true); + if (ret) + goto delete; + + return 0; + +delete: + system_vlandev_del(&mvdev->dev); +release: + device_release(&mvdev->parent); + return ret; +} + +static int +vlandev_set_state(struct device *dev, bool up) +{ + struct vlandev_device *mvdev; + + D(SYSTEM, "vlandev_set_state(%s, %u)\n", dev->ifname, up); + + mvdev = container_of(dev, struct vlandev_device, dev); + if (up) + return vlandev_set_up(mvdev); + else + return vlandev_set_down(mvdev); +} + +static void +vlandev_free(struct device *dev) +{ + struct vlandev_device *mvdev; + + mvdev = container_of(dev, struct vlandev_device, dev); + device_remove_user(&mvdev->parent); + free(mvdev->config_data); + free(mvdev); +} + +static void +vlandev_dump_info(struct device *dev, struct blob_buf *b) +{ + struct vlandev_device *mvdev; + + mvdev = container_of(dev, struct vlandev_device, dev); + blobmsg_add_string(b, "parent", mvdev->parent.dev->ifname); + system_if_dump_info(dev, b); +} + +static void +vlandev_config_init(struct device *dev) +{ + struct vlandev_device *mvdev; + struct device *basedev = NULL; + + mvdev = container_of(dev, struct vlandev_device, dev); + if (mvdev->ifname) + basedev = device_get(blobmsg_data(mvdev->ifname), true); + + device_add_user(&mvdev->parent, basedev); +} + +static void +vlandev_apply_settings(struct vlandev_device *mvdev, struct blob_attr **tb) +{ + struct vlandev_config *cfg = &mvdev->config; + struct blob_attr *cur; + + cfg->proto = VLAN_PROTO_8021Q; + cfg->vid = 1; + + if ((cur = tb[VLANDEV_ATTR_TYPE])) + { + if(!strcmp(blobmsg_data(cur), "8021ad")) + cfg->proto = VLAN_PROTO_8021AD; + } + + if ((cur = tb[VLANDEV_ATTR_VID])) + cfg->vid = (uint16_t) blobmsg_get_u32(cur); +} + +static enum dev_change_type +vlandev_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + struct blob_attr *tb_mv[__VLANDEV_ATTR_MAX]; + enum dev_change_type ret = DEV_CONFIG_APPLIED; + struct vlandev_device *mvdev; + + mvdev = container_of(dev, struct vlandev_device, dev); + attr = blob_memdup(attr); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + blobmsg_parse(vlandev_attrs, __VLANDEV_ATTR_MAX, tb_mv, + blob_data(attr), blob_len(attr)); + + device_init_settings(dev, tb_dev); + vlandev_apply_settings(mvdev, tb_mv); + mvdev->ifname = tb_mv[VLANDEV_ATTR_IFNAME]; + + if (mvdev->config_data) { + struct blob_attr *otb_dev[__DEV_ATTR_MAX]; + struct blob_attr *otb_mv[__VLANDEV_ATTR_MAX]; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_dev, otb_dev, &device_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + blobmsg_parse(vlandev_attrs, __VLANDEV_ATTR_MAX, otb_mv, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_mv, otb_mv, &vlandev_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + vlandev_config_init(dev); + } + + free(mvdev->config_data); + mvdev->config_data = attr; + return ret; +} + +static struct device * +vlandev_create(const char *name, struct blob_attr *attr) +{ + struct vlandev_device *mvdev; + struct device *dev = NULL; + + mvdev = calloc(1, sizeof(*mvdev)); + if (!mvdev) + return NULL; + + dev = &mvdev->dev; + device_init(dev, &vlandev_device_type, name); + dev->config_pending = true; + + mvdev->set_state = dev->set_state; + dev->set_state = vlandev_set_state; + + dev->hotplug_ops = NULL; + mvdev->parent.cb = vlandev_base_cb; + + vlandev_reload(dev, attr); + + return dev; +} + +const struct device_type vlandev_device_type = { + .name = "VLANDEV", + .config_params = &vlandev_attr_list, + .create = vlandev_create, + .config_init = vlandev_config_init, + .reload = vlandev_reload, + .free = vlandev_free, + .dump_info = vlandev_dump_info, +}; diff --git a/src/3P/netifd/wireless.c b/src/3P/netifd/wireless.c new file mode 100644 index 00000000..34dd328e --- /dev/null +++ b/src/3P/netifd/wireless.c @@ -0,0 +1,996 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2013 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include "netifd.h" +#include "wireless.h" +#include "handler.h" +#include "ubus.h" + +#define WIRELESS_SETUP_RETRY 3 + +struct vlist_tree wireless_devices; +struct avl_tree wireless_drivers; +static struct blob_buf b; +static int drv_fd; + +static const struct blobmsg_policy wdev_policy = + { .name = "disabled", .type = BLOBMSG_TYPE_BOOL }; + +static const struct uci_blob_param_list wdev_param = { + .n_params = 1, + .params = &wdev_policy, +}; + +enum { + VIF_ATTR_DISABLED, + VIF_ATTR_NETWORK, + VIF_ATTR_ISOLATE, + VIF_ATTR_MODE, + __VIF_ATTR_MAX, +}; + +static const struct blobmsg_policy vif_policy[__VIF_ATTR_MAX] = { + [VIF_ATTR_DISABLED] = { .name = "disabled", .type = BLOBMSG_TYPE_BOOL }, + [VIF_ATTR_NETWORK] = { .name = "network", .type = BLOBMSG_TYPE_ARRAY }, + [VIF_ATTR_ISOLATE] = { .name = "isolate", .type = BLOBMSG_TYPE_BOOL }, + [VIF_ATTR_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_STRING }, +}; + +static const struct uci_blob_param_list vif_param = { + .n_params = ARRAY_SIZE(vif_policy), + .params = vif_policy, +}; + +static void +put_container(struct blob_buf *buf, struct blob_attr *attr, const char *name) +{ + void *c = blobmsg_open_table(buf, name); + blob_put_raw(buf, blob_data(attr), blob_len(attr)); + blobmsg_close_table(buf, c); +} + +static void +vif_config_add_bridge(struct blob_buf *buf, struct blob_attr *networks, bool prepare) +{ + struct interface *iface; + struct device *dev = NULL; + struct blob_attr *cur; + const char *network; + int rem; + + if (!networks) + return; + + blobmsg_for_each_attr(cur, networks, rem) { + network = blobmsg_data(cur); + + iface = vlist_find(&interfaces, network, iface, node); + if (!iface) + continue; + + dev = iface->main_dev.dev; + if (!dev) + return; + + if (dev->type != &bridge_device_type) + return; + } + + if (!dev) + return; + + if (dev->hotplug_ops && dev->hotplug_ops->prepare) + dev->hotplug_ops->prepare(dev); + + blobmsg_add_string(buf, "bridge", dev->ifname); + + if (dev->settings.flags & DEV_OPT_MULTICAST_TO_UNICAST) + blobmsg_add_u8(buf, "multicast_to_unicast", + dev->settings.multicast_to_unicast); +} + +static void +prepare_config(struct wireless_device *wdev, struct blob_buf *buf, bool up) +{ + struct wireless_interface *vif; + void *l, *i; + + blob_buf_init(&b, 0); + put_container(&b, wdev->config, "config"); + if (wdev->data) + blobmsg_add_blob(&b, wdev->data); + + l = blobmsg_open_table(&b, "interfaces"); + vlist_for_each_element(&wdev->interfaces, vif, node) { + i = blobmsg_open_table(&b, vif->name); + vif_config_add_bridge(&b, vif->network, up); + put_container(&b, vif->config, "config"); + if (vif->data) + blobmsg_add_blob(&b, vif->data); + blobmsg_close_table(&b, i); + } + blobmsg_close_table(&b, l); +} + +static bool +wireless_process_check(struct wireless_process *proc) +{ + return check_pid_path(proc->pid, proc->exe); +} + +static void +wireless_complete_kill_request(struct wireless_device *wdev) +{ + if (!wdev->kill_request) + return; + + ubus_complete_deferred_request(ubus_ctx, wdev->kill_request, 0); + free(wdev->kill_request); + wdev->kill_request = NULL; +} + +static void +wireless_process_free(struct wireless_device *wdev, struct wireless_process *proc) +{ + D(WIRELESS, "Wireless device '%s' free pid %d\n", wdev->name, proc->pid); + list_del(&proc->list); + free(proc); + + if (list_empty(&wdev->script_proc)) + wireless_complete_kill_request(wdev); +} + +static void +wireless_close_script_proc_fd(struct wireless_device *wdev) +{ + if (wdev->script_proc_fd.fd < 0) + return; + + uloop_fd_delete(&wdev->script_proc_fd); + close(wdev->script_proc_fd.fd); + wdev->script_proc_fd.fd = -1; +} + +static void +wireless_process_kill_all(struct wireless_device *wdev, int signal, bool free) +{ + struct wireless_process *proc, *tmp; + + list_for_each_entry_safe(proc, tmp, &wdev->script_proc, list) { + bool check = wireless_process_check(proc); + + if (check) { + D(WIRELESS, "Wireless device '%s' kill pid %d\n", wdev->name, proc->pid); + kill(proc->pid, signal); + } + + if (free || !check) + wireless_process_free(wdev, proc); + } + + if (free) + wireless_close_script_proc_fd(wdev); +} + +static void +wireless_device_free_state(struct wireless_device *wdev) +{ + struct wireless_interface *vif; + + uloop_timeout_cancel(&wdev->script_check); + uloop_timeout_cancel(&wdev->timeout); + wireless_complete_kill_request(wdev); + free(wdev->data); + wdev->data = NULL; + vlist_for_each_element(&wdev->interfaces, vif, node) { + free(vif->data); + vif->data = NULL; + vif->ifname = NULL; + } +} + +static void wireless_interface_handle_link(struct wireless_interface *vif, bool up) +{ + struct interface *iface; + struct blob_attr *cur; + const char *network; + int rem; + + if (!vif->network || !vif->ifname) + return; + + if (up) { + struct device *dev = device_get(vif->ifname, 2); + if (dev) { + dev->wireless_isolate = vif->isolate; + dev->wireless = true; + dev->wireless_ap = vif->ap_mode; + } + } + + blobmsg_for_each_attr(cur, vif->network, rem) { + network = blobmsg_data(cur); + + iface = vlist_find(&interfaces, network, iface, node); + if (!iface) + continue; + + interface_handle_link(iface, vif->ifname, up, true); + } +} + +static void +wireless_device_setup_cancel(struct wireless_device *wdev) +{ + if (wdev->cancel) + return; + + D(WIRELESS, "Cancel wireless device '%s' setup\n", wdev->name); + wdev->cancel = true; + uloop_timeout_set(&wdev->timeout, 10 * 1000); +} + +static void +wireless_device_run_handler(struct wireless_device *wdev, bool up) +{ + const char *action = up ? "setup" : "teardown"; + const char *argv[6]; + char *config; + int i = 0; + int fds[2] = { -1, -1 }; + + D(WIRELESS, "Wireless device '%s' run %s handler\n", wdev->name, action); + if (!up && wdev->prev_config) { + config = blobmsg_format_json(wdev->prev_config, true); + free(wdev->prev_config); + wdev->prev_config = NULL; + } else { + prepare_config(wdev, &b, up); + config = blobmsg_format_json(b.head, true); + } + + argv[i++] = wdev->drv->script; + argv[i++] = wdev->drv->name; + argv[i++] = action; + argv[i++] = wdev->name; + argv[i++] = config; + argv[i] = NULL; + + if (up && pipe(fds) == 0) { + wdev->script_proc_fd.fd = fds[0]; + uloop_fd_add(&wdev->script_proc_fd, + ULOOP_READ | ULOOP_EDGE_TRIGGER); + } + + netifd_start_process(argv, NULL, &wdev->script_task); + + if (fds[1] >= 0) + close(fds[1]); + + free(config); +} + +static void +__wireless_device_set_up(struct wireless_device *wdev) +{ + if (wdev->disabled) + return; + + if (wdev->state != IFS_DOWN || config_init) + return; + + free(wdev->prev_config); + wdev->prev_config = NULL; + wdev->state = IFS_SETUP; + wireless_device_run_handler(wdev, true); +} + +static void +wireless_device_free(struct wireless_device *wdev) +{ + vlist_flush_all(&wdev->interfaces); + avl_delete(&wireless_devices.avl, &wdev->node.avl); + free(wdev->config); + free(wdev->prev_config); + free(wdev); +} + +static void +wdev_handle_config_change(struct wireless_device *wdev) +{ + enum interface_config_state state = wdev->config_state; + + switch(state) { + case IFC_NORMAL: + case IFC_RELOAD: + wdev->config_state = IFC_NORMAL; + if (wdev->autostart) + __wireless_device_set_up(wdev); + break; + case IFC_REMOVE: + wireless_device_free(wdev); + break; + } +} + +static void +wireless_device_mark_down(struct wireless_device *wdev) +{ + struct wireless_interface *vif; + + D(WIRELESS, "Wireless device '%s' is now down\n", wdev->name); + + vlist_for_each_element(&wdev->interfaces, vif, node) + wireless_interface_handle_link(vif, false); + + wireless_process_kill_all(wdev, SIGTERM, true); + + wdev->cancel = false; + wdev->state = IFS_DOWN; + wireless_device_free_state(wdev); + wdev_handle_config_change(wdev); +} + +static void +wireless_device_setup_timeout(struct uloop_timeout *timeout) +{ + struct wireless_device *wdev = container_of(timeout, struct wireless_device, timeout); + + netifd_kill_process(&wdev->script_task); + wdev->script_task.cb(&wdev->script_task, -1); + wireless_device_mark_down(wdev); +} + +void +wireless_device_set_up(struct wireless_device *wdev) +{ + wdev->retry = WIRELESS_SETUP_RETRY; + wdev->autostart = true; + __wireless_device_set_up(wdev); +} + +static void +__wireless_device_set_down(struct wireless_device *wdev) +{ + if (wdev->state == IFS_TEARDOWN || wdev->state == IFS_DOWN) + return; + + if (wdev->script_task.uloop.pending) { + wireless_device_setup_cancel(wdev); + return; + } + + wdev->state = IFS_TEARDOWN; + wireless_device_run_handler(wdev, false); +} + +static void +wireless_device_mark_up(struct wireless_device *wdev) +{ + struct wireless_interface *vif; + + if (wdev->cancel) { + wdev->cancel = false; + __wireless_device_set_down(wdev); + return; + } + + D(WIRELESS, "Wireless device '%s' is now up\n", wdev->name); + wdev->state = IFS_UP; + vlist_for_each_element(&wdev->interfaces, vif, node) + wireless_interface_handle_link(vif, true); +} + +static void +wireless_device_retry_setup(struct wireless_device *wdev) +{ + if (wdev->state == IFS_TEARDOWN || wdev->state == IFS_DOWN || wdev->cancel) + return; + + if (--wdev->retry < 0) + wdev->autostart = false; + + __wireless_device_set_down(wdev); +} + +static void +wireless_device_script_task_cb(struct netifd_process *proc, int ret) +{ + struct wireless_device *wdev = container_of(proc, struct wireless_device, script_task); + + switch (wdev->state) { + case IFS_SETUP: + wireless_device_retry_setup(wdev); + break; + case IFS_TEARDOWN: + wireless_device_mark_down(wdev); + break; + default: + break; + } +} + +void +wireless_device_set_down(struct wireless_device *wdev) +{ + wdev->autostart = false; + __wireless_device_set_down(wdev); +} + +static void +wdev_set_config_state(struct wireless_device *wdev, enum interface_config_state s) +{ + if (wdev->config_state != IFC_NORMAL) + return; + + wdev->config_state = s; + if (wdev->state == IFS_DOWN) + wdev_handle_config_change(wdev); + else + __wireless_device_set_down(wdev); +} + +static void +wdev_prepare_prev_config(struct wireless_device *wdev) +{ + if (wdev->prev_config) + return; + + prepare_config(wdev, &b, false); + wdev->prev_config = blob_memdup(b.head); +} + +static void +wdev_change_config(struct wireless_device *wdev, struct wireless_device *wd_new) +{ + struct blob_attr *new_config = wd_new->config; + bool disabled = wd_new->disabled; + + free(wd_new); + + wdev_prepare_prev_config(wdev); + if (blob_attr_equal(wdev->config, new_config) && wdev->disabled == disabled) + return; + + D(WIRELESS, "Update configuration of wireless device '%s'\n", wdev->name); + free(wdev->config); + wdev->config = blob_memdup(new_config); + wdev->disabled = disabled; + wdev_set_config_state(wdev, IFC_RELOAD); +} + +static void +wdev_create(struct wireless_device *wdev) +{ + wdev->retry = WIRELESS_SETUP_RETRY; + wdev->config = blob_memdup(wdev->config); +} + +static void +wdev_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct wireless_device *wd_old = container_of(node_old, struct wireless_device, node); + struct wireless_device *wd_new = container_of(node_new, struct wireless_device, node); + + if (wd_old && wd_new) { + wdev_change_config(wd_old, wd_new); + } else if (wd_old) { + D(WIRELESS, "Delete wireless device '%s'\n", wd_old->name); + wdev_set_config_state(wd_old, IFC_REMOVE); + } else if (wd_new) { + D(WIRELESS, "Create wireless device '%s'\n", wd_new->name); + wdev_create(wd_new); + } +} + +static void +wireless_add_handler(const char *script, const char *name, json_object *obj) +{ + struct wireless_driver *drv; + char *name_str, *script_str; + json_object *dev_config_obj, *iface_config_obj; + struct uci_blob_param_list *dev_config, *iface_config; + + dev_config_obj = json_get_field(obj, "device", json_type_array); + iface_config_obj = json_get_field(obj, "iface", json_type_array); + + if (!dev_config_obj || !iface_config_obj) + return; + + drv = calloc_a(sizeof(*drv), + &dev_config, sizeof(*dev_config) + sizeof(void *), + &iface_config, sizeof(*iface_config) + sizeof(void *), + &name_str, strlen(name) + 1, + &script_str, strlen(script) + 1); + + drv->name = strcpy(name_str, name); + drv->script = strcpy(script_str, script); + + dev_config->n_next = 1; + dev_config->next[0] = &wdev_param; + drv->device.config = dev_config; + + iface_config->n_next = 1; + iface_config->next[0] = &vif_param; + drv->interface.config = iface_config; + + drv->device.buf = netifd_handler_parse_config(drv->device.config, dev_config_obj); + drv->interface.buf = netifd_handler_parse_config(drv->interface.config, iface_config_obj); + + drv->node.key = drv->name; + avl_insert(&wireless_drivers, &drv->node); + D(WIRELESS, "Add handler for script %s: %s\n", script, name); +} + +void wireless_init(void) +{ + vlist_init(&wireless_devices, avl_strcmp, wdev_update); + wireless_devices.keep_old = true; + wireless_devices.no_delete = true; + + avl_init(&wireless_drivers, avl_strcmp, false, NULL); + drv_fd = netifd_open_subdir("wireless"); + if (drv_fd < 0) + return; + + netifd_init_script_handlers(drv_fd, wireless_add_handler); +} + +static void +wireless_interface_init_config(struct wireless_interface *vif) +{ + struct blob_attr *tb[__VIF_ATTR_MAX]; + struct blob_attr *cur; + + vif->network = NULL; + blobmsg_parse(vif_policy, __VIF_ATTR_MAX, tb, blob_data(vif->config), blob_len(vif->config)); + + if ((cur = tb[VIF_ATTR_NETWORK])) + vif->network = cur; + + cur = tb[VIF_ATTR_ISOLATE]; + if (cur) + vif->isolate = blobmsg_get_bool(cur); + + cur = tb[VIF_ATTR_MODE]; + if (cur) + vif->ap_mode = !strcmp(blobmsg_get_string(cur), "ap"); +} + +static void +vif_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct wireless_interface *vif_old = container_of(node_old, struct wireless_interface, node); + struct wireless_interface *vif_new = container_of(node_new, struct wireless_interface, node); + struct wireless_device *wdev; + + if (vif_old) + wdev = vif_old->wdev; + else + wdev = vif_new->wdev; + + if (vif_old && vif_new) { + free((void *) vif_old->section); + vif_old->section = strdup(vif_new->section); + if (blob_attr_equal(vif_old->config, vif_new->config)) { + free(vif_new); + return; + } + + D(WIRELESS, "Update wireless interface %s on device %s\n", vif_new->name, wdev->name); + wireless_interface_handle_link(vif_old, false); + free(vif_old->config); + vif_old->config = blob_memdup(vif_new->config); + vif_old->isolate = vif_new->isolate; + vif_old->ap_mode = vif_new->ap_mode; + wireless_interface_init_config(vif_old); + free(vif_new); + } else if (vif_new) { + D(WIRELESS, "Create new wireless interface %s on device %s\n", vif_new->name, wdev->name); + vif_new->section = strdup(vif_new->section); + vif_new->config = blob_memdup(vif_new->config); + wireless_interface_init_config(vif_new); + } else if (vif_old) { + D(WIRELESS, "Delete wireless interface %s on device %s\n", vif_old->name, wdev->name); + free((void *) vif_old->section); + free(vif_old->config); + free(vif_old); + } + + wdev_set_config_state(wdev, IFC_RELOAD); +} + +static void +wireless_proc_poll_fd(struct uloop_fd *fd, unsigned int events) +{ + struct wireless_device *wdev = container_of(fd, struct wireless_device, script_proc_fd); + char buf[128]; + + while (1) { + int b = read(fd->fd, buf, sizeof(buf)); + if (b < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return; + + goto done; + } + + if (!b) + goto done; + } + +done: + uloop_timeout_set(&wdev->script_check, 0); + wireless_close_script_proc_fd(wdev); +} + +static void +wireless_device_check_script_tasks(struct uloop_timeout *timeout) +{ + struct wireless_device *wdev = container_of(timeout, struct wireless_device, script_check); + struct wireless_process *proc, *tmp; + bool restart = false; + + list_for_each_entry_safe(proc, tmp, &wdev->script_proc, list) { + if (wireless_process_check(proc)) + continue; + + D(WIRELESS, "Wireless device '%s' pid %d has terminated\n", wdev->name, proc->pid); + if (proc->required) + restart = true; + + wireless_process_free(wdev, proc); + } + + if (restart) + wireless_device_retry_setup(wdev); + else + uloop_timeout_set(&wdev->script_check, 1000); +} + +void +wireless_device_create(struct wireless_driver *drv, const char *name, struct blob_attr *data) +{ + struct wireless_device *wdev; + char *name_buf; + struct blob_attr *disabled; + + blobmsg_parse(&wdev_policy, 1, &disabled, blob_data(data), blob_len(data)); + + wdev = calloc_a(sizeof(*wdev), &name_buf, strlen(name) + 1); + if (disabled && blobmsg_get_bool(disabled)) + wdev->disabled = true; + wdev->drv = drv; + wdev->state = IFS_DOWN; + wdev->config_state = IFC_NORMAL; + wdev->name = strcpy(name_buf, name); + wdev->config = data; + wdev->config_autostart = true; + wdev->autostart = wdev->config_autostart; + INIT_LIST_HEAD(&wdev->script_proc); + vlist_init(&wdev->interfaces, avl_strcmp, vif_update); + wdev->interfaces.keep_old = true; + + wdev->timeout.cb = wireless_device_setup_timeout; + wdev->script_task.cb = wireless_device_script_task_cb; + wdev->script_task.dir_fd = drv_fd; + wdev->script_task.log_prefix = wdev->name; + + wdev->script_proc_fd.fd = -1; + wdev->script_proc_fd.cb = wireless_proc_poll_fd; + + wdev->script_check.cb = wireless_device_check_script_tasks; + + vlist_add(&wireless_devices, &wdev->node, wdev->name); +} + +void wireless_interface_create(struct wireless_device *wdev, struct blob_attr *data, const char *section) +{ + struct wireless_interface *vif; + struct blob_attr *tb[__VIF_ATTR_MAX]; + struct blob_attr *cur; + char *name_buf; + char name[8]; + + blobmsg_parse(vif_policy, __VIF_ATTR_MAX, tb, blob_data(data), blob_len(data)); + + cur = tb[VIF_ATTR_DISABLED]; + if (cur && blobmsg_get_bool(cur)) + return; + + sprintf(name, "%d", wdev->vif_idx++); + + vif = calloc_a(sizeof(*vif), + &name_buf, strlen(name) + 1); + vif->name = strcpy(name_buf, name); + vif->wdev = wdev; + vif->config = data; + vif->section = section; + vif->isolate = false; + + vlist_add(&wdev->interfaces, &vif->node, vif->name); +} + +static void +wireless_interface_status(struct wireless_interface *iface, struct blob_buf *b) +{ + void *i; + + i = blobmsg_open_table(b, NULL); + if (iface->section) + blobmsg_add_string(b, "section", iface->section); + if (iface->ifname) + blobmsg_add_string(b, "ifname", iface->ifname); + put_container(b, iface->config, "config"); + blobmsg_close_table(b, i); +} + +void +wireless_device_status(struct wireless_device *wdev, struct blob_buf *b) +{ + struct wireless_interface *iface; + void *c, *i; + + c = blobmsg_open_table(b, wdev->name); + blobmsg_add_u8(b, "up", wdev->state == IFS_UP); + blobmsg_add_u8(b, "pending", wdev->state == IFS_SETUP || wdev->state == IFS_TEARDOWN); + blobmsg_add_u8(b, "autostart", wdev->autostart); + blobmsg_add_u8(b, "disabled", wdev->disabled); + put_container(b, wdev->config, "config"); + + i = blobmsg_open_array(b, "interfaces"); + vlist_for_each_element(&wdev->interfaces, iface, node) + wireless_interface_status(iface, b); + blobmsg_close_array(b, i); + blobmsg_close_table(b, c); +} + +void +wireless_device_get_validate(struct wireless_device *wdev, struct blob_buf *b) +{ + struct uci_blob_param_list *p; + void *c, *d; + int i; + + c = blobmsg_open_table(b, wdev->name); + + d = blobmsg_open_table(b, "device"); + p = wdev->drv->device.config; + for (i = 0; i < p->n_params; i++) + blobmsg_add_string(b, p->params[i].name, uci_get_validate_string(p, i)); + blobmsg_close_table(b, d); + + d = blobmsg_open_table(b, "interface"); + p = wdev->drv->interface.config; + for (i = 0; i < p->n_params; i++) + blobmsg_add_string(b, p->params[i].name, uci_get_validate_string(p, i)); + blobmsg_close_table(b, d); + + blobmsg_close_table(b, c); +} + +static void +wireless_interface_set_data(struct wireless_interface *vif) +{ + enum { + VIF_DATA_IFNAME, + __VIF_DATA_MAX, + }; + static const struct blobmsg_policy data_policy[__VIF_DATA_MAX] = { + [VIF_DATA_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING }, + }; + struct blob_attr *tb[__VIF_DATA_MAX]; + struct blob_attr *cur; + + blobmsg_parse(data_policy, __VIF_DATA_MAX, tb, + blobmsg_data(vif->data), blobmsg_data_len(vif->data)); + + if ((cur = tb[VIF_DATA_IFNAME])) + vif->ifname = blobmsg_data(cur); +} + +static int +wireless_device_add_process(struct wireless_device *wdev, struct blob_attr *data) +{ + enum { + PROC_ATTR_PID, + PROC_ATTR_EXE, + PROC_ATTR_REQUIRED, + __PROC_ATTR_MAX + }; + static const struct blobmsg_policy proc_policy[__PROC_ATTR_MAX] = { + [PROC_ATTR_PID] = { .name = "pid", .type = BLOBMSG_TYPE_INT32 }, + [PROC_ATTR_EXE] = { .name = "exe", .type = BLOBMSG_TYPE_STRING }, + [PROC_ATTR_REQUIRED] = { .name = "required", .type = BLOBMSG_TYPE_BOOL }, + }; + struct blob_attr *tb[__PROC_ATTR_MAX]; + struct wireless_process *proc; + char *name; + int pid; + + if (!data) + return UBUS_STATUS_INVALID_ARGUMENT; + + blobmsg_parse(proc_policy, __PROC_ATTR_MAX, tb, blobmsg_data(data), blobmsg_data_len(data)); + if (!tb[PROC_ATTR_PID] || !tb[PROC_ATTR_EXE]) + return UBUS_STATUS_INVALID_ARGUMENT; + + pid = blobmsg_get_u32(tb[PROC_ATTR_PID]); + if (pid < 2) + return UBUS_STATUS_INVALID_ARGUMENT; + + proc = calloc_a(sizeof(*proc), + &name, strlen(blobmsg_data(tb[PROC_ATTR_EXE])) + 1); + + proc->pid = pid; + proc->exe = strcpy(name, blobmsg_data(tb[PROC_ATTR_EXE])); + + if (tb[PROC_ATTR_REQUIRED]) + proc->required = blobmsg_get_bool(tb[PROC_ATTR_REQUIRED]); + + D(WIRELESS, "Wireless device '%s' add pid %d\n", wdev->name, proc->pid); + list_add(&proc->list, &wdev->script_proc); + uloop_timeout_set(&wdev->script_check, 0); + + return 0; +} + +static int +wireless_device_process_kill_all(struct wireless_device *wdev, struct blob_attr *data, + struct ubus_request_data *req) +{ + enum { + KILL_ATTR_SIGNAL, + KILL_ATTR_IMMEDIATE, + __KILL_ATTR_MAX + }; + static const struct blobmsg_policy kill_policy[__KILL_ATTR_MAX] = { + [KILL_ATTR_SIGNAL] = { .name = "signal", .type = BLOBMSG_TYPE_INT32 }, + [KILL_ATTR_IMMEDIATE] = { .name = "immediate", .type = BLOBMSG_TYPE_BOOL }, + }; + struct blob_attr *tb[__KILL_ATTR_MAX]; + struct blob_attr *cur; + bool immediate = false; + int signal = SIGTERM; + + blobmsg_parse(kill_policy, __KILL_ATTR_MAX, tb, blobmsg_data(data), blobmsg_data_len(data)); + + if ((cur = tb[KILL_ATTR_SIGNAL])) + signal = blobmsg_get_u32(cur); + + if ((cur = tb[KILL_ATTR_IMMEDIATE])) + immediate = blobmsg_get_bool(cur); + + if (wdev->state != IFS_TEARDOWN || wdev->kill_request) + return UBUS_STATUS_PERMISSION_DENIED; + + wireless_process_kill_all(wdev, signal, immediate); + + if (list_empty(&wdev->script_proc)) + return 0; + + wdev->kill_request = calloc(1, sizeof(*wdev->kill_request)); + ubus_defer_request(ubus_ctx, req, wdev->kill_request); + + return 0; +} + +static int +wireless_device_set_retry(struct wireless_device *wdev, struct blob_attr *data) +{ + static const struct blobmsg_policy retry_policy = { + .name = "retry", .type = BLOBMSG_TYPE_INT32 + }; + struct blob_attr *val; + + blobmsg_parse(&retry_policy, 1, &val, blobmsg_data(data), blobmsg_data_len(data)); + if (!val) + return UBUS_STATUS_INVALID_ARGUMENT; + + wdev->retry = blobmsg_get_u32(val); + return 0; +} + +enum { + NOTIFY_CMD_UP = 0, + NOTIFY_CMD_SET_DATA = 1, + NOTIFY_CMD_PROCESS_ADD = 2, + NOTIFY_CMD_PROCESS_KILL_ALL = 3, + NOTIFY_CMD_SET_RETRY = 4, +}; + +int +wireless_device_notify(struct wireless_device *wdev, struct blob_attr *data, + struct ubus_request_data *req) +{ + enum { + NOTIFY_ATTR_COMMAND, + NOTIFY_ATTR_VIF, + NOTIFY_ATTR_DATA, + __NOTIFY_MAX, + }; + static const struct blobmsg_policy notify_policy[__NOTIFY_MAX] = { + [NOTIFY_ATTR_COMMAND] = { .name = "command", .type = BLOBMSG_TYPE_INT32 }, + [NOTIFY_ATTR_VIF] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, + [NOTIFY_ATTR_DATA] = { .name = "data", .type = BLOBMSG_TYPE_TABLE }, + }; + struct wireless_interface *vif = NULL; + struct blob_attr *tb[__NOTIFY_MAX]; + struct blob_attr *cur, **pdata; + + blobmsg_parse(notify_policy, __NOTIFY_MAX, tb, blob_data(data), blob_len(data)); + + if (!tb[NOTIFY_ATTR_COMMAND]) + return UBUS_STATUS_INVALID_ARGUMENT; + + if ((cur = tb[NOTIFY_ATTR_VIF]) != NULL) { + vif = vlist_find(&wdev->interfaces, blobmsg_data(cur), vif, node); + if (!vif) + return UBUS_STATUS_NOT_FOUND; + } + + cur = tb[NOTIFY_ATTR_DATA]; + if (!cur) + return UBUS_STATUS_INVALID_ARGUMENT; + + switch (blobmsg_get_u32(tb[NOTIFY_ATTR_COMMAND])) { + case NOTIFY_CMD_UP: + if (vif) + return UBUS_STATUS_INVALID_ARGUMENT; + + if (wdev->state != IFS_SETUP) + return UBUS_STATUS_PERMISSION_DENIED; + + wireless_device_mark_up(wdev); + break; + case NOTIFY_CMD_SET_DATA: + if (vif) + pdata = &vif->data; + else + pdata = &wdev->data; + + if (*pdata) + return UBUS_STATUS_INVALID_ARGUMENT; + + *pdata = blob_memdup(cur); + if (vif) + wireless_interface_set_data(vif); + break; + case NOTIFY_CMD_PROCESS_ADD: + return wireless_device_add_process(wdev, cur); + case NOTIFY_CMD_PROCESS_KILL_ALL: + return wireless_device_process_kill_all(wdev, cur, req); + case NOTIFY_CMD_SET_RETRY: + return wireless_device_set_retry(wdev, cur); + default: + return UBUS_STATUS_INVALID_ARGUMENT; + } + + return 0; +} + +void +wireless_start_pending(void) +{ + struct wireless_device *wdev; + + vlist_for_each_element(&wireless_devices, wdev, node) + if (wdev->autostart) + __wireless_device_set_up(wdev); +} diff --git a/src/3P/netifd/wireless.h b/src/3P/netifd/wireless.h new file mode 100644 index 00000000..665cdb7c --- /dev/null +++ b/src/3P/netifd/wireless.h @@ -0,0 +1,105 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2013 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef __NETIFD_WIRELESS_H +#define __NETIFD_WIRELESS_H + +#include +#include "interface.h" + +extern struct vlist_tree wireless_devices; +extern struct avl_tree wireless_drivers; + +struct wireless_driver { + struct avl_node node; + + const char *name; + const char *script; + + struct { + char *buf; + struct uci_blob_param_list *config; + } device, interface; +}; + +struct wireless_device { + struct vlist_node node; + + struct wireless_driver *drv; + struct vlist_tree interfaces; + char *name; + + struct netifd_process script_task; + struct uloop_timeout timeout; + struct uloop_timeout poll; + + struct list_head script_proc; + struct uloop_fd script_proc_fd; + struct uloop_timeout script_check; + + struct ubus_request_data *kill_request; + + struct blob_attr *prev_config; + struct blob_attr *config; + struct blob_attr *data; + + bool config_autostart; + bool autostart; + bool disabled; + + enum interface_state state; + enum interface_config_state config_state; + bool cancel; + int retry; + + int vif_idx; +}; + +struct wireless_interface { + struct vlist_node node; + const char *section; + char *name; + + struct wireless_device *wdev; + + struct blob_attr *config; + struct blob_attr *data; + + const char *ifname; + struct blob_attr *network; + bool isolate; + bool ap_mode; +}; + +struct wireless_process { + struct list_head list; + + const char *exe; + int pid; + + bool required; +}; + +void wireless_device_create(struct wireless_driver *drv, const char *name, struct blob_attr *data); +void wireless_device_set_up(struct wireless_device *wdev); +void wireless_device_set_down(struct wireless_device *wdev); +void wireless_device_status(struct wireless_device *wdev, struct blob_buf *b); +void wireless_device_get_validate(struct wireless_device *wdev, struct blob_buf *b); +void wireless_interface_create(struct wireless_device *wdev, struct blob_attr *data, const char *section); +int wireless_device_notify(struct wireless_device *wdev, struct blob_attr *data, + struct ubus_request_data *req); + +void wireless_start_pending(void); +void wireless_init(void); + +#endif