lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1330308337-21365-3-git-send-email-andrey.warkentin@gmail.com>
Date:	Sun, 26 Feb 2012 21:05:37 -0500
From:	Andrei Warkentin <andrey.warkentin@...il.com>
To:	netdev@...r.kernel.org, linux-kernel@...r.kernel.org
Cc:	Andrei Warkentin <andreiw@...are.com>,
	kgdb-bugreport@...ts.sourceforge.net,
	Jason Wessel <jason.wessel@...driver.com>,
	Matt Mackall <mpm@...enic.com>,
	Andrei Warkentin <andrey.warkentin@...il.com>
Subject: [PATCHv2 2/2] NETKGDB: Ethernet/UDP/IP KDB transport.

From: Andrei Warkentin <andreiw@...are.com>

Allows debugging a crashed kernel, and breaking into a
live kernel via a network interface.

Useful in testing/QA farms where physical access is not
necessarily easy/desired, and iLO-like solutions end up
using USB HID and not i8042-based keyboard.

Cc: kgdb-bugreport@...ts.sourceforge.net
Cc: Jason Wessel <jason.wessel@...driver.com>
Cc: Matt Mackall <mpm@...enic.com>
Signed-off-by: Andrei Warkentin <andreiw@...are.com>
Signed-off-by: Andrei Warkentin <andrey.warkentin@...il.com>
---
 Documentation/networking/netkgdb.txt |  104 +++++
 drivers/net/Kconfig                  |    8 +-
 drivers/net/Makefile                 |    1 +
 drivers/net/netkgdb.c                |  728 ++++++++++++++++++++++++++++++++++
 4 files changed, 840 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/networking/netkgdb.txt
 create mode 100644 drivers/net/netkgdb.c

diff --git a/Documentation/networking/netkgdb.txt b/Documentation/networking/netkgdb.txt
new file mode 100644
index 0000000..b2c70c1
--- /dev/null
+++ b/Documentation/networking/netkgdb.txt
@@ -0,0 +1,104 @@
+(based off netconsole.txt)
+
+Started by Andrei Warkentin <andrey.warkentin@...il.com>, 2012.02.24
+
+Please send bug reports to Andrey Warkentin <andrey.warkentin@...il.com>
+
+Introduction:
+=============
+
+This module allows debugging a crashed kernel over the network,
+where other means are not practical.
+
+It can be used either built-in or as a module. As a built-in,
+netkgdb initializes immediately after NIC cards and will bring up
+the specified interface as soon as possible. While this doesn't allow
+early kernel debugging, it's useful for handling random crashes and
+such.
+
+Sender and receiver configuration:
+==================================
+
+It takes an optional string configuration parameter "netkgdb" in the
+following format:
+
+ netkgdb=[src-port]@[src-ip]/[<dev>],[tgt-port]@<tgt-ip>/[tgt-macaddr]
+
+   where
+        src-port      source for UDP packets (defaults to 7777)
+        src-ip        source IP to use (interface address)
+        dev           network interface (eth0)
+        tgt-port      port for remote agent (7777)
+        tgt-ip        IP address for remote agent
+        tgt-macaddr   ethernet MAC address for remote agent (broadcast)
+
+Examples:
+
+ linux netkgdb=4444@...0.0.1/eth1,9353@...0.0.2/12:34:56:78:9a:bc
+
+  or
+
+ insmod netkgdb netkgdb=@...0.0.5/,@10.0.0.2/
+
+Note: the parameter is optional and largely unneeded unless you
+are running a listen server - netkgdb will accept connection from any
+IP on all interfaces and will reconfigure itself appropriately if
+the assigned interface IP address changes. This makes it useful
+in an environment where it's not known ahead of time what computer
+will connect to perform the crash analysis.
+
+Note: The format for the string is not netkgdb specific, and its
+parsing is part of Linux netpoll support. The netpoll code does
+need at least a local IP assigned, so the following will result
+in an error and netkgdb will not load if you don't already have
+an IP address assigned.
+
+Note: If you specify a local/remote netkgdb string as a kernel
+parameter, with the idea of having a remote server listen on
+a socket and wait for the kdb crash connection, don't expect
+to be able to connect from the remote host. This is peculiar
+behavior of the netpoll code - the interface is not *really*
+configured and your packets will never reach netkgdb. You
+will need to "ifconfig eth0 x.y.z.w' first. Or just wait
+for the crash :-).
+
+insmod netkgdb netkgdb=@/,@10.0.0.2/
+
+The remote host can run either 'netcat -u -l -p <port>' or 'nc -l -u <port>'.
+
+Using:
+======
+
+If the kernel is running, then any input will result in following string
+to be sent back -
+                Alive - '!!!BREAK!!!' to break in.
+
+This lets you know the machine is alive.
+
+Sending "!!!BREAK!!!", followed by a '\n' will result in breaking into
+KGDB/KDB.
+
+Miscellaneous notes:
+====================
+
+The following notes are only relevant if you want the debugged
+host to automatically send the KDB/KGDB data on a crash to a
+host preconfigured with the netkgdb= parameter.
+
+WARNING: the default target ethernet setting uses the broadcast
+ethernet address to send packets, which can cause increased load on
+other systems on the same ethernet segment. After the first reply
+connection, the target ethernet address is updated to match the source.
+
+TIP: some LAN switches may be configured to suppress ethernet broadcasts
+so it is advised to explicitly specify the remote agents' MAC addresses
+from the config parameters passed to netkgdb.
+
+TIP: to find out the MAC address of, say, 10.0.0.2, you may try using:
+
+ ping -c 1 10.0.0.2 ; /sbin/arp -n | grep 10.0.0.2
+
+TIP: in case the remote agent is on a separate LAN subnet than
+the sender, it is suggested to try specifying the MAC address of the
+default gateway (you may use /sbin/route -n to find it out) as the
+remote MAC address instead.
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index b982854..8deb605 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -160,6 +160,12 @@ config NETCONSOLE
 	If you want to log kernel messages over the network, enable this.
 	See <file:Documentation/networking/netconsole.txt> for details.
 
+config NETKGDB
+	tristate "Network kgdb I/O backend"
+	---help---
+	If you want to debug the kernel over the network, enable this.
+	See <file:Documentation/networking/netkgdb.txt> for details.
+
 config NETCONSOLE_DYNAMIC
 	bool "Dynamic reconfiguration of logging targets"
 	depends on NETCONSOLE && SYSFS && CONFIGFS_FS && \
@@ -171,7 +177,7 @@ config NETCONSOLE_DYNAMIC
 	  See <file:Documentation/networking/netconsole.txt> for details.
 
 config NETPOLL
-	def_bool NETCONSOLE
+	def_bool NETCONSOLE || NETKGDB
 
 config NETPOLL_TRAP
 	bool "Netpoll traffic trapping"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index a6b8ce1..6eaa21f 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_MII) += mii.o
 obj-$(CONFIG_MDIO) += mdio.o
 obj-$(CONFIG_NET) += Space.o loopback.o
 obj-$(CONFIG_NETCONSOLE) += netconsole.o
+obj-$(CONFIG_NETKGDB) += netkgdb.o
 obj-$(CONFIG_PHYLIB) += phy/
 obj-$(CONFIG_RIONET) += rionet.o
 obj-$(CONFIG_NET_TEAM) += team/
diff --git a/drivers/net/netkgdb.c b/drivers/net/netkgdb.c
new file mode 100644
index 0000000..6a7bd90
--- /dev/null
+++ b/drivers/net/netkgdb.c
@@ -0,0 +1,728 @@
+/*
+ *  linux/drivers/net/netkgdb.c
+ *
+ *  Copyright (C) 2012 Andrei Warkentin <andreiw@...are.com>
+ *
+ *  Based on Matt Mackall's netconsole and
+ *  my FIQ/KGDB support code.
+ *
+ */
+
+#define pr_fmt(fmt) "netkgdb: " fmt
+
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/netpoll.h>
+#include <linux/workqueue.h>
+#include <linux/inet.h>
+#include <linux/inetdevice.h>
+#include <linux/kgdb.h>
+
+MODULE_AUTHOR("Maintainer: Andrei Warkentin <andreiw@...are.com>");
+MODULE_DESCRIPTION("KGDB I/O driver for network interfaces");
+MODULE_LICENSE("GPL");
+
+#define DEFAULT_PORT		7777
+#define MAX_PARAM_LENGTH	256
+#define MAX_RING_SIZE		PAGE_SIZE
+
+static char config[MAX_PARAM_LENGTH];
+module_param_string(netkgdb, config, MAX_PARAM_LENGTH, 0);
+MODULE_PARM_DESC(netkgdb, " netkgdb=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]");
+
+static LIST_HEAD(netkgdb_nts);
+
+/*
+ * Protects netkgdb_nts list and access to stored netkgdb targets
+ * between the net notifiers.
+ */
+static DEFINE_MUTEX(netkgdb_nts_lock);
+
+/**
+ * struct netkgdb_target - Represents a configured netkgdb target.
+ * @list:	Links this target into the netkgdb_nts.
+ * @tx_np:	netpoll structue used for KGDB/KDB traffic.
+ * @rx_np:	netpoll structure used to listed to inbound connections.
+ * @bp:		work_struct to break into KGDB/KDB.
+ * @ping:	work_struct to return alive message.
+ * @tx_ready:	set if there is an outgoing IP address assigned.
+ */
+struct netkgdb_target {
+	struct list_head	list;
+	struct netpoll		tx_np;
+	struct netpoll		rx_np;
+	struct work_struct	bp;
+	struct work_struct	ping;
+	bool			tx_ready;
+};
+
+static struct netkgdb_ring {
+	u8 buf[MAX_RING_SIZE];
+	int head;
+	int tail;
+} netkgdb_rx_ring, netkgdb_tx_ring;
+static int netkgdb_trapped = 0;
+
+/*
+ * Returns the offset of the first unconsumed character.
+ */
+static inline u8 *netkgdb_ring_off(struct netkgdb_ring *ring)
+{
+	return &ring->buf[ring->tail];
+}
+
+/*
+ * Returns the length of a contiguous buffer of unconsumed
+ * characters, to deal with ring buffer wraparound.
+ */
+static inline int netkgdb_ring_cont(struct netkgdb_ring *ring)
+{
+	if (ring->head < ring->tail)
+		return MAX_RING_SIZE - ring->tail;
+	else
+		return ring->head - ring->tail;
+}
+
+/*
+ * Returns the number of characters present in the ring.
+ */
+static inline int netkgdb_ring_level(struct netkgdb_ring *ring)
+{
+	int level = ring->head - ring->tail;
+
+	if (level < 0)
+		level = MAX_RING_SIZE + level;
+
+	return level;
+}
+
+/*
+ * Returns the number of characters that could be added to the ring.
+ */
+static inline int netkgdb_ring_room(struct netkgdb_ring *ring)
+{
+	return MAX_RING_SIZE - netkgdb_ring_level(ring) - 1;
+}
+
+/*
+ * Returns the character at a specific position in the ring,
+ * without consuming it.
+ */
+static inline u8 netkgdb_ring_peek(struct netkgdb_ring *ring, int i)
+{
+	return ring->buf[(ring->tail + i) % MAX_RING_SIZE];
+}
+
+/*
+ * Consumes a number of characters (up to count) in the ring.
+ */
+static inline int ring_consume(struct netkgdb_ring *ring, int count)
+{
+	count = min(count, netkgdb_ring_level(ring));
+
+	ring->tail = (ring->tail + count) % MAX_RING_SIZE;
+	smp_mb();
+	return count;
+}
+
+/*
+ * Adds a character to the ring, updating the number of unconsumed
+ * characters.
+ */
+static inline int ring_push(struct netkgdb_ring *ring, u8 data)
+{
+	if (netkgdb_ring_room(ring) == 0)
+		return 0;
+
+	ring->buf[ring->head] = data;
+	smp_mb();
+	ring->head = (ring->head + 1) % MAX_RING_SIZE;
+	smp_mb();
+
+	return 1;
+}
+
+/*
+ * Flushes the contents of the outgoing (TX) ring
+ * to all netkgdb targets that are up and have a remote
+ * host configured.
+ */
+static inline void netkgdb_tx_flush(void)
+{
+	int frag;
+	u8 *start;
+	struct netkgdb_target *nt;
+
+	/* In interrupt context on one cpu. Forget about the locks. */
+	while (netkgdb_ring_level(&netkgdb_tx_ring))
+	{
+		start = netkgdb_ring_off(&netkgdb_tx_ring);
+		frag = netkgdb_ring_cont(&netkgdb_tx_ring);
+
+		list_for_each_entry(nt, &netkgdb_nts, list) {
+			if (nt->tx_ready &&
+			    nt->tx_np.dev &&
+			    netif_running(nt->tx_np.dev))
+				netpoll_send_udp(&nt->tx_np, start, frag);
+		}
+
+		ring_consume(&netkgdb_tx_ring, frag);
+	}
+}
+
+/*
+ * Called from KGDB/KDB, returns the the next character read
+ * from the network interface(s), while processing any
+ * connect() requests from remote hosts and flushing
+ * the TX ring.
+ */
+static int netkgdb_char_get(void)
+{
+	u8 c;
+	struct netkgdb_target *nt;
+
+	/* In interrupt context on ONE cpu. Forget about the locks. */
+	list_for_each_entry(nt, &netkgdb_nts, list) {
+
+		/*
+		 * Polls the devices, both for KGDB I/O and
+		 * new incoming connections.
+		 */
+		if (netif_running(nt->rx_np.dev))
+			netpoll_poll_dev(nt->rx_np.dev);
+	}
+
+	/* Flush any output we didn't get to in char_put. */
+	netkgdb_tx_flush();
+
+	if (!netkgdb_ring_level(&netkgdb_rx_ring))
+		return NO_POLL_CHAR;
+
+	c = netkgdb_ring_peek(&netkgdb_rx_ring, 0);
+	if (c == '\n')
+		c = '\r';
+	ring_consume(&netkgdb_rx_ring, 1);
+	return c;
+}
+
+/*
+ * Sends a character to remote hosts, taking care to
+ * avoid singe-character packets. Only flush the ring
+ * on a full ring or at the end of the line. Whatever
+ * didn't get flushed will get flushed in netkgdb_char_get.
+ * There is, of course, some potential for missed data,
+ * like if KDB/KGDB crashed while sending data, but it's
+ * probably insignificant given the likelyhood and the
+ * amount of lost data (less than a line).
+ */
+static void netkgdb_char_put(u8 c)
+{
+	while (!ring_push(&netkgdb_tx_ring, c))
+		netkgdb_tx_flush();
+
+	if (c == '\n')
+		netkgdb_tx_flush();
+}
+
+/*
+ * Invoked before KDB/KGDB takes control.
+ */
+static void netkgdb_exp_pre(void)
+{
+	netpoll_set_trap(1);
+	netkgdb_trapped = 1;
+}
+
+/*
+ * Invoked on KDB/KGDB exit.
+ */
+static void netkgdb_exp_post(void)
+{
+	netkgdb_trapped = 0;
+	netpoll_set_trap(0);
+}
+
+static struct kgdb_io netkgdb_io_ops = {
+	.name = "netkgdb",
+	.read_char = netkgdb_char_get,
+	.write_char = netkgdb_char_put,
+	.pre_exception = netkgdb_exp_pre,
+	.post_exception = netkgdb_exp_post,
+};
+
+/*
+ * Handles receiving the '!!!BREAK!!!' string from
+ * a remote host. We cannot do this from the rx_hook
+ * itself, otherwise we'll deadlock due to reentering
+ * netpoll.
+ */
+void netkgdb_work_bp(struct work_struct *work)
+{
+	struct netkgdb_target *nt = container_of(work,
+						 struct netkgdb_target,
+						 bp);
+
+	pr_emerg("breaking into KGDB from %pI4:%d\n",
+	       &nt->tx_np.remote_ip,
+	       nt->tx_np.remote_port);
+	kgdb_breakpoint();
+}
+
+/*
+ * Handles sending an alive message to a remote host,
+ * as a response to network messages while the local
+ * host is not crashed. Once again, cannot be done
+ * from rx_hook context, due to netpoll reentrance
+ * issue.
+ */
+void netkgdb_work_ping(struct work_struct *work)
+{
+	char break_string[] = "Alive - '!!!BREAK!!!' to break in.\n";
+	struct netkgdb_target *nt = container_of(work,
+						 struct netkgdb_target,
+						 ping);
+
+	if (nt->tx_ready &&
+	    nt->tx_np.dev &&
+	    netif_running(nt->tx_np.dev))
+		netpoll_send_udp(&nt->tx_np, break_string,
+				 sizeof(break_string) - 1);
+}
+
+/*
+ * Invoked on every received UDP/IP message received from
+ * a known host, setup in netkgdb_nt_initial, or by
+ * netkgdb_in_rx_hook. If the host is not crashed, it will
+ * return an alive message and allow breaking in, otherwise
+ * it fills the RX ring with received data. Note that there
+ * is no locking on the RX ring - when we crash we are guaranteed
+ * to run on one CP.
+ */
+void netkgdb_io_rx_hook(struct netpoll *np,
+			u8 *h_source,
+			__be32 saddr,
+			struct udphdr *uh,
+			char *data,
+			int len)
+{
+	int count = 0;
+	char break_string[] = "!!!BREAK!!!\n";
+	struct netkgdb_target *nt = container_of(np,
+						 struct netkgdb_target,
+						 tx_np);
+
+	if (!netkgdb_trapped) {
+		if ((len == sizeof(break_string) - 1) &&
+		    !memcmp(data, break_string,
+			    sizeof(break_string) - 1))
+			schedule_work(&nt->bp);
+		else
+			schedule_work(&nt->ping);
+		return;
+	}
+
+	while (count < len)
+		ring_push(&netkgdb_rx_ring, data[count++]);
+}
+
+/*
+ * Invoked on every UDP/IP message received from a remote
+ * host. For a known host, which we configured for, does nothing.
+ * For an unknown host, modifies the KGDB I/O netpoll struct
+ * with the new remote address, so that netkgdb_io_rx_hook can
+ * receive messages and KDB can send data to the new adddress.
+ */
+void netkgdb_in_rx_hook(struct netpoll *np,
+		   u8 *h_source,
+		   __be32 saddr,
+		   struct udphdr *uh,
+		   char *data,
+		   int len)
+{
+	struct netkgdb_target *nt = container_of(np,
+						 struct netkgdb_target,
+						 rx_np);
+
+	if (!nt->tx_np.dev)
+		return;
+
+	if (nt->tx_np.remote_ip != saddr ||
+	    nt->tx_np.remote_port != ntohs(uh->source) ||
+	    memcmp(np->remote_mac, h_source, ETH_ALEN)) {
+
+		/*
+		 * This is safe because npinfo->rx_lock is taken
+		 * and is shared with tx_np. The npinfo->rx_lock
+		 * also means this is safe with any up() or down()
+		 * work going on.
+		 */
+		nt->tx_np.remote_ip = saddr;
+		nt->tx_np.remote_port = ntohs(uh->source);
+		memcpy(np->remote_mac, h_source, ETH_ALEN);
+		nt->tx_ready = true;
+
+		pr_info("accepted from %pI4:%d\n",
+			&saddr, ntohs(uh->source));
+
+		netkgdb_io_rx_hook(&nt->tx_np, h_source, saddr, uh, data, len);
+	}
+}
+
+/*
+ * Cleans up a netkgdb_target structure.
+ */
+static void netkgdb_nt_free(struct netkgdb_target *nt)
+{
+	nt->tx_ready = false;
+	flush_work(&nt->bp);
+	flush_work(&nt->ping);
+	if (nt->rx_np.dev)
+		netpoll_cleanup(&nt->rx_np);
+	if (nt->tx_np.dev)
+		netpoll_cleanup(&nt->tx_np);
+	kfree(nt);
+}
+
+/*
+ * Creates a new netkgdb_target structure, filling in
+ * defaults. Does not add it to target list or register
+ * with netpoll.
+ */
+static struct netkgdb_target *netkgdb_nt_alloc(char *dev_name)
+{
+	struct netkgdb_target *nt;
+
+	nt = kzalloc(sizeof(*nt), GFP_KERNEL);
+	if (!nt) {
+		pr_err("failed to allocate memory\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	INIT_WORK(&nt->bp, netkgdb_work_bp);
+	INIT_WORK(&nt->ping, netkgdb_work_ping);
+	nt->tx_np.name = "netkgdb-io";
+	strlcpy(nt->tx_np.dev_name, dev_name, IFNAMSIZ);
+	nt->tx_np.local_port = DEFAULT_PORT;
+	nt->tx_np.remote_port = DEFAULT_PORT;
+	nt->tx_np.rx_hook = netkgdb_io_rx_hook;
+	memset(nt->tx_np.remote_mac, 0xff, ETH_ALEN);
+
+	nt->rx_np.name = "netkgdb-inbound";
+	strlcpy(nt->rx_np.dev_name, dev_name, IFNAMSIZ);
+	nt->rx_np.local_port = DEFAULT_PORT;
+	nt->rx_np.rx_hook = netkgdb_in_rx_hook;
+	memset(nt->rx_np.remote_mac, 0xff, ETH_ALEN);
+	return nt;
+}
+
+/*
+ * Called in response to network interface going down.
+ * __netpoll_cleanup must be used over netpoll_cleanup, due
+ * rtnl_lock being taken by network dev notifiers.
+ */
+static void netkgdb_nt_down(struct netkgdb_target *nt)
+{
+
+	if (nt->tx_np.dev ||
+	    nt->rx_np.dev)
+		pr_info("down %s\n", nt->tx_np.dev->name);
+
+	nt->tx_ready = false;
+	if (nt->tx_np.dev) {
+		__netpoll_cleanup(&nt->tx_np);
+		dev_put(nt->tx_np.dev);
+		nt->tx_np.dev = NULL;
+	}
+	if (nt->rx_np.dev) {
+		__netpoll_cleanup(&nt->rx_np);
+		dev_put(nt->rx_np.dev);
+		nt->rx_np.dev = NULL;
+	}
+}
+
+/*
+ * Called in response to network interface going up.
+ * __netpoll_setup must be used over netpoll_setup, due
+ * rtnl_lock being taken by network dev notifiers.
+ */
+static int netkgdb_nt_up(struct netkgdb_target *nt,
+			      struct in_ifaddr *ifa)
+{
+	int err;
+
+	nt->tx_np.local_ip = ifa->ifa_local;
+	nt->tx_np.dev = ifa->ifa_dev->dev;
+	dev_hold(nt->tx_np.dev);
+	err = __netpoll_setup(&nt->tx_np);
+	if (err)
+		goto done;
+
+	nt->rx_np.local_ip = ifa->ifa_local;
+	nt->rx_np.dev = ifa->ifa_dev->dev;
+	dev_hold(nt->rx_np.dev);
+	err = __netpoll_setup(&nt->rx_np);
+	if (err) {
+		netpoll_cleanup(&nt->tx_np);
+		goto done;
+	}
+
+	if (nt->tx_np.remote_ip && nt->tx_np.remote_port)
+		nt->tx_ready = true;
+
+done:
+	if (!err)
+		pr_info("up %s (%pI4:%d)\n",
+		       ifa->ifa_dev->dev->name,
+		       &ifa->ifa_local,
+		       nt->tx_np.local_port);
+	else {
+		pr_err("couldn't configure %s (%pI4)\n",
+		       ifa->ifa_dev->dev->name,
+		       &ifa->ifa_local);
+		if (nt->tx_np.dev) {
+			dev_put(nt->tx_np.dev);
+			nt->tx_np.dev = NULL;
+		}
+		if (nt->rx_np.dev) {
+			dev_put(nt->rx_np.dev);
+			nt->rx_np.dev = NULL;
+		}
+	}
+	return err;
+}
+
+/*
+ * Allocates a netkgdb_target and sets it up
+ * for a new network interface coming up.
+ */
+static void netkgdb_nt_late(struct in_ifaddr *ifa)
+{
+	int err;
+	struct netkgdb_target *nt;
+
+	nt = netkgdb_nt_alloc(ifa->ifa_dev->dev->name);
+	if (IS_ERR(nt))
+		return;
+
+	err = netkgdb_nt_up(nt, ifa);
+
+	if (!err)
+		list_add(&nt->list, &netkgdb_nts);
+
+	if (err)
+		netkgdb_nt_free(nt);
+}
+
+/*
+ * Allocate a target (from boot/module param) and
+ * setup netpoll for it
+ */
+static int netkgdb_nt_initial(char *target_config)
+{
+	int err;
+	struct netkgdb_target *nt;
+
+	nt = netkgdb_nt_alloc("eth0");
+	if (IS_ERR(nt))
+		return PTR_ERR(nt);
+
+	/* Parse parameters and setup netpoll */
+	err = netpoll_parse_options(&nt->tx_np, target_config);
+	if (err)
+		goto done;
+
+	if (nt->tx_np.remote_ip && nt->tx_np.remote_port)
+		nt->tx_ready = true;
+
+	err = netpoll_setup(&nt->tx_np);
+	if (err)
+		goto done;
+
+	/* Synchronize inbound np with I/O np options. */
+	strlcpy(nt->rx_np.dev_name, nt->tx_np.dev_name, IFNAMSIZ);
+	nt->rx_np.local_port = nt->tx_np.local_port;
+	nt->rx_np.local_ip = nt->tx_np.local_ip;
+
+	err = netpoll_setup(&nt->rx_np);
+	if (err)
+		goto done;
+
+	mutex_lock(&netkgdb_nts_lock);
+	list_add(&nt->list, &netkgdb_nts);
+	mutex_unlock(&netkgdb_nts_lock);
+
+done:
+	if (err)
+		netkgdb_nt_free(nt);
+	return err;
+}
+
+/*
+ * Handle network address notifications.
+ */
+static int netkgdb_inetaddr_event(struct notifier_block *this,
+				  unsigned long event, void *ptr)
+{
+	struct netkgdb_target *nt;
+	struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+	struct net_device *dev = ifa->ifa_dev->dev;
+	bool found = false;
+
+	mutex_lock(&netkgdb_nts_lock);
+	switch (event) {
+	case NETDEV_UP:
+		list_for_each_entry(nt, &netkgdb_nts, list) {
+			if (!strcmp(nt->tx_np.dev_name, dev->name)) {
+				found = true;
+				netkgdb_nt_down(nt);
+				netkgdb_nt_up(nt, ifa);
+				break;
+			}
+		}
+		if (!found)
+			netkgdb_nt_late(ifa);
+		break;
+	case NETDEV_DOWN:
+		list_for_each_entry(nt, &netkgdb_nts, list) {
+			if (nt->tx_np.dev == dev)
+				netkgdb_nt_down(nt);
+		}
+		break;
+	}
+	mutex_unlock(&netkgdb_nts_lock);
+
+	return NOTIFY_DONE;
+}
+
+/*
+ * Handle network interface device notifications.
+ */
+static int netkgdb_netdev_event(struct notifier_block *this,
+				unsigned long event,
+				void *ptr)
+{
+	struct netkgdb_target *nt;
+	struct net_device *dev = ptr;
+
+	if (!(event == NETDEV_CHANGENAME ||
+	      event == NETDEV_UNREGISTER ||
+	      event == NETDEV_RELEASE ||
+	      event == NETDEV_JOIN))
+		return NOTIFY_DONE;
+
+	mutex_lock(&netkgdb_nts_lock);
+	list_for_each_entry(nt, &netkgdb_nts, list) {
+		if (nt->tx_np.dev == dev) {
+			switch (event) {
+			case NETDEV_CHANGENAME:
+				strlcpy(nt->tx_np.dev_name, dev->name, IFNAMSIZ);
+				strlcpy(nt->rx_np.dev_name, dev->name, IFNAMSIZ);
+				break;
+			case NETDEV_RELEASE:
+			case NETDEV_JOIN:
+			case NETDEV_UNREGISTER:
+				/*
+				 * rtnl_lock already held
+				 */
+				netkgdb_nt_down(nt);
+				break;
+			}
+		}
+	}
+	mutex_unlock(&netkgdb_nts_lock);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block netkgdb_netdev_notifier = {
+	.notifier_call  = netkgdb_netdev_event,
+};
+
+static struct notifier_block netkgdb_inetaddr_notifier = {
+	.notifier_call  = netkgdb_inetaddr_event,
+};
+
+static int __init netkgdb_init(void)
+{
+	int err;
+	struct netkgdb_target *nt, *tmp;
+	char *input = config;
+
+	/*
+	 * Not having a netkgdb= parameter itself is not
+	 * a mistake, asn netkgdb_nt_late will take care
+	 * of things. However, passing a bad parameter
+	 * is a mistake (like not having a local IP address).
+	 */
+	if (strnlen(input, MAX_PARAM_LENGTH)) {
+		err = netkgdb_nt_initial(input);
+		if (err)
+			goto fail;
+	}
+
+	err = register_netdevice_notifier(&netkgdb_netdev_notifier);
+	if (err)
+		goto fail;
+
+	err = register_inetaddr_notifier(&netkgdb_inetaddr_notifier);
+	if (err)
+		goto undo_netdev;
+
+	err = kgdb_register_io_module(&netkgdb_io_ops);
+	if (err) {
+		pr_err("failed to register I/O ops\n");
+		goto undo_inetaddr;
+	}
+
+	pr_info("started\n");
+	return err;
+
+undo_inetaddr:
+	unregister_inetaddr_notifier(&netkgdb_inetaddr_notifier);
+
+undo_netdev:
+	unregister_netdevice_notifier(&netkgdb_netdev_notifier);
+
+fail:
+	pr_err("cleaning up\n");
+	list_for_each_entry_safe(nt, tmp, &netkgdb_nts, list) {
+		list_del(&nt->list);
+		netkgdb_nt_free(nt);
+	}
+
+	return err;
+}
+
+static void __exit netkgdb_cleanup(void)
+{
+	struct netkgdb_target *nt, *tmp;
+
+	kgdb_register_io_module(&netkgdb_io_ops);
+	unregister_netdevice_notifier(&netkgdb_netdev_notifier);
+
+	list_for_each_entry_safe(nt, tmp, &netkgdb_nts, list) {
+		list_del(&nt->list);
+		netkgdb_nt_free(nt);
+	}
+}
+
+/*
+ * Use late_initcall to ensure netkgdb is
+ * initialized after network device driver if built-in.
+ *
+ * late_initcall() and module_init() are identical if built as module.
+ */
+late_initcall(netkgdb_init);
+module_exit(netkgdb_cleanup);
+
+#ifndef MODULE
+static int __init option_setup(char *opt)
+{
+	strlcpy(config, opt, MAX_PARAM_LENGTH);
+	return 1;
+}
+__setup("netkgdb=", option_setup);
+#endif /* MODULE */
-- 
1.7.8.3

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ