LCOV - code coverage report
Current view: top level - libsystemd-network - sd-ndisc.c (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 145 210 69.0 %
Date: 2019-08-22 15:41:25 Functions: 19 23 82.6 %

          Line data    Source code
       1             : /* SPDX-License-Identifier: LGPL-2.1+ */
       2             : /***
       3             :   Copyright © 2014 Intel Corporation. All rights reserved.
       4             : ***/
       5             : 
       6             : #include <netinet/icmp6.h>
       7             : #include <netinet/in.h>
       8             : 
       9             : #include "sd-ndisc.h"
      10             : 
      11             : #include "alloc-util.h"
      12             : #include "event-util.h"
      13             : #include "fd-util.h"
      14             : #include "icmp6-util.h"
      15             : #include "in-addr-util.h"
      16             : #include "memory-util.h"
      17             : #include "ndisc-internal.h"
      18             : #include "ndisc-router.h"
      19             : #include "random-util.h"
      20             : #include "socket-util.h"
      21             : #include "string-table.h"
      22             : #include "string-util.h"
      23             : 
      24             : #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS)
      25             : 
      26             : static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = {
      27             :         [SD_NDISC_EVENT_TIMEOUT] = "timeout",
      28             :         [SD_NDISC_EVENT_ROUTER] = "router",
      29             : };
      30             : 
      31          13 : DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event);
      32             : 
      33           5 : static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) {
      34           5 :         assert(ndisc);
      35           5 :         assert(event >= 0 && event < _SD_NDISC_EVENT_MAX);
      36             : 
      37           5 :         if (!ndisc->callback) {
      38           0 :                 log_ndisc("Received '%s' event.", ndisc_event_to_string(event));
      39           0 :                 return;
      40             :         }
      41             : 
      42           5 :         log_ndisc("Invoking callback for '%s' event.", ndisc_event_to_string(event));
      43           5 :         ndisc->callback(ndisc, event, rt, ndisc->userdata);
      44             : }
      45             : 
      46           1 : _public_ int sd_ndisc_set_callback(
      47             :                 sd_ndisc *nd,
      48             :                 sd_ndisc_callback_t callback,
      49             :                 void *userdata) {
      50             : 
      51           1 :         assert_return(nd, -EINVAL);
      52             : 
      53           1 :         nd->callback = callback;
      54           1 :         nd->userdata = userdata;
      55             : 
      56           1 :         return 0;
      57             : }
      58             : 
      59           2 : _public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) {
      60           2 :         assert_return(nd, -EINVAL);
      61           2 :         assert_return(ifindex > 0, -EINVAL);
      62           2 :         assert_return(nd->fd < 0, -EBUSY);
      63             : 
      64           2 :         nd->ifindex = ifindex;
      65           2 :         return 0;
      66             : }
      67             : 
      68           2 : _public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) {
      69           2 :         assert_return(nd, -EINVAL);
      70             : 
      71           2 :         if (mac_addr)
      72           2 :                 nd->mac_addr = *mac_addr;
      73             :         else
      74           0 :                 zero(nd->mac_addr);
      75             : 
      76           2 :         return 0;
      77             : }
      78             : 
      79           2 : _public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) {
      80             :         int r;
      81             : 
      82           2 :         assert_return(nd, -EINVAL);
      83           2 :         assert_return(nd->fd < 0, -EBUSY);
      84           2 :         assert_return(!nd->event, -EBUSY);
      85             : 
      86           2 :         if (event)
      87           2 :                 nd->event = sd_event_ref(event);
      88             :         else {
      89           0 :                 r = sd_event_default(&nd->event);
      90           0 :                 if (r < 0)
      91           0 :                         return 0;
      92             :         }
      93             : 
      94           2 :         nd->event_priority = priority;
      95             : 
      96           2 :         return 0;
      97             : }
      98             : 
      99           2 : _public_ int sd_ndisc_detach_event(sd_ndisc *nd) {
     100             : 
     101           2 :         assert_return(nd, -EINVAL);
     102           2 :         assert_return(nd->fd < 0, -EBUSY);
     103             : 
     104           2 :         nd->event = sd_event_unref(nd->event);
     105           2 :         return 0;
     106             : }
     107             : 
     108           0 : _public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) {
     109           0 :         assert_return(nd, NULL);
     110             : 
     111           0 :         return nd->event;
     112             : }
     113             : 
     114           3 : static void ndisc_reset(sd_ndisc *nd) {
     115           3 :         assert(nd);
     116             : 
     117           3 :         (void) event_source_disable(nd->timeout_event_source);
     118           3 :         (void) event_source_disable(nd->timeout_no_ra);
     119           3 :         nd->retransmit_time = 0;
     120           3 :         nd->recv_event_source = sd_event_source_unref(nd->recv_event_source);
     121           3 :         nd->fd = safe_close(nd->fd);
     122           3 : }
     123             : 
     124           2 : static sd_ndisc *ndisc_free(sd_ndisc *nd) {
     125           2 :         assert(nd);
     126             : 
     127           2 :         nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source);
     128           2 :         nd->timeout_no_ra = sd_event_source_unref(nd->timeout_no_ra);
     129             : 
     130           2 :         ndisc_reset(nd);
     131           2 :         sd_ndisc_detach_event(nd);
     132           2 :         return mfree(nd);
     133             : }
     134             : 
     135           8 : DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc, sd_ndisc, ndisc_free);
     136             : 
     137           2 : _public_ int sd_ndisc_new(sd_ndisc **ret) {
     138           2 :         _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL;
     139             : 
     140           2 :         assert_return(ret, -EINVAL);
     141             : 
     142           2 :         nd = new(sd_ndisc, 1);
     143           2 :         if (!nd)
     144           0 :                 return -ENOMEM;
     145             : 
     146           2 :         *nd = (sd_ndisc) {
     147             :                 .n_ref = 1,
     148             :                 .fd = -1,
     149             :         };
     150             : 
     151           2 :         *ret = TAKE_PTR(nd);
     152             : 
     153           2 :         return 0;
     154             : }
     155             : 
     156           1 : _public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) {
     157           1 :         assert_return(nd, -EINVAL);
     158           1 :         assert_return(mtu, -EINVAL);
     159             : 
     160           1 :         if (nd->mtu == 0)
     161           1 :                 return -ENODATA;
     162             : 
     163           0 :         *mtu = nd->mtu;
     164           0 :         return 0;
     165             : }
     166             : 
     167           0 : _public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) {
     168           0 :         assert_return(nd, -EINVAL);
     169           0 :         assert_return(ret, -EINVAL);
     170             : 
     171           0 :         if (nd->hop_limit == 0)
     172           0 :                 return -ENODATA;
     173             : 
     174           0 :         *ret = nd->hop_limit;
     175           0 :         return 0;
     176             : }
     177             : 
     178           5 : static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) {
     179             :         int r;
     180             : 
     181           5 :         assert(nd);
     182           5 :         assert(rt);
     183             : 
     184           5 :         r = ndisc_router_parse(rt);
     185           5 :         if (r == -EBADMSG) /* Bad packet */
     186           0 :                 return 0;
     187           5 :         if (r < 0)
     188           0 :                 return 0;
     189             : 
     190             :         /* Update global variables we keep */
     191           5 :         if (rt->mtu > 0)
     192           0 :                 nd->mtu = rt->mtu;
     193           5 :         if (rt->hop_limit > 0)
     194           5 :                 nd->hop_limit = rt->hop_limit;
     195             : 
     196           5 :         log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec",
     197             :                   rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none",
     198             :                   rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium",
     199             :                   rt->lifetime);
     200             : 
     201           5 :         ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt);
     202           5 :         return 0;
     203             : }
     204             : 
     205           5 : static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
     206           5 :         _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL;
     207           5 :         sd_ndisc *nd = userdata;
     208             :         ssize_t buflen;
     209             :         int r;
     210           5 :         _cleanup_free_ char *addr = NULL;
     211             : 
     212           5 :         assert(s);
     213           5 :         assert(nd);
     214           5 :         assert(nd->event);
     215             : 
     216           5 :         buflen = next_datagram_size_fd(fd);
     217           5 :         if (buflen < 0)
     218           0 :                 return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m");
     219             : 
     220           5 :         rt = ndisc_router_new(buflen);
     221           5 :         if (!rt)
     222           0 :                 return -ENOMEM;
     223             : 
     224           5 :         r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
     225           5 :                      &rt->timestamp);
     226           5 :         if (r < 0) {
     227           0 :                 switch (r) {
     228           0 :                 case -EADDRNOTAVAIL:
     229           0 :                         (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr);
     230           0 :                         log_ndisc("Received RA from non-link-local address %s. Ignoring", addr);
     231           0 :                         break;
     232             : 
     233           0 :                 case -EMULTIHOP:
     234           0 :                         log_ndisc("Received RA with invalid hop limit. Ignoring.");
     235           0 :                         break;
     236             : 
     237           0 :                 case -EPFNOSUPPORT:
     238           0 :                         log_ndisc("Received invalid source address from ICMPv6 socket. Ignoring.");
     239           0 :                         break;
     240             : 
     241           0 :                 case -EAGAIN: /* ignore spurious wakeups */
     242           0 :                         break;
     243             : 
     244           0 :                 default:
     245           0 :                         log_ndisc_errno(r, "Unexpected error while reading from ICMPv6, ignoring: %m");
     246           0 :                         break;
     247             :                 }
     248             : 
     249           0 :                 return 0;
     250             :         }
     251             : 
     252           5 :         (void) event_source_disable(nd->timeout_event_source);
     253             : 
     254           5 :         return ndisc_handle_datagram(nd, rt);
     255             : }
     256             : 
     257          21 : static usec_t ndisc_timeout_compute_random(usec_t val) {
     258             :         /* compute a time that is random within ±10% of the given value */
     259          63 :         return val - val / 10 +
     260          21 :                 (random_u64() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
     261             : }
     262             : 
     263          21 : static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
     264             :         char time_string[FORMAT_TIMESPAN_MAX];
     265          21 :         sd_ndisc *nd = userdata;
     266             :         usec_t time_now;
     267             :         int r;
     268             : 
     269          21 :         assert(s);
     270          21 :         assert(nd);
     271          21 :         assert(nd->event);
     272             : 
     273          21 :         assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0);
     274             : 
     275          21 :         if (!nd->retransmit_time)
     276           2 :                 nd->retransmit_time = ndisc_timeout_compute_random(NDISC_ROUTER_SOLICITATION_INTERVAL);
     277             :         else {
     278          19 :                 if (nd->retransmit_time > NDISC_MAX_ROUTER_SOLICITATION_INTERVAL / 2)
     279          10 :                         nd->retransmit_time = ndisc_timeout_compute_random(NDISC_MAX_ROUTER_SOLICITATION_INTERVAL);
     280             :                 else
     281           9 :                         nd->retransmit_time += ndisc_timeout_compute_random(nd->retransmit_time);
     282             :         }
     283             : 
     284          21 :         r = event_reset_time(nd->event, &nd->timeout_event_source,
     285             :                              clock_boottime_or_monotonic(),
     286          21 :                              time_now + nd->retransmit_time, 10 * USEC_PER_MSEC,
     287             :                              ndisc_timeout, nd,
     288          21 :                              nd->event_priority, "ndisc-timeout-no-ra", true);
     289          21 :         if (r < 0)
     290           0 :                 goto fail;
     291             : 
     292          21 :         r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr);
     293          21 :         if (r < 0) {
     294           0 :                 log_ndisc_errno(r, "Error sending Router Solicitation: %m");
     295           0 :                 goto fail;
     296             :         }
     297             : 
     298          21 :         log_ndisc("Sent Router Solicitation, next solicitation in %s",
     299             :                   format_timespan(time_string, FORMAT_TIMESPAN_MAX,
     300             :                                   nd->retransmit_time, USEC_PER_SEC));
     301             : 
     302          21 :         return 0;
     303             : 
     304           0 : fail:
     305           0 :         (void) sd_ndisc_stop(nd);
     306           0 :         return 0;
     307             : }
     308             : 
     309           0 : static int ndisc_timeout_no_ra(sd_event_source *s, uint64_t usec, void *userdata) {
     310           0 :         sd_ndisc *nd = userdata;
     311             : 
     312           0 :         assert(s);
     313           0 :         assert(nd);
     314             : 
     315           0 :         log_ndisc("No RA received before link confirmation timeout");
     316             : 
     317           0 :         (void) event_source_disable(nd->timeout_no_ra);
     318           0 :         ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL);
     319             : 
     320           0 :         return 0;
     321             : }
     322             : 
     323           2 : _public_ int sd_ndisc_stop(sd_ndisc *nd) {
     324           2 :         assert_return(nd, -EINVAL);
     325             : 
     326           2 :         if (nd->fd < 0)
     327           1 :                 return 0;
     328             : 
     329           1 :         log_ndisc("Stopping IPv6 Router Solicitation client");
     330             : 
     331           1 :         ndisc_reset(nd);
     332           1 :         return 1;
     333             : }
     334             : 
     335           3 : _public_ int sd_ndisc_start(sd_ndisc *nd) {
     336             :         int r;
     337             :         usec_t time_now;
     338             : 
     339           3 :         assert_return(nd, -EINVAL);
     340           3 :         assert_return(nd->event, -EINVAL);
     341           3 :         assert_return(nd->ifindex > 0, -EINVAL);
     342             : 
     343           3 :         if (nd->fd >= 0)
     344           0 :                 return 0;
     345             : 
     346           3 :         assert(!nd->recv_event_source);
     347             : 
     348           3 :         r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now);
     349           3 :         if (r < 0)
     350           0 :                 goto fail;
     351             : 
     352           3 :         nd->fd = icmp6_bind_router_solicitation(nd->ifindex);
     353           3 :         if (nd->fd < 0)
     354           0 :                 return nd->fd;
     355             : 
     356           3 :         r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd);
     357           3 :         if (r < 0)
     358           0 :                 goto fail;
     359             : 
     360           3 :         r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority);
     361           3 :         if (r < 0)
     362           0 :                 goto fail;
     363             : 
     364           3 :         (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message");
     365             : 
     366           3 :         r = event_reset_time(nd->event, &nd->timeout_event_source,
     367             :                              clock_boottime_or_monotonic(),
     368             :                              0, 0,
     369             :                              ndisc_timeout, nd,
     370           3 :                              nd->event_priority, "ndisc-timeout", true);
     371           3 :         if (r < 0)
     372           0 :                 goto fail;
     373             : 
     374           3 :         r = event_reset_time(nd->event, &nd->timeout_no_ra,
     375             :                              clock_boottime_or_monotonic(),
     376             :                              time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC,
     377             :                              ndisc_timeout_no_ra, nd,
     378           3 :                              nd->event_priority, "ndisc-timeout-no-ra", true);
     379           3 :         if (r < 0)
     380           0 :                 goto fail;
     381             : 
     382           3 :         log_ndisc("Started IPv6 Router Solicitation client");
     383           3 :         return 1;
     384             : 
     385           0 : fail:
     386           0 :         ndisc_reset(nd);
     387           0 :         return r;
     388             : }

Generated by: LCOV version 1.14