xref: /plan9-contrib/sys/src/nboot/efi/pxe.c (revision 529c1f209803c78c4f2cda11b13818a57f01c872)
1 #include <u.h>
2 #include "fns.h"
3 #include "efi.h"
4 
5 typedef UINT16	EFI_PXE_BASE_CODE_UDP_PORT;
6 
7 typedef struct {
8 	UINT8		Addr[4];
9 } EFI_IPv4_ADDRESS;
10 
11 typedef struct {
12 	UINT8		Addr[16];
13 } EFI_IPv6_ADDRESS;
14 
15 typedef union {
16 	UINT32			Addr[4];
17 	EFI_IPv4_ADDRESS	v4;
18 	EFI_IPv6_ADDRESS	v6;
19 } EFI_IP_ADDRESS;
20 
21 typedef struct {
22 	UINT8		Addr[32];
23 } EFI_MAC_ADDRESS;
24 
25 typedef struct {
26 	UINT8		BootpOpcode;
27 	UINT8		BootpHwType;
28 	UINT8		BootpHwAddrLen;
29 	UINT8		BootpGateHops;
30 	UINT32		BootpIdent;
31 	UINT16		BootpSeconds;
32 	UINT16		BootpFlags;
33 	UINT8		BootpCiAddr[4];
34 	UINT8		BootpYiAddr[4];
35 	UINT8		BootpSiAddr[4];
36 	UINT8		BootpGiAddr[4];
37 	UINT8		BootpHwAddr[16];
38 	UINT8		BootpSrvName[64];
39 	UINT8		BootpBootFile[128];
40 	UINT32		DhcpMagik;
41 	UINT8		DhcpOptions[56];
42 } EFI_PXE_BASE_CODE_DHCPV4_PACKET;
43 
44 typedef struct {
45 	BOOLEAN		Started;
46 	BOOLEAN		Ipv6Available;
47 	BOOLEAN		Ipv6Supported;
48 	BOOLEAN		UsingIpv6;
49 	BOOLEAN		BisSupported;
50 	BOOLEAN		BisDetected;
51 	BOOLEAN		AutoArp;
52 	BOOLEAN		SendGUID;
53 	BOOLEAN		DhcpDiscoverValid;
54 	BOOLEAN		DhcpAckReceived;
55 	BOOLEAN		ProxyOfferReceived;
56 	BOOLEAN		PxeDiscoverValid;
57 	BOOLEAN		PxeReplyReceived;
58 	BOOLEAN		PxeBisReplyReceived;
59 	BOOLEAN		IcmpErrorReceived;
60 	BOOLEAN		TftpErrorReceived;
61 	BOOLEAN		MakeCallbacks;
62 
63 	UINT8		TTL;
64 	UINT8		ToS;
65 
66 	UINT8		Reserved;
67 
68 	UINT8		StationIp[16];
69 	UINT8		SubnetMask[16];
70 
71 	UINT8		DhcpDiscover[1472];
72 	UINT8		DhcpAck[1472];
73 	UINT8		ProxyOffer[1472];
74 	UINT8		PxeDiscover[1472];
75 	UINT8		PxeReply[1472];
76 	UINT8		PxeBisReply[1472];
77 
78 } EFI_PXE_BASE_CODE_MODE;
79 
80 typedef struct {
81 	UINT64		Revision;
82 	void		*Start;
83 	void		*Stop;
84 	void		*Dhcp;
85 	void		*Discover;
86 	void		*Mtftp;
87 	void		*UdpWrite;
88 	void		*UdpRead;
89 	void		*SetIpFilter;
90 	void		*Arp;
91 	void		*SetParameters;
92 	void		*SetStationIp;
93 	void		*SetPackets;
94 	EFI_PXE_BASE_CODE_MODE	*Mode;
95 } EFI_PXE_BASE_CODE_PROTOCOL;
96 
97 
98 enum {
99 	Tftp_READ	= 1,
100 	Tftp_WRITE	= 2,
101 	Tftp_DATA	= 3,
102 	Tftp_ACK	= 4,
103 	Tftp_ERROR	= 5,
104 	Tftp_OACK	= 6,
105 
106 	TftpPort	= 69,
107 
108 	Segsize		= 512,
109 };
110 
111 static
112 EFI_GUID EFI_PXE_BASE_CODE_PROTOCOL_GUID = {
113 	0x03C4E603, 0xAC28, 0x11D3,
114 	0x9A, 0x2D, 0x00, 0x90,
115 	0x27, 0x3F, 0xC1, 0x4D,
116 };
117 
118 static
119 EFI_PXE_BASE_CODE_PROTOCOL *pxe;
120 
121 static uchar mymac[6];
122 static uchar myip[16];
123 static uchar serverip[16];
124 
125 typedef struct Tftp Tftp;
126 struct Tftp
127 {
128 	EFI_IP_ADDRESS sip;
129 	EFI_IP_ADDRESS dip;
130 
131 	EFI_PXE_BASE_CODE_UDP_PORT sport;
132 	EFI_PXE_BASE_CODE_UDP_PORT dport;
133 
134 	char *rp;
135 	char *ep;
136 
137 	int seq;
138 	int eof;
139 
140 	char pkt[2+2+Segsize];
141 	char nul;
142 };
143 
144 static void
puts(void * x,ushort v)145 puts(void *x, ushort v)
146 {
147 	uchar *p = x;
148 
149 	p[1] = (v>>8) & 0xFF;
150 	p[0] = v & 0xFF;
151 }
152 
153 static ushort
gets(void * x)154 gets(void *x)
155 {
156 	uchar *p = x;
157 
158 	return p[1]<<8 | p[0];
159 }
160 
161 static void
hnputs(void * x,ushort v)162 hnputs(void *x, ushort v)
163 {
164 	uchar *p = x;
165 
166 	p[0] = (v>>8) & 0xFF;
167 	p[1] = v & 0xFF;
168 }
169 
170 static ushort
nhgets(void * x)171 nhgets(void *x)
172 {
173 	uchar *p = x;
174 
175 	return p[0]<<8 | p[1];
176 }
177 
178 enum {
179 	ANY_SRC_IP	= 0x0001,
180 	ANY_SRC_PORT	= 0x0002,
181 	ANY_DEST_IP	= 0x0004,
182 	ANY_DEST_PORT	= 0x0008,
183 	USE_FILTER	= 0x0010,
184 	MAY_FRAGMENT	= 0x0020,
185 };
186 
187 static int
udpread(EFI_IP_ADDRESS * sip,EFI_IP_ADDRESS * dip,EFI_PXE_BASE_CODE_UDP_PORT * sport,EFI_PXE_BASE_CODE_UDP_PORT dport,int * len,void * data)188 udpread(EFI_IP_ADDRESS *sip, EFI_IP_ADDRESS *dip,
189 	EFI_PXE_BASE_CODE_UDP_PORT *sport,
190 	EFI_PXE_BASE_CODE_UDP_PORT dport,
191 	int *len, void *data)
192 {
193 	UINTN size;
194 
195 	size = *len;
196 	if(eficall(pxe->UdpRead, pxe, (UINTN)ANY_SRC_PORT, dip, &dport, sip, sport, nil, nil, &size, data))
197 		return -1;
198 
199 	*len = size;
200 	return 0;
201 }
202 
203 static int
udpwrite(EFI_IP_ADDRESS * dip,EFI_PXE_BASE_CODE_UDP_PORT sport,EFI_PXE_BASE_CODE_UDP_PORT dport,int len,void * data)204 udpwrite(EFI_IP_ADDRESS *dip,
205 	EFI_PXE_BASE_CODE_UDP_PORT sport,
206 	EFI_PXE_BASE_CODE_UDP_PORT dport,
207 	int len, void *data)
208 {
209 	UINTN size;
210 
211 	size = len;
212 	if(eficall(pxe->UdpWrite, pxe, (UINTN)MAY_FRAGMENT, dip, &dport, nil, nil, &sport, nil, nil, &size, data))
213 		return -1;
214 
215 	return 0;
216 }
217 
218 static int
pxeread(void * f,void * data,int len)219 pxeread(void *f, void *data, int len)
220 {
221 	Tftp *t = f;
222 	int seq, n;
223 
224 	while(!t->eof && t->rp >= t->ep){
225 		for(;;){
226 			n = sizeof(t->pkt);
227 			if(udpread(&t->dip, &t->sip, &t->dport, t->sport, &n, t->pkt))
228 				continue;
229 			if(n >= 4)
230 				break;
231 		}
232 		switch(nhgets(t->pkt)){
233 		case Tftp_DATA:
234 			seq = nhgets(t->pkt+2);
235 			if(seq > t->seq){
236 				putc('?');
237 				continue;
238 			}
239 			hnputs(t->pkt, Tftp_ACK);
240 			while(udpwrite(&t->dip, t->sport, t->dport, 4, t->pkt))
241 				putc('!');
242 			if(seq < t->seq){
243 				putc('@');
244 				continue;
245 			}
246 			t->seq = seq+1;
247 			n -= 4;
248 			t->rp = t->pkt + 4;
249 			t->ep = t->rp + n;
250 			t->eof = n < Segsize;
251 			break;
252 		case Tftp_ERROR:
253 			print(t->pkt+4);
254 			print("\n");
255 		default:
256 			t->eof = 1;
257 			return -1;
258 		}
259 		break;
260 	}
261 	n = t->ep - t->rp;
262 	if(len > n)
263 		len = n;
264 	memmove(data, t->rp, len);
265 	t->rp += len;
266 	return len;
267 }
268 
269 static void
pxeclose(void * f)270 pxeclose(void *f)
271 {
272 	Tftp *t = f;
273 	t->eof = 1;
274 }
275 
276 
277 static int
tftpopen(Tftp * t,char * path)278 tftpopen(Tftp *t, char *path)
279 {
280 	static EFI_PXE_BASE_CODE_UDP_PORT xport = 6666;
281 	int r, n;
282 	char *p;
283 
284 	t->sport = xport++;
285 	t->dport = 0;
286 	t->rp = t->ep = 0;
287 	t->seq = 1;
288 	t->eof = 0;
289 	t->nul = 0;
290 	p = t->pkt;
291 	hnputs(p, Tftp_READ); p += 2;
292 	n = strlen(path)+1;
293 	memmove(p, path, n); p += n;
294 	memmove(p, "octet", 6); p += 6;
295 	n = p - t->pkt;
296 	for(;;){
297 		if(r = udpwrite(&t->dip, t->sport, TftpPort, n, t->pkt))
298 			break;
299 		if(r = pxeread(t, 0, 0))
300 			break;
301 		return 0;
302 	}
303 	pxeclose(t);
304 	return r;
305 }
306 
307 static void*
pxeopen(char * name)308 pxeopen(char *name)
309 {
310 	static uchar buf[sizeof(Tftp)+8];
311 	Tftp *t = (Tftp*)((uintptr)(buf+7)&~7);
312 
313 	memset(t, 0, sizeof(Tftp));
314 	memmove(&t->sip, myip, sizeof(myip));
315 	memmove(&t->dip, serverip, sizeof(serverip));
316 	if(tftpopen(t, name))
317 		return nil;
318 	return t;
319 }
320 
321 static int
parseipv6(uchar to[16],char * from)322 parseipv6(uchar to[16], char *from)
323 {
324 	int i, dig, elipsis;
325 	char *p;
326 
327 	elipsis = 0;
328 	memset(to, 0, 16);
329 	for(i = 0; i < 16; i += 2){
330 		dig = 0;
331 		for(p = from;; p++){
332 			if(*p >= '0' && *p <= '9')
333 				dig = (dig << 4) | (*p - '0');
334 			else if(*p >= 'a' && *p <= 'f')
335 				dig = (dig << 4) | (*p - 'a'+10);
336 			else if(*p >= 'A' && *p <= 'F')
337 				dig = (dig << 4) | (*p - 'A'+10);
338 			else
339 				break;
340 			if(dig > 0xFFFF)
341 				return -1;
342 		}
343 		to[i]   = dig>>8;
344 		to[i+1] = dig;
345 		if(*p == ':'){
346 			if(*++p == ':'){	/* :: is elided zero short(s) */
347 				if (elipsis)
348 					return -1;	/* second :: */
349 				elipsis = i+2;
350 				p++;
351 			}
352 		} else if (p == from)
353 			break;
354 		from = p;
355 	}
356 	if(i < 16){
357 		memmove(&to[elipsis+16-i], &to[elipsis], i-elipsis);
358 		memset(&to[elipsis], 0, 16-i);
359 	}
360 	return 0;
361 }
362 
363 static void
parsedhcp(EFI_PXE_BASE_CODE_DHCPV4_PACKET * dhcp)364 parsedhcp(EFI_PXE_BASE_CODE_DHCPV4_PACKET *dhcp)
365 {
366 	uchar *p, *e;
367 	char *x;
368 	int opt;
369 	int len;
370 	uint type;
371 
372 	memset(mymac, 0, sizeof(mymac));
373 	memset(serverip, 0, sizeof(serverip));
374 
375 	/* DHCPv4 */
376 	if(pxe->Mode->UsingIpv6 == 0){
377 		memmove(mymac, dhcp->BootpHwAddr, 6);
378 		memmove(serverip, dhcp->BootpSiAddr, 4);
379 		return;
380 	}
381 
382 	/* DHCPv6 */
383 
384 	/*
385 	 * some UEFI implementations use random UUID based DUID instead of
386 	 * ethernet address, but use ethernet derived link-local addresses.
387 	 * so extract the MAC from our IPv6 address as a fallback.
388 	 */
389 	p = pxe->Mode->StationIp;
390 	mymac[0] = p[8] ^ 2;
391 	mymac[1] = p[9];
392 	mymac[2] = p[10];
393 	mymac[3] = p[13];
394 	mymac[4] = p[14];
395 	mymac[5] = p[15];
396 
397 	e = (uchar*)dhcp + sizeof(*dhcp);
398 	p = (uchar*)dhcp + 4;
399 	while(p+4 <= e){
400 		opt = p[0]<<8 | p[1];
401 		len = p[2]<<8 | p[3];
402 		p += 4;
403 		if(p + len > e)
404 			break;
405 		switch(opt){
406 		case 1:	/* Client DUID */
407 			if(len < 4+6)
408 				break;
409 			type = p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3];
410 			switch(type){
411 			case 0x00010001:
412 			case 0x00030001:
413 				memmove(mymac, p+len-6, 6);
414 				break;
415 			}
416 			break;
417 		case 59: /* Boot File URL */
418 			for(x = (char*)p; x < (char*)p+len; x++){
419 				if(*x == '['){
420 					parseipv6(serverip, x+1);
421 					break;
422 				}
423 			}
424 			break;
425 		}
426 		p += len;
427 	}
428 }
429 
430 int
pxeinit(void ** pf)431 pxeinit(void **pf)
432 {
433 	EFI_PXE_BASE_CODE_DHCPV4_PACKET	*dhcp;
434 	EFI_PXE_BASE_CODE_MODE *mode;
435 	EFI_HANDLE *Handles;
436 	UINTN Count;
437 	int i;
438 
439 	pxe = nil;
440 	Count = 0;
441 	Handles = nil;
442 	if(eficall(ST->BootServices->LocateHandleBuffer,
443 		ByProtocol, &EFI_PXE_BASE_CODE_PROTOCOL_GUID, nil, &Count, &Handles))
444 		return -1;
445 
446 	for(i=0; i<Count; i++){
447 		pxe = nil;
448 		if(eficall(ST->BootServices->HandleProtocol,
449 			Handles[i], &EFI_PXE_BASE_CODE_PROTOCOL_GUID, &pxe))
450 			continue;
451 		mode = pxe->Mode;
452 		if(mode == nil || mode->Started == 0)
453 			continue;
454 		if(mode->DhcpAckReceived){
455 			dhcp = (EFI_PXE_BASE_CODE_DHCPV4_PACKET*)mode->DhcpAck;
456 			goto Found;
457 		}
458 		if(mode->PxeReplyReceived){
459 			dhcp = (EFI_PXE_BASE_CODE_DHCPV4_PACKET*)mode->PxeReply;
460 			goto Found;
461 		}
462 	}
463 	return -1;
464 
465 Found:
466 	parsedhcp(dhcp);
467 	memmove(myip, mode->StationIp, 16);
468 
469 	open = pxeopen;
470 	read = pxeread;
471 	close = pxeclose;
472 
473 	if(pf != nil){
474 		char ini[24];
475 
476 		memmove(ini, "/cfg/pxe/", 9);
477 		for(i=0; i<6; i++){
478 			ini[9+i*2+0] = hex[mymac[i] >> 4];
479 			ini[9+i*2+1] = hex[mymac[i] & 0xF];
480 		}
481 		ini[9+12] = '\0';
482 		if((*pf = pxeopen(ini)) == nil)
483 			*pf = pxeopen("/cfg/pxe/default");
484 	}
485 
486 	return 0;
487 }
488