From e6d01e16f4b791e585112a8edd0724dd10a7eddf Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 4 Oct 2019 11:54:56 -0700 Subject: [PATCH] ICMP6 Router Advertisement feature This commit adds "Router Advertisement" feature which emits periodic Neighbor Discovery ICMPv6 Router Advertisement (RA) messages announcing routes on other network interfaces related to Thread network. The routes included in RA message mirror all the routes added on the host primary interface (from `IPv6:Routes`) corresponding to off-mesh routes and/or on-mesh prefixes within the Thread network. This feature can be enabled through property `RouterAdvert:Enable` (by default it is disabled) and its behavior can modified through a group of wpan properties (all start with prefix `RouterAdver`). A readme/guide is also added under doc folder which describes the wpan properties and shows example use/behavior of this feature. --- Android.mk | 1 + doc/router-advert-feature-guide.md | 170 +++++++++++++ .../SpinelNCPInstance-Protothreads.cpp | 6 + src/util/netif-mgmt.c | 21 ++ src/util/netif-mgmt.h | 2 + src/wpantund/ICMP6RouterAdvertiser.cpp | 230 ++++++++++++++++++ src/wpantund/ICMP6RouterAdvertiser.h | 86 +++++++ src/wpantund/Makefile.am | 2 + src/wpantund/NCPInstanceBase-Addresses.cpp | 3 + src/wpantund/NCPInstanceBase-AsyncIO.cpp | 18 +- src/wpantund/NCPInstanceBase.cpp | 137 ++++++++++- src/wpantund/NCPInstanceBase.h | 17 ++ src/wpantund/wpan-properties.h | 6 + 13 files changed, 697 insertions(+), 2 deletions(-) create mode 100644 doc/router-advert-feature-guide.md create mode 100644 src/wpantund/ICMP6RouterAdvertiser.cpp create mode 100644 src/wpantund/ICMP6RouterAdvertiser.h diff --git a/Android.mk b/Android.mk index d5237050..3d3229b2 100644 --- a/Android.mk +++ b/Android.mk @@ -89,6 +89,7 @@ LOCAL_SRC_FILES := \ src/wpantund/NCPInstance.cpp \ src/wpantund/NCPInstanceBase.cpp \ src/wpantund/FirmwareUpgrade.cpp \ + src/wpantund/ICMP6RouterAdvertiser.cpp \ src/wpantund/StatCollector.cpp \ src/wpantund/RunawayResetBackoffManager.cpp \ src/wpantund/NCPInstanceBase-NetInterface.cpp \ diff --git a/doc/router-advert-feature-guide.md b/doc/router-advert-feature-guide.md new file mode 100644 index 00000000..c48d099d --- /dev/null +++ b/doc/router-advert-feature-guide.md @@ -0,0 +1,170 @@ +# `wpantund` ICMPv6 Router Advertisement Feature # + +The `"Router Advertisement"` feature enables `wpantund` to emit periodic Neighbor Discovery ICMPv6 Router Advertisement (RA) messages announcing routes on other network interfaces related to Thread network routes. + +The routes included in RA message mirror all the routes added on the host primary interface corresponding to the Thread network: +- Host routes associated with off-mesh routes within the Thread network (when `Daemon:OffMeshRoute:AutoAddOnInterface` feature is enabled). +- Host routes associated with on-mesh prefixes within the Thread network (when `Daemon:OnMeshPrefix:AutoAddAsInterfaceRoute` feature is enabled). +- The list of interface routes is available form wpan property `IPv6:Routes`. + +The wpantund RA feature can be enabled through property `RouterAdvert:Enable` (by default it is disabled) and its behavior can modified through a group of wpan properties (all starting with `RouterAdvert:` prefix). + +- `RouterAdvert:Enable` can be used to enable or disable the whole feature (default is false, i.e., disabled) +- `RouterAdvert:Netifs` defines the list of netif names to send RA messages on. This is a list-based property (we can set the entire list using space separated interface names, or use `add` or `remove` commands to update the list item by item). On start the list is empty. +- `RouterAdvert:TxPeriod` the tx period of RA messages in units of seconds. Minimum period is 4 seconds, max period is 1800 seconds. The period is set to min or max if the value being set is out of the supported range. On start it is set to 10 seconds. +- `RouterAdvert:DefaultRoute:Lifetime` specifies the lifetime value in RA header (non-zero indicates that we are a default route). By default it is set to zero (i.e. not a default route). +- `RouterAdvert:DefaultRoute:Preference` specifies the default route preference. Positive value indicates high, zero indicates medium, and negative indicates low preference. Default value is zero (medium). + +# Example of behavior + +We enable the feature: + + wpanctl:wpan1> set RouterAdvert:Enable true + +Add the netif names we want ICMPv6 RA messages to be sent on (this is a list): + + wpanctl:wpan1> add RouterAdvert:Netifs wpan1 + wpanctl:wpan1> get RouterAdvert:Netifs + RouterAdvert:Netifs = [ + "wpan1" + ] + +When a route is added on host, the same route is announced through the RA message: + + wpanctl:wpan1> add-prefix fd00:1234:: -l 64 -o -c + Successfully added prefix "fd00:1234::" len:64 stable:0 [on-mesh:1 def-route:0 config:1 dhcp:0 slaac:0 pref:0 prio:med] + wpanctl:wpan1> + wpanctl:wpan1> + + wpanctl:wpan1> get IPv6:Routes + IPv6:Routes = [ + "fd00:1234::/64 metric:256 " + ] + +Output of `tcpdump` on the interface shows the RA message: + + sudo tcpdump -n -i wpan1 icmp6 -vv -X + + 11:01:58.991522 IP6 (flowlabel 0xba663, hlim 1, next-header ICMPv6 (58) payload length: 40) fe80::4801:7e22:7895:a656 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 40 + hop limit 255, Flags [none], pref medium, router lifetime 0s, reachable time 3600s, retrans time 0s + source link-address option (1), length 8 (1): 00:00:00:00:00:00 + 0x0000: 0000 0000 0000 + route info option (24), length 16 (2): fd00:1234::/64, pref=medium, lifetime=3600s + 0x0000: 4000 0000 0e10 fd00 1234 0000 0000 + 0x0000: 600b a663 0028 3a01 fe80 0000 0000 0000 `..c.(:......... + 0x0010: 4801 7e22 7895 a656 ff02 0000 0000 0000 H.~"x..V........ + 0x0020: 0000 0000 0000 0001 8600 13af ff00 0000 ................ + 0x0030: 0000 0e10 0000 0000 0101 0000 0000 0000 ................ + 0x0040: 1802 4000 0000 0e10 fd00 1234 0000 0000 ..@........4.... + +We can add or remove interface names to `RouterAdvert:Netifs` property. The RA messages are sent over all given interfaces: + + wpanctl:wpan1> add RouterAdvert:Netifs eno1 + + wpanctl:wpan1> + wpanctl:wpan1> get RouterAdvert:Netifs + RouterAdvert:Netifs = [ + "eno1" + "wpan1" + ] + + sudo tcpdump -n -i eno1 icmp6 -vv -X + + 11:22:23.785865 IP6 (flowlabel 0xbcd66, hlim 1, next-header ICMPv6 (58) payload length: 40) fe80::6d87:d70b:c949:762a > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 40 + hop limit 255, Flags [none], pref medium, router lifetime 0s, reachable time 3600s, retrans time 0s + source link-address option (1), length 8 (1): ec:b1:d7:2f:7c:b9 + 0x0000: ecb1 d72f 7cb9 + route info option (24), length 16 (2): fd00:1234::/64, pref=medium, lifetime=3600s + 0x0000: 4000 0000 0e10 fd00 1234 0000 0000 + 0x0000: 600b cd66 0028 3a01 fe80 0000 0000 0000 `..f.(:......... + 0x0010: 6d87 d70b c949 762a ff02 0000 0000 0000 m....Iv*........ + 0x0020: 0000 0000 0000 0001 8600 341c ff00 0000 ..........4..... + 0x0030: 0000 0e10 0000 0000 0101 ecb1 d72f 7cb9 ............./|. + 0x0040: 1802 4000 0000 0e10 fd00 1234 0000 0000 ..@........4.... + +The `wpantund` logs indicate when a RA message is sent over an interface: + + wpantund[69029]: Sent ICMP6 RouterAdvert on "eno1" (1 route info options) + wpantund[69029]: Sent ICMP6 RouterAdvert on "wpan1" (1 route info options) + +The entire list of interfaces can be set in one command (via space separated list of names). + + wpanctl:wpan1> set RouterAdvert:Netifs "wpan1 eno1" + + wpanctl:wpan1> get RouterAdvert:Netifs + RouterAdvert:Netifs = [ + "eno1" + "wpan1" + ] + +RA messages are sent every `RouterAdvert:TxPeriod` seconds or when a route is added/removed (some state changes) + + wpanctl:wpan1> set RouterAdvert:TxPeriod 4 + +When a route is added/removed (state changes), a new RA is sent immediately. + + wpanctl:wpan1> add-prefix fd00:baba:cafe:: -l 48 -o -c + Successfully added prefix "fd00:baba:cafe::" len:48 stable:0 [on-mesh:1 def-route:0 config:1 dhcp:0 slaac:0 pref:0 prio:med] + + wpanctl:wpan1> get IPv6:Routes + IPv6:Routes = [ + "fd00:baba:cafe::/48 metric:256 " + "fd00:1234::/64 metric:256 " + ] + +`tcpdump` output now contains two route info options and is sent every 4 seconds: + + 11:07:04.441659 IP6 (flowlabel 0xba663, hlim 1, next-header ICMPv6 (58) payload length: 56) fe80::4801:7e22:7895:a656 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56 + hop limit 255, Flags [none], pref medium, router lifetime 0s, reachable time 3600s, retrans time 0s + source link-address option (1), length 8 (1): 00:00:00:00:00:00 + 0x0000: 0000 0000 0000 + route info option (24), length 16 (2): fd00:baba:cafe::/48, pref=medium, lifetime=3600s + 0x0000: 3000 0000 0e10 fd00 baba cafe 0000 + route info option (24), length 16 (2): fd00:1234::/64, pref=medium, lifetime=3600s + 0x0000: 4000 0000 0e10 fd00 1234 0000 0000 + 0x0000: 600b a663 0038 3a01 fe80 0000 0000 0000 `..c.8:......... + 0x0010: 4801 7e22 7895 a656 ff02 0000 0000 0000 H.~"x..V........ + 0x0020: 0000 0000 0000 0001 8600 3ad2 ff00 0000 ..........:..... + 0x0030: 0000 0e10 0000 0000 0101 0000 0000 0000 ................ + 0x0040: 1802 3000 0000 0e10 fd00 baba cafe 0000 ..0............. + 0x0050: 1802 4000 0000 0e10 fd00 1234 0000 0000 ..@........4.... + + 11:07:08.445622 IP6 (flowlabel 0xba663, hlim 1, next-header ICMPv6 (58) payload length: 56) fe80::4801:7e22:7895:a656 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56 + hop limit 255, Flags [none], pref medium, router lifetime 0s, reachable time 3600s, retrans time 0s + source link-address option (1), length 8 (1): 00:00:00:00:00:00 + 0x0000: 0000 0000 0000 + route info option (24), length 16 (2): fd00:baba:cafe::/48, pref=medium, lifetime=3600s + 0x0000: 3000 0000 0e10 fd00 baba cafe 0000 + route info option (24), length 16 (2): fd00:1234::/64, pref=medium, lifetime=3600s + 0x0000: 4000 0000 0e10 fd00 1234 0000 0000 + 0x0000: 600b a663 0038 3a01 fe80 0000 0000 0000 `..c.8:......... + 0x0010: 4801 7e22 7895 a656 ff02 0000 0000 0000 H.~"x..V........ + 0x0020: 0000 0000 0000 0001 8600 3ad2 ff00 0000 ..........:..... + 0x0030: 0000 0e10 0000 0000 0101 0000 0000 0000 ................ + 0x0040: 1802 3000 0000 0e10 fd00 baba cafe 0000 ..0............. + 0x0050: 1802 4000 0000 0e10 fd00 1234 0000 0000 ..@........4.... + + +The property `RouterAdvert:DefaultRoute:Lifetime` specifies the lifetime value in RA header (non-zero value indicates that this router is a default route): + + wpanctl:wpan1> set RouterAdvert:DefaultRoute:Lifetime 1000 + + 11:10:05.929627 IP6 (flowlabel 0xba663, hlim 1, next-header ICMPv6 (58) payload length: 56) fe80::4801:7e22:7895:a656 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56 + hop limit 255, Flags [none], pref medium, router lifetime 1000s, reachable time 3600s, retrans time 0s + source link-address option (1), length 8 (1): 00:00:00:00:00:00 + 0x0000: 0000 0000 0000 + +The property `RouterAdvert:DefaultRoute:Preference` determines default route preference. Positive value indicates high, zero indicates medium, and negative indicates low preference. + + wpanctl:wpan1> set RouterAdvert:DefaultRoute:Preference 1 + + 11:12:19.989599 IP6 (flowlabel 0xba663, hlim 1, next-header ICMPv6 (58) payload length: 56) fe80::4801:7e22:7895:a656 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56 + hop limit 255, Flags [none], pref high, router lifetime 1000s, reachable time 3600s, retrans time 0s + + wpanctl:wpan1> set RouterAdvert:DefaultRoute:Preference -v -1 + + 11:13:08.078489 IP6 (flowlabel 0xba663, hlim 1, next-header ICMPv6 (58) payload length: 56) fe80::4801:7e22:7895:a656 > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 56 + hop limit 255, Flags [none], pref low, router lifetime 1000s, reachable time 3600s, retrans time 0s + +Limitations: +- The current implementation does not support replying to Router Solicitation messages. diff --git a/src/ncp-spinel/SpinelNCPInstance-Protothreads.cpp b/src/ncp-spinel/SpinelNCPInstance-Protothreads.cpp index 978a3e1e..16cf7ff8 100644 --- a/src/ncp-spinel/SpinelNCPInstance-Protothreads.cpp +++ b/src/ncp-spinel/SpinelNCPInstance-Protothreads.cpp @@ -560,12 +560,18 @@ SpinelNCPInstance::vprocess_init(int event, va_list args) int SpinelNCPInstance::vprocess_event(int event, va_list args) { + va_list args_tmp; + if (get_ncp_state() == FAULT) { // We perform no processing in the fault state. PT_INIT(&mControlPT); return 0; } + va_copy(args_tmp, args); + NCPInstanceBase::vprocess_event(event, args_tmp); + va_end(args_tmp); + while (!mTaskQueue.empty()) { va_list tmp; boost::shared_ptr current_task(mTaskQueue.front()); diff --git a/src/util/netif-mgmt.c b/src/util/netif-mgmt.c index cb5baeba..2ec9fdf5 100644 --- a/src/util/netif-mgmt.c +++ b/src/util/netif-mgmt.c @@ -532,3 +532,24 @@ netif_mgmt_leave_ipv6_multicast_address(int reqfd, const char* if_name, const ui bail: return ret; } + +int +netif_mgmt_get_hw_address(int fd, const char* if_name, uint8_t addr[6]) +{ + int ret = -1; + +#ifdef SIOCGIFHWADDR + struct ifreq ifr; + + memset(&ifr, 0, sizeof(struct ifreq)); + strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name)); + + ret = ioctl(fd, SIOCGIFHWADDR, &ifr); + + if (ret >= 0) { + memcpy(addr, ifr.ifr_hwaddr.sa_data, 6); + } +#endif + + return ret; +} diff --git a/src/util/netif-mgmt.h b/src/util/netif-mgmt.h index 710f87f9..0a6b2763 100644 --- a/src/util/netif-mgmt.h +++ b/src/util/netif-mgmt.h @@ -55,6 +55,8 @@ extern int netif_mgmt_remove_ipv6_route(int fd, const char* if_name, const uint8 extern int netif_mgmt_join_ipv6_multicast_address(int reqfd, const char* if_name, const uint8_t addr[16]); extern int netif_mgmt_leave_ipv6_multicast_address(int reqfd, const char* if_name, const uint8_t addr[16]); +extern int netif_mgmt_get_hw_address(int fd, const char* if_name, uint8_t hw_addr[6]); + #if defined(__cplusplus) } #endif diff --git a/src/wpantund/ICMP6RouterAdvertiser.cpp b/src/wpantund/ICMP6RouterAdvertiser.cpp new file mode 100644 index 00000000..754b8b70 --- /dev/null +++ b/src/wpantund/ICMP6RouterAdvertiser.cpp @@ -0,0 +1,230 @@ +/* + * + * Copyright (c) 2019 Google + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Description: + * Module sending ICMPv6 Router Advertisement. + * + */ + + +#if HAVE_CONFIG_H +#include +#endif + +#include "assert-macros.h" +#include +#include + +#include +#include +#include +#include +#include + +#include "netif-mgmt.h" +#include "NCPTypes.h" +#include "ICMP6RouterAdvertiser.h" +#include "NCPInstanceBase.h" + + +using namespace nl; +using namespace nl::wpantund; + +nl::wpantund::ICMP6RouterAdvertiser::ICMP6RouterAdvertiser(NCPInstanceBase* instance) + : mInstance(instance) + , mNetifMgmtFD(netif_mgmt_open()) + , mEnabled(false) + , mTxPeriod(DEFAULT_ROUTER_ADV_TX_PERIOD) + , mDefaultRoutePreference(0) + , mDefaultRouteLifetime(0) + , mStateChanged(false) +{ + mSocket = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); +} + +nl::wpantund::ICMP6RouterAdvertiser::~ICMP6RouterAdvertiser(void) +{ + netif_mgmt_close(mNetifMgmtFD); + close(mSocket); +} + +void +nl::wpantund::ICMP6RouterAdvertiser::set_tx_period(uint32_t period) +{ + if (period > MAX_ROUTER_ADV_TX_PERIOD) { + mTxPeriod = MAX_ROUTER_ADV_TX_PERIOD; + } else if (period < MIN_ROUTER_ADV_TX_PERIOD) { + mTxPeriod = MIN_ROUTER_ADV_TX_PERIOD; + } else { + mTxPeriod = period; + } + + mStateChanged = true; +} + +void +nl::wpantund::ICMP6RouterAdvertiser::send_router_advert(const char *netif_name) +{ + enum { + ROUTE_INFO_OPTION_TYPE = 24, + ROUTE_INFO_OPTION_PRF_HIGH = (0x1 << 3), + ROUTE_INFO_OPTION_PRF_MEDIUM = (0x0 << 3), + ROUTE_INFO_OPTION_PRF_LOW = (0x3 << 3), + ROUTE_INFO_OPTION_LIFETIME = 3600, // in seconds + + HW_ADDRESS_SIZE = 6, + }; + + int ifindex; + uint8_t hw_addr[HW_ADDRESS_SIZE]; + Data msg; + struct sockaddr_in6 saddr; + struct nd_router_advert ra; + struct nd_opt_hdr opt_hdr; + struct + { + uint8_t mType; + uint8_t mLength; // (in units of 8 octets) + uint8_t mPrefixLength; + uint8_t mPreferenceFlags; + uint32_t mLifetime; + /* followed by the prefix (variable-length) */ + } opt_route_info; + std::map::iterator iter; + int num_route_info_opt = 0; + + if (mInstance->mInterfaceRoutes.empty() && (mDefaultRouteLifetime == 0)) { + goto bail; + } + + ifindex = netif_mgmt_get_ifindex(mNetifMgmtFD, netif_name); + + if (ifindex < 0) { + syslog(LOG_WARNING, "Could not find ifindex for netif \"%s\"", netif_name); + goto bail; + } + + if (netif_mgmt_get_hw_address(mNetifMgmtFD, netif_name, hw_addr) < 0) { + syslog(LOG_WARNING, "Could not get the hw address for netif \"%s\"", netif_name); + goto bail; + } + + // Prepare the dest IPv6 address ff02::1 + memset(&saddr, 0 , sizeof(saddr)); + saddr.sin6_family = AF_INET6; + saddr.sin6_addr.s6_addr[0] = 0xff; + saddr.sin6_addr.s6_addr[1] = 0x02; + saddr.sin6_addr.s6_addr[15] = 0x01; + saddr.sin6_scope_id = ifindex; + + // Prepare the Router Advertisement ICMP6 message header + ra.nd_ra_type = ND_ROUTER_ADVERT; + ra.nd_ra_code = 0; + ra.nd_ra_cksum = 0; + ra.nd_ra_curhoplimit = 255; + + if (mDefaultRoutePreference > 0) { + ra.nd_ra_flags_reserved = ROUTE_INFO_OPTION_PRF_HIGH; + } else if (mDefaultRoutePreference == 0) { + ra.nd_ra_flags_reserved = ROUTE_INFO_OPTION_PRF_MEDIUM; + } else { + ra.nd_ra_flags_reserved = ROUTE_INFO_OPTION_PRF_LOW; + } + + ra.nd_ra_router_lifetime = htons(mDefaultRouteLifetime); + ra.nd_ra_reachable = htonl(ROUTE_INFO_OPTION_LIFETIME); + ra.nd_ra_retransmit = htonl(0); + + msg.append(reinterpret_cast(&ra), sizeof(ra)); + + // Prepare the `Source Link-Layer Address` option + opt_hdr.nd_opt_type = ND_OPT_SOURCE_LINKADDR; + opt_hdr.nd_opt_len = 1; // in units of 8 octets + msg.append(reinterpret_cast(&opt_hdr), sizeof(opt_hdr)); + msg.append(hw_addr, sizeof(hw_addr)); + + // Prepare `Route Info` option for each interface route + for (iter = mInstance->mInterfaceRoutes.begin(); iter != mInstance->mInterfaceRoutes.end(); ++iter) { + uint8_t prefix_len = iter->first.get_length(); + uint32_t metric = iter->second.get_metric(); + + opt_route_info.mType = ROUTE_INFO_OPTION_TYPE; + + if (prefix_len > 64) { + opt_route_info.mLength = 3; + } else if (prefix_len > 0) { + opt_route_info.mLength = 2; + } else { + opt_route_info.mLength = 1; + } + + opt_route_info.mPrefixLength = prefix_len; + + // Note that smaller metric is higher preference. + if (metric < NCPInstanceBase::InterfaceRouteEntry::kRouteMetricMedium) { + opt_route_info.mPreferenceFlags = ROUTE_INFO_OPTION_PRF_HIGH; + } else if (metric < NCPInstanceBase::InterfaceRouteEntry::kRouteMetricLow) { + opt_route_info.mPreferenceFlags = ROUTE_INFO_OPTION_PRF_MEDIUM; + } else { + opt_route_info.mPreferenceFlags = ROUTE_INFO_OPTION_PRF_LOW; + } + + opt_route_info.mLifetime = htonl(ROUTE_INFO_OPTION_LIFETIME); + + msg.append(reinterpret_cast(&opt_route_info), sizeof(opt_route_info)); + + if (prefix_len > 64) { + msg.append(iter->first.get_prefix().s6_addr, 16); + } else if (prefix_len > 0) { + msg.append(iter->first.get_prefix().s6_addr, 8); + } + + num_route_info_opt++; + } + + if (sendto(mSocket, msg.data(), msg.size(), 0, reinterpret_cast(&saddr), sizeof(saddr)) < 0) { + syslog(LOG_WARNING, "could not send router advert on netif \"%s\"", netif_name); + goto bail; + } + + syslog(LOG_INFO, "Sent ICMP6 RouterAdvert on \"%s\" (%d route info options)", netif_name, num_route_info_opt); + +bail: + return; +} + +int +nl::wpantund::ICMP6RouterAdvertiser::vprocess_event(int event, va_list args) +{ + EH_BEGIN(); + + while (true) { + EH_WAIT_UNTIL(ncp_state_is_associated(mInstance->get_ncp_state())); + + EH_WAIT_UNTIL_WITH_TIMEOUT(mTxPeriod, mStateChanged); + mStateChanged = false; + + if (mEnabled) { + for (std::set::iterator iter = mNetifs.begin(); iter != mNetifs.end(); ++iter) { + send_router_advert(iter->c_str()); + } + } + } + + EH_END(); +} + diff --git a/src/wpantund/ICMP6RouterAdvertiser.h b/src/wpantund/ICMP6RouterAdvertiser.h new file mode 100644 index 00000000..98b00f62 --- /dev/null +++ b/src/wpantund/ICMP6RouterAdvertiser.h @@ -0,0 +1,86 @@ +/* + * + * Copyright (c) 2019 Google. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Description: + * Module sending ICMPv6 Router Advertisement. + * + */ + +#ifndef __wpantund__ICMP6RouterAdvetiser__ +#define __wpantund__ICMP6RouterAdvetiser__ + +#include +#include + +#include "EventHandler.h" + +namespace nl { +namespace wpantund { + +class NCPInstanceBase; + +class ICMP6RouterAdvertiser : public EventHandler +{ +public: + enum { + MIN_ROUTER_ADV_TX_PERIOD = 4, // in seconds + MAX_ROUTER_ADV_TX_PERIOD = 1800, // in seconds + DEFAULT_ROUTER_ADV_TX_PERIOD = 10, // in seconds + }; + + ICMP6RouterAdvertiser(NCPInstanceBase* instance); + ~ICMP6RouterAdvertiser(void); + + void set_enabled(bool enabled) { mEnabled = enabled; mStateChanged = true; } + bool is_enabled(void) const { return mEnabled; } + + const std::set &get_netifs(void) const { return mNetifs; } + void add_netif(const std::string &netif) { mNetifs.insert(netif); mStateChanged = true;} + void remove_netif(const std::string &netif) { mNetifs.erase(netif); } + void clear_netifs(void) { mNetifs.clear(); } + + // RA period in seconds + void set_tx_period(uint32_t period); + uint32_t get_tx_period(void) const { return mTxPeriod; } + + void set_default_route_preference(int prf) { mDefaultRoutePreference = prf; mStateChanged = true; } + int get_default_route_preference(void) const { return mDefaultRoutePreference; } + + void set_default_route_lifetime(uint16_t lifetime) { mDefaultRouteLifetime = lifetime; mStateChanged = true; } + uint16_t get_default_route_lifetime(void) const { return mDefaultRouteLifetime; } + + void signal_routes_changed(void) { mStateChanged = true; } + + virtual int vprocess_event(int event, va_list args); +private: + void send_router_advert(const char *netif_name); + + NCPInstanceBase *mInstance; + int mSocket; + int mNetifMgmtFD; + bool mEnabled; + std::set mNetifs; + uint32_t mTxPeriod; + int mDefaultRoutePreference; + uint16_t mDefaultRouteLifetime; + bool mStateChanged; +}; + +}; // namespace wpantund +}; // namespace nl + +#endif /* defined(__wpantund__ICMP6RouterAdvetiser__) */ diff --git a/src/wpantund/Makefile.am b/src/wpantund/Makefile.am index 057e7465..71b07321 100644 --- a/src/wpantund/Makefile.am +++ b/src/wpantund/Makefile.am @@ -64,6 +64,8 @@ SOURCES = \ NCPConstants.h \ FirmwareUpgrade.h \ FirmwareUpgrade.cpp \ + ICMP6RouterAdvertiser.h \ + ICMP6RouterAdvertiser.cpp \ StatCollector.h \ StatCollector.cpp \ RunawayResetBackoffManager.cpp \ diff --git a/src/wpantund/NCPInstanceBase-Addresses.cpp b/src/wpantund/NCPInstanceBase-Addresses.cpp index 239ccd9c..0fa306b3 100644 --- a/src/wpantund/NCPInstanceBase-Addresses.cpp +++ b/src/wpantund/NCPInstanceBase-Addresses.cpp @@ -1209,6 +1209,7 @@ NCPInstanceBase::refresh_routes_on_interface(void) // new updated `mInterfaceRoutes` list. did_remove = true; + mICMP6RouterAdvertiser.signal_routes_changed(); break; } } @@ -1230,6 +1231,7 @@ NCPInstanceBase::refresh_routes_on_interface(void) syslog(LOG_INFO, "InterfaceRoutes: Adding %s", iter->second.get_description(iter->first).c_str()); mPrimaryInterface->add_route(&iter->first.get_prefix(), iter->first.get_length(), metric); mInterfaceRoutes[iter->first] = InterfaceRouteEntry(metric); + mICMP6RouterAdvertiser.signal_routes_changed(); } } } @@ -1248,6 +1250,7 @@ NCPInstanceBase::refresh_routes_on_interface(void) iter->second.get_description(iter->first).c_str()); mPrimaryInterface->add_route(&iter->first.get_prefix(), iter->first.get_length(), metric); mInterfaceRoutes[iter->first] = InterfaceRouteEntry(metric); + mICMP6RouterAdvertiser.signal_routes_changed(); } } } diff --git a/src/wpantund/NCPInstanceBase-AsyncIO.cpp b/src/wpantund/NCPInstanceBase-AsyncIO.cpp index 9f4d104e..dea73238 100644 --- a/src/wpantund/NCPInstanceBase-AsyncIO.cpp +++ b/src/wpantund/NCPInstanceBase-AsyncIO.cpp @@ -136,7 +136,8 @@ NCPInstanceBase::set_socket_adapter(const boost::shared_ptr &adap cms_t NCPInstanceBase::get_ms_to_next_event(void) { - cms_t ret(EventHandler::get_ms_to_next_event()); + cms_t ret = EventHandler::get_ms_to_next_event(); + cms_t router_adv_cms = mICMP6RouterAdvertiser.get_ms_to_next_event(); mSerialAdapter->update_fd_set(NULL, NULL, NULL, NULL, &ret); mPrimaryInterface->update_fd_set(NULL, NULL, NULL, NULL, &ret); @@ -153,6 +154,10 @@ NCPInstanceBase::get_ms_to_next_event(void) } } + if (ret > router_adv_cms) { + ret = router_adv_cms; + } + if (mRequestRouteRefresh) { ret = 0; } @@ -258,3 +263,14 @@ NCPInstanceBase::process(void) signal_fatal_error(ret); return; } + +int +NCPInstanceBase::vprocess_event(int event, va_list args) +{ + va_list tmp; + va_copy(tmp, args); + mICMP6RouterAdvertiser.vprocess_event(event, tmp); + va_end(tmp); + + return 0; +} \ No newline at end of file diff --git a/src/wpantund/NCPInstanceBase.cpp b/src/wpantund/NCPInstanceBase.cpp index bb342d31..0677f7d9 100644 --- a/src/wpantund/NCPInstanceBase.cpp +++ b/src/wpantund/NCPInstanceBase.cpp @@ -43,7 +43,8 @@ using namespace wpantund; NCPInstanceBase::NCPInstanceBase(const Settings& settings): mCommissioningRule(), - mCommissioningExpiration(0) + mCommissioningExpiration(0), + mICMP6RouterAdvertiser(this) { std::string wpan_interface_name = "wpan0"; @@ -330,6 +331,12 @@ NCPInstanceBase::get_supported_property_keys(void) const properties.insert(kWPANTUNDProperty_NestLabs_NetworkPassthruPort); + properties.insert(kWPANTUNDProperty_RouterAdvertEnable); + properties.insert(kWPANTUNDProperty_RouterAdvertNetifs); + properties.insert(kWPANTUNDProperty_RouterAdvertTxPeriod); + properties.insert(kWPANTUNDProperty_RouterAdvertDefaultRoutePreference); + properties.insert(kWPANTUNDProperty_RouterAdvertDefaultRouteLifetime); + return properties; } @@ -406,6 +413,11 @@ NCPInstanceBase::regsiter_all_get_handlers(void) REGISTER_GET_HANDLER(IPv6MulticastAddresses); REGISTER_GET_HANDLER(IPv6InterfaceRoutes); REGISTER_GET_HANDLER(DaemonSyslogMask); + REGISTER_GET_HANDLER(RouterAdvertEnable); + REGISTER_GET_HANDLER(RouterAdvertNetifs); + REGISTER_GET_HANDLER(RouterAdvertTxPeriod); + REGISTER_GET_HANDLER(RouterAdvertDefaultRoutePreference); + REGISTER_GET_HANDLER(RouterAdvertDefaultRouteLifetime); #undef REGISTER_GET_HANDLER } @@ -819,6 +831,36 @@ NCPInstanceBase::get_prop_DaemonSyslogMask(CallbackWithStatusArg1 cb) cb(kWPANTUNDStatus_Ok, mask_string); } +void +NCPInstanceBase::get_prop_RouterAdvertEnable(CallbackWithStatusArg1 cb) +{ + cb(kWPANTUNDStatus_Ok, boost::any(mICMP6RouterAdvertiser.is_enabled())); +} + +void +NCPInstanceBase::get_prop_RouterAdvertNetifs(CallbackWithStatusArg1 cb) +{ + cb(kWPANTUNDStatus_Ok, boost::any(mICMP6RouterAdvertiser.get_netifs())); +} + +void +NCPInstanceBase::get_prop_RouterAdvertTxPeriod(CallbackWithStatusArg1 cb) +{ + cb(kWPANTUNDStatus_Ok, boost::any(mICMP6RouterAdvertiser.get_tx_period())); +} + +void +NCPInstanceBase::get_prop_RouterAdvertDefaultRoutePreference(CallbackWithStatusArg1 cb) +{ + cb(kWPANTUNDStatus_Ok, boost::any(mICMP6RouterAdvertiser.get_default_route_preference())); +} + +void +NCPInstanceBase::get_prop_RouterAdvertDefaultRouteLifetime(CallbackWithStatusArg1 cb) +{ + cb(kWPANTUNDStatus_Ok, boost::any(mICMP6RouterAdvertiser.get_default_route_lifetime())); +} + // ---------------------------------------------------------------------------- // MARK: - // MARK: Property Set Handlers @@ -854,6 +896,11 @@ NCPInstanceBase::regsiter_all_set_handlers(void) REGISTER_SET_HANDLER(IPv6MeshLocalAddress); REGISTER_SET_HANDLER(DaemonAutoDeepSleep); REGISTER_SET_HANDLER(DaemonSyslogMask); + REGISTER_SET_HANDLER(RouterAdvertEnable); + REGISTER_SET_HANDLER(RouterAdvertNetifs); + REGISTER_SET_HANDLER(RouterAdvertTxPeriod); + REGISTER_SET_HANDLER(RouterAdvertDefaultRoutePreference); + REGISTER_SET_HANDLER(RouterAdvertDefaultRouteLifetime); #undef REGISTER_SET_HANDLER } @@ -1091,6 +1138,78 @@ NCPInstanceBase::set_prop_DaemonSyslogMask(const boost::any &value, CallbackWith } +void +NCPInstanceBase::set_prop_RouterAdvertEnable(const boost::any &value, CallbackWithStatus cb) +{ + mICMP6RouterAdvertiser.set_enabled(any_to_bool(value)); + cb(kWPANTUNDStatus_Ok); +} + +void +NCPInstanceBase::set_prop_RouterAdvertNetifs(const boost::any &value, CallbackWithStatus cb) +{ + std::string names_str = any_to_string(value); + char names[500]; + char *p, *start; + bool did_end = false; + + mICMP6RouterAdvertiser.clear_netifs(); + + if (names_str.size() < sizeof(names) - 1) { + memcpy(names, names_str.c_str(), names_str.size()); + names[names_str.size()] = '\0'; + } else { + memcpy(names, names_str.c_str(), sizeof(names) - 1); + names[sizeof(names) - 1] = '\0'; + } + + p = names; + + do { + while ((*p != '\0') && (*p == ' ')) { + p++; + } + + start = p; + + while ((*p != '\0') && (*p != ' ')) { + p++; + } + + did_end = (*p == '\0'); + + if (start != p) { + *p = '\0'; + mICMP6RouterAdvertiser.add_netif(std::string(start)); + p++; + } + } while (!did_end); + +bail: + cb(kWPANTUNDStatus_Ok); +} + +void +NCPInstanceBase::set_prop_RouterAdvertTxPeriod(const boost::any &value, CallbackWithStatus cb) +{ + mICMP6RouterAdvertiser.set_tx_period(any_to_int(value)); + cb(kWPANTUNDStatus_Ok); +} + +void +NCPInstanceBase::set_prop_RouterAdvertDefaultRoutePreference(const boost::any &value, CallbackWithStatus cb) +{ + mICMP6RouterAdvertiser.set_default_route_preference(any_to_int(value)); + cb(kWPANTUNDStatus_Ok); +} + +void +NCPInstanceBase::set_prop_RouterAdvertDefaultRouteLifetime(const boost::any &value, CallbackWithStatus cb) +{ + mICMP6RouterAdvertiser.set_default_route_lifetime(any_to_int(value)); + cb(kWPANTUNDStatus_Ok); +} + // ---------------------------------------------------------------------------- // MARK: - // MARK: Property Insert Handlers @@ -1111,6 +1230,7 @@ NCPInstanceBase::regsiter_all_insert_handlers(void) boost::bind(&NCPInstanceBase::insert_prop_##name, this, _1, _2)) REGISTER_INSERT_HANDLER(IPv6MulticastAddresses); + REGISTER_INSERT_HANDLER(RouterAdvertNetifs); #undef REGISTER_INSERT_HANDLER } @@ -1151,6 +1271,13 @@ NCPInstanceBase::insert_prop_IPv6MulticastAddresses(const boost::any &value, Cal multicast_address_was_joined(kOriginUser, address, cb); } +void +NCPInstanceBase::insert_prop_RouterAdvertNetifs(const boost::any &value, CallbackWithStatus cb) +{ + mICMP6RouterAdvertiser.add_netif(any_to_string(value)); + cb(kWPANTUNDStatus_Ok); +} + // ---------------------------------------------------------------------------- // MARK: - // MARK: Property Remove Handlers @@ -1171,6 +1298,7 @@ NCPInstanceBase::regsiter_all_remove_handlers(void) boost::bind(&NCPInstanceBase::remove_prop_##name, this, _1, _2)) REGISTER_REMOVE_HANDLER(IPv6MulticastAddresses); + REGISTER_REMOVE_HANDLER(RouterAdvertNetifs); #undef REGISTER_REMOVE_HANDLER } @@ -1210,6 +1338,13 @@ NCPInstanceBase::remove_prop_IPv6MulticastAddresses(const boost::any &value, Cal multicast_address_was_left(kOriginUser, address, cb); } +void +NCPInstanceBase::remove_prop_RouterAdvertNetifs(const boost::any &value, CallbackWithStatus cb) +{ + mICMP6RouterAdvertiser.remove_netif(any_to_string(value)); + cb(kWPANTUNDStatus_Ok); +} + void NCPInstanceBase::signal_property_changed( const std::string& key, diff --git a/src/wpantund/NCPInstanceBase.h b/src/wpantund/NCPInstanceBase.h index 57a9bbd5..ce43eea1 100644 --- a/src/wpantund/NCPInstanceBase.h +++ b/src/wpantund/NCPInstanceBase.h @@ -30,12 +30,14 @@ #include "StatCollector.h" #include "NetworkRetain.h" #include "RunawayResetBackoffManager.h" +#include "ICMP6RouterAdvertiser.h" #include "Pcap.h" namespace nl { namespace wpantund { class NCPInstanceBase : public NCPInstance, public EventHandler { + friend class ICMP6RouterAdvertiser; public: enum { @@ -66,6 +68,7 @@ class NCPInstanceBase : public NCPInstance, public EventHandler { // MARK: ASync I/O virtual cms_t get_ms_to_next_event(void); + virtual int vprocess_event(int event, va_list args); virtual int update_fd_set( fd_set *read_fd_set, @@ -364,6 +367,11 @@ class NCPInstanceBase : public NCPInstance, public EventHandler { void get_prop_IPv6MulticastAddresses(CallbackWithStatusArg1 cb); void get_prop_IPv6InterfaceRoutes(CallbackWithStatusArg1 cb); void get_prop_DaemonSyslogMask(CallbackWithStatusArg1 cb); + void get_prop_RouterAdvertEnable(CallbackWithStatusArg1 cb); + void get_prop_RouterAdvertNetifs(CallbackWithStatusArg1 cb); + void get_prop_RouterAdvertTxPeriod(CallbackWithStatusArg1 cb); + void get_prop_RouterAdvertDefaultRoutePreference(CallbackWithStatusArg1 cb); + void get_prop_RouterAdvertDefaultRouteLifetime(CallbackWithStatusArg1 cb); void regsiter_all_set_handlers(void); @@ -385,14 +393,21 @@ class NCPInstanceBase : public NCPInstance, public EventHandler { void set_prop_IPv6MeshLocalAddress(const boost::any &value, CallbackWithStatus cb); void set_prop_DaemonAutoDeepSleep(const boost::any &value, CallbackWithStatus cb); void set_prop_DaemonSyslogMask(const boost::any &value, CallbackWithStatus cb); + void set_prop_RouterAdvertEnable(const boost::any &value, CallbackWithStatus cb); + void set_prop_RouterAdvertNetifs(const boost::any &value, CallbackWithStatus cb); + void set_prop_RouterAdvertTxPeriod(const boost::any &value, CallbackWithStatus cb); + void set_prop_RouterAdvertDefaultRoutePreference(const boost::any &value, CallbackWithStatus cb); + void set_prop_RouterAdvertDefaultRouteLifetime(const boost::any &value, CallbackWithStatus cb); void regsiter_all_insert_handlers(void); void insert_prop_IPv6MulticastAddresses(const boost::any &value, CallbackWithStatus cb); + void insert_prop_RouterAdvertNetifs(const boost::any &value, CallbackWithStatus cb); void regsiter_all_remove_handlers(void); void remove_prop_IPv6MulticastAddresses(const boost::any &value, CallbackWithStatus cb); + void remove_prop_RouterAdvertNetifs(const boost::any &value, CallbackWithStatus cb); private: @@ -776,6 +791,8 @@ class NCPInstanceBase : public NCPInstance, public EventHandler { RunawayResetBackoffManager mRunawayResetBackoffManager; + ICMP6RouterAdvertiser mICMP6RouterAdvertiser; + protected: // ======================================================================== // MARK: Legacy Interface Support diff --git a/src/wpantund/wpan-properties.h b/src/wpantund/wpan-properties.h index 5cc099aa..c49542e3 100644 --- a/src/wpantund/wpan-properties.h +++ b/src/wpantund/wpan-properties.h @@ -339,6 +339,12 @@ #define kWPANTUNDProperty_ThreadLeaderServices "Thread:Leader:Services" #define kWPANTUNDProperty_ThreadLeaderServicesAsValMap "Thread:Leader:Services:AsValMap" +#define kWPANTUNDProperty_RouterAdvertEnable "RouterAdvert:Enable" +#define kWPANTUNDProperty_RouterAdvertNetifs "RouterAdvert:Netifs" +#define kWPANTUNDProperty_RouterAdvertTxPeriod "RouterAdvert:TxPeriod" +#define kWPANTUNDProperty_RouterAdvertDefaultRoutePreference "RouterAdvert:DefaultRoute:Preference" +#define kWPANTUNDProperty_RouterAdvertDefaultRouteLifetime "RouterAdvert:DefaultRoute:Lifetime" + // ---------------------------------------------------------------------------- #define kWPANTUNDNodeType_Unknown "unknown"