LCOV - code coverage report
Current view: top level - AP_Networking - AP_Networking.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 30 156 19.2 %
Date: 2026-09-09 11:02:08 Functions: 6 21 28.6 %

          Line data    Source code
       1             : 
       2             : #include "AP_Networking_Config.h"
       3             : 
       4             : #if AP_NETWORKING_ENABLED
       5             : 
       6             : #include "AP_Networking.h"
       7             : #include "AP_Networking_Backend.h"
       8             : #include <GCS_MAVLink/GCS.h>
       9             : #include <AP_Math/crc.h>
      10             : #include <AP_InternalError/AP_InternalError.h>
      11             : #include <AP_Filesystem/AP_Filesystem.h>
      12             : #include <AP_HAL/CANIface.h>
      13             : 
      14             : extern const AP_HAL::HAL& hal;
      15             : 
      16             : #if AP_NETWORKING_BACKEND_CHIBIOS
      17             : #include "AP_Networking_ChibiOS.h"
      18             : #include <hal_mii.h>
      19             : #endif
      20             : 
      21             : #include <lwipopts.h>
      22             : #include <errno.h>
      23             : 
      24             : 
      25             : #include <AP_HAL/utility/Socket.h>
      26             : 
      27             : #if AP_NETWORKING_BACKEND_PPP
      28             : #include "AP_Networking_PPP.h"
      29             : #endif
      30             : 
      31             : #if AP_NETWORKING_BACKEND_SITL
      32             : #include "AP_Networking_SITL.h"
      33             : #endif
      34             : 
      35             : #if AP_NETWORKING_BACKEND_SITL_TUN
      36             : #include "AP_Networking_SITL_TUN.h"
      37             : #endif
      38             : 
      39             : const AP_Param::GroupInfo AP_Networking::var_info[] = {
      40             :     // @Param: ENABLE
      41             :     // @DisplayName: Networking Enable
      42             :     // @Description: Networking Enable
      43             :     // @Values: 0:Disable,1:Enable
      44             :     // @RebootRequired: True
      45             :     // @User: Advanced
      46             :     AP_GROUPINFO_FLAGS("ENABLE",  1, AP_Networking, param.enabled, 0, AP_PARAM_FLAG_ENABLE),
      47             : 
      48             : #if AP_NETWORKING_CONTROLS_HOST_IP_SETTINGS_ENABLED
      49             :     // @Group: IPADDR
      50             :     // @Path: AP_Networking_address.cpp
      51             :     AP_SUBGROUPINFO(param.ipaddr, "IPADDR", 2,  AP_Networking, AP_Networking_IPV4),
      52             : 
      53             :     // @Param: NETMASK
      54             :     // @DisplayName: IP Subnet mask
      55             :     // @Description: Allows setting static subnet mask. The value is a count of consecutive bits. Examples: 24 = 255.255.255.0, 16 = 255.255.0.0
      56             :     // @Range: 0 32
      57             :     // @RebootRequired: True
      58             :     // @User: Advanced
      59             :     AP_GROUPINFO("NETMASK", 3,  AP_Networking,    param.netmask,   AP_NETWORKING_DEFAULT_NETMASK),
      60             : 
      61             : #if AP_NETWORKING_DHCP_AVAILABLE
      62             :     // @Param: DHCP
      63             :     // @DisplayName: DHCP client
      64             :     // @Description: Enable/Disable DHCP client
      65             :     // @Values: 0:Disable, 1:Enable
      66             :     // @RebootRequired: True
      67             :     // @User: Advanced
      68             :     AP_GROUPINFO("DHCP", 4,  AP_Networking,    param.dhcp,   AP_NETWORKING_DEFAULT_DHCP_ENABLE),
      69             : #endif
      70             : 
      71             :     // @Group: GWADDR
      72             :     // @Path: AP_Networking_address.cpp
      73             :     AP_SUBGROUPINFO(param.gwaddr, "GWADDR", 5,  AP_Networking, AP_Networking_IPV4),
      74             : 
      75             :     // @Group: MACADDR
      76             :     // @Path: AP_Networking_macaddr.cpp
      77             :     AP_SUBGROUPINFO(param.macaddr, "MACADDR", 6,  AP_Networking, AP_Networking_MAC),
      78             : #endif // AP_NETWORKING_CONTROLS_HOST_IP_SETTINGS_ENABLED
      79             : 
      80             : #if AP_NETWORKING_TESTS_ENABLED
      81             :     // @Param: TESTS
      82             :     // @DisplayName: Test enable flags
      83             :     // @Description: Enable/Disable networking tests
      84             :     // @Bitmask: 0:UDP echo test,1:TCP echo test, 2:TCP discard test, 3:TCP reflect test
      85             :     // @RebootRequired: True
      86             :     // @User: Advanced
      87             :     AP_GROUPINFO("TESTS", 7,  AP_Networking,    param.tests,   0),
      88             : 
      89             :     // @Group: TEST_IP
      90             :     // @Path: AP_Networking_address.cpp
      91             :     AP_SUBGROUPINFO(param.test_ipaddr, "TEST_IP", 8,  AP_Networking, AP_Networking_IPV4),
      92             : #endif
      93             : 
      94             :     // @Param: OPTIONS
      95             :     // @DisplayName: Networking options
      96             :     // @Description: Networking options
      97             :     // @Bitmask: 0:EnablePPP Ethernet gateway, 1:Enable CAN1 multicast endpoint, 2:Enable CAN2 multicast endpoint, 3:Enable CAN1 multicast bridged, 4:Enable CAN2 multicast bridged, 5:DisablePPPTimeout, 6:DisablePPPEchoLimit, 7:Capture to file
      98             :     // @RebootRequired: True
      99             :     // @User: Advanced
     100             :     AP_GROUPINFO("OPTIONS", 9,  AP_Networking,    param.options, 0),
     101             : 
     102             : #if AP_NETWORKING_PPP_GATEWAY_ENABLED
     103             :     // @Group: REMPPP_IP
     104             :     // @Path: AP_Networking_address.cpp
     105             :     AP_SUBGROUPINFO(param.remote_ppp_ip, "REMPPP_IP", 10,  AP_Networking, AP_Networking_IPV4),
     106             : #endif
     107             :     
     108             :     AP_GROUPEND
     109             : };
     110             : 
     111             : /*
     112             :   constructor
     113             :  */
     114       10490 : AP_Networking::AP_Networking(void)
     115             : {
     116             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     117        2096 :     if (singleton != nullptr) {
     118           0 :         AP_HAL::panic("AP_Networking must be singleton");
     119             :     }
     120             : #endif
     121        2098 :     singleton = this;
     122        2098 :     AP_Param::setup_object_defaults(this, var_info);
     123        2098 : }
     124             : 
     125             : /*
     126             :   initialise networking subsystem
     127             :  */
     128        2094 : void AP_Networking::init()
     129             : {
     130        2094 :     if (!param.enabled || backend != nullptr) {
     131        2086 :         return;
     132             :     }
     133             : 
     134             : #if AP_NETWORKING_CONTROLS_HOST_IP_SETTINGS_ENABLED
     135             :     // set default MAC Address as lower 3 bytes of the CRC of the UID
     136             :     uint8_t uid[50];
     137             :     uint8_t uid_len = sizeof(uid);
     138             :     if (hal.util->get_system_id_unformatted(uid, uid_len)) {
     139             :         union {
     140             :             uint8_t bytes[4];
     141             :             uint32_t value32;
     142             :         } crc;
     143             :         crc.value32 = crc_crc32(0, uid, uid_len);
     144             : 
     145             :         param.macaddr.set_default_address_byte(3, crc.bytes[0]);
     146             :         param.macaddr.set_default_address_byte(4, crc.bytes[1]);
     147             :         param.macaddr.set_default_address_byte(5, crc.bytes[2]);
     148             :     }
     149             : #endif
     150             : 
     151             : #if AP_NETWORKING_BACKEND_CHIBIOS && AP_NETWORKING_PPP_GATEWAY_ENABLED
     152             :     if (option_is_set(OPTION::PPP_ETHERNET_GATEWAY)) {
     153             :         /*
     154             :           when we are a PPP/Ethernet gateway we bring up the ethernet first
     155             :          */
     156             :         backend = NEW_NOTHROW AP_Networking_ChibiOS(*this);
     157             :         backend_PPP = NEW_NOTHROW AP_Networking_PPP(*this);
     158             :     }
     159             : #endif
     160             : 
     161             : #if AP_NETWORKING_BACKEND_SITL_TUN && AP_NETWORKING_PPP_GATEWAY_ENABLED
     162             :     // SITL TAP backend takes the same "ethernet" slot the ChibiOS backend
     163             :     // would on real hardware. Lets a sitl_periph_PPP build act as a PPPGW
     164             :     // whose ethernet side is a host TAP device, so developer tools on the
     165             :     // host can talk to the lwIP-served sockets (e.g. the PPPGW web UI).
     166             :     if (backend == nullptr && option_is_set(OPTION::PPP_ETHERNET_GATEWAY)) {
     167             :         backend = NEW_NOTHROW AP_Networking_SITL_TUN(*this);
     168             :         backend_PPP = NEW_NOTHROW AP_Networking_PPP(*this);
     169             :     }
     170             : #endif
     171             : 
     172             : 
     173             : #if AP_NETWORKING_BACKEND_PPP
     174             :     if (backend == nullptr && AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_PPP, 0)) {
     175             :         backend = NEW_NOTHROW AP_Networking_PPP(*this);
     176             :     }
     177             : #endif
     178             : 
     179             : #if AP_NETWORKING_BACKEND_CHIBIOS
     180             :     if (backend == nullptr) {
     181             :         backend = NEW_NOTHROW AP_Networking_ChibiOS(*this);
     182             :     }
     183             : #endif
     184             : #if AP_NETWORKING_BACKEND_SITL
     185           8 :     if (backend == nullptr) {
     186           8 :         backend = NEW_NOTHROW AP_Networking_SITL(*this);
     187             :     }
     188             : #endif
     189             : 
     190           8 :     if (backend == nullptr) {
     191           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "NET: backend failed");
     192           0 :         return;
     193             :     }
     194             : 
     195           8 :     if (!backend->init()) {
     196           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "NET: backend init failed");
     197             :         // the backend init function creates a thread which references the backend pointer; that thread may be running so don't remove the backend allocation.
     198           0 :         backend = nullptr;
     199           0 :         return;
     200             :     }
     201             : 
     202             : #if AP_NETWORKING_PPP_GATEWAY_ENABLED
     203             :     if (backend_PPP != nullptr && !backend_PPP->init()) {
     204             :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "NET: backend_PPP init failed");
     205             :         backend_PPP = nullptr;
     206             :     }
     207             : #endif
     208             : 
     209           8 :     announce_address_changes();
     210             : 
     211           8 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO,"NET: Initialized");
     212             : 
     213             : #if AP_NETWORKING_TESTS_ENABLED
     214             :     start_tests();
     215             : #endif
     216             : 
     217             : #if AP_NETWORKING_CAN_MCAST_BRIDGING_ENABLED
     218             :     if (option_is_set(OPTION::CAN1_MCAST_ENDPOINT) ||
     219             :         option_is_set(OPTION::CAN2_MCAST_ENDPOINT)) {
     220             :         // get mask of enabled buses
     221             :         uint8_t bus_mask = 0;
     222             :         if (option_is_set(OPTION::CAN1_MCAST_ENDPOINT)) {
     223             :             bus_mask |= (1U<<0);
     224             :         }
     225             :         if (option_is_set(OPTION::CAN2_MCAST_ENDPOINT)) {
     226             :             bus_mask |= (1U<<1);
     227             :         }
     228             :         mcast_server.start(bus_mask);
     229             :     }
     230             : #endif
     231             :     
     232             : #if AP_NETWORKING_REGISTER_PORT_ENABLED
     233             :     // init network mapped serialmanager ports
     234           8 :     ports_init();
     235             : #endif
     236             : }
     237             : 
     238             : /*
     239             :   check if we should announce changes to IP addresses
     240             :  */
     241        5597 : void AP_Networking::announce_address_changes()
     242             : {
     243        5597 :     const auto &as = backend->activeSettings;
     244             : 
     245        5597 :     if (as.last_change_ms == 0 || as.last_change_ms == announce_ms) {
     246             :         // nothing changed and we've already printed it at least once. Nothing to do.
     247        5597 :         return;
     248             :     }
     249             : 
     250             : #if AP_HAVE_GCS_SEND_TEXT
     251             :     char ipstr[16];
     252           0 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "NET: IP      %s", SocketAPM::inet_addr_to_str(get_ip_active(), ipstr, sizeof(ipstr)));
     253           0 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "NET: Mask    %s", SocketAPM::inet_addr_to_str(get_netmask_active(), ipstr, sizeof(ipstr)));
     254           0 :     GCS_SEND_TEXT(MAV_SEVERITY_INFO, "NET: Gateway %s", SocketAPM::inet_addr_to_str(get_gateway_active(), ipstr, sizeof(ipstr)));
     255             : #endif
     256             : 
     257           0 :     announce_ms = as.last_change_ms;
     258             : }
     259             : 
     260             : /*
     261             :   update called at 10Hz
     262             :  */
     263     1504089 : void AP_Networking::update()
     264             : {
     265     1504089 :     if (!is_healthy()) {
     266     1498500 :         return;
     267             :     }
     268        5589 :     backend->update();
     269        5589 :     announce_address_changes();
     270             : }
     271             : 
     272           0 : uint32_t AP_Networking::convert_netmask_bitcount_to_ip(const uint32_t netmask_bitcount)
     273             : {
     274           0 :     if (netmask_bitcount >= 32) {
     275           0 :         return 0xFFFFFFFFU;
     276             :     }
     277           0 :     return ~((1U<<(32U-netmask_bitcount))-1U);
     278             : }
     279             : 
     280           0 : uint8_t AP_Networking::convert_netmask_ip_to_bitcount(const uint32_t netmask_ip)
     281             : {
     282           0 :     uint32_t netmask_bitcount = 0;
     283           0 :     for (uint32_t i=0; i<32; i++) {
     284             :         // note, netmask LSB is IP MSB
     285           0 :         if ((netmask_ip & (1UL<<i)) == 0) {
     286           0 :             break;
     287             :         }
     288           0 :         netmask_bitcount++;
     289             :     }
     290           0 :     return netmask_bitcount;
     291             : }
     292             : 
     293             : /*
     294             :   convert a string to an ethernet MAC address
     295             :  */
     296           0 : bool AP_Networking::convert_str_to_macaddr(const char *mac_str, uint8_t addr[6])
     297             : {
     298           0 :     if (strlen(mac_str) != 17) {
     299           0 :         return false;
     300             :     }
     301             :     char s2[18];
     302           0 :     strncpy(s2, mac_str, sizeof(s2)-1);
     303           0 :     s2[17] = 0;
     304           0 :     char *ptr = nullptr;
     305           0 :     const char *s = strtok_r(s2, ":", &ptr);
     306           0 :     for (uint8_t i=0; i<6; i++) {
     307           0 :         if (s == nullptr) {
     308           0 :             return false;
     309             :         }
     310           0 :         auto v = strtoul(s, nullptr, 16);
     311           0 :         if (v > 255) {
     312           0 :             return false;
     313             :         }
     314           0 :         addr[i] = v;
     315           0 :         s = strtok_r(nullptr, ":", &ptr);
     316             :     }
     317           0 :     return true;
     318             : }
     319             : 
     320             : // returns the 32bit value of the active IP address that is currently in use
     321           0 : uint32_t AP_Networking::get_ip_active() const
     322             : {
     323           0 :     return backend?backend->activeSettings.ip:0;
     324             : }
     325             : 
     326             : // returns the 32bit value of the active Netmask that is currently in use
     327           0 : uint32_t AP_Networking::get_netmask_active() const
     328             : {
     329           0 :     return backend?backend->activeSettings.nm:0;
     330             : }
     331             : 
     332           0 : uint32_t AP_Networking::get_gateway_active() const
     333             : {
     334           0 :     return backend?backend->activeSettings.gw:0;
     335             : }
     336             : 
     337             : /*
     338             :   wait for networking to be active
     339             :  */
     340          11 : void AP_Networking::startup_wait(void) const
     341             : {
     342          11 :     if (hal.scheduler->in_main_thread()) {
     343           0 :         INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
     344           0 :         return;
     345             :     }
     346         235 :     while (!hal.scheduler->is_system_initialized()) {
     347         224 :         hal.scheduler->delay(100);
     348             :     }
     349             : #if AP_NETWORKING_BACKEND_CHIBIOS
     350             :     do {
     351             :         hal.scheduler->delay(250);
     352             :     } while (get_ip_active() == 0);
     353             : #endif
     354             : }
     355             : 
     356             : /*
     357             :   send the rest of a file to a socket
     358             :  */
     359           0 : bool AP_Networking::sendfile(SocketAPM *sock, int fd)
     360             : {
     361           0 :     WITH_SEMAPHORE(sem);
     362           0 :     if (sendfile_buf == nullptr) {
     363           0 :         uint32_t bufsize = AP_NETWORKING_SENDFILE_BUFSIZE;
     364             :         do {
     365           0 :             sendfile_buf = (uint8_t *)hal.util->malloc_type(bufsize, AP_HAL::Util::MEM_FILESYSTEM);
     366           0 :             if (sendfile_buf != nullptr) {
     367           0 :                 sendfile_bufsize = bufsize;
     368           0 :                 break;
     369             :             }
     370           0 :             bufsize /= 2;
     371           0 :         } while (bufsize >= 4096);
     372           0 :         if (sendfile_buf == nullptr) {
     373           0 :             return false;
     374             :         }
     375             :     }
     376           0 :     if (!sendfile_thread_started) {
     377           0 :         if (!hal.scheduler->thread_create(FUNCTOR_BIND_MEMBER(&AP_Networking::sendfile_check, void),
     378             :                                           "sendfile",
     379             :                                           2048, AP_HAL::Scheduler::PRIORITY_UART, 0)) {
     380           0 :             return false;
     381             :         }
     382           0 :         sendfile_thread_started = true;
     383             :     }
     384           0 :     for (auto &s : sendfiles) {
     385           0 :         if (s.sock == nullptr) {
     386           0 :             s.sock = sock->duplicate();
     387           0 :             if (s.sock == nullptr) {
     388           0 :                 return false;
     389             :             }
     390           0 :             s.fd = fd;
     391           0 :             return true;
     392             :         }
     393             :     }
     394           0 :     return false;
     395           0 : }
     396             : 
     397           0 : void AP_Networking::SendFile::close(void)
     398             : {
     399           0 :     AP::FS().close(fd);
     400           0 :     delete sock;
     401           0 :     sock = nullptr;
     402           0 : }
     403             : 
     404             : #include <stdio.h>
     405             : /*
     406             :   check for sendfile updates
     407             :  */
     408           0 : void AP_Networking::sendfile_check(void)
     409             : {
     410             :     while (true) {
     411           0 :         hal.scheduler->delay(1);
     412           0 :         WITH_SEMAPHORE(sem);
     413           0 :         bool none_active = true;
     414           0 :         for (auto &s : sendfiles) {
     415           0 :             if (s.sock == nullptr) {
     416           0 :                 continue;
     417             :             }
     418           0 :             none_active = false;
     419           0 :             if (!s.sock->pollout(0)) {
     420           0 :                 continue;
     421             :             }
     422           0 :             const auto nread = AP::FS().read(s.fd, sendfile_buf, sendfile_bufsize);
     423           0 :             if (nread <= 0) {
     424           0 :                 s.close();
     425           0 :                 continue;
     426             :             }
     427           0 :             const auto nsent = s.sock->send(sendfile_buf, nread);
     428           0 :             if (nsent <= 0) {
     429           0 :                 s.close();
     430           0 :                 continue;
     431             :             }
     432           0 :             if (nsent < nread) {
     433           0 :                 AP::FS().lseek(s.fd, nsent - nread, SEEK_CUR);
     434             :             }
     435             :         }
     436           0 :         if (none_active) {
     437           0 :             free(sendfile_buf);
     438           0 :             sendfile_buf = nullptr;
     439             :         }
     440           0 :     }
     441             : }
     442             : 
     443             : AP_Networking *AP_Networking::singleton;
     444             : 
     445             : namespace AP
     446             : {
     447          11 : AP_Networking &network()
     448             : {
     449          11 :     return *AP_Networking::get_singleton();
     450             : }
     451             : }
     452             : 
     453             : /*
     454             :   debug printfs from LWIP
     455             :  */
     456           0 : int ap_networking_printf(const char *fmt, ...)
     457             : {
     458           0 :     WITH_SEMAPHORE(AP::network().get_semaphore());
     459             : #ifdef AP_NETWORKING_LWIP_DEBUG_FILE
     460             :     static int fd = -1;
     461             :     if (fd == -1) {
     462             :         fd = AP::FS().open(AP_NETWORKING_LWIP_DEBUG_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644);
     463             :         if (fd == -1) {
     464             :             return -1;
     465             :         }
     466             :     }
     467             :     va_list ap;
     468             :     va_start(ap, fmt);
     469             :     vdprintf(fd, fmt, ap);
     470             :     va_end(ap);
     471             : #else
     472             :     va_list ap;
     473           0 :     va_start(ap, fmt);
     474           0 :     hal.console->vprintf(fmt, ap);
     475           0 :     va_end(ap);
     476             : #endif
     477           0 :     return 0;
     478           0 : }
     479             : 
     480             : // address to string using a static return buffer
     481           0 : const char *AP_Networking::address_to_str(uint32_t addr)
     482             : {
     483             :     static char buf[16]; // 16 for aaa.bbb.ccc.ddd
     484           0 :     return SocketAPM::inet_addr_to_str(addr, buf, sizeof(buf));
     485             : }
     486             : 
     487             : #ifdef LWIP_PLATFORM_ASSERT
     488             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL || defined(HAL_DEBUG_BUILD)
     489           0 : void ap_networking_platform_assert(const char *msg, int line)
     490             : {
     491           0 :     AP_HAL::panic("LWIP:%u %s", line, msg);
     492             : }
     493             : #else
     494             : void ap_networking_platform_assert(int line)
     495             : {
     496             :     AP_HAL::panic("LWIP:%u", line);
     497             : }
     498             : #endif
     499             : #endif
     500             : 
     501             : #ifdef LWIP_HOOK_IP4_ROUTE
     502             : #include <lwip/ip4_addr.h>
     503           0 : struct netif *ap_networking_routing_hook(const struct ip4_addr *dest_ip)
     504             : {
     505           0 :     if (dest_ip == nullptr) {
     506           0 :         return nullptr;
     507             :     }
     508           0 :     return AP::network().routing_hook(ntohl(dest_ip->addr));
     509             : }
     510             : #endif
     511             : 
     512             : /*
     513             :   check for custom routes
     514             :  */
     515           0 : struct netif *AP_Networking::routing_hook(uint32_t dest)
     516             : {
     517           0 :     if (backend) {
     518           0 :         auto *iface = backend->routing_hook(dest);
     519           0 :         if (iface != nullptr) {
     520           0 :             return iface;
     521             :         }
     522             :     }
     523             : #if AP_NETWORKING_PPP_GATEWAY_ENABLED
     524             :     if (backend_PPP) {
     525             :         auto *iface = backend_PPP->routing_hook(dest);
     526             :         if (iface != nullptr) {
     527             :             return iface;
     528             :         }
     529             :     }
     530             : #endif
     531           0 :     return nullptr;
     532             : }
     533             : 
     534             : // add new routes for an interface.
     535             : // Returns true if the route is added or the route already exists
     536           0 : bool AP_Networking::add_route(uint8_t backend_idx, uint8_t iface_idx, uint32_t dest_ip, uint8_t mask_len)
     537             : {
     538           0 :     if (backend_idx == 0 &&
     539           0 :         backend != nullptr &&
     540           0 :         backend->add_route(iface_idx, dest_ip, mask_len)) {
     541           0 :         return true;
     542             :     }
     543             : #if AP_NETWORKING_PPP_GATEWAY_ENABLED
     544             :     if (backend_idx == 1 &&
     545             :         backend_PPP != nullptr &&
     546             :         backend_PPP->add_route(iface_idx, dest_ip, mask_len)) {
     547             :         return true;
     548             :     }
     549             : #endif
     550           0 :     return false;
     551             : }
     552             : 
     553             : 
     554             : #endif // AP_NETWORKING_ENABLED

Generated by: LCOV version 1.14