#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/ctype.h>
#include <sys/eventhandler.h>
#include <sys/jail.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_var.h>
#include <net/if_private.h>
#include <net/vnet.h>
#include <compat/linux/linux.h>
#include <compat/linux/linux_common.h>
#include <compat/linux/linux_mib.h>
_Static_assert(LINUX_IFNAMSIZ == IFNAMSIZ, "Linux IFNAMSIZ");
static bool use_real_ifnames = false;
SYSCTL_BOOL(_compat_linux, OID_AUTO, use_real_ifnames, CTLFLAG_RWTUN,
&use_real_ifnames, 0,
"Use FreeBSD interface names instead of generating ethN aliases");
VNET_DEFINE_STATIC(struct unrhdr *, linux_eth_unr);
#define V_linux_eth_unr VNET(linux_eth_unr)
static eventhandler_tag ifnet_arrival_tag;
static eventhandler_tag ifnet_departure_tag;
static void
linux_ifnet_arrival(void *arg __unused, struct ifnet *ifp)
{
if (ifp->if_type == IFT_ETHER)
ifp->if_linux_ethno = alloc_unr(V_linux_eth_unr);
}
static void
linux_ifnet_departure(void *arg __unused, struct ifnet *ifp)
{
if (ifp->if_type == IFT_ETHER)
free_unr(V_linux_eth_unr, ifp->if_linux_ethno);
}
void
linux_ifnet_init(void)
{
ifnet_arrival_tag = EVENTHANDLER_REGISTER(ifnet_arrival_event,
linux_ifnet_arrival, NULL, EVENTHANDLER_PRI_FIRST);
ifnet_departure_tag = EVENTHANDLER_REGISTER(ifnet_departure_event,
linux_ifnet_departure, NULL, EVENTHANDLER_PRI_LAST);
}
void
linux_ifnet_uninit(void)
{
EVENTHANDLER_DEREGISTER(ifnet_arrival_event, ifnet_arrival_tag);
EVENTHANDLER_DEREGISTER(ifnet_departure_event, ifnet_departure_tag);
}
static void
linux_ifnet_vnet_init(void *arg __unused)
{
struct epoch_tracker et;
struct if_iter it;
if_t ifp;
V_linux_eth_unr = new_unrhdr(0, INT_MAX, NULL);
NET_EPOCH_ENTER(et);
for (ifp = if_iter_start(&it); ifp != NULL; ifp = if_iter_next(&it))
linux_ifnet_arrival(NULL, ifp);
NET_EPOCH_EXIT(et);
}
VNET_SYSINIT(linux_ifnet_vnet_init, SI_SUB_PROTO_IF, SI_ORDER_ANY,
linux_ifnet_vnet_init, NULL);
static void
linux_ifnet_vnet_uninit(void *arg __unused)
{
clear_unrhdr(V_linux_eth_unr);
delete_unrhdr(V_linux_eth_unr);
}
VNET_SYSUNINIT(linux_ifnet_vnet_uninit, SI_SUB_PROTO_IF, SI_ORDER_ANY,
linux_ifnet_vnet_uninit, NULL);
int
ifname_bsd_to_linux_idx(u_int idx, char *lxname, size_t len)
{
struct epoch_tracker et;
struct ifnet *ifp;
int ret;
ret = 0;
CURVNET_SET(TD_TO_VNET(curthread));
NET_EPOCH_ENTER(et);
ifp = ifnet_byindex(idx);
if (ifp != NULL)
ret = ifname_bsd_to_linux_ifp(ifp, lxname, len);
NET_EPOCH_EXIT(et);
CURVNET_RESTORE();
return (ret);
}
int
ifname_bsd_to_linux_ifp(const struct ifnet *ifp, char *lxname, size_t len)
{
if (ifp->if_type == IFT_LOOP &&
strncmp(ifp->if_xname, "lo0", IFNAMSIZ) == 0)
return (strlcpy(lxname, "lo", len));
if (ifp->if_type != IFT_ETHER || use_real_ifnames)
return (strlcpy(lxname, ifp->if_xname, len));
return (snprintf(lxname, len, "eth%d", ifp->if_linux_ethno));
}
struct ifname_linux_to_ifp_cb_s {
bool is_lo;
bool is_eth;
int unit;
const char *lxname;
if_t ifp;
};
static int
ifname_linux_to_ifp_cb(if_t ifp, void *arg)
{
struct ifname_linux_to_ifp_cb_s *cbs = arg;
NET_EPOCH_ASSERT();
if (strncmp(if_name(ifp), cbs->lxname, LINUX_IFNAMSIZ) == 0)
goto out;
if (cbs->is_eth && ifp->if_type == IFT_ETHER &&
ifp->if_linux_ethno == cbs->unit)
goto out;
if (cbs->is_lo && ifp->if_type == IFT_LOOP)
goto out;
return (0);
out:
cbs->ifp = ifp;
return (1);
}
struct ifnet *
ifname_linux_to_ifp(const char *lxname)
{
struct ifname_linux_to_ifp_cb_s arg = {
.lxname = lxname,
};
int len;
char *ep;
NET_EPOCH_ASSERT();
for (len = 0; len < LINUX_IFNAMSIZ; ++len)
if (!isalpha(lxname[len]) || lxname[len] == '\0')
break;
if (len == 0 || len == LINUX_IFNAMSIZ)
return (NULL);
arg.is_lo = (len == 2 && strncmp(lxname, "lo", LINUX_IFNAMSIZ) == 0);
arg.unit = (int)strtoul(lxname + len, &ep, 10);
if ((ep == NULL || ep == lxname + len || ep >= lxname + LINUX_IFNAMSIZ) &&
arg.is_lo == 0)
return (NULL);
arg.is_eth = (len == 3 && strncmp(lxname, "eth", len) == 0);
if_foreach(ifname_linux_to_ifp_cb, &arg);
return (arg.ifp);
}
int
ifname_linux_to_bsd(struct thread *td, const char *lxname, char *bsdname)
{
struct epoch_tracker et;
struct ifnet *ifp;
CURVNET_SET(TD_TO_VNET(td));
NET_EPOCH_ENTER(et);
ifp = ifname_linux_to_ifp(lxname);
if (ifp != NULL && bsdname != NULL)
strlcpy(bsdname, if_name(ifp), IFNAMSIZ);
NET_EPOCH_EXIT(et);
CURVNET_RESTORE();
return (ifp != NULL ? 0 : EINVAL);
}
unsigned short
linux_ifflags(struct ifnet *ifp)
{
unsigned short flags;
NET_EPOCH_ASSERT();
flags = if_getflags(ifp) | if_getdrvflags(ifp);
return (bsd_to_linux_ifflags(flags));
}
unsigned short
bsd_to_linux_ifflags(int fl)
{
unsigned short flags = 0;
if (fl & IFF_UP)
flags |= LINUX_IFF_UP;
if (fl & IFF_BROADCAST)
flags |= LINUX_IFF_BROADCAST;
if (fl & IFF_DEBUG)
flags |= LINUX_IFF_DEBUG;
if (fl & IFF_LOOPBACK)
flags |= LINUX_IFF_LOOPBACK;
if (fl & IFF_POINTOPOINT)
flags |= LINUX_IFF_POINTOPOINT;
if (fl & IFF_DRV_RUNNING)
flags |= LINUX_IFF_RUNNING;
if (fl & IFF_NOARP)
flags |= LINUX_IFF_NOARP;
if (fl & IFF_PROMISC)
flags |= LINUX_IFF_PROMISC;
if (fl & IFF_ALLMULTI)
flags |= LINUX_IFF_ALLMULTI;
if (fl & IFF_MULTICAST)
flags |= LINUX_IFF_MULTICAST;
return (flags);
}
static u_int
linux_ifhwaddr_cb(void *arg, struct ifaddr *ifa, u_int count)
{
struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
struct l_sockaddr *lsa = arg;
if (count > 0)
return (0);
if (sdl->sdl_type != IFT_ETHER)
return (0);
bzero(lsa, sizeof(*lsa));
lsa->sa_family = LINUX_ARPHRD_ETHER;
bcopy(LLADDR(sdl), lsa->sa_data, LINUX_IFHWADDRLEN);
return (1);
}
int
linux_ifhwaddr(struct ifnet *ifp, struct l_sockaddr *lsa)
{
NET_EPOCH_ASSERT();
if (ifp->if_type == IFT_LOOP) {
bzero(lsa, sizeof(*lsa));
lsa->sa_family = LINUX_ARPHRD_LOOPBACK;
return (0);
}
if (ifp->if_type != IFT_ETHER)
return (ENOENT);
if (if_foreach_addr_type(ifp, AF_LINK, linux_ifhwaddr_cb, lsa) > 0)
return (0);
return (ENOENT);
}