nfs_bootdhcp.c revision 1.43
138032Speter/*	$NetBSD: nfs_bootdhcp.c,v 1.43 2008/10/24 17:17:12 cegger Exp $	*/
2261363Sgshapiro
364565Sgshapiro/*-
438032Speter * Copyright (c) 1995, 1997 The NetBSD Foundation, Inc.
538032Speter * All rights reserved.
638032Speter *
738032Speter * This code is derived from software contributed to The NetBSD Foundation
838032Speter * by Adam Glass and Gordon W. Ross.
938032Speter *
1038032Speter * Redistribution and use in source and binary forms, with or without
11102533Sgshapiro * modification, are permitted provided that the following conditions
12102533Sgshapiro * are met:
1338032Speter * 1. Redistributions of source code must retain the above copyright
1438032Speter *    notice, this list of conditions and the following disclaimer.
1590795Sgshapiro * 2. Redistributions in binary form must reproduce the above copyright
1690795Sgshapiro *    notice, this list of conditions and the following disclaimer in the
1790795Sgshapiro *    documentation and/or other materials provided with the distribution.
18261363Sgshapiro *
1964565Sgshapiro * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2064565Sgshapiro * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2190795Sgshapiro * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2238032Speter * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23266692Sgshapiro * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2438032Speter * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2538032Speter * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2638032Speter * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2738032Speter * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2838032Speter * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2938032Speter * POSSIBILITY OF SUCH DAMAGE.
3064565Sgshapiro */
3138032Speter
3238032Speter/*
3338032Speter * Support for NFS diskless booting with BOOTP (RFC951, RFC1048)
3438032Speter *
3538032Speter * History:
3638032Speter *
3738032Speter * Tor Egge developed the initial version of this code based on
3838032Speter * the Sun RPC/bootparam sources nfs_boot.c and krpc_subr.c and
3938032Speter * submitted that work to NetBSD as bugreport "kern/2351" on
4038032Speter * 29 Apr 1996.
4138032Speter *
4238032Speter * Gordon Ross reorganized Tor's version into this form and
4338032Speter * integrated it into the NetBSD sources during Aug 1997.
4438032Speter */
4538032Speter
4638032Speter#include <sys/cdefs.h>
4738032Speter__KERNEL_RCSID(0, "$NetBSD: nfs_bootdhcp.c,v 1.43 2008/10/24 17:17:12 cegger Exp $");
4838032Speter
4964565Sgshapiro#include "opt_nfs_boot.h"
5064565Sgshapiro#include "opt_tftproot.h"
5138032Speter
5238032Speter#include <sys/param.h>
5338032Speter#include <sys/systm.h>
5438032Speter#include <sys/kernel.h>
5538032Speter#include <sys/device.h>
5638032Speter#include <sys/ioctl.h>
5790795Sgshapiro#include <sys/proc.h>
5838032Speter#include <sys/mount.h>
5990795Sgshapiro#include <sys/mbuf.h>
6038032Speter#include <sys/reboot.h>
6138032Speter#include <sys/socket.h>
6238032Speter#include <sys/socketvar.h>
6364565Sgshapiro
6438032Speter#include <net/if.h>
6538032Speter#include <net/if_types.h>
6690795Sgshapiro#include <net/if_arp.h> 	/* ARPHRD_ETHER, etc. */
6790795Sgshapiro#include <net/if_dl.h>
6890795Sgshapiro#include <net/if_ether.h>
6938032Speter#include <net/route.h>
7064565Sgshapiro
7164565Sgshapiro#include <netinet/in.h>
7264565Sgshapiro#include <netinet/if_inarp.h>
7364565Sgshapiro
7438032Speter#include <nfs/rpcv2.h>
7538032Speter
7664565Sgshapiro#include <nfs/nfsproto.h>
7738032Speter#include <nfs/nfs.h>
7838032Speter#include <nfs/nfsmount.h>
7938032Speter#include <nfs/nfsdiskless.h>
8038032Speter
8138032Speter/*
8238032Speter * There are two implementations of NFS diskless boot.
8338032Speter * This implementation uses BOOTP (RFC951, RFC1048), and
8438032Speter * the other uses Sun RPC/bootparams (nfs_bootparam.c).
8538032Speter *
8690795Sgshapiro * This method gets everything it needs with one BOOTP
8738032Speter * request and reply.  Note that this actually uses only
8838032Speter * the old BOOTP functionality subset of DHCP.  It is not
8938032Speter * clear that DHCP provides any advantage over BOOTP for
9038032Speter * diskless boot.  DHCP allows the server to assign an IP
9138032Speter * address without any a-priori knowledge of the client,
9238032Speter * but we require that the server has a-priori knowledge
9338032Speter * of the client so it can export our (unique) NFS root.
9438032Speter * Given that the server needs a-priori knowledge about
9538032Speter * the client anyway, it might as well assign a fixed IP
9638032Speter * address for the client and support BOOTP.
9764565Sgshapiro *
9842580Speter * On the other hand, disk-FULL clients may use DHCP, but
9938032Speter * in that case the DHCP client should be user-mode code,
10090795Sgshapiro * and has no bearing on the code below. -gwr
10142580Speter */
10238032Speter
10366497Sgshapiro/* Begin stuff from bootp.h */
10464565Sgshapiro/* Definitions from RFC951 */
10564565Sgshapiro#define BP_CHADDR_LEN	 16
10664565Sgshapiro#define BP_SNAME_LEN	 64
10738032Speter#define BP_FILE_LEN	128
10838032Speter#define BP_VEND_LEN	 64
10938032Speterstruct bootp {
11038076Speter	u_int8_t	bp_op;		/* packet opcode type */
11164565Sgshapiro	u_int8_t	bp_htype;	/* hardware addr type */
11264565Sgshapiro	u_int8_t	bp_hlen;	/* hardware addr length */
11364565Sgshapiro	u_int8_t	bp_hops;	/* gateway hops */
11464565Sgshapiro	u_int32_t	bp_xid;		/* transaction ID */
11538032Speter	u_int16_t	bp_secs;	/* seconds since boot began */
11638032Speter	u_int16_t	bp_flags;	/* RFC1532 broadcast, etc. */
11764565Sgshapiro	struct in_addr	bp_ciaddr;	/* client IP address */
11864565Sgshapiro	struct in_addr	bp_yiaddr;	/* 'your' IP address */
11938032Speter	struct in_addr	bp_siaddr;	/* server IP address */
12038032Speter	struct in_addr	bp_giaddr;	/* gateway IP address */
12164565Sgshapiro	u_int8_t bp_chaddr[BP_CHADDR_LEN]; /* client hardware address */
12264565Sgshapiro	char	bp_sname[BP_SNAME_LEN]; /* server host name */
12364565Sgshapiro	char	bp_file[BP_FILE_LEN];	/* boot file name */
12438032Speter	u_int8_t bp_vend[BP_VEND_LEN];	/* RFC1048 options */
12538032Speter	/*
12664565Sgshapiro	 * Note that BOOTP packets are allowed to be longer
12764565Sgshapiro	 * (see RFC 1532 sect. 2.1) and common practice is to
12838032Speter	 * allow the option data in bp_vend to extend into the
12938032Speter	 * additional space provided in longer packets.
13038032Speter	 */
13138032Speter};
13238032Speter
13338032Speter#define IPPORT_BOOTPS 67
13438032Speter#define IPPORT_BOOTPC 68
13564565Sgshapiro
13664565Sgshapiro#define BOOTREQUEST		1
13738032Speter#define BOOTREPLY		2
13890795Sgshapiro
139249729Sgshapiro/*
14090795Sgshapiro * Is this available from the sockaddr_dl somehow?
14138032Speter * Perhaps (struct arphdr)->ar_hrd = ARPHRD_ETHER?
14238032Speter * The interface has ->if_type but not the ARP fmt.
14338032Speter */
14438032Speter#define HTYPE_ETHERNET		1
14538032Speter#define HTYPE_IEEE802		6
14638032Speter
14738032Speter/*
14838032Speter * Vendor magic cookie (v_magic) for RFC1048
14938032Speter */
15038032Speterstatic const u_int8_t vm_rfc1048[4] = { 99, 130, 83, 99 };
15138032Speter
15238032Speter/*
15364565Sgshapiro * Tag values used to specify what information is being supplied in
15464565Sgshapiro * the vendor (options) data area of the packet.
15538032Speter */
15638032Speter/* RFC 1048 */
15738032Speter#define TAG_END			((unsigned char) 255)
15838032Speter#define TAG_PAD			((unsigned char)   0)
15964565Sgshapiro#define TAG_SUBNET_MASK		((unsigned char)   1)
16038032Speter#define TAG_TIME_OFFSET		((unsigned char)   2)
16138032Speter#define TAG_GATEWAY		((unsigned char)   3)
16238032Speter#define TAG_TIME_SERVER		((unsigned char)   4)
16364565Sgshapiro#define TAG_NAME_SERVER		((unsigned char)   5)
16464565Sgshapiro#define TAG_DOMAIN_SERVER	((unsigned char)   6)
16564565Sgshapiro#define TAG_LOG_SERVER		((unsigned char)   7)
16664565Sgshapiro#define TAG_COOKIE_SERVER	((unsigned char)   8)
16764565Sgshapiro#define TAG_LPR_SERVER		((unsigned char)   9)
16864565Sgshapiro#define TAG_IMPRESS_SERVER	((unsigned char)  10)
16964565Sgshapiro#define TAG_RLP_SERVER		((unsigned char)  11)
17064565Sgshapiro#define TAG_HOST_NAME		((unsigned char)  12)
17164565Sgshapiro#define TAG_BOOT_SIZE		((unsigned char)  13)
17238032Speter/* RFC 1395 */
17338032Speter#define TAG_DUMP_FILE		((unsigned char)  14)
17490795Sgshapiro#define TAG_DOMAIN_NAME		((unsigned char)  15)
17590795Sgshapiro#define TAG_SWAP_SERVER		((unsigned char)  16)
17690795Sgshapiro#define TAG_ROOT_PATH		((unsigned char)  17)
17790795Sgshapiro/* End of stuff from bootp.h */
17838032Speter
17938032Speter#ifdef NFS_BOOT_DHCP
18064565Sgshapiro#define TAG_REQ_ADDR		((unsigned char)  50)
18138032Speter#define TAG_LEASETIME		((unsigned char)  51)
18238032Speter#define TAG_OVERLOAD		((unsigned char)  52)
18364565Sgshapiro#define TAG_DHCP_MSGTYPE	((unsigned char)  53)
18464565Sgshapiro#define TAG_SERVERID		((unsigned char)  54)
18538032Speter#define TAG_PARAM_REQ		((unsigned char)  55)
18664565Sgshapiro#define TAG_MSG			((unsigned char)  56)
18764565Sgshapiro#define TAG_MAXSIZE		((unsigned char)  57)
18864565Sgshapiro#define TAG_T1			((unsigned char)  58)
18964565Sgshapiro#define TAG_T2			((unsigned char)  59)
19038032Speter#define TAG_CLASSID		((unsigned char)  60)
19138032Speter#define TAG_CLIENTID		((unsigned char)  61)
19238032Speter#endif
19338032Speter
19438032Speter#ifdef NFS_BOOT_DHCP
19538032Speter#define DHCPDISCOVER 1
19638032Speter#define DHCPOFFER 2
19790795Sgshapiro#define DHCPREQUEST 3
19890795Sgshapiro#define DHCPDECLINE 4
19990795Sgshapiro#define DHCPACK 5
20038032Speter#define DHCPNAK 6
20138032Speter#define DHCPRELEASE 7
20264565Sgshapiro#endif
20338032Speter
20464565Sgshapiro#ifdef NFS_BOOT_DHCP
20564565Sgshapiro#define BOOTP_SIZE_MAX	(sizeof(struct bootp)+312-64)
20638032Speter#else
20764565Sgshapiro/*
20864565Sgshapiro * The "extended" size is somewhat arbitrary, but is
20964565Sgshapiro * constrained by the maximum message size specified
21064565Sgshapiro * by RFC1533 (567 total).  This value increases the
21164565Sgshapiro * space for options from 64 bytes to 256 bytes.
21238032Speter */
21338032Speter#define BOOTP_SIZE_MAX	(sizeof(struct bootp)+256-64)
21438032Speter#endif
21564565Sgshapiro#define BOOTP_SIZE_MIN	(sizeof(struct bootp))
21664565Sgshapiro
21738032Speter/* Convenience macro */
21838032Speter#define INTOHL(ina) ((u_int32_t)ntohl((ina).s_addr))
21990795Sgshapiro
22090795Sgshapirostatic int bootpc_call (struct nfs_diskless *, struct lwp *);
22190795Sgshapirostatic void bootp_extract (struct bootp *, int, struct nfs_diskless *);
22290795Sgshapiro
22338032Speter#ifdef	DEBUG_NFS_BOOT_DHCP
22438032Speter#define DPRINTF(s)  printf s
22538032Speter#else
22638032Speter#define DPRINTF(s)
22764565Sgshapiro#endif
22864565Sgshapiro
22938032Speter
23038032Speter/*
23190795Sgshapiro * Get our boot parameters using BOOTP.
23238032Speter */
23364565Sgshapiroint
23464565Sgshapironfs_bootdhcp(struct nfs_diskless *nd, struct lwp *lwp)
23590795Sgshapiro{
23664565Sgshapiro	struct ifnet *ifp = nd->nd_ifp;
23764565Sgshapiro	int error;
23890795Sgshapiro
23938032Speter	/*
24038032Speter	 * Do enough of ifconfig(8) so that the chosen interface
24138032Speter	 * can talk to the servers.  Use address zero for now.
24238032Speter	 */
24338032Speter	error = nfs_boot_setaddress(ifp, lwp, INADDR_ANY, INADDR_ANY,
24438032Speter				    INADDR_BROADCAST);
24538032Speter	if (error) {
24638032Speter		printf("nfs_boot: set ifaddr zero, error=%d\n", error);
24764565Sgshapiro		return (error);
24864565Sgshapiro	}
24964565Sgshapiro
25064565Sgshapiro	/* This function call does the real send/recv work. */
25164565Sgshapiro	error = bootpc_call(nd, lwp);
25238032Speter
25338032Speter	/* Get rid of the temporary (zero) IP address. */
25438032Speter	(void) nfs_boot_deladdress(ifp, lwp, INADDR_ANY);
25538032Speter
25638032Speter	/* NOW we can test the error from bootpc_call. */
25738032Speter	if (error)
25838032Speter		goto out;
25964565Sgshapiro
26064565Sgshapiro	/*
26138032Speter	 * Do ifconfig with our real IP address and mask.
26290795Sgshapiro	 */
26390795Sgshapiro	error = nfs_boot_setaddress(ifp, lwp, nd->nd_myip.s_addr,
26490795Sgshapiro				    nd->nd_mask.s_addr, INADDR_ANY);
26590795Sgshapiro	if (error) {
26690795Sgshapiro		printf("nfs_boot: set ifaddr real, error=%d\n", error);
26738032Speter		goto out;
26838032Speter	}
26938032Speter
27090795Sgshapiroout:
27138032Speter	if (error) {
27238032Speter		(void) nfs_boot_ifupdown(ifp, lwp, 0);
27366497Sgshapiro		nfs_boot_flushrt(ifp);
27490795Sgshapiro	}
27566497Sgshapiro	return (error);
27666497Sgshapiro}
27738032Speter
27838032Speterstruct bootpcontext {
27990795Sgshapiro	int xid;
28038032Speter	const u_char *haddr;
28138080Speter	u_char halen;
28238032Speter	struct bootp *replybuf;
28338080Speter	int replylen;
28438080Speter#ifdef NFS_BOOT_DHCP
28538080Speter	char expected_dhcpmsgtype, dhcp_ok;
28638032Speter	struct in_addr dhcp_serverip;
28738032Speter#endif
28838032Speter};
28938032Speter
29090795Sgshapirostatic int bootpset (struct mbuf*, void*, int);
29138032Speterstatic int bootpcheck (struct mbuf*, void*);
29290795Sgshapiro
29338032Speterstatic int
29438032Speterbootpset(struct mbuf *m, void *context, int waited)
29590795Sgshapiro{
29638032Speter	struct bootp *bootp;
29738032Speter
29864565Sgshapiro	/* we know it's contigous (in 1 mbuf cluster) */
29990795Sgshapiro	bootp = mtod(m, struct bootp*);
30064565Sgshapiro
30138032Speter	bootp->bp_secs = htons(waited);
30238032Speter
30338032Speter	return (0);
30464565Sgshapiro}
30564565Sgshapiro
30664565Sgshapirostatic int
30764565Sgshapirobootpcheck(struct mbuf *m, void *context)
30864565Sgshapiro{
30964565Sgshapiro	struct bootp *bootp;
31066497Sgshapiro	struct bootpcontext *bpc = context;
31164565Sgshapiro	u_int tag, len;
31238032Speter	u_char *p, *limit;
31338032Speter
31464565Sgshapiro	/*
31538032Speter	 * Is this a valid reply?
31638032Speter	 */
31764565Sgshapiro	if (m->m_pkthdr.len < BOOTP_SIZE_MIN) {
31864565Sgshapiro		DPRINTF(("bootpcheck: short packet %d < %d\n", m->m_pkthdr.len,
31964565Sgshapiro		    BOOTP_SIZE_MIN));
32064565Sgshapiro		return (-1);
32138032Speter	}
32290795Sgshapiro	if (m->m_pkthdr.len > BOOTP_SIZE_MAX) {
32338032Speter		DPRINTF(("Bootpcheck: long packet %d > %d\n", m->m_pkthdr.len,
32438032Speter		    BOOTP_SIZE_MAX));
32566497Sgshapiro		return (-1);
32666497Sgshapiro	}
32766497Sgshapiro
32866497Sgshapiro	/*
32966497Sgshapiro	 * don't make first checks more expensive than necessary
33064565Sgshapiro	 */
33166497Sgshapiro	if (m->m_len < offsetof(struct bootp, bp_sname)) {
33238032Speter		m = m_pullup(m, offsetof(struct bootp, bp_sname));
33364565Sgshapiro		if (m == NULL) {
33464565Sgshapiro			DPRINTF(("bootpcheck: m_pullup failed\n"));
33590795Sgshapiro			return (-1);
33690795Sgshapiro		}
33766497Sgshapiro	}
33890795Sgshapiro	bootp = mtod(m, struct bootp*);
33990795Sgshapiro
34038032Speter	if (bootp->bp_op != BOOTREPLY) {
34138032Speter		DPRINTF(("bootpcheck: op %d is not reply\n", bootp->bp_op));
34238032Speter		return (-1);
34364565Sgshapiro	}
34464565Sgshapiro	if (bootp->bp_hlen != bpc->halen) {
34564565Sgshapiro		DPRINTF(("bootpcheck: hlen %d != %d\n", bootp->bp_hlen,
34664565Sgshapiro		    bpc->halen));
34764565Sgshapiro		return (-1);
34864565Sgshapiro	}
34964565Sgshapiro	if (memcmp(bootp->bp_chaddr, bpc->haddr, bpc->halen)) {
35064565Sgshapiro#ifdef DEBUG_NFS_BOOT_DHCP
35138032Speter		char *bp_chaddr, *haddr;
35238032Speter
35364565Sgshapiro		bp_chaddr = malloc(3 * bpc->halen, M_TEMP, M_WAITOK);
35438032Speter		haddr     = malloc(3 * bpc->halen, M_TEMP, M_WAITOK);
35538032Speter
35638032Speter		DPRINTF(("bootpcheck: incorrect hwaddr %s != %s\n",
35738032Speter		    ether_snprintf(bp_chaddr, 3 * bpc->halen,
35890795Sgshapiro		    bootp->bp_chaddr),
35938032Speter		    ether_snprintf(haddr, 3 * bpc->halen, bpc->haddr)));
36090795Sgshapiro
36164565Sgshapiro		free(bp_chaddr, M_TEMP);
36264565Sgshapiro		free(haddr, M_TEMP);
36390795Sgshapiro#endif
36464565Sgshapiro		return (-1);
36564565Sgshapiro	}
36664565Sgshapiro	if (bootp->bp_xid != bpc->xid) {
36764565Sgshapiro		DPRINTF(("bootpcheck: xid %d != %d\n", bootp->bp_xid,
36864565Sgshapiro		    bpc->xid));
36964565Sgshapiro		return (-1);
37064565Sgshapiro	}
37138032Speter
37264565Sgshapiro	/*
37364565Sgshapiro	 * OK, it's worth to look deeper.
37490795Sgshapiro	 * We copy the mbuf into a flat buffer here because
37538032Speter	 * m_pullup() is a bit limited for this purpose
37638032Speter	 * (doesn't allocate a cluster if necessary).
37738032Speter	 */
37890795Sgshapiro	bpc->replylen = m->m_pkthdr.len;
37990795Sgshapiro	m_copydata(m, 0, bpc->replylen, (void *)bpc->replybuf);
38090795Sgshapiro	bootp = bpc->replybuf;
38164565Sgshapiro
38238032Speter	/*
38338032Speter	 * Check if the IP address we get looks correct.
38464565Sgshapiro	 * (DHCP servers can send junk to unknown clients.)
38564565Sgshapiro	 * XXX more checks might be needed
38690795Sgshapiro	 */
38790795Sgshapiro	if (bootp->bp_yiaddr.s_addr == INADDR_ANY ||
388249729Sgshapiro	    bootp->bp_yiaddr.s_addr == INADDR_BROADCAST) {
38938032Speter		printf("nfs_boot: wrong IP addr %s",
39090795Sgshapiro		       inet_ntoa(bootp->bp_yiaddr));
39190795Sgshapiro		goto warn;
39238032Speter	}
39390795Sgshapiro
39490795Sgshapiro	/*
39538032Speter	 * Check the vendor data.
39638032Speter	 */
39738032Speter	if (memcmp(bootp->bp_vend, vm_rfc1048, 4)) {
39838032Speter		printf("nfs_boot: reply missing options");
39938032Speter		goto warn;
40064565Sgshapiro	}
40138032Speter	p = &bootp->bp_vend[4];
40238032Speter	limit = ((u_char*)bootp) + bpc->replylen;
40338032Speter	while (p < limit) {
40438032Speter		tag = *p++;
40538032Speter		if (tag == TAG_END)
40664565Sgshapiro			break;
40764565Sgshapiro		if (tag == TAG_PAD)
40838032Speter			continue;
40938032Speter		len = *p++;
41064565Sgshapiro		if ((p + len) > limit) {
41138032Speter			printf("nfs_boot: option %d too long", tag);
41238032Speter			goto warn;
41390795Sgshapiro		}
41490795Sgshapiro		switch (tag) {
41538032Speter#ifdef NFS_BOOT_DHCP
41638032Speter		case TAG_DHCP_MSGTYPE:
41738032Speter			if (*p != bpc->expected_dhcpmsgtype)
41864565Sgshapiro				return (-1);
41938032Speter			bpc->dhcp_ok = 1;
42038032Speter			break;
42164565Sgshapiro		case TAG_SERVERID:
42238032Speter			memcpy(&bpc->dhcp_serverip.s_addr, p,
42338032Speter			      sizeof(bpc->dhcp_serverip.s_addr));
42438032Speter			break;
42538032Speter#endif
42664565Sgshapiro		default:
42738032Speter			break;
42890795Sgshapiro		}
42990795Sgshapiro		p += len;
43090795Sgshapiro	}
43190795Sgshapiro	return (0);
43290795Sgshapiro
43390795Sgshapirowarn:
43490795Sgshapiro	printf(" (bad reply from %s)\n", inet_ntoa(bootp->bp_siaddr));
43590795Sgshapiro	return (-1);
43690795Sgshapiro}
43790795Sgshapiro
43838032Speterstatic int
43938032Speterbootpc_call(struct nfs_diskless *nd, struct lwp *lwp)
440{
441	struct socket *so;
442	struct ifnet *ifp = nd->nd_ifp;
443	static u_int32_t xid = ~0xFF;
444	struct bootp *bootp;	/* request */
445	struct mbuf *m, *nam;
446	struct sockaddr_in *sin;
447	int error;
448	const u_char *haddr;
449	u_char hafmt, halen;
450	struct bootpcontext bpc;
451#ifdef NFS_BOOT_DHCP
452	char vci[64];
453	int vcilen;
454#endif
455
456	error = socreate(AF_INET, &so, SOCK_DGRAM, 0, lwp, NULL);
457	if (error) {
458		printf("bootp: socreate, error=%d\n", error);
459		return (error);
460	}
461
462	/*
463	 * Initialize to NULL anything that will hold an allocation,
464	 * and free each at the end if not null.
465	 */
466	bpc.replybuf = NULL;
467	m = nam = NULL;
468
469	/* Record our H/W (Ethernet) address. */
470	{	const struct sockaddr_dl *sdl = ifp->if_sadl;
471		switch (sdl->sdl_type) {
472		    case IFT_ISO88025:
473			hafmt = HTYPE_IEEE802;
474			break;
475		    case IFT_ETHER:
476		    case IFT_FDDI:
477			hafmt = HTYPE_ETHERNET;
478			break;
479		    default:
480			printf("bootp: unsupported interface type %d\n",
481			       sdl->sdl_type);
482			error = EINVAL;
483			goto out;
484		}
485		halen = sdl->sdl_alen;
486		haddr = (const unsigned char *)CLLADDR(sdl);
487	}
488
489	/*
490	 * Skip the route table when sending on this socket.
491	 * If this is not done, ip_output finds the loopback
492	 * interface (why?) and then fails because broadcast
493	 * is not supported on that interface...
494	 */
495	{	int32_t opt;
496
497		opt = 1;
498		error = so_setsockopt(NULL, so, SOL_SOCKET, SO_DONTROUTE, &opt,
499		    sizeof(opt));
500	}
501	if (error) {
502		DPRINTF(("bootpc_call: SO_DONTROUTE failed %d\n", error));
503		goto out;
504	}
505
506	/* Enable broadcast. */
507	if ((error = nfs_boot_enbroadcast(so))) {
508		DPRINTF(("bootpc_call: SO_BROADCAST failed %d\n", error));
509		goto out;
510	}
511
512	/*
513	 * Set some TTL so we can boot through routers.
514	 * Real BOOTP forwarding agents don't need this; they obey "bp_hops"
515	 * and set "bp_giaddr", thus rewrite the packet anyway.
516	 * The "helper-address" feature of some popular router vendor seems
517	 * to do simple IP forwarding and drops packets with (ip_ttl == 1).
518	 */
519	{	u_char opt;
520
521		opt = 7;
522		error = so_setsockopt(NULL, so, IPPROTO_IP, IP_MULTICAST_TTL,
523		    &opt, sizeof(opt));
524	}
525	if (error) {
526		DPRINTF(("bootpc_call: IP_MULTICAST_TTL failed %d\n", error));
527		goto out;
528	}
529
530	/* Set the receive timeout for the socket. */
531	if ((error = nfs_boot_setrecvtimo(so))) {
532		DPRINTF(("bootpc_call: SO_RCVTIMEO failed %d\n", error));
533		goto out;
534	}
535
536	/*
537	 * Bind the local endpoint to a bootp client port.
538	 */
539	if ((error = nfs_boot_sobind_ipport(so, IPPORT_BOOTPC, lwp))) {
540		DPRINTF(("bootpc_call: bind failed %d\n", error));
541		goto out;
542	}
543
544	/*
545	 * Setup socket address for the server.
546	 */
547	nam = m_get(M_WAIT, MT_SONAME);
548	sin = mtod(nam, struct sockaddr_in *);
549	sin->sin_len = nam->m_len = sizeof(*sin);
550	sin->sin_family = AF_INET;
551	sin->sin_addr.s_addr = INADDR_BROADCAST;
552	sin->sin_port = htons(IPPORT_BOOTPS);
553
554	/*
555	 * Allocate buffer used for request
556	 */
557	m = m_gethdr(M_WAIT, MT_DATA);
558	m_clget(m, M_WAIT);
559	bootp = mtod(m, struct bootp*);
560	m->m_pkthdr.len = m->m_len = BOOTP_SIZE_MAX;
561	m->m_pkthdr.rcvif = NULL;
562
563	/*
564	 * Build the BOOTP reqest message.
565	 * Note: xid is host order! (opaque to server)
566	 */
567	memset((void *)bootp, 0, BOOTP_SIZE_MAX);
568	bootp->bp_op    = BOOTREQUEST;
569	bootp->bp_htype = hafmt;
570	bootp->bp_hlen  = halen;	/* Hardware address length */
571	bootp->bp_xid = ++xid;
572	memcpy(bootp->bp_chaddr, haddr, halen);
573#ifdef NFS_BOOT_BOOTP_REQFILE
574	strncpy(bootp->bp_file, NFS_BOOT_BOOTP_REQFILE, sizeof(bootp->bp_file));
575#endif
576	/* Fill-in the vendor data. */
577	memcpy(bootp->bp_vend, vm_rfc1048, 4);
578#ifdef NFS_BOOT_DHCP
579	bootp->bp_vend[4] = TAG_DHCP_MSGTYPE;
580	bootp->bp_vend[5] = 1;
581	bootp->bp_vend[6] = DHCPDISCOVER;
582	/*
583	 * Insert a NetBSD Vendor Class Identifier option.
584	 */
585	snprintf(vci, sizeof(vci), "%s:%s:kernel:%s", ostype, MACHINE,
586	    osrelease);
587	vcilen = strlen(vci);
588	bootp->bp_vend[7] = TAG_CLASSID;
589	bootp->bp_vend[8] = vcilen;
590	memcpy(&bootp->bp_vend[9], vci, vcilen);
591	bootp->bp_vend[9 + vcilen] = TAG_END;
592#else
593	bootp->bp_vend[4] = TAG_END;
594#endif
595
596	bpc.xid = xid;
597	bpc.haddr = haddr;
598	bpc.halen = halen;
599	bpc.replybuf = malloc(BOOTP_SIZE_MAX, M_DEVBUF, M_WAITOK);
600	if (bpc.replybuf == NULL)
601		panic("nfs_boot: malloc reply buf");
602#ifdef NFS_BOOT_DHCP
603	bpc.expected_dhcpmsgtype = DHCPOFFER;
604	bpc.dhcp_ok = 0;
605#endif
606
607	error = nfs_boot_sendrecv(so, nam, bootpset, m,
608				  bootpcheck, 0, 0, &bpc, lwp);
609	if (error)
610		goto out;
611
612#ifdef NFS_BOOT_DHCP
613	if (bpc.dhcp_ok) {
614		u_int32_t leasetime;
615		bootp->bp_vend[6] = DHCPREQUEST;
616		bootp->bp_vend[7] = TAG_REQ_ADDR;
617		bootp->bp_vend[8] = 4;
618		memcpy(&bootp->bp_vend[9], &bpc.replybuf->bp_yiaddr, 4);
619		bootp->bp_vend[13] = TAG_SERVERID;
620		bootp->bp_vend[14] = 4;
621		memcpy(&bootp->bp_vend[15], &bpc.dhcp_serverip.s_addr, 4);
622		bootp->bp_vend[19] = TAG_LEASETIME;
623		bootp->bp_vend[20] = 4;
624		leasetime = htonl(300);
625		memcpy(&bootp->bp_vend[21], &leasetime, 4);
626		bootp->bp_vend[25] = TAG_CLASSID;
627		bootp->bp_vend[26] = vcilen;
628		memcpy(&bootp->bp_vend[27], vci, vcilen);
629		bootp->bp_vend[27 + vcilen] = TAG_END;
630
631		bpc.expected_dhcpmsgtype = DHCPACK;
632
633		error = nfs_boot_sendrecv(so, nam, bootpset, m,
634					  bootpcheck, 0, 0, &bpc, lwp);
635		if (error)
636			goto out;
637	}
638#endif
639
640	/*
641	 * bootpcheck() has copied the receive mbuf into
642	 * the buffer at bpc.replybuf.
643	 */
644#ifdef NFS_BOOT_DHCP
645	printf("nfs_boot: %s next-server: %s\n",
646	       (bpc.dhcp_ok ? "DHCP" : "BOOTP"),
647#else
648	printf("nfs_boot: BOOTP next-server: %s\n",
649#endif
650	       inet_ntoa(bpc.replybuf->bp_siaddr));
651
652	bootp_extract(bpc.replybuf, bpc.replylen, nd);
653
654out:
655	if (bpc.replybuf)
656		free(bpc.replybuf, M_DEVBUF);
657	if (m)
658		m_freem(m);
659	if (nam)
660		m_freem(nam);
661	soclose(so);
662	return (error);
663}
664
665static void
666bootp_extract(struct bootp *bootp, int replylen, struct nfs_diskless *nd)
667{
668	struct sockaddr_in *sin;
669	struct in_addr netmask;
670	struct in_addr gateway;
671	struct in_addr rootserver;
672	char *myname;	/* my hostname */
673	char *mydomain;	/* my domainname */
674	char *rootpath;
675	int mynamelen;
676	int mydomainlen;
677	int rootpathlen;
678	int overloaded;
679	u_int tag, len;
680	u_char *p, *limit;
681
682	/* Default these to "unspecified". */
683	netmask.s_addr = 0;
684	gateway.s_addr = 0;
685	mydomain    = myname    = rootpath = NULL;
686	mydomainlen = mynamelen = rootpathlen = 0;
687
688	/* default root server to bootp next-server */
689	rootserver = bootp->bp_siaddr;
690	/* assume that server name field is not overloaded by default */
691	overloaded = 0;
692
693	p = &bootp->bp_vend[4];
694	limit = ((u_char*)bootp) + replylen;
695	while (p < limit) {
696		tag = *p++;
697		if (tag == TAG_END)
698			break;
699		if (tag == TAG_PAD)
700			continue;
701		len = *p++;
702#if 0 /* already done in bootpcheck() */
703		if ((p + len) > limit) {
704			printf("nfs_boot: option %d too long\n", tag);
705			break;
706		}
707#endif
708		switch (tag) {
709		    case TAG_SUBNET_MASK:
710			memcpy(&netmask, p, 4);
711			break;
712		    case TAG_GATEWAY:
713			/* Routers */
714			memcpy(&gateway, p, 4);
715			break;
716		    case TAG_HOST_NAME:
717			if (len >= sizeof(hostname)) {
718				printf("nfs_boot: host name >= %lu bytes",
719				       (u_long)sizeof(hostname));
720				break;
721			}
722			myname = p;
723			mynamelen = len;
724			break;
725		    case TAG_DOMAIN_NAME:
726			if (len >= sizeof(domainname)) {
727				printf("nfs_boot: domain name >= %lu bytes",
728				       (u_long)sizeof(domainname));
729				break;
730			}
731			mydomain = p;
732			mydomainlen = len;
733			break;
734		    case TAG_ROOT_PATH:
735			/* Leave some room for the server name. */
736			if (len >= (MNAMELEN-10)) {
737				printf("nfs_boot: rootpath >=%d bytes",
738				       (MNAMELEN-10));
739				break;
740			}
741			rootpath = p;
742			rootpathlen = len;
743			break;
744		    case TAG_SWAP_SERVER:
745			/* override NFS server address */
746			memcpy(&rootserver, p, 4);
747			break;
748#ifdef NFS_BOOT_DHCP
749		    case TAG_OVERLOAD:
750			if (len > 0 && ((*p & 0x02) != 0))
751				/*
752				 * The server name field in the dhcp packet
753				 * is overloaded and we can't find server
754				 * name there.
755				 */
756				overloaded = 1;
757			break;
758#endif
759		    default:
760			break;
761		}
762		p += len;
763	}
764
765	/*
766	 * Store and print network config info.
767	 */
768	if (myname) {
769		myname[mynamelen] = '\0';
770		strncpy(hostname, myname, sizeof(hostname));
771		hostnamelen = mynamelen;
772		printf("nfs_boot: my_name=%s\n", hostname);
773	}
774	if (mydomain) {
775		mydomain[mydomainlen] = '\0';
776		strncpy(domainname, mydomain, sizeof(domainname));
777		domainnamelen = mydomainlen;
778		printf("nfs_boot: my_domain=%s\n", domainname);
779	}
780	nd->nd_myip = bootp->bp_yiaddr;
781	if (nd->nd_myip.s_addr)
782		printf("nfs_boot: my_addr=%s\n", inet_ntoa(nd->nd_myip));
783	nd->nd_mask = netmask;
784	if (nd->nd_mask.s_addr)
785		printf("nfs_boot: my_mask=%s\n", inet_ntoa(nd->nd_mask));
786	nd->nd_gwip = gateway;
787	if (nd->nd_gwip.s_addr)
788		printf("nfs_boot: gateway=%s\n", inet_ntoa(nd->nd_gwip));
789
790	/*
791	 * Store the information about our NFS root mount.
792	 * The caller will print it, so be silent here.
793	 */
794	{
795		struct nfs_dlmount *ndm = &nd->nd_root;
796
797		/* Server IP address. */
798		sin = (struct sockaddr_in *) &ndm->ndm_saddr;
799		memset((void *)sin, 0, sizeof(*sin));
800		sin->sin_len = sizeof(*sin);
801		sin->sin_family = AF_INET;
802		sin->sin_addr = rootserver;
803		/* Server name. */
804		if (!overloaded && bootp->bp_sname[0] != 0 &&
805		    !memcmp(&rootserver, &bootp->bp_siaddr,
806			  sizeof(struct in_addr))) {
807			/* standard root server, we have the name */
808			strncpy(ndm->ndm_host, bootp->bp_sname, BP_SNAME_LEN-1);
809		} else {
810			/* Show the server IP address numerically. */
811			strncpy(ndm->ndm_host, inet_ntoa(rootserver),
812				BP_SNAME_LEN-1);
813		}
814		len = strlen(ndm->ndm_host);
815		if (rootpath &&
816		    len + 1 + rootpathlen + 1 <= sizeof(ndm->ndm_host)) {
817			ndm->ndm_host[len++] = ':';
818			strncpy(ndm->ndm_host + len,
819				rootpath, rootpathlen);
820			ndm->ndm_host[len + rootpathlen] = '\0';
821		} /* else: upper layer will handle error */
822	}
823
824#ifdef TFTPROOT
825#if BP_FILE_LEN > MNAMELEN
826#define BOOTFILELEN MNAMELEN
827#else
828#define BOOTFILELEN BP_FILE_LEN
829#endif
830	strncpy(nd->nd_bootfile, bootp->bp_file, BOOTFILELEN);
831	nd->nd_bootfile[BOOTFILELEN - 1] = '\0';
832#undef BOOTFILELEN
833#endif /* TFTPROOT */
834}
835