netdump: Ref the interface we're attached to

Serialize netdump configuration / deconfiguration, and discard our
configuration when the affiliated interface goes away by monitoring
ifnet_departure_event.

Reviewed by:	markj, with input from vangyzen@ (earlier version)
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D20206
This commit is contained in:
Conrad Meyer 2019-05-10 23:12:59 +00:00
parent 12cd7ded68
commit 64e7d18f34

View File

@ -93,6 +93,8 @@ static int netdump_configure(struct diocskerneldump_arg *,
struct thread *);
static int netdump_dumper(void *priv __unused, void *virtual,
vm_offset_t physical __unused, off_t offset, size_t length);
static bool netdump_enabled(void);
static int netdump_enabled_sysctl(SYSCTL_HANDLER_ARGS);
static int netdump_ether_output(struct mbuf *m, struct ifnet *ifp,
struct ether_addr dst, u_short etype);
static void netdump_handle_arp(struct mbuf **mb);
@ -102,11 +104,13 @@ static int netdump_ioctl(struct cdev *dev __unused, u_long cmd,
static int netdump_modevent(module_t mod, int type, void *priv);
static void netdump_network_poll(void);
static void netdump_pkt_in(struct ifnet *ifp, struct mbuf *m);
static void netdump_reinit_internal(struct ifnet *ifp);
static int netdump_send(uint32_t type, off_t offset, unsigned char *data,
uint32_t datalen);
static int netdump_send_arp(in_addr_t dst);
static int netdump_start(struct dumperinfo *di);
static int netdump_udp_output(struct mbuf *m);
static void netdump_unconfigure(void);
/* Must be at least as big as the chunks dumpsys() gives us. */
static unsigned char nd_buf[MAXDUMPPGS * PAGE_SIZE];
@ -131,8 +135,17 @@ static struct {
#define nd_gateway nd_conf.ndc_gateway.in4
/* General dynamic settings. */
static struct sx nd_conf_lk;
SX_SYSINIT(nd_conf, &nd_conf_lk, "netdump configuration lock");
#define NETDUMP_WLOCK() sx_xlock(&nd_conf_lk)
#define NETDUMP_WUNLOCK() sx_xunlock(&nd_conf_lk)
#define NETDUMP_RLOCK() sx_slock(&nd_conf_lk)
#define NETDUMP_RUNLOCK() sx_sunlock(&nd_conf_lk)
#define NETDUMP_ASSERT_WLOCKED() sx_assert(&nd_conf_lk, SA_XLOCKED)
#define NETDUMP_ASSERT_LOCKED() sx_assert(&nd_conf_lk, SA_LOCKED)
static struct ether_addr nd_gw_mac;
static struct ifnet *nd_ifp;
static eventhandler_tag nd_detach_cookie;
static uint16_t nd_server_port = NETDUMP_PORT;
FEATURE(netdump, "Netdump client support");
@ -144,10 +157,8 @@ static int nd_debug;
SYSCTL_INT(_net_netdump, OID_AUTO, debug, CTLFLAG_RWTUN,
&nd_debug, 0,
"Debug message verbosity");
static int nd_enabled;
SYSCTL_INT(_net_netdump, OID_AUTO, enabled, CTLFLAG_RD,
&nd_enabled, 0,
"netdump configuration status");
SYSCTL_PROC(_net_netdump, OID_AUTO, enabled, CTLFLAG_RD | CTLTYPE_INT,
&nd_ifp, 0, netdump_enabled_sysctl, "I", "netdump configuration status");
static char nd_path[MAXPATHLEN];
SYSCTL_STRING(_net_netdump, OID_AUTO, path, CTLFLAG_RW,
nd_path, sizeof(nd_path),
@ -165,6 +176,29 @@ SYSCTL_INT(_net_netdump, OID_AUTO, arp_retries, CTLFLAG_RWTUN,
&nd_arp_retries, 0,
"Number of ARP attempts before giving up");
static bool
netdump_enabled(void)
{
NETDUMP_ASSERT_LOCKED();
return (nd_ifp != NULL);
}
static int
netdump_enabled_sysctl(SYSCTL_HANDLER_ARGS)
{
int en, error;
NETDUMP_RLOCK();
en = netdump_enabled();
NETDUMP_RUNLOCK();
error = SYSCTL_OUT(req, &en, sizeof(en));
if (error != 0 || req->newptr == NULL)
return (error);
return (EPERM);
}
/*
* Checks for netdump support on a network interface
*
@ -248,7 +282,7 @@ netdump_udp_output(struct mbuf *m)
struct udpiphdr *ui;
struct ip *ip;
MPASS(nd_ifp != NULL);
MPASS(netdump_enabled());
M_PREPEND(m, sizeof(struct udpiphdr), M_NOWAIT);
if (m == NULL) {
@ -306,7 +340,7 @@ netdump_send_arp(in_addr_t dst)
struct arphdr *ah;
int pktlen;
MPASS(nd_ifp != NULL);
MPASS(netdump_enabled());
/* Fill-up a broadcast address. */
memset(&bcast, 0xFF, ETHER_ADDR_LEN);
@ -409,7 +443,7 @@ netdump_send(uint32_t type, off_t offset, unsigned char *data, uint32_t datalen)
rcvd_acks = 0;
retries = 0;
MPASS(nd_ifp != NULL);
MPASS(netdump_enabled());
retransmit:
/* Chunks can be too big to fit in packets. */
@ -875,7 +909,7 @@ static void
netdump_network_poll(void)
{
MPASS(nd_ifp != NULL);
MPASS(netdump_enabled());
nd_ifp->if_netdump_methods->nd_poll(nd_ifp, 1000);
}
@ -945,7 +979,7 @@ netdump_start(struct dumperinfo *di)
error = 0;
/* Check if the dumping is allowed to continue. */
if (nd_enabled == 0)
if (!netdump_enabled())
return (EINVAL);
if (panicstr == NULL) {
@ -954,8 +988,6 @@ netdump_start(struct dumperinfo *di)
return (EINVAL);
}
MPASS(nd_ifp != NULL);
if (nd_server.s_addr == INADDR_ANY) {
printf("netdump_start: can't netdump; no server IP given\n");
return (EINVAL);
@ -1065,36 +1097,68 @@ static struct cdevsw netdump_cdevsw = {
static struct cdev *netdump_cdev;
static void
netdump_unconfigure(void)
{
struct diocskerneldump_arg kda;
NETDUMP_ASSERT_WLOCKED();
KASSERT(netdump_enabled(), ("%s: nd_ifp NULL", __func__));
bzero(&kda, sizeof(kda));
kda.kda_index = KDA_REMOVE_DEV;
(void)dumper_remove(nd_conf.ndc_iface, &kda);
netdump_mbuf_drain();
if_rele(nd_ifp);
nd_ifp = NULL;
bzero(&nd_conf, sizeof(nd_conf));
}
static void
netdump_ifdetach(void *arg __unused, struct ifnet *ifp)
{
NETDUMP_WLOCK();
if (ifp == nd_ifp)
netdump_unconfigure();
NETDUMP_WUNLOCK();
}
static int
netdump_configure(struct diocskerneldump_arg *conf, struct thread *td)
{
struct epoch_tracker et;
struct ifnet *ifp;
NETDUMP_ASSERT_WLOCKED();
CURVNET_SET(TD_TO_VNET(td));
if (!IS_DEFAULT_VNET(curvnet)) {
CURVNET_RESTORE();
return (EINVAL);
}
NET_EPOCH_ENTER(et);
CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
if (strcmp(ifp->if_xname, conf->kda_iface) == 0)
break;
}
/* XXX ref */
NET_EPOCH_EXIT(et);
ifp = ifunit_ref(conf->kda_iface);
CURVNET_RESTORE();
if (ifp == NULL)
return (ENOENT);
if ((if_getflags(ifp) & IFF_UP) == 0)
if ((if_getflags(ifp) & IFF_UP) == 0) {
if_rele(ifp);
return (ENXIO);
if (!netdump_supported_nic(ifp) || ifp->if_type != IFT_ETHER)
}
if (!netdump_supported_nic(ifp) || ifp->if_type != IFT_ETHER) {
if_rele(ifp);
return (ENODEV);
}
if (netdump_enabled())
if_rele(nd_ifp);
nd_ifp = ifp;
netdump_reinit(ifp);
netdump_reinit_internal(ifp);
#define COPY_SIZED(elm) do { \
_Static_assert(sizeof(nd_conf.ndc_ ## elm) == \
sizeof(conf->kda_ ## elm), "elm " __XSTRING(elm) " mismatch"); \
@ -1107,22 +1171,34 @@ netdump_configure(struct diocskerneldump_arg *conf, struct thread *td)
COPY_SIZED(gateway);
COPY_SIZED(af);
#undef COPY_SIZED
nd_enabled = 1;
return (0);
}
/*
* Reinitialize the mbuf pool used by drivers while dumping. This is called
* from the generic ioctl handler for SIOCSIFMTU after the driver has
* reconfigured itself.
* from the generic ioctl handler for SIOCSIFMTU after any NIC driver has
* reconfigured itself. (I.e., it may not be a configured netdump interface.)
*/
void
netdump_reinit(struct ifnet *ifp)
{
NETDUMP_WLOCK();
if (ifp != nd_ifp) {
NETDUMP_WUNLOCK();
return;
}
netdump_reinit_internal(ifp);
NETDUMP_WUNLOCK();
}
static void
netdump_reinit_internal(struct ifnet *ifp)
{
int clsize, nmbuf, ncl, nrxr;
if (ifp != nd_ifp)
return;
NETDUMP_ASSERT_WLOCKED();
ifp->if_netdump_methods->nd_init(ifp, &nrxr, &ncl, &clsize);
KASSERT(nrxr > 0, ("invalid receive ring count %d", nrxr));
@ -1168,6 +1244,8 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
conf = NULL;
error = 0;
NETDUMP_WLOCK();
switch (cmd) {
#ifdef COMPAT_FREEBSD11
case DIOCSKERNELDUMP_FREEBSD11:
@ -1177,10 +1255,8 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
error = ENXIO;
break;
}
if (nd_enabled) {
nd_enabled = 0;
netdump_mbuf_drain();
}
if (netdump_enabled())
netdump_unconfigure();
break;
#endif
#ifdef COMPAT_FREEBSD12
@ -1197,17 +1273,15 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
error = ENXIO;
break;
}
if (nd_enabled) {
nd_enabled = 0;
netdump_mbuf_drain();
}
if (netdump_enabled())
netdump_unconfigure();
break;
case NETDUMPGCONF_FREEBSD12:
gone_in(14, "FreeBSD 12.x ABI compat");
conf12 = (void *)addr;
if (!nd_enabled) {
if (!netdump_enabled()) {
error = ENXIO;
break;
}
@ -1232,7 +1306,7 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
* For now, index is ignored; netdump doesn't support multiple
* configurations (yet).
*/
if (!nd_enabled) {
if (!netdump_enabled()) {
error = ENXIO;
conf = NULL;
break;
@ -1293,13 +1367,10 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
if (conf->kda_index == KDA_REMOVE ||
conf->kda_index == KDA_REMOVE_DEV ||
conf->kda_index == KDA_REMOVE_ALL) {
if (nd_enabled || conf->kda_index == KDA_REMOVE_ALL) {
error = dumper_remove(conf->kda_iface, conf);
if (error == 0) {
nd_enabled = 0;
netdump_mbuf_drain();
}
}
if (netdump_enabled())
netdump_unconfigure();
if (conf->kda_index == KDA_REMOVE_ALL)
error = dumper_remove(NULL, conf);
break;
}
@ -1342,10 +1413,8 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
conf->kda_encryptedkeysize);
free(encryptedkey, M_TEMP);
}
if (error != 0) {
nd_enabled = 0;
netdump_mbuf_drain();
}
if (error != 0)
netdump_unconfigure();
break;
default:
error = ENOTTY;
@ -1354,6 +1423,7 @@ netdump_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t addr,
explicit_bzero(&kda_copy, sizeof(kda_copy));
if (conf != NULL)
explicit_bzero(conf, sizeof(*conf));
NETDUMP_WUNLOCK();
return (error);
}
@ -1385,6 +1455,9 @@ netdump_modevent(module_t mod __unused, int what, void *priv __unused)
if (error != 0)
return (error);
nd_detach_cookie = EVENTHANDLER_REGISTER(ifnet_departure_event,
netdump_ifdetach, NULL, EVENTHANDLER_PRI_ANY);
if ((arg = kern_getenv("net.dump.iface")) != NULL) {
strlcpy(conf.kda_iface, arg, sizeof(conf.kda_iface));
freeenv(arg);
@ -1404,23 +1477,21 @@ netdump_modevent(module_t mod __unused, int what, void *priv __unused)
conf.kda_af = AF_INET;
/* Ignore errors; we print a message to the console. */
NETDUMP_WLOCK();
(void)netdump_configure(&conf, curthread);
NETDUMP_WUNLOCK();
}
break;
case MOD_UNLOAD:
if (nd_enabled) {
struct diocskerneldump_arg kda;
NETDUMP_WLOCK();
if (netdump_enabled()) {
printf("netdump: disabling dump device for unload\n");
bzero(&kda, sizeof(kda));
kda.kda_index = KDA_REMOVE_DEV;
(void)dumper_remove(nd_conf.ndc_iface, &kda);
netdump_mbuf_drain();
nd_enabled = 0;
netdump_unconfigure();
}
NETDUMP_WUNLOCK();
destroy_dev(netdump_cdev);
EVENTHANDLER_DEREGISTER(ifnet_departure_event,
nd_detach_cookie);
break;
default:
error = EOPNOTSUPP;