Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 5 Dec 2008 17:13:40 +0000 (UTC)
From:      Luigi Rizzo <luigi@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r185643 - head/lib/libstand
Message-ID:  <200812051713.mB5HDerC068258@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: luigi
Date: Fri Dec  5 17:13:40 2008
New Revision: 185643
URL: http://svn.freebsd.org/changeset/base/185643

Log:
  Some libstand/bootp.c extension (written by Danny Braniss, slightly
  revised/modified by me) to store dhcp options into kenv variables,
  so the information is available to the boot loader and can be used
  to customize the boot process.
  
  The change is totally unintrusive, essentially made of a single
  function to be called while parsing a dhcp response, and a couple
  of tables to classify options.  The values extracted from dhcp
  options are stored in the kenv environment in one of these forms:
  
   + options whose name and type is known are saved as
          dhcp.name = value     (string, or number/ip addresses lists)
  
   + unknown options are assumed to be strings and saved as
          dhcp.option-NNN = "value"
  
   + options listed as '__INDIR' and sent on the wire as e.g.
          option unknown-252 "some.name=the actual value"
     are saved as
          some.name = "the actual value"
  
   + options listed as '__ILIST' and sent on the wire as e.g.
          option unknown-249 "a.b=foo bar; c.d= 123; e.f=done"
     are saved as multiple values
          a.b="foo bar"
          c.d="123"
          e.f="done"
  
  As you can see there is quite a bit of flexibility on what can
  be passed to the loader or the kernel.
  
  For the time being the vendor-specific table is mostly disabled,
  because there is no standard set of options for FreeBSD, and I don't
  know all the pxe-specific vendor options.
  
  Also, applications using libstand may live in memory-constrained
  environments, so it makes sense to keep these tables as small as
  possible, especially considering that one can generate arbitrary
  name=value pairs using site-specific options of type __INDIR or
  __ILIST (there are 4 __ILIST and 5 __INDIR in the table, numbered
  246..249 and 250..254).
  
  Actually, considering that probably 75% of the standard dhcp options
  are totally useless, it might make sense to remove them as well.
  
  Submitted by:	Danny Braniss
  MFC after:	4 weeks

Modified:
  head/lib/libstand/bootp.c

Modified: head/lib/libstand/bootp.c
==============================================================================
--- head/lib/libstand/bootp.c	Fri Dec  5 16:47:30 2008	(r185642)
+++ head/lib/libstand/bootp.c	Fri Dec  5 17:13:40 2008	(r185643)
@@ -47,6 +47,12 @@ __FBSDID("$FreeBSD$");
 #define BOOTP_DEBUGxx
 #define SUPPORT_DHCP
 
+#define	DHCP_ENV_NOVENDOR	1	/* do not parse vendor options */
+#define	DHCP_ENV_PXE		10	/* assume pxe vendor options */
+#define	DHCP_ENV_FREEBSD	11	/* assume freebsd vendor options
+/* set DHCP_ENV to one of the values above to export dhcp options to kenv */
+#define DHCP_ENV		DHCP_ENV_NO_VENDOR
+
 #include "stand.h"
 #include "net.h"
 #include "netif.h"
@@ -72,6 +78,13 @@ static	int vend_rfc1048(u_char *, u_int)
 static	void vend_cmu(u_char *);
 #endif
 
+#ifdef DHCP_ENV		/* export the dhcp response to kenv */
+struct dhcp_opt;
+static void setenv_(u_char *cp,  u_char *ep, struct dhcp_opt *opts);
+#else
+#define setenv_(a, b, c)
+#endif
+
 #ifdef SUPPORT_DHCP
 static char expected_dhcpmsgtype = -1, dhcp_ok;
 struct in_addr dhcp_serverip;
@@ -351,6 +364,8 @@ vend_rfc1048(cp, len)
 	/* Step over magic cookie */
 	cp += sizeof(int);
 
+	setenv_(cp, ep, NULL);
+
 	while (cp < ep) {
 		tag = *cp++;
 		size = *cp++;
@@ -412,3 +427,316 @@ vend_cmu(cp)
 	}
 }
 #endif
+
+#ifdef DHCP_ENV
+/*
+ * Parse DHCP options and store them into kenv variables.
+ * Original code from Danny Braniss, modifications by Luigi Rizzo.
+ *
+ * The parser is driven by tables which specify the type and name of
+ * each dhcp option and how it appears in kenv.
+ * The first entry in the list contains the prefix used to set the kenv
+ * name (including the . if needed), the last entry must have a 0 tag.
+ * Entries do not need to be sorted though it helps for readability.
+ *
+ * Certain vendor-specific tables can be enabled according to DHCP_ENV.
+ * Set it to 0 if you don't want any.
+ */
+enum opt_fmt { __NONE = 0,
+	__8 = 1, __16 = 2, __32 = 4,	/* Unsigned fields, value=size	*/
+	__IP,				/* IPv4 address			*/
+	__TXT,				/* C string			*/
+	__BYTES,			/* byte sequence, printed %02x	*/
+	__INDIR,			/* name=value			*/
+	__ILIST,			/* name=value;name=value ... */
+	__VE,				/* vendor specific, recurse	*/
+};
+
+struct dhcp_opt {
+	uint8_t	tag;
+	uint8_t	fmt;
+	const char	*desc;
+};
+
+static struct dhcp_opt vndr_opt[] = { /* Vendor Specific Options */
+#if DHCP_ENV == DHCP_ENV_FREEBSD /* FreeBSD table in the original code */
+	{0,	0,	"FreeBSD"},		/* prefix */
+	{1,	__TXT,	"kernel"},
+	{2,	__TXT,	"kernelname"},
+	{3,	__TXT,	"kernel_options"},
+	{4,	__IP,	"usr-ip"},
+	{5,	__TXT,	"conf-path"},
+	{6,	__TXT,	"rc.conf0"},
+	{7,	__TXT,	"rc.conf1"},
+	{8,	__TXT,	"rc.conf2"},
+	{9,	__TXT,	"rc.conf3"},
+	{10,	__TXT,	"rc.conf4"},
+	{11,	__TXT,	"rc.conf5"},
+	{12,	__TXT,	"rc.conf6"},
+	{13,	__TXT,	"rc.conf7"},
+	{14,	__TXT,	"rc.conf8"},
+	{15,	__TXT,	"rc.conf9"},
+
+	{20,	__TXT,  "boot.nfsroot.options"},
+
+	{245,	__INDIR, ""},
+	{246,	__INDIR, ""},
+	{247,	__INDIR, ""},
+	{248,	__INDIR, ""},
+	{249,	__INDIR, ""},
+	{250,	__INDIR, ""},
+	{251,	__INDIR, ""},
+	{252,	__INDIR, ""},
+	{253,	__INDIR, ""},
+	{254,	__INDIR, ""},
+
+#elif DHCP_ENV == DHCP_ENV_PXE		/* some pxe options, RFC4578 */
+	{0,	0,	"pxe"},		/* prefix */
+	{93,	__16,	"system-architecture"},
+	{94,	__BYTES,	"network-interface"},
+	{97,	__BYTES,	"machine-identifier"},
+#else					/* default (empty) table */
+	{0,	0,	""},		/* prefix */
+#endif
+	{0,	__TXT,	"%soption-%d"}
+};
+
+static struct dhcp_opt dhcp_opt[] = {
+	/* DHCP Option names, formats and codes, from RFC2132. */
+	{0,	0,	"dhcp."},	// prefix
+	{1,	__IP,	"subnet-mask"},
+	{2,	__32,	"time-offset"}, /* this is signed */
+	{3,	__IP,	"routers"},
+	{4,	__IP,	"time-servers"},
+	{5,	__IP,	"ien116-name-servers"},
+	{6,	__IP,	"domain-name-servers"},
+	{7,	__IP,	"log-servers"},
+	{8,	__IP,	"cookie-servers"},
+	{9,	__IP,	"lpr-servers"},
+	{10,	__IP,	"impress-servers"},
+	{11,	__IP,	"resource-location-servers"},
+	{12,	__TXT,	"host-name"},
+	{13,	__16,	"boot-size"},
+	{14,	__TXT,	"merit-dump"},
+	{15,	__TXT,	"domain-name"},
+	{16,	__IP,	"swap-server"},
+	{17,	__TXT,	"root-path"},
+	{18,	__TXT,	"extensions-path"},
+	{19,	__8,	"ip-forwarding"},
+	{20,	__8,	"non-local-source-routing"},
+	{21,	__IP,	"policy-filter"},
+	{22,	__16,	"max-dgram-reassembly"},
+	{23,	__8,	"default-ip-ttl"},
+	{24,	__32,	"path-mtu-aging-timeout"},
+	{25,	__16,	"path-mtu-plateau-table"},
+	{26,	__16,	"interface-mtu"},
+	{27,	__8,	"all-subnets-local"},
+	{28,	__IP,	"broadcast-address"},
+	{29,	__8,	"perform-mask-discovery"},
+	{30,	__8,	"mask-supplier"},
+	{31,	__8,	"perform-router-discovery"},
+	{32,	__IP,	"router-solicitation-address"},
+	{33,	__IP,	"static-routes"},
+	{34,	__8,	"trailer-encapsulation"},
+	{35,	__32,	"arp-cache-timeout"},
+	{36,	__8,	"ieee802-3-encapsulation"},
+	{37,	__8,	"default-tcp-ttl"},
+	{38,	__32,	"tcp-keepalive-interval"},
+	{39,	__8,	"tcp-keepalive-garbage"},
+	{40,	__TXT,	"nis-domain"},
+	{41,	__IP,	"nis-servers"},
+	{42,	__IP,	"ntp-servers"},
+	{43,	__VE,	"vendor-encapsulated-options"},
+	{44,	__IP,	"netbios-name-servers"},
+	{45,	__IP,	"netbios-dd-server"},
+	{46,	__8,	"netbios-node-type"},
+	{47,	__TXT,	"netbios-scope"},
+	{48,	__IP,	"x-font-servers"},
+	{49,	__IP,	"x-display-managers"},
+	{50,	__IP,	"dhcp-requested-address"},
+	{51,	__32,	"dhcp-lease-time"},
+	{52,	__8,	"dhcp-option-overload"},
+	{53,	__8,	"dhcp-message-type"},
+	{54,	__IP,	"dhcp-server-identifier"},
+	{55,	__8,	"dhcp-parameter-request-list"},
+	{56,	__TXT,	"dhcp-message"},
+	{57,	__16,	"dhcp-max-message-size"},
+	{58,	__32,	"dhcp-renewal-time"},
+	{59,	__32,	"dhcp-rebinding-time"},
+	{60,	__TXT,	"vendor-class-identifier"},
+	{61,	__TXT,	"dhcp-client-identifier"},
+	{64,	__TXT,	"nisplus-domain"},
+	{65,	__IP,	"nisplus-servers"},
+	{66,	__TXT,	"tftp-server-name"},
+	{67,	__TXT,	"bootfile-name"},
+	{68,	__IP,	"mobile-ip-home-agent"},
+	{69,	__IP,	"smtp-server"},
+	{70,	__IP,	"pop-server"},
+	{71,	__IP,	"nntp-server"},
+	{72,	__IP,	"www-server"},
+	{73,	__IP,	"finger-server"},
+	{74,	__IP,	"irc-server"},
+	{75,	__IP,	"streettalk-server"},
+	{76,	__IP,	"streettalk-directory-assistance-server"},
+	{77,	__TXT,	"user-class"},
+	{85,	__IP,	"nds-servers"},
+	{86,	__TXT,	"nds-tree-name"},
+	{87,	__TXT,	"nds-context"},
+	{210,	__TXT,	"authenticate"},
+
+	/* use the following entries for arbitrary variables */
+	{246,	__ILIST, ""},
+	{247,	__ILIST, ""},
+	{248,	__ILIST, ""},
+	{249,	__ILIST, ""},
+	{250,	__INDIR, ""},
+	{251,	__INDIR, ""},
+	{252,	__INDIR, ""},
+	{253,	__INDIR, ""},
+	{254,	__INDIR, ""},
+	{0,	__TXT,	"%soption-%d"}
+};
+
+/*
+ * parse a dhcp response, set environment variables translating options
+ * names and values according to the tables above. Also set dhcp.tags
+ * to the list of selected tags.
+ */
+static void
+setenv_(u_char *cp,  u_char *ep, struct dhcp_opt *opts)
+{
+    u_char	*ncp;
+    u_char	tag;
+    char	tags[512], *tp;	/* the list of tags */
+
+#define FLD_SEP	','	/* separator in list of elements */
+    ncp = cp;
+    tp = tags;
+    if (opts == NULL)
+	opts = dhcp_opt;
+
+    while (ncp < ep) {
+	unsigned int	size;		/* option size */
+	char *vp, *endv, buf[256];	/* the value buffer */
+	struct dhcp_opt *op;
+
+	tag = *ncp++;			/* extract tag and size */
+	size = *ncp++;
+	cp = ncp;			/* current payload */
+	ncp += size;			/* point to the next option */
+
+	if (tag == TAG_END)
+	    break;
+	if (tag == 0)
+	    continue;
+
+	for (op = opts+1; op->tag && op->tag != tag; op++)
+		;
+	/* if not found we end up on the default entry */
+
+	/*
+	 * Copy data into the buffer. libstand does not have snprintf so we
+	 * need to be careful with sprintf(). With strings, the source is
+	 * always <256 char so shorter than the buffer so we are safe; with
+	 * other arguments, the longest string is inet_ntoa which is 16 bytes
+	 * so we make sure to have always enough room in the string before
+	 * trying an sprint.
+	 */
+	vp = buf;
+	*vp = '\0';
+	endv = buf + sizeof(buf) - 1 - 16;	/* last valid write position */
+
+	switch(op->fmt) {
+	case __NONE:
+	    break;	/* should not happen */
+
+	case __VE: /* recurse, vendor specific */
+	    setenv_(cp, cp+size, vndr_opt);
+	    break;
+
+	case __IP:	/* ip address */
+	    for (; size > 0 && vp < endv; size -= 4, cp += 4) {
+		struct	in_addr in_ip;		/* ip addresses */
+		if (vp != buf)
+		    *vp++ = FLD_SEP;
+		bcopy(cp, &in_ip.s_addr, sizeof(in_ip.s_addr));
+		sprintf(vp, "%s", inet_ntoa(in_ip));
+		vp += strlen(vp);
+	    }
+	    break;
+
+	case __BYTES:	/* opaque byte string */
+	    for (; size > 0 && vp < endv; size -= 1, cp += 1) {
+		sprintf(vp, "%02x", *cp);
+		vp += strlen(vp);
+	    }
+	    break;
+
+	case __TXT:
+	    bcopy(cp, buf, size);	/* cannot overflow */
+	    buf[size] = 0;
+	    break;
+
+	case __32:
+	case __16:
+	case __8:	/* op->fmt is also the length of each field */
+	    for (; size > 0 && vp < endv; size -= op->fmt, cp += op->fmt) {
+		uint32_t v;
+		if (op->fmt == __32)
+			v = (cp[0]<<24) + (cp[1]<<16) + (cp[2]<<8) + cp[3];
+		else if (op->fmt == __16)
+			v = (cp[0]<<8) + cp[1];
+		else
+			v = cp[0];
+		if (vp != buf)
+		    *vp++ = FLD_SEP;
+		sprintf(vp, "%u", v);
+		vp += strlen(vp);
+	    }
+	    break;
+
+	case __INDIR:	/* name=value */
+	case __ILIST:	/* name=value;name=value... */
+	    bcopy(cp, buf, size);	/* cannot overflow */
+	    buf[size] = '\0';
+	    for (endv = buf; endv; endv = vp) {
+		u_char *s = NULL;	/* semicolon ? */
+
+		/* skip leading whitespace */
+		while (*endv && index(" \t\n\r", *endv))
+		    endv++;
+		vp = index(endv, '=');	/* find name=value separator */
+		if (!vp)
+		    break;
+		*vp++ = 0;
+		if (op->fmt == __ILIST && (s = index(vp, ';')))
+		    *s++ = '\0';
+		setenv(endv, vp, 1);
+		vp = s;	/* prepare for next round */
+	    }
+	    buf[0] = '\0';	/* option already done */
+	}
+
+	if (tp - tags < sizeof(tags) - 5) {	/* add tag to the list */
+	    if (tp != tags)
+		*tp++ = FLD_SEP;
+	    sprintf(tp, "%d", tag);
+	    tp += strlen(tp);
+	}
+	if (buf[0]) {
+	    char	env[128];	/* the string name */
+
+	    if (op->tag == 0)
+		sprintf(env, op->desc, opts[0].desc, tag);
+	    else
+		sprintf(env, "%s%s", opts[0].desc, op->desc);
+	    setenv(env, buf, 1);
+	}
+    }
+    if (tp != tags) {
+	char	env[128];	/* the string name */
+	sprintf(env, "%stags", opts[0].desc);
+	setenv(env, tags, 1);
+    }
+}
+#endif /* additional dhcp */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200812051713.mB5HDerC068258>