zebra: ifi_link_state is the link state

SIOCGIFMEDIA returns the media state.
SIOCGIFDATA returns interface data which includes the link state.

While the status of the former is usually indicitive of the latter,
this is not always the case.
Ifact some recent net80211 changes in at least NetBSD and OpenBSD
have MONITOR media set to active but the link status set to DOWN.

All interfaces will return link state with SIOCGIFDATA, unlike
SIOCGIFMEDIA. However not all BSD's support SIOCGIFDATA - it has
recently been accepted into FreeBSD-13.
However, all BSD's do report the same structure in ifa_data for
AF_LINK addresses from getifaddrs(3) so the information has always
been available.

Signed-off-by: Roy Marples <roy@marples.name>
This commit is contained in:
Roy Marples 2020-10-12 20:53:14 +01:00
parent 7563bd3fd8
commit 98f3df554b

View File

@ -411,47 +411,91 @@ void if_get_flags(struct interface *ifp)
{ {
int ret; int ret;
struct ifreq ifreq; struct ifreq ifreq;
#ifdef HAVE_BSD_LINK_DETECT
struct ifmediareq ifmr;
#endif /* HAVE_BSD_LINK_DETECT */
ifreq_set_name(&ifreq, ifp); ifreq_set_name(&ifreq, ifp);
ret = vrf_if_ioctl(SIOCGIFFLAGS, (caddr_t)&ifreq, ifp->vrf_id); ret = vrf_if_ioctl(SIOCGIFFLAGS, (caddr_t)&ifreq, ifp->vrf_id);
if (ret < 0) { if (ret < 0) {
flog_err_sys(EC_LIB_SYSTEM_CALL, flog_err_sys(EC_LIB_SYSTEM_CALL,
"vrf_if_ioctl(SIOCGIFFLAGS) failed: %s", "vrf_if_ioctl(SIOCGIFFLAGS %s) failed: %s",
safe_strerror(errno)); ifp->name, safe_strerror(errno));
return; return;
} }
#ifdef HAVE_BSD_LINK_DETECT /* Detect BSD link-state at start-up */
/* Per-default, IFF_RUNNING is held high, unless link-detect says if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION))
* otherwise - we abuse IFF_RUNNING inside zebra as a link-state flag, goto out;
* following practice on Linux and Solaris kernels
/* Per-default, IFF_RUNNING is held high, unless link-detect
* says otherwise - we abuse IFF_RUNNING inside zebra as a
* link-state flag, following practice on Linux and Solaris
* kernels
*/ */
SET_FLAG(ifreq.ifr_flags, IFF_RUNNING);
if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_LINKDETECTION)) { #ifdef SIOCGIFDATA
(void)memset(&ifmr, 0, sizeof(ifmr)); /*
strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name)); * BSD gets link state from ifi_link_link in struct if_data.
* All BSD's have this in getifaddrs(3) ifa_data for AF_LINK
* addresses. We can also access it via SIOCGIFDATA.
*/
/* Seems not all interfaces implement this ioctl */ #ifdef __NetBSD__
if (if_ioctl(SIOCGIFMEDIA, (caddr_t)&ifmr) == -1 && struct ifdatareq ifdr = {.ifdr_data.ifi_link_state = 0};
errno != EINVAL) struct if_data *ifdata = &ifdr.ifdr_data;
strlcpy(ifdr.ifdr_name, ifp->name, sizeof(ifdr.ifdr_name));
ret = vrf_if_ioctl(SIOCGIFDATA, (caddr_t)&ifdr, ifp->vrf_id);
#else
struct if_data ifd = {.ifi_link_state = 0};
struct if_data *ifdata = &ifd;
ifreq.ifr_data = (caddr_t)ifdata;
ret = vrf_if_ioctl(SIOCGIFDATA, (caddr_t)&ifreq, ifp->vrf_id);
#endif
if (ret == -1)
/* Very unlikely. Did the interface disappear? */
flog_err_sys(EC_LIB_SYSTEM_CALL, flog_err_sys(EC_LIB_SYSTEM_CALL,
"if_ioctl(SIOCGIFMEDIA) failed: %s", "if_ioctl(SIOCGIFDATA %s) failed: %s", ifp->name,
safe_strerror(errno)); safe_strerror(errno));
else if (ifmr.ifm_status & IFM_AVALID) /* Link state is valid */ else {
{ if (ifdata->ifi_link_state >= LINK_STATE_UP)
if (ifmr.ifm_status & IFM_ACTIVE) SET_FLAG(ifreq.ifr_flags, IFF_RUNNING);
else if (ifdata->ifi_link_state == LINK_STATE_UNKNOWN)
/* BSD traditionally treats UNKNOWN as UP */
SET_FLAG(ifreq.ifr_flags, IFF_RUNNING); SET_FLAG(ifreq.ifr_flags, IFF_RUNNING);
else else
UNSET_FLAG(ifreq.ifr_flags, IFF_RUNNING); UNSET_FLAG(ifreq.ifr_flags, IFF_RUNNING);
} }
#elif defined(HAVE_BSD_LINK_DETECT)
/*
* This is only needed for FreeBSD older than FreeBSD-13.
* Valid and active media generally means the link state is
* up, but this is not always the case.
* For example, some BSD's with a net80211 interface in MONITOR
* mode will treat the media as valid and active but the
* link state is down - because we cannot send anything.
* Also, virtual interfaces such as PPP, VLAN, etc generally
* don't support media at all, so the ioctl will just fail.
*/
struct ifmediareq ifmr = {.ifm_status = 0};
strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name));
if (if_ioctl(SIOCGIFMEDIA, (caddr_t)&ifmr) == -1) {
if (errno != EINVAL)
flog_err_sys(EC_LIB_SYSTEM_CALL,
"if_ioctl(SIOCGIFMEDIA %s) failed: %s",
ifp->name, safe_strerror(errno));
} else if (ifmr.ifm_status & IFM_AVALID) { /* media state is valid */
if (ifmr.ifm_status & IFM_ACTIVE) /* media is active */
SET_FLAG(ifreq.ifr_flags, IFF_RUNNING);
else
UNSET_FLAG(ifreq.ifr_flags, IFF_RUNNING);
} }
#endif /* HAVE_BSD_LINK_DETECT */ #endif /* HAVE_BSD_LINK_DETECT */
out:
if_flags_update(ifp, (ifreq.ifr_flags & 0x0000ffff)); if_flags_update(ifp, (ifreq.ifr_flags & 0x0000ffff));
} }