started by Ingo Molnar <mingo@redhat.com>, 2001.09.17
2.6 port and netpoll api by Matt Mackall <mpm@selenic.com>, Sep 9 2003
IPv6 support by Cong Wang <xiyou.wangcong@gmail.com>, Jan 1 2013
+Extended console support by Tejun Heo <tj@kernel.org>, May 1 2015
Please send bug reports to Matt Mackall <mpm@selenic.com>
Satyam Sharma <satyam.sharma@gmail.com>, and Cong Wang <xiyou.wangcong@gmail.com>
It takes a string configuration parameter "netconsole" in the
following format:
- netconsole=[src-port]@[src-ip]/[<dev>],[tgt-port]@<tgt-ip>/[tgt-macaddr]
+ netconsole=[+][src-port]@[src-ip]/[<dev>],[tgt-port]@<tgt-ip>/[tgt-macaddr]
where
+ + if present, enable extended console support
src-port source for UDP packets (defaults to 6665)
src-ip source IP to use (interface address)
dev network interface (eth0)
The interface exposes these parameters of a netconsole target to userspace:
enabled Is this target currently enabled? (read-write)
+ extended Extended mode enabled (read-write)
dev_name Local network interface name (read-write)
local_port Source UDP port to use (read-write)
remote_port Remote agent's UDP port (read-write)
useful if you want to use interfaces that have newly come up (and may not
have existed when netconsole was loaded / initialized).
+Extended console:
+=================
+
+If '+' is prefixed to the configuration line or "extended" config file
+is set to 1, extended console support is enabled. An example boot
+param follows.
+
+ linux netconsole=+4444@10.0.0.1/eth1,9353@10.0.0.2/12:34:56:78:9a:bc
+
+Log messages are transmitted with extended metadata header in the
+following format which is the same as /dev/kmsg.
+
+ <level>,<sequnum>,<timestamp>,<contflag>;<message text>
+
+Non printable characters in <message text> are escaped using "\xff"
+notation. If the message contains optional dictionary, verbatim
+newline is used as the delimeter.
+
+If a message doesn't fit in certain number of bytes (currently 1000),
+the message is split into multiple fragments by netconsole. These
+fragments are transmitted with "ncfrag" header field added.
+
+ ncfrag=<byte-offset>/<total-bytes>
+
+For example, assuming a lot smaller chunk size, a message "the first
+chunk, the 2nd chunk." may be split as follows.
+
+ 6,416,1758426,-,ncfrag=0/31;the first chunk,
+ 6,416,1758426,-,ncfrag=16/31; the 2nd chunk.
+
Miscellaneous notes:
====================
/* This needs to be a spinlock because write_msg() cannot sleep */
static DEFINE_SPINLOCK(target_list_lock);
+/*
+ * Console driver for extended netconsoles. Registered on the first use to
+ * avoid unnecessarily enabling ext message formatting.
+ */
+static struct console netconsole_ext;
+
/**
* struct netconsole_target - Represents a configured netconsole target.
* @list: Links this target into the target_list.
struct config_item item;
#endif
bool enabled;
+ bool extended;
struct netpoll np;
};
nt->np.remote_port = 6666;
eth_broadcast_addr(nt->np.remote_mac);
+ if (*target_config == '+') {
+ nt->extended = true;
+ target_config++;
+ }
+
/* Parse parameters and setup netpoll */
err = netpoll_parse_options(&nt->np, target_config);
if (err)
return snprintf(buf, PAGE_SIZE, "%d\n", nt->enabled);
}
+static ssize_t show_extended(struct netconsole_target *nt, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", nt->extended);
+}
+
static ssize_t show_dev_name(struct netconsole_target *nt, char *buf)
{
return snprintf(buf, PAGE_SIZE, "%s\n", nt->np.dev_name);
}
if (enabled) { /* true */
+ if (nt->extended && !(netconsole_ext.flags & CON_ENABLED)) {
+ netconsole_ext.flags |= CON_ENABLED;
+ register_console(&netconsole_ext);
+ }
+
/*
* Skip netpoll_parse_options() -- all the attributes are
* already configured via configfs. Just print them out.
return strnlen(buf, count);
}
+static ssize_t store_extended(struct netconsole_target *nt,
+ const char *buf,
+ size_t count)
+{
+ int extended;
+ int err;
+
+ if (nt->enabled) {
+ pr_err("target (%s) is enabled, disable to update parameters\n",
+ config_item_name(&nt->item));
+ return -EINVAL;
+ }
+
+ err = kstrtoint(buf, 10, &extended);
+ if (err < 0)
+ return err;
+ if (extended < 0 || extended > 1)
+ return -EINVAL;
+
+ nt->extended = extended;
+
+ return strnlen(buf, count);
+}
+
static ssize_t store_dev_name(struct netconsole_target *nt,
const char *buf,
size_t count)
__CONFIGFS_ATTR(_name, S_IRUGO | S_IWUSR, show_##_name, store_##_name)
NETCONSOLE_TARGET_ATTR_RW(enabled);
+NETCONSOLE_TARGET_ATTR_RW(extended);
NETCONSOLE_TARGET_ATTR_RW(dev_name);
NETCONSOLE_TARGET_ATTR_RW(local_port);
NETCONSOLE_TARGET_ATTR_RW(remote_port);
static struct configfs_attribute *netconsole_target_attrs[] = {
&netconsole_target_enabled.attr,
+ &netconsole_target_extended.attr,
&netconsole_target_dev_name.attr,
&netconsole_target_local_port.attr,
&netconsole_target_remote_port.attr,
.notifier_call = netconsole_netdev_event,
};
+/**
+ * send_ext_msg_udp - send extended log message to target
+ * @nt: target to send message to
+ * @msg: extended log message to send
+ * @msg_len: length of message
+ *
+ * Transfer extended log @msg to @nt. If @msg is longer than
+ * MAX_PRINT_CHUNK, it'll be split and transmitted in multiple chunks with
+ * ncfrag header field added to identify them.
+ */
+static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
+ int msg_len)
+{
+ static char buf[MAX_PRINT_CHUNK]; /* protected by target_list_lock */
+ const char *header, *body;
+ int offset = 0;
+ int header_len, body_len;
+
+ if (msg_len <= MAX_PRINT_CHUNK) {
+ netpoll_send_udp(&nt->np, msg, msg_len);
+ return;
+ }
+
+ /* need to insert extra header fields, detect header and body */
+ header = msg;
+ body = memchr(msg, ';', msg_len);
+ if (WARN_ON_ONCE(!body))
+ return;
+
+ header_len = body - header;
+ body_len = msg_len - header_len - 1;
+ body++;
+
+ /*
+ * Transfer multiple chunks with the following extra header.
+ * "ncfrag=<byte-offset>/<total-bytes>"
+ */
+ memcpy(buf, header, header_len);
+
+ while (offset < body_len) {
+ int this_header = header_len;
+ int this_chunk;
+
+ this_header += scnprintf(buf + this_header,
+ sizeof(buf) - this_header,
+ ",ncfrag=%d/%d;", offset, body_len);
+
+ this_chunk = min(body_len - offset,
+ MAX_PRINT_CHUNK - this_header);
+ if (WARN_ON_ONCE(this_chunk <= 0))
+ return;
+
+ memcpy(buf + this_header, body + offset, this_chunk);
+
+ netpoll_send_udp(&nt->np, buf, this_header + this_chunk);
+
+ offset += this_chunk;
+ }
+}
+
+static void write_ext_msg(struct console *con, const char *msg,
+ unsigned int len)
+{
+ struct netconsole_target *nt;
+ unsigned long flags;
+
+ if ((oops_only && !oops_in_progress) || list_empty(&target_list))
+ return;
+
+ spin_lock_irqsave(&target_list_lock, flags);
+ list_for_each_entry(nt, &target_list, list)
+ if (nt->extended && nt->enabled && netif_running(nt->np.dev))
+ send_ext_msg_udp(nt, msg, len);
+ spin_unlock_irqrestore(&target_list_lock, flags);
+}
+
static void write_msg(struct console *con, const char *msg, unsigned int len)
{
int frag, left;
spin_lock_irqsave(&target_list_lock, flags);
list_for_each_entry(nt, &target_list, list) {
- if (nt->enabled && netif_running(nt->np.dev)) {
+ if (!nt->extended && nt->enabled && netif_running(nt->np.dev)) {
/*
* We nest this inside the for-each-target loop above
* so that we're able to get as much logging out to
spin_unlock_irqrestore(&target_list_lock, flags);
}
+static struct console netconsole_ext = {
+ .name = "netcon_ext",
+ .flags = CON_EXTENDED, /* starts disabled, registered on first use */
+ .write = write_ext_msg,
+};
+
static struct console netconsole = {
.name = "netcon",
.flags = CON_ENABLED,
goto fail;
}
/* Dump existing printks when we register */
- netconsole.flags |= CON_PRINTBUFFER;
+ if (nt->extended)
+ netconsole_ext.flags |= CON_PRINTBUFFER |
+ CON_ENABLED;
+ else
+ netconsole.flags |= CON_PRINTBUFFER;
spin_lock_irqsave(&target_list_lock, flags);
list_add(&nt->list, &target_list);
if (err)
goto undonotifier;
+ if (netconsole_ext.flags & CON_ENABLED)
+ register_console(&netconsole_ext);
register_console(&netconsole);
pr_info("network logging started\n");
{
struct netconsole_target *nt, *tmp;
+ unregister_console(&netconsole_ext);
unregister_console(&netconsole);
dynamic_netconsole_exit();
unregister_netdevice_notifier(&netconsole_netdev_notifier);