diff options
| author | Michael Brown | 2005-03-08 19:53:11 +0100 |
|---|---|---|
| committer | Michael Brown | 2005-03-08 19:53:11 +0100 |
| commit | 3d6123e69ab879c72ff489afc5bf93ef0b7a94ce (patch) | |
| tree | 9f3277569153a550fa8d81ebd61bd88f266eb8da /src/core/nic.c | |
| download | ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.gz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.xz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.zip | |
Initial revision
Diffstat (limited to 'src/core/nic.c')
| -rw-r--r-- | src/core/nic.c | 1778 |
1 files changed, 1778 insertions, 0 deletions
diff --git a/src/core/nic.c b/src/core/nic.c new file mode 100644 index 000000000..44f80c356 --- /dev/null +++ b/src/core/nic.c @@ -0,0 +1,1778 @@ +/************************************************************************** +Etherboot - Network Bootstrap Program + +Literature dealing with the network protocols: + ARP - RFC826 + RARP - RFC903 + IP - RFC791 + UDP - RFC768 + BOOTP - RFC951, RFC2132 (vendor extensions) + DHCP - RFC2131, RFC2132, RFC3004 (options) + TFTP - RFC1350, RFC2347 (options), RFC2348 (blocksize), RFC2349 (tsize) + RPC - RFC1831, RFC1832 (XDR), RFC1833 (rpcbind/portmapper) + NFS - RFC1094, RFC1813 (v3, useful for clarifications, not implemented) + IGMP - RFC1112, RFC2113, RFC2365, RFC2236, RFC3171 + +**************************************************************************/ +#include "etherboot.h" +#include "nic.h" +#include "elf.h" /* FOR EM_CURRENT */ + +struct arptable_t arptable[MAX_ARP]; +#if MULTICAST_LEVEL2 +unsigned long last_igmpv1 = 0; +struct igmptable_t igmptable[MAX_IGMP]; +#endif +/* Put rom_info in .nocompress section so romprefix.S can write to it */ +struct rom_info rom __attribute__ ((section (".text16.nocompress"))) = {0,0}; +static unsigned long netmask; +/* Used by nfs.c */ +char *hostname = ""; +int hostnamelen = 0; +static uint32_t xid; +unsigned char *end_of_rfc1533 = NULL; +static int vendorext_isvalid; +static const unsigned char vendorext_magic[] = {0xE4,0x45,0x74,0x68}; /* äEth */ +static const unsigned char broadcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; +static const in_addr zeroIP = { 0L }; + +struct bootpd_t bootp_data; + +#ifdef NO_DHCP_SUPPORT +static unsigned char rfc1533_cookie[5] = { RFC1533_COOKIE, RFC1533_END }; +#else /* !NO_DHCP_SUPPORT */ +static int dhcp_reply; +static in_addr dhcp_server = { 0L }; +static in_addr dhcp_addr = { 0L }; +static unsigned char rfc1533_cookie[] = { RFC1533_COOKIE }; +#define DHCP_MACHINE_INFO_SIZE (sizeof dhcp_machine_info) +static unsigned char dhcp_machine_info[] = { + /* Our enclosing DHCP tag */ + RFC1533_VENDOR_ETHERBOOT_ENCAP, 11, + /* Our boot device */ + RFC1533_VENDOR_NIC_DEV_ID, 5, PCI_BUS_TYPE, 0, 0, 0, 0, + /* Our current architecture */ + RFC1533_VENDOR_ARCH, 2, EM_CURRENT & 0xff, (EM_CURRENT >> 8) & 0xff, +#ifdef EM_CURRENT_64 + /* The 64bit version of our current architecture */ + RFC1533_VENDOR_ARCH, 2, EM_CURRENT_64 & 0xff, (EM_CURRENT_64 >> 8) & 0xff, +#undef DHCP_MACHINE_INFO_SIZE +#define DHCP_MACHINE_INFO_SIZE (sizeof(dhcp_machine_info) - (EM_CURRENT_64_PRESENT? 0: 4)) +#endif /* EM_CURRENT_64 */ +}; +static const unsigned char dhcpdiscover[] = { + RFC2132_MSG_TYPE,1,DHCPDISCOVER, + RFC2132_MAX_SIZE,2, /* request as much as we can */ + ETH_MAX_MTU / 256, ETH_MAX_MTU % 256, +#ifdef PXE_DHCP_STRICT + RFC3679_PXE_CLIENT_UUID,RFC3679_PXE_CLIENT_UUID_LENGTH,RFC3679_PXE_CLIENT_UUID_DEFAULT, + RFC3679_PXE_CLIENT_ARCH,RFC3679_PXE_CLIENT_ARCH_LENGTH,RFC3679_PXE_CLIENT_ARCH_IAX86PC, + RFC3679_PXE_CLIENT_NDI, RFC3679_PXE_CLIENT_NDI_LENGTH, RFC3679_PXE_CLIENT_NDI_21, + RFC2132_VENDOR_CLASS_ID,RFC2132_VENDOR_CLASS_ID_PXE_LENGTH,RFC2132_VENDOR_CLASS_ID_PXE, +#else + RFC2132_VENDOR_CLASS_ID,13,'E','t','h','e','r','b','o','o','t', + '-',VERSION_MAJOR+'0','.',VERSION_MINOR+'0', +#endif /* PXE_DHCP_STRICT */ +#ifdef DHCP_CLIENT_ID + /* Client ID Option */ + RFC2132_CLIENT_ID, ( DHCP_CLIENT_ID_LEN + 1 ), + DHCP_CLIENT_ID_TYPE, DHCP_CLIENT_ID, +#endif /* DHCP_CLIENT_ID */ +#ifdef DHCP_USER_CLASS + /* User Class Option */ + RFC3004_USER_CLASS, DHCP_USER_CLASS_LEN, DHCP_USER_CLASS, +#endif /* DHCP_USER_CLASS */ + RFC2132_PARAM_LIST, +#define DHCPDISCOVER_PARAMS_BASE 4 +#ifdef PXE_DHCP_STRICT +#define DHCPDISCOVER_PARAMS_PXE ( 1 + 8 ) +#else +#define DHCPDISCOVER_PARAMS_PXE 0 +#endif /* PXE_DHCP_STRICT */ +#ifdef DNS_RESOLVER +#define DHCPDISCOVER_PARAMS_DNS 1 +#else +#define DHCPDISCOVER_PARAMS_DNS 0 +#endif /* DNS_RESOLVER */ + ( DHCPDISCOVER_PARAMS_BASE + + DHCPDISCOVER_PARAMS_PXE+ + DHCPDISCOVER_PARAMS_DNS ), + RFC1533_NETMASK, + RFC1533_GATEWAY, + RFC1533_HOSTNAME, + RFC1533_VENDOR +#ifdef PXE_DHCP_STRICT + ,RFC2132_VENDOR_CLASS_ID, + RFC1533_VENDOR_PXE_OPT128, + RFC1533_VENDOR_PXE_OPT129, + RFC1533_VENDOR_PXE_OPT130, + RFC1533_VENDOR_PXE_OPT131, + RFC1533_VENDOR_PXE_OPT132, + RFC1533_VENDOR_PXE_OPT133, + RFC1533_VENDOR_PXE_OPT134, + RFC1533_VENDOR_PXE_OPT135 +#endif /* PXE_DHCP_STRICT */ +#ifdef DNS_RESOLVER + ,RFC1533_DNS +#endif +}; +static const unsigned char dhcprequest [] = { + RFC2132_MSG_TYPE,1,DHCPREQUEST, + RFC2132_SRV_ID,4,0,0,0,0, + RFC2132_REQ_ADDR,4,0,0,0,0, + RFC2132_MAX_SIZE,2, /* request as much as we can */ + ETH_MAX_MTU / 256, ETH_MAX_MTU % 256, +#ifdef PXE_DHCP_STRICT + RFC3679_PXE_CLIENT_UUID,RFC3679_PXE_CLIENT_UUID_LENGTH,RFC3679_PXE_CLIENT_UUID_DEFAULT, + RFC3679_PXE_CLIENT_ARCH,RFC3679_PXE_CLIENT_ARCH_LENGTH,RFC3679_PXE_CLIENT_ARCH_IAX86PC, + RFC3679_PXE_CLIENT_NDI, RFC3679_PXE_CLIENT_NDI_LENGTH, RFC3679_PXE_CLIENT_NDI_21, + RFC2132_VENDOR_CLASS_ID,RFC2132_VENDOR_CLASS_ID_PXE_LENGTH,RFC2132_VENDOR_CLASS_ID_PXE, +#else + RFC2132_VENDOR_CLASS_ID,13,'E','t','h','e','r','b','o','o','t', + '-',VERSION_MAJOR+'0','.',VERSION_MINOR+'0', +#endif /* PXE_DHCP_STRICT */ +#ifdef DHCP_CLIENT_ID + /* Client ID Option */ + RFC2132_CLIENT_ID, ( DHCP_CLIENT_ID_LEN + 1 ), + DHCP_CLIENT_ID_TYPE, DHCP_CLIENT_ID, +#endif /* DHCP_CLIENT_ID */ +#ifdef DHCP_USER_CLASS + /* User Class Option */ + RFC3004_USER_CLASS, DHCP_USER_CLASS_LEN, DHCP_USER_CLASS, +#endif /* DHCP_USER_CLASS */ + /* request parameters */ + RFC2132_PARAM_LIST, +#define DHCPREQUEST_PARAMS_BASE 5 +#ifdef PXE_DHCP_STRICT +#define DHCPREQUEST_PARAMS_PXE 1 +#define DHCPREQUEST_PARAMS_VENDOR_PXE 8 +#define DHCPREQUEST_PARAMS_VENDOR_EB 0 +#else +#define DHCPREQUEST_PARAMS_PXE 0 +#define DHCPREQUEST_PARAMS_VENDOR_PXE 0 +#define DHCPREQUEST_PARAMS_VENDOR_EB 4 +#endif /* PXE_DHCP_STRICT */ +#ifdef IMAGE_FREEBSD +#define DHCPREQUEST_PARAMS_FREEBSD 2 +#else +#define DHCPREQUEST_PARAMS_FREEBSD 0 +#endif /* IMAGE_FREEBSD */ +#ifdef DNS_RESOLVER +#define DHCPREQUEST_PARAMS_DNS 1 +#else +#define DHCPREQUEST_PARAMS_DNS 0 +#endif /* DNS_RESOLVER */ + ( DHCPREQUEST_PARAMS_BASE + + DHCPREQUEST_PARAMS_PXE + + DHCPREQUEST_PARAMS_VENDOR_PXE + + DHCPREQUEST_PARAMS_VENDOR_EB + + DHCPREQUEST_PARAMS_DNS + + DHCPREQUEST_PARAMS_FREEBSD ), + /* 5 Standard parameters */ + RFC1533_NETMASK, + RFC1533_GATEWAY, + RFC1533_HOSTNAME, + RFC1533_VENDOR, + RFC1533_ROOTPATH, /* only passed to the booted image */ +#ifndef PXE_DHCP_STRICT + /* 4 Etherboot vendortags */ + RFC1533_VENDOR_MAGIC, + RFC1533_VENDOR_ADDPARM, + RFC1533_VENDOR_ETHDEV, + RFC1533_VENDOR_ETHERBOOT_ENCAP, +#endif /* ! PXE_DHCP_STRICT */ +#ifdef IMAGE_FREEBSD + /* 2 FreeBSD options */ + RFC1533_VENDOR_HOWTO, + RFC1533_VENDOR_KERNEL_ENV, +#endif +#ifdef DNS_RESOLVER + /* 1 DNS option */ + RFC1533_DNS, +#endif +#ifdef PXE_DHCP_STRICT + RFC2132_VENDOR_CLASS_ID, + RFC1533_VENDOR_PXE_OPT128, + RFC1533_VENDOR_PXE_OPT129, + RFC1533_VENDOR_PXE_OPT130, + RFC1533_VENDOR_PXE_OPT131, + RFC1533_VENDOR_PXE_OPT132, + RFC1533_VENDOR_PXE_OPT133, + RFC1533_VENDOR_PXE_OPT134, + RFC1533_VENDOR_PXE_OPT135, +#endif /* PXE_DHCP_STRICT */ +}; +#ifdef PXE_EXPORT +static const unsigned char proxydhcprequest [] = { + RFC2132_MSG_TYPE,1,DHCPREQUEST, + RFC2132_MAX_SIZE,2, /* request as much as we can */ + ETH_MAX_MTU / 256, ETH_MAX_MTU % 256, +#ifdef PXE_DHCP_STRICT + RFC3679_PXE_CLIENT_UUID,RFC3679_PXE_CLIENT_UUID_LENGTH,RFC3679_PXE_CLIENT_UUID_DEFAULT, + RFC3679_PXE_CLIENT_ARCH,RFC3679_PXE_CLIENT_ARCH_LENGTH,RFC3679_PXE_CLIENT_ARCH_IAX86PC, + RFC3679_PXE_CLIENT_NDI, RFC3679_PXE_CLIENT_NDI_LENGTH, RFC3679_PXE_CLIENT_NDI_21, + RFC2132_VENDOR_CLASS_ID,RFC2132_VENDOR_CLASS_ID_PXE_LENGTH,RFC2132_VENDOR_CLASS_ID_PXE, +#endif /* PXE_DHCP_STRICT */ +}; +#endif + +#ifdef REQUIRE_VCI_ETHERBOOT +int vci_etherboot; +#endif +#endif /* NO_DHCP_SUPPORT */ + +static int dummy(void *unused __unused) +{ + return (0); +} + +/* Careful. We need an aligned buffer to avoid problems on machines + * that care about alignment. To trivally align the ethernet data + * (the ip hdr and arp requests) we offset the packet by 2 bytes. + * leaving the ethernet data 16 byte aligned. Beyond this + * we use memmove but this makes the common cast simple and fast. + */ +static char packet[ETH_FRAME_LEN + ETH_DATA_ALIGN] __aligned; + +struct nic nic = +{ + { + 0, /* dev.disable */ + { + 0, + 0, + PCI_BUS_TYPE, + }, /* dev.devid */ + 0, /* index */ + 0, /* type */ + PROBE_FIRST, /* how_pobe */ + PROBE_NONE, /* to_probe */ + 0, /* failsafe */ + 0, /* type_index */ + {}, /* state */ + }, + (int (*)(struct nic *, int))dummy, /* poll */ + (void (*)(struct nic *, const char *, + unsigned int, unsigned int, + const char *))dummy, /* transmit */ + (void (*)(struct nic *, + irq_action_t))dummy, /* irq */ + 0, /* flags */ + &rom, /* rom_info */ + arptable[ARP_CLIENT].node, /* node_addr */ + packet + ETH_DATA_ALIGN, /* packet */ + 0, /* packetlen */ + 0, /* ioaddr */ + 0, /* irqno */ + 0, /* priv_data */ +}; + +#ifdef RARP_NOT_BOOTP +static int rarp(void); +#else +static int bootp(void); +#endif +static unsigned short tcpudpchksum(struct iphdr *ip); + + +int eth_probe(struct dev *dev) +{ + return probe(dev); +} + +int eth_poll(int retrieve) +{ + return ((*nic.poll)(&nic, retrieve)); +} + +void eth_transmit(const char *d, unsigned int t, unsigned int s, const void *p) +{ + (*nic.transmit)(&nic, d, t, s, p); + if (t == ETH_P_IP) twiddle(); +} + +void eth_disable(void) +{ +#ifdef MULTICAST_LEVEL2 + int i; + for(i = 0; i < MAX_IGMP; i++) { + leave_group(i); + } +#endif + disable(&nic.dev); +} + +void eth_irq (irq_action_t action) +{ + (*nic.irq)(&nic,action); +} + +/* + * Find out what our boot parameters are + */ +int eth_load_configuration(struct dev *dev __unused) +{ + int server_found; + /* Find a server to get BOOTP reply from */ +#ifdef RARP_NOT_BOOTP + printf("Searching for server (RARP)..."); +#else +#ifndef NO_DHCP_SUPPORT + printf("Searching for server (DHCP)..."); +#else + printf("Searching for server (BOOTP)..."); +#endif +#endif + +#ifdef RARP_NOT_BOOTP + server_found = rarp(); +#else + server_found = bootp(); +#endif + if (!server_found) { + printf("No Server found\n"); + longjmp(restart_etherboot, -1); + } + return 0; +} + + +/************************************************************************** +LOAD - Try to get booted +**************************************************************************/ +int eth_load(struct dev *dev __unused) +{ + const char *kernel; + printf("\nMe: %@", arptable[ARP_CLIENT].ipaddr.s_addr ); +#ifndef NO_DHCP_SUPPORT + printf(", DHCP: %@", dhcp_server ); +#ifdef PXE_EXPORT + if (arptable[ARP_PROXYDHCP].ipaddr.s_addr) + printf(" (& %@)", + arptable[ARP_PROXYDHCP].ipaddr.s_addr); +#endif /* PXE_EXPORT */ +#endif /* ! NO_DHCP_SUPPORT */ + printf(", TFTP: %@", arptable[ARP_SERVER].ipaddr.s_addr); + if (BOOTP_DATA_ADDR->bootp_reply.bp_giaddr.s_addr) + printf(", Relay: %@", + BOOTP_DATA_ADDR->bootp_reply.bp_giaddr.s_addr); + if (arptable[ARP_GATEWAY].ipaddr.s_addr) + printf(", Gateway %@", arptable[ARP_GATEWAY].ipaddr.s_addr); +#ifdef DNS_RESOLVER + if (arptable[ARP_NAMESERVER].ipaddr.s_addr) + printf(", Nameserver %@", arptable[ARP_NAMESERVER].ipaddr.s_addr); +#endif + putchar('\n'); + +#ifdef MDEBUG + printf("\n=>>"); getchar(); +#endif + + /* Now use TFTP to load file */ +#ifdef DOWNLOAD_PROTO_NFS + rpc_init(); +#endif + kernel = KERNEL_BUF[0] == '\0' ? +#ifdef DEFAULT_BOOTFILE + DEFAULT_BOOTFILE +#else + NULL +#endif + : KERNEL_BUF; + if ( kernel ) { + loadkernel(kernel); /* We don't return except on error */ + printf("Unable to load file.\n"); + } else { + printf("No filename\n"); + } + interruptible_sleep(2); /* lay off the server for a while */ + longjmp(restart_etherboot, -1); +} + + +/************************************************************************** +DEFAULT_NETMASK - Return default netmask for IP address +**************************************************************************/ +static inline unsigned long default_netmask(void) +{ + int net = ntohl(arptable[ARP_CLIENT].ipaddr.s_addr) >> 24; + if (net <= 127) + return(htonl(0xff000000)); + else if (net < 192) + return(htonl(0xffff0000)); + else + return(htonl(0xffffff00)); +} + +/************************************************************************** +IP_TRANSMIT - Send an IP datagram +**************************************************************************/ +static int await_arp(int ival, void *ptr, + unsigned short ptype, struct iphdr *ip __unused, struct udphdr *udp __unused, + struct tcphdr *tcp __unused) +{ + struct arprequest *arpreply; + if (ptype != ETH_P_ARP) + return 0; + if (nic.packetlen < ETH_HLEN + sizeof(struct arprequest)) + return 0; + arpreply = (struct arprequest *)&nic.packet[ETH_HLEN]; + + if (arpreply->opcode != htons(ARP_REPLY)) + return 0; + if (memcmp(arpreply->sipaddr, ptr, sizeof(in_addr)) != 0) + return 0; + memcpy(arptable[ival].node, arpreply->shwaddr, ETH_ALEN); + return 1; +} + +int ip_transmit(int len, const void *buf) +{ + unsigned long destip; + struct iphdr *ip; + struct arprequest arpreq; + int arpentry, i; + int retry; + + ip = (struct iphdr *)buf; + destip = ip->dest.s_addr; + if (destip == IP_BROADCAST) { + eth_transmit(broadcast, ETH_P_IP, len, buf); +#ifdef MULTICAST_LEVEL1 + } else if ((destip & htonl(MULTICAST_MASK)) == htonl(MULTICAST_NETWORK)) { + unsigned char multicast[6]; + unsigned long hdestip; + hdestip = ntohl(destip); + multicast[0] = 0x01; + multicast[1] = 0x00; + multicast[2] = 0x5e; + multicast[3] = (hdestip >> 16) & 0x7; + multicast[4] = (hdestip >> 8) & 0xff; + multicast[5] = hdestip & 0xff; + eth_transmit(multicast, ETH_P_IP, len, buf); +#endif + } else { + if (((destip & netmask) != + (arptable[ARP_CLIENT].ipaddr.s_addr & netmask)) && + arptable[ARP_GATEWAY].ipaddr.s_addr) + destip = arptable[ARP_GATEWAY].ipaddr.s_addr; + for(arpentry = 0; arpentry<MAX_ARP; arpentry++) + if (arptable[arpentry].ipaddr.s_addr == destip) break; + if (arpentry == MAX_ARP) { + printf("%@ is not in my arp table!\n", destip); + return(0); + } + for (i = 0; i < ETH_ALEN; i++) + if (arptable[arpentry].node[i]) + break; + if (i == ETH_ALEN) { /* Need to do arp request */ + arpreq.hwtype = htons(1); + arpreq.protocol = htons(IP); + arpreq.hwlen = ETH_ALEN; + arpreq.protolen = 4; + arpreq.opcode = htons(ARP_REQUEST); + memcpy(arpreq.shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); + memcpy(arpreq.sipaddr, &arptable[ARP_CLIENT].ipaddr, sizeof(in_addr)); + memset(arpreq.thwaddr, 0, ETH_ALEN); + memcpy(arpreq.tipaddr, &destip, sizeof(in_addr)); + for (retry = 1; retry <= MAX_ARP_RETRIES; retry++) { + long timeout; + eth_transmit(broadcast, ETH_P_ARP, sizeof(arpreq), + &arpreq); + timeout = rfc2131_sleep_interval(TIMEOUT, retry); + if (await_reply(await_arp, arpentry, + arpreq.tipaddr, timeout)) goto xmit; + } + return(0); + } +xmit: + eth_transmit(arptable[arpentry].node, ETH_P_IP, len, buf); + } + return 1; +} + +void build_ip_hdr(unsigned long destip, int ttl, int protocol, int option_len, + int len, const void *buf) +{ + struct iphdr *ip; + ip = (struct iphdr *)buf; + ip->verhdrlen = 0x45; + ip->verhdrlen += (option_len/4); + ip->service = 0; + ip->len = htons(len); + ip->ident = 0; + ip->frags = 0; /* Should we set don't fragment? */ + ip->ttl = ttl; + ip->protocol = protocol; + ip->chksum = 0; + ip->src.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; + ip->dest.s_addr = destip; + ip->chksum = ipchksum(buf, sizeof(struct iphdr) + option_len); +} + +void build_udp_hdr(unsigned long destip, + unsigned int srcsock, unsigned int destsock, int ttl, + int len, const void *buf) +{ + struct iphdr *ip; + struct udphdr *udp; + ip = (struct iphdr *)buf; + build_ip_hdr(destip, ttl, IP_UDP, 0, len, buf); + udp = (struct udphdr *)((char *)buf + sizeof(struct iphdr)); + udp->src = htons(srcsock); + udp->dest = htons(destsock); + udp->len = htons(len - sizeof(struct iphdr)); + udp->chksum = 0; + if ((udp->chksum = tcpudpchksum(ip)) == 0) + udp->chksum = 0xffff; +} + +#ifdef DOWNLOAD_PROTO_HTTP +void build_tcp_hdr(unsigned long destip, unsigned int srcsock, + unsigned int destsock, long send_seq, long recv_seq, + int window, int flags, int ttl, int len, const void *buf) +{ + struct iphdr *ip; + struct tcphdr *tcp; + ip = (struct iphdr *)buf; + build_ip_hdr(destip, ttl, IP_TCP, 0, len, buf); + tcp = (struct tcphdr *)(ip + 1); + tcp->src = htons(srcsock); + tcp->dst = htons(destsock); + tcp->seq = htonl(send_seq); + tcp->ack = htonl(recv_seq); + tcp->ctrl = htons(flags + (5 << 12)); /* No TCP options */ + tcp->window = htons(window); + tcp->chksum = 0; + if ((tcp->chksum = tcpudpchksum(ip)) == 0) + tcp->chksum = 0xffff; +} +#endif + + +/************************************************************************** +UDP_TRANSMIT - Send an UDP datagram +**************************************************************************/ +int udp_transmit(unsigned long destip, unsigned int srcsock, + unsigned int destsock, int len, const void *buf) +{ + build_udp_hdr(destip, srcsock, destsock, 60, len, buf); + return ip_transmit(len, buf); +} + +/************************************************************************** +TCP_TRANSMIT - Send a TCP packet +**************************************************************************/ +#ifdef DOWNLOAD_PROTO_HTTP +int tcp_transmit(unsigned long destip, unsigned int srcsock, + unsigned int destsock, long send_seq, long recv_seq, + int window, int flags, int len, const void *buf) +{ + build_tcp_hdr(destip, srcsock, destsock, send_seq, recv_seq, + window, flags, 60, len, buf); + return ip_transmit(len, buf); +} + +int tcp_reset(struct iphdr *ip) { + struct tcphdr *tcp = (struct tcphdr *)(ip + 1); + char buf[sizeof(struct iphdr) + sizeof(struct tcphdr)]; + + if (!(tcp->ctrl & htons(RST))) { + long seq = ntohl(tcp->seq) + ntohs(ip->len) - + sizeof(struct iphdr) - + ((ntohs(tcp->ctrl) >> 10) & 0x3C); + if (tcp->ctrl & htons(SYN|FIN)) + seq++; + return tcp_transmit(ntohl(ip->src.s_addr), + ntohs(tcp->dst), ntohs(tcp->src), + tcp->ctrl&htons(ACK) ? ntohl(tcp->ack) : 0, + seq, TCP_MAX_WINDOW, RST, sizeof(buf), buf); + } + return (1); +} +#endif + +/************************************************************************** +QDRAIN - clear the nic's receive queue +**************************************************************************/ +static int await_qdrain(int ival __unused, void *ptr __unused, + unsigned short ptype __unused, + struct iphdr *ip __unused, struct udphdr *udp __unused, + struct tcphdr *tcp __unused) +{ + return 0; +} + +void rx_qdrain(void) +{ + /* Clear out the Rx queue first. It contains nothing of interest, + * except possibly ARP requests from the DHCP/TFTP server. We use + * polling throughout Etherboot, so some time may have passed since we + * last polled the receive queue, which may now be filled with + * broadcast packets. This will cause the reply to the packets we are + * about to send to be lost immediately. Not very clever. */ + await_reply(await_qdrain, 0, NULL, 0); +} + +#ifdef DOWNLOAD_PROTO_TFTP +/************************************************************************** +TFTP - Download extended BOOTP data, or kernel image +**************************************************************************/ +static int await_tftp(int ival, void *ptr __unused, + unsigned short ptype __unused, struct iphdr *ip, struct udphdr *udp, + struct tcphdr *tcp __unused) +{ + if (!udp) { + return 0; + } + if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) + return 0; + if (ntohs(udp->dest) != ival) + return 0; + return 1; +} + +int tftp ( const char *name, + int (*fnc)(unsigned char *, unsigned int, unsigned int, int) ) +{ + struct tftpreq_info_t request_data = + { name, TFTP_PORT, TFTP_MAX_PACKET }; + struct tftpreq_info_t *request = &request_data; + struct tftpblk_info_t block; + int rc; + + while ( tftp_block ( request, &block ) ) { + request = NULL; /* Send request only once */ + rc = fnc ( block.data, block.block, block.len, block.eof ); + if ( rc <= 0 ) return (rc); + if ( block.eof ) { + /* fnc should not have returned */ + printf ( "TFTP download complete, but\n" ); + return (0); + } + } + return (0); +} + +int tftp_block ( struct tftpreq_info_t *request, struct tftpblk_info_t *block ) +{ + static unsigned short lport = 2000; /* local port */ + static unsigned short rport = TFTP_PORT; /* remote port */ + struct tftp_t *rcvd = NULL; + static struct tftpreq_t xmit; + static unsigned short xmitlen = 0; + static unsigned short blockidx = 0; /* Last block received */ + static unsigned short retry = 0; /* Retry attempts on last block */ + static int blksize = 0; + unsigned short recvlen = 0; + + /* If this is a new request (i.e. if name is set), fill in + * transmit block with RRQ and send it. + */ + if ( request ) { + rx_qdrain(); /* Flush receive queue */ + xmit.opcode = htons(TFTP_RRQ); + xmitlen = (void*)&xmit.u.rrq - (void*)&xmit + + sprintf((char*)xmit.u.rrq, "%s%coctet%cblksize%c%d", + request->name, 0, 0, 0, request->blksize) + + 1; /* null terminator */ + blockidx = 0; /* Reset counters */ + retry = 0; + blksize = TFTP_DEFAULTSIZE_PACKET; + lport++; /* Use new local port */ + rport = request->port; + if ( !udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, lport, + rport, xmitlen, &xmit) ) + return (0); + } + /* Exit if no transfer in progress */ + if ( !blksize ) return (0); + /* Loop to wait until we get a packet we're interested in */ + block->data = NULL; /* Used as flag */ + while ( block->data == NULL ) { + long timeout = rfc2131_sleep_interval ( blockidx ? TFTP_REXMT : + TIMEOUT, retry ); + if ( !await_reply(await_tftp, lport, NULL, timeout) ) { + /* No packet received */ + if ( retry++ > MAX_TFTP_RETRIES ) break; + /* Retransmit last packet */ + if ( !blockidx ) lport++; /* New lport if new RRQ */ + if ( !udp_transmit(arptable[ARP_SERVER].ipaddr.s_addr, + lport, rport, xmitlen, &xmit) ) + return (0); + continue; /* Back to waiting for packet */ + } + /* Packet has been received */ + rcvd = (struct tftp_t *)&nic.packet[ETH_HLEN]; + recvlen = ntohs(rcvd->udp.len) - sizeof(struct udphdr) + - sizeof(rcvd->opcode); + rport = ntohs(rcvd->udp.src); + retry = 0; /* Reset retry counter */ + switch ( htons(rcvd->opcode) ) { + case TFTP_ERROR : { + printf ( "TFTP error %d (%s)\n", + ntohs(rcvd->u.err.errcode), + rcvd->u.err.errmsg ); + return (0); /* abort */ + } + case TFTP_OACK : { + const char *p = rcvd->u.oack.data; + const char *e = p + recvlen - 10; /* "blksize\0\d\0" */ + + *((char*)(p+recvlen-1)) = '\0'; /* Force final 0 */ + if ( blockidx || !request ) break; /* Too late */ + if ( recvlen <= TFTP_MAX_PACKET ) /* sanity */ { + /* Check for blksize option honoured */ + while ( p < e ) { + if ( strcasecmp("blksize",p) == 0 && + p[7] == '\0' ) { + blksize = strtoul(p+8,&p,10); + p++; /* skip null */ + } + while ( *(p++) ) {}; + } + } + if ( blksize < TFTP_DEFAULTSIZE_PACKET || blksize > request->blksize ) { + /* Incorrect blksize - error and abort */ + xmit.opcode = htons(TFTP_ERROR); + xmit.u.err.errcode = 8; + xmitlen = (void*)&xmit.u.err.errmsg + - (void*)&xmit + + sprintf((char*)xmit.u.err.errmsg, + "RFC1782 error") + + 1; + udp_transmit( + arptable[ARP_SERVER].ipaddr.s_addr, + lport, rport, xmitlen, &xmit); + return (0); + } + } break; + case TFTP_DATA : + if ( ntohs(rcvd->u.data.block) != ( blockidx + 1 ) ) + break; /* Re-ACK last block sent */ + if ( recvlen > ( blksize+sizeof(rcvd->u.data.block) ) ) + break; /* Too large; ignore */ + block->data = rcvd->u.data.download; + block->block = ++blockidx; + block->len = recvlen - sizeof(rcvd->u.data.block); + block->eof = ( (unsigned short)block->len < blksize ); + /* If EOF, zero blksize to indicate transfer done */ + if ( block->eof ) blksize = 0; + break; + default: break; /* Do nothing */ + } + /* Send ACK */ + xmit.opcode = htons(TFTP_ACK); + xmit.u.ack.block = htons(blockidx); + xmitlen = TFTP_MIN_PACKET; + udp_transmit ( arptable[ARP_SERVER].ipaddr.s_addr, + lport, rport, xmitlen, &xmit ); + } + return ( block->data ? 1 : 0 ); +} +#endif /* DOWNLOAD_PROTO_TFTP */ + +#ifdef RARP_NOT_BOOTP +/************************************************************************** +RARP - Get my IP address and load information +**************************************************************************/ +static int await_rarp(int ival, void *ptr, + unsigned short ptype, struct iphdr *ip, struct udphdr *udp, + struct tcphdr *tcp __unused) +{ + struct arprequest *arpreply; + if (ptype != ETH_P_RARP) + return 0; + if (nic.packetlen < ETH_HLEN + sizeof(struct arprequest)) + return 0; + arpreply = (struct arprequest *)&nic.packet[ETH_HLEN]; + if (arpreply->opcode != htons(RARP_REPLY)) + return 0; + if ((arpreply->opcode == htons(RARP_REPLY)) && + (memcmp(arpreply->thwaddr, ptr, ETH_ALEN) == 0)) { + memcpy(arptable[ARP_SERVER].node, arpreply->shwaddr, ETH_ALEN); + memcpy(&arptable[ARP_SERVER].ipaddr, arpreply->sipaddr, sizeof(in_addr)); + memcpy(&arptable[ARP_CLIENT].ipaddr, arpreply->tipaddr, sizeof(in_addr)); + return 1; + } + return 0; +} + +static int rarp(void) +{ + int retry; + + /* arp and rarp requests share the same packet structure. */ + struct arprequest rarpreq; + + memset(&rarpreq, 0, sizeof(rarpreq)); + + rarpreq.hwtype = htons(1); + rarpreq.protocol = htons(IP); + rarpreq.hwlen = ETH_ALEN; + rarpreq.protolen = 4; + rarpreq.opcode = htons(RARP_REQUEST); + memcpy(&rarpreq.shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); + /* sipaddr is already zeroed out */ + memcpy(&rarpreq.thwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); + /* tipaddr is already zeroed out */ + + for (retry = 0; retry < MAX_ARP_RETRIES; ++retry) { + long timeout; + eth_transmit(broadcast, ETH_P_RARP, sizeof(rarpreq), &rarpreq); + + timeout = rfc2131_sleep_interval(TIMEOUT, retry); + if (await_reply(await_rarp, 0, rarpreq.shwaddr, timeout)) + break; + } + + if (retry < MAX_ARP_RETRIES) { + (void)sprintf(KERNEL_BUF, DEFAULT_KERNELPATH, arptable[ARP_CLIENT].ipaddr); + + return (1); + } + return (0); +} + +#else + +/************************************************************************** +BOOTP - Get my IP address and load information +**************************************************************************/ +static int await_bootp(int ival __unused, void *ptr __unused, + unsigned short ptype __unused, struct iphdr *ip __unused, + struct udphdr *udp, struct tcphdr *tcp __unused) +{ + struct bootp_t *bootpreply; + if (!udp) { + return 0; + } + bootpreply = (struct bootp_t *)&nic.packet[ETH_HLEN + + sizeof(struct iphdr) + sizeof(struct udphdr)]; + if (nic.packetlen < ETH_HLEN + sizeof(struct iphdr) + + sizeof(struct udphdr) + +#ifdef NO_DHCP_SUPPORT + sizeof(struct bootp_t) +#else + sizeof(struct bootp_t) - DHCP_OPT_LEN +#endif /* NO_DHCP_SUPPORT */ + ) { + return 0; + } + if (udp->dest != htons(BOOTP_CLIENT)) + return 0; + if (bootpreply->bp_op != BOOTP_REPLY) + return 0; + if (bootpreply->bp_xid != xid) + return 0; + if (memcmp(&bootpreply->bp_siaddr, &zeroIP, sizeof(in_addr)) == 0) + return 0; + if ((memcmp(broadcast, bootpreply->bp_hwaddr, ETH_ALEN) != 0) && + (memcmp(arptable[ARP_CLIENT].node, bootpreply->bp_hwaddr, ETH_ALEN) != 0)) { + return 0; + } + if ( bootpreply->bp_siaddr.s_addr ) { + arptable[ARP_SERVER].ipaddr.s_addr = bootpreply->bp_siaddr.s_addr; + memset(arptable[ARP_SERVER].node, 0, ETH_ALEN); /* Kill arp */ + } + if ( bootpreply->bp_giaddr.s_addr ) { + arptable[ARP_GATEWAY].ipaddr.s_addr = bootpreply->bp_giaddr.s_addr; + memset(arptable[ARP_GATEWAY].node, 0, ETH_ALEN); /* Kill arp */ + } + if (bootpreply->bp_yiaddr.s_addr) { + /* Offer with an IP address */ + arptable[ARP_CLIENT].ipaddr.s_addr = bootpreply->bp_yiaddr.s_addr; +#ifndef NO_DHCP_SUPPORT + dhcp_addr.s_addr = bootpreply->bp_yiaddr.s_addr; +#endif /* NO_DHCP_SUPPORT */ + netmask = default_netmask(); + /* bootpreply->bp_file will be copied to KERNEL_BUF in the memcpy */ + memcpy((char *)BOOTP_DATA_ADDR, (char *)bootpreply, sizeof(struct bootpd_t)); + decode_rfc1533(BOOTP_DATA_ADDR->bootp_reply.bp_vend, 0, +#ifdef NO_DHCP_SUPPORT + BOOTP_VENDOR_LEN + MAX_BOOTP_EXTLEN, +#else + DHCP_OPT_LEN + MAX_BOOTP_EXTLEN, +#endif /* NO_DHCP_SUPPORT */ + 1); +#ifdef PXE_EXPORT + } else { + /* Offer without an IP address - use as ProxyDHCP server */ + arptable[ARP_PROXYDHCP].ipaddr.s_addr = bootpreply->bp_siaddr.s_addr; + memset(arptable[ARP_PROXYDHCP].node, 0, ETH_ALEN); /* Kill arp */ + /* Grab only the bootfile name from a ProxyDHCP packet */ + memcpy(KERNEL_BUF, bootpreply->bp_file, sizeof(KERNEL_BUF)); +#endif /* PXE_EXPORT */ + } +#ifdef REQUIRE_VCI_ETHERBOOT + if (!vci_etherboot) + return (0); +#endif + return(1); +} + +static int bootp(void) +{ + int retry; +#ifndef NO_DHCP_SUPPORT + int reqretry; +#endif /* NO_DHCP_SUPPORT */ + struct bootpip_t ip; + unsigned long starttime; + unsigned char *bp_vend; + +#ifndef NO_DHCP_SUPPORT + dhcp_machine_info[4] = nic.dev.devid.bus_type; + dhcp_machine_info[5] = nic.dev.devid.vendor_id & 0xff; + dhcp_machine_info[6] = ((nic.dev.devid.vendor_id) >> 8) & 0xff; + dhcp_machine_info[7] = nic.dev.devid.device_id & 0xff; + dhcp_machine_info[8] = ((nic.dev.devid.device_id) >> 8) & 0xff; +#endif /* NO_DHCP_SUPPORT */ + memset(&ip, 0, sizeof(struct bootpip_t)); + ip.bp.bp_op = BOOTP_REQUEST; + ip.bp.bp_htype = 1; + ip.bp.bp_hlen = ETH_ALEN; + starttime = currticks(); + /* Use lower 32 bits of node address, more likely to be + distinct than the time since booting */ + memcpy(&xid, &arptable[ARP_CLIENT].node[2], sizeof(xid)); + ip.bp.bp_xid = xid += htonl(starttime); + memcpy(ip.bp.bp_hwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); +#ifdef NO_DHCP_SUPPORT + memcpy(ip.bp.bp_vend, rfc1533_cookie, 5); /* request RFC-style options */ +#else + memcpy(ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie); /* request RFC-style options */ + memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie, dhcpdiscover, sizeof dhcpdiscover); + /* Append machine_info to end, in encapsulated option */ + bp_vend = ip.bp.bp_vend + sizeof rfc1533_cookie + sizeof dhcpdiscover; + memcpy(bp_vend, dhcp_machine_info, DHCP_MACHINE_INFO_SIZE); + bp_vend += DHCP_MACHINE_INFO_SIZE; + *bp_vend++ = RFC1533_END; +#endif /* NO_DHCP_SUPPORT */ + + for (retry = 0; retry < MAX_BOOTP_RETRIES; ) { + uint8_t my_hwaddr[ETH_ALEN]; + unsigned long stop_time; + long remaining_time; + + rx_qdrain(); + + /* Kill arptable to avoid keeping stale entries */ + memcpy ( my_hwaddr, arptable[ARP_CLIENT].node, ETH_ALEN ); + memset ( arptable, 0, sizeof(arptable) ); + memcpy ( arptable[ARP_CLIENT].node, my_hwaddr, ETH_ALEN ); + + udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER, + sizeof(struct bootpip_t), &ip); + remaining_time = rfc2131_sleep_interval(BOOTP_TIMEOUT, retry++); + stop_time = currticks() + remaining_time; +#ifdef NO_DHCP_SUPPORT + if (await_reply(await_bootp, 0, NULL, timeout)) + return(1); +#else + while ( remaining_time > 0 ) { + if (await_reply(await_bootp, 0, NULL, remaining_time)){ + } + remaining_time = stop_time - currticks(); + } + if ( ! arptable[ARP_CLIENT].ipaddr.s_addr ) { + printf("No IP address\n"); + continue; + } + /* If not a DHCPOFFER then must be just a BOOTP reply, + * be backward compatible with BOOTP then */ + if (dhcp_reply != DHCPOFFER) + return(1); + dhcp_reply = 0; + /* Construct the DHCPREQUEST packet */ + memcpy(ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie); + memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie, dhcprequest, sizeof dhcprequest); + /* Beware: the magic numbers 9 and 15 depend on + the layout of dhcprequest */ + memcpy(&ip.bp.bp_vend[9], &dhcp_server, sizeof(in_addr)); + memcpy(&ip.bp.bp_vend[15], &dhcp_addr, sizeof(in_addr)); + bp_vend = ip.bp.bp_vend + sizeof rfc1533_cookie + sizeof dhcprequest; + /* Append machine_info to end, in encapsulated option */ + memcpy(bp_vend, dhcp_machine_info, DHCP_MACHINE_INFO_SIZE); + bp_vend += DHCP_MACHINE_INFO_SIZE; + *bp_vend++ = RFC1533_END; + for (reqretry = 0; reqretry < MAX_BOOTP_RETRIES; ) { + unsigned long timeout; + + udp_transmit(IP_BROADCAST, BOOTP_CLIENT, BOOTP_SERVER, + sizeof(struct bootpip_t), &ip); + dhcp_reply=0; + timeout = rfc2131_sleep_interval(TIMEOUT, reqretry++); + if (!await_reply(await_bootp, 0, NULL, timeout)) + continue; + if (dhcp_reply != DHCPACK) + continue; + dhcp_reply = 0; +#ifdef PXE_EXPORT + if ( arptable[ARP_PROXYDHCP].ipaddr.s_addr ) { + /* Construct the ProxyDHCPREQUEST packet */ + memcpy(ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie); + memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie, proxydhcprequest, sizeof proxydhcprequest); + for (reqretry = 0; reqretry < MAX_BOOTP_RETRIES; ) { + printf ( "\nSending ProxyDHCP request to %@...", arptable[ARP_PROXYDHCP].ipaddr.s_addr); + udp_transmit(arptable[ARP_PROXYDHCP].ipaddr.s_addr, BOOTP_CLIENT, PROXYDHCP_SERVER, + sizeof(struct bootpip_t), &ip); + timeout = rfc2131_sleep_interval(TIMEOUT, reqretry++); + if (await_reply(await_bootp, 0, NULL, timeout)) { + break; + } + } + } +#endif /* PXE_EXPORT */ + return(1); + } +#endif /* NO_DHCP_SUPPORT */ + ip.bp.bp_secs = htons((currticks()-starttime)/TICKS_PER_SEC); + } + return(0); +} +#endif /* RARP_NOT_BOOTP */ + +static uint16_t tcpudpchksum(struct iphdr *ip) +{ + struct udp_pseudo_hdr pseudo; + uint16_t checksum; + + /* Compute the pseudo header */ + pseudo.src.s_addr = ip->src.s_addr; + pseudo.dest.s_addr = ip->dest.s_addr; + pseudo.unused = 0; + pseudo.protocol = ip->protocol; + pseudo.len = htons(ntohs(ip->len) - sizeof(struct iphdr)); + + /* Sum the pseudo header */ + checksum = ipchksum(&pseudo, 12); + + /* Sum the rest of the tcp/udp packet */ + checksum = add_ipchksums(12, checksum, ipchksum(ip + 1, + ntohs(ip->len) - sizeof(struct iphdr))); + return checksum; +} + +#ifdef MULTICAST_LEVEL2 +static void send_igmp_reports(unsigned long now) +{ + int i; + for(i = 0; i < MAX_IGMP; i++) { + if (igmptable[i].time && (now >= igmptable[i].time)) { + struct igmp_ip_t igmp; + igmp.router_alert[0] = 0x94; + igmp.router_alert[1] = 0x04; + igmp.router_alert[2] = 0; + igmp.router_alert[3] = 0; + build_ip_hdr(igmptable[i].group.s_addr, + 1, IP_IGMP, sizeof(igmp.router_alert), sizeof(igmp), &igmp); + igmp.igmp.type = IGMPv2_REPORT; + if (last_igmpv1 && + (now < last_igmpv1 + IGMPv1_ROUTER_PRESENT_TIMEOUT)) { + igmp.igmp.type = IGMPv1_REPORT; + } + igmp.igmp.response_time = 0; + igmp.igmp.chksum = 0; + igmp.igmp.group.s_addr = igmptable[i].group.s_addr; + igmp.igmp.chksum = ipchksum(&igmp.igmp, sizeof(igmp.igmp)); + ip_transmit(sizeof(igmp), &igmp); +#ifdef MDEBUG + printf("Sent IGMP report to: %@\n", igmp.igmp.group.s_addr); +#endif + /* Don't send another igmp report until asked */ + igmptable[i].time = 0; + } + } +} + +static void process_igmp(struct iphdr *ip, unsigned long now) +{ + struct igmp *igmp; + int i; + unsigned iplen; + if (!ip || (ip->protocol == IP_IGMP) || + (nic.packetlen < sizeof(struct iphdr) + sizeof(struct igmp))) { + return; + } + iplen = (ip->verhdrlen & 0xf)*4; + igmp = (struct igmp *)&nic.packet[sizeof(struct iphdr)]; + if (ipchksum(igmp, ntohs(ip->len) - iplen) != 0) + return; + if ((igmp->type == IGMP_QUERY) && + (ip->dest.s_addr == htonl(GROUP_ALL_HOSTS))) { + unsigned long interval = IGMP_INTERVAL; + if (igmp->response_time == 0) { + last_igmpv1 = now; + } else { + interval = (igmp->response_time * TICKS_PER_SEC)/10; + } + +#ifdef MDEBUG + printf("Received IGMP query for: %@\n", igmp->group.s_addr); +#endif + for(i = 0; i < MAX_IGMP; i++) { + uint32_t group = igmptable[i].group.s_addr; + if ((group == 0) || (group == igmp->group.s_addr)) { + unsigned long time; + time = currticks() + rfc1112_sleep_interval(interval, 0); + if (time < igmptable[i].time) { + igmptable[i].time = time; + } + } + } + } + if (((igmp->type == IGMPv1_REPORT) || (igmp->type == IGMPv2_REPORT)) && + (ip->dest.s_addr == igmp->group.s_addr)) { +#ifdef MDEBUG + printf("Received IGMP report for: %@\n", igmp->group.s_addr); +#endif + for(i = 0; i < MAX_IGMP; i++) { + if ((igmptable[i].group.s_addr == igmp->group.s_addr) && + igmptable[i].time != 0) { + igmptable[i].time = 0; + } + } + } +} + +void leave_group(int slot) +{ + /* Be very stupid and always send a leave group message if + * I have subscribed. Imperfect but it is standards + * compliant, easy and reliable to implement. + * + * The optimal group leave method is to only send leave when, + * we were the last host to respond to a query on this group, + * and igmpv1 compatibility is not enabled. + */ + if (igmptable[slot].group.s_addr) { + struct igmp_ip_t igmp; + igmp.router_alert[0] = 0x94; + igmp.router_alert[1] = 0x04; + igmp.router_alert[2] = 0; + igmp.router_alert[3] = 0; + build_ip_hdr(htonl(GROUP_ALL_HOSTS), + 1, IP_IGMP, sizeof(igmp.router_alert), sizeof(igmp), &igmp); + igmp.igmp.type = IGMP_LEAVE; + igmp.igmp.response_time = 0; + igmp.igmp.chksum = 0; + igmp.igmp.group.s_addr = igmptable[slot].group.s_addr; + igmp.igmp.chksum = ipchksum(&igmp.igmp, sizeof(igmp)); + ip_transmit(sizeof(igmp), &igmp); +#ifdef MDEBUG + printf("Sent IGMP leave for: %@\n", igmp.igmp.group.s_addr); +#endif + } + memset(&igmptable[slot], 0, sizeof(igmptable[0])); +} + +void join_group(int slot, unsigned long group) +{ + /* I have already joined */ + if (igmptable[slot].group.s_addr == group) + return; + if (igmptable[slot].group.s_addr) { + leave_group(slot); + } + /* Only join a group if we are given a multicast ip, this way + * code can be given a non-multicast (broadcast or unicast ip) + * and still work... + */ + if ((group & htonl(MULTICAST_MASK)) == htonl(MULTICAST_NETWORK)) { + igmptable[slot].group.s_addr = group; + igmptable[slot].time = currticks(); + } +} +#else +#define send_igmp_reports(now) do {} while(0) +#define process_igmp(ip, now) do {} while(0) +#endif + +#include "proto_eth_slow.c" + +/************************************************************************** +TCP - Simple-minded TCP stack. Can only send data once and then + receive the response. The algorithm for computing window + sizes and delaying ack's is currently broken, and thus + disabled. Performance would probably improve a little, if + this gets fixed. FIXME +**************************************************************************/ +#ifdef DOWNLOAD_PROTO_HTTP +static int await_tcp(int ival, void *ptr, unsigned short ptype __unused, + struct iphdr *ip, struct udphdr *udp __unused, + struct tcphdr *tcp) +{ + if (!tcp) { + return 0; + } + if (arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr) + return 0; + if (ntohs(tcp->dst) != ival) { + tcp_reset(ip); + return 0; + } + *(void **)ptr = tcp; + return 1; +} + +int tcp_transaction(unsigned long destip, unsigned int destsock, void *ptr, + int (*send)(int len, void *buf, void *ptr), + int (*recv)(int len, const void *buf, void *ptr)) { + static uint16_t srcsock = 0; + int rc = 1; + long send_seq = currticks(); + long recv_seq = 0; + int can_send = 0; + int sent_all = 0; + struct iphdr *ip; + struct tcphdr *tcp; + int ctrl = SYN; + char buf[128]; /* Small outgoing buffer */ + long payload; + int header_size; + int window = 3*TCP_MIN_WINDOW; + long last_ack = 0; + long last_sent = 0; + long rtt = 0; + long srtt = 0; + long rto = TCP_INITIAL_TIMEOUT; + int retry = TCP_MAX_TIMEOUT/TCP_INITIAL_TIMEOUT; + enum { CLOSED, SYN_RCVD, ESTABLISHED, + FIN_WAIT_1, FIN_WAIT_2 } state = CLOSED; + + if (!srcsock) { + srcsock = currticks(); + } + if (++srcsock < 1024) + srcsock += 1024; + + await_reply(await_qdrain, 0, NULL, 0); + + send_data: + if (ctrl & ACK) + last_ack = recv_seq; + if (!tcp_transmit(destip, srcsock, destsock, send_seq, + recv_seq, window, ctrl, + sizeof(struct iphdr) + sizeof(struct tcphdr)+ + can_send, buf)) { + return (0); + } + last_sent = currticks(); + + recv_data: + if (!await_reply(await_tcp, srcsock, &tcp, + (state == ESTABLISHED && !can_send) + ? TCP_MAX_TIMEOUT : rto)) { + if (state == ESTABLISHED) { + close: + ctrl = FIN|ACK; + state = FIN_WAIT_1; + rc = 0; + goto send_data; + } + + if (state == FIN_WAIT_1 || state == FIN_WAIT_2) + return (rc); + + if (--retry <= 0) { + /* time out */ + if (state == SYN_RCVD) { + tcp_transmit(destip, srcsock, destsock, + send_seq, 0, window, RST, + sizeof(struct iphdr) + + sizeof(struct tcphdr), buf); + } + return (0); + } + /* retransmit */ + goto send_data; + } + got_data: + retry = TCP_MAX_RETRY; + + if (tcp->ctrl & htons(ACK) ) { + char *data; + int syn_ack, consumed; + + if (state == FIN_WAIT_1 || state == FIN_WAIT_2) { + state = FIN_WAIT_2; + ctrl = ACK; + goto consume_data; + } + syn_ack = state == CLOSED || state == SYN_RCVD; + consumed = ntohl(tcp->ack) - send_seq - syn_ack; + if (consumed < 0 || consumed > can_send) { + tcp_reset((struct iphdr *)&nic.packet[ETH_HLEN]); + goto recv_data; + } + + rtt = currticks() - last_sent; + srtt = !srtt ? rtt : (srtt*4 + rtt)/5; + rto = srtt + srtt/2; + if (rto < TCP_MIN_TIMEOUT) + rto = TCP_MIN_TIMEOUT; + else if (rto > TCP_MAX_TIMEOUT) + rto = TCP_MAX_TIMEOUT; + + can_send -= consumed; + send_seq += consumed + syn_ack; + data = buf + sizeof(struct iphdr) + sizeof(struct tcphdr); + if (can_send) { + memmove(data, data + consumed, can_send); + } + if (!sent_all) { + int more_data; + data += can_send; + more_data = buf + sizeof(buf) - data; + if (more_data > 0) { + more_data = send(more_data, data, ptr); + can_send += more_data; + } + sent_all = !more_data; + } + if (state == SYN_RCVD) { + state = ESTABLISHED; + ctrl = PSH|ACK; + goto consume_data; + } + if (tcp->ctrl & htons(RST)) + return (0); + } else if (tcp->ctrl & htons(RST)) { + if (state == CLOSED) + goto recv_data; + return (0); + } + + consume_data: + ip = (struct iphdr *)&nic.packet[ETH_HLEN]; + header_size = sizeof(struct iphdr) + ((ntohs(tcp->ctrl)>>10)&0x3C); + payload = ntohs(ip->len) - header_size; + if (payload > 0 && state == ESTABLISHED) { + int old_bytes = recv_seq - (long)ntohl(tcp->seq); + if (old_bytes >= 0 && payload - old_bytes > 0) { + recv_seq += payload - old_bytes; + if (state != FIN_WAIT_1 && state != FIN_WAIT_2 && + !recv(payload - old_bytes, + &nic.packet[ETH_HLEN+header_size+old_bytes], + ptr)) { + goto close; + } + if ((state == ESTABLISHED || state == SYN_RCVD) && + !(tcp->ctrl & htons(FIN))) { + int in_window = window - 2*TCP_MIN_WINDOW > + recv_seq - last_ack; + ctrl = can_send ? PSH|ACK : ACK; + if (!can_send && in_window) { +/* Window scaling is broken right now, just fall back to acknowledging every */ +/* packet immediately and unconditionally. FIXME */ /***/ +/* if (await_reply(await_tcp, srcsock, + &tcp, rto)) + goto got_data; + else */ + goto send_data; + } + if (!in_window) { + window += TCP_MIN_WINDOW; + if (window > TCP_MAX_WINDOW) + window = TCP_MAX_WINDOW; + } + goto send_data; + } + } else { + /* saw old data again, must have lost packets */ + window /= 2; + if (window < 2*TCP_MIN_WINDOW) + window = 2*TCP_MIN_WINDOW; + } + } + + if (tcp->ctrl & htons(FIN)) { + if (state == ESTABLISHED) { + ctrl = FIN|ACK; + } else if (state == FIN_WAIT_1 || state == FIN_WAIT_2) { + ctrl = ACK; + } else { + ctrl = RST; + } + return (tcp_transmit(destip, srcsock, destsock, + send_seq, recv_seq + 1, window, ctrl, + sizeof(struct iphdr) + + sizeof(struct tcphdr), buf) && + (state == ESTABLISHED || + state == FIN_WAIT_1 || state == FIN_WAIT_2) && + !can_send); + } + + if (state == CLOSED) { + if (tcp->ctrl & htons(SYN)) { + recv_seq = ntohl(tcp->seq) + 1; + if (!(tcp->ctrl & htons(ACK))) { + state = SYN_RCVD; + ctrl = SYN|ACK|PSH; + goto send_data; + } else { + state = ESTABLISHED; + ctrl = PSH|ACK; + } + } + } + + if (can_send || payload) { + goto send_data; + } + goto recv_data; +} +#endif + +/************************************************************************** +AWAIT_REPLY - Wait until we get a response for our request +************f**************************************************************/ +int await_reply(reply_t reply, int ival, void *ptr, long timeout) +{ + unsigned long time, now; + struct iphdr *ip; + unsigned iplen = 0; + struct udphdr *udp; + struct tcphdr *tcp; + unsigned short ptype; + int result; + + time = timeout + currticks(); + /* The timeout check is done below. The timeout is only checked if + * there is no packet in the Rx queue. This assumes that eth_poll() + * needs a negligible amount of time. + */ + for (;;) { + now = currticks(); + send_eth_slow_reports(now); + send_igmp_reports(now); + result = eth_poll(1); + if (result == 0) { + /* We don't have anything */ + + /* Check for abort key only if the Rx queue is empty - + * as long as we have something to process, don't + * assume that something failed. It is unlikely that + * we have no processing time left between packets. */ + poll_interruptions(); + /* Do the timeout after at least a full queue walk. */ + if ((timeout == 0) || (currticks() > time)) { + break; + } + continue; + } + + /* We have something! */ + + /* Find the Ethernet packet type */ + if (nic.packetlen >= ETH_HLEN) { + ptype = ((unsigned short) nic.packet[12]) << 8 + | ((unsigned short) nic.packet[13]); + } else continue; /* what else could we do with it? */ + /* Verify an IP header */ + ip = 0; + if ((ptype == ETH_P_IP) && (nic.packetlen >= ETH_HLEN + sizeof(struct iphdr))) { + unsigned ipoptlen; + ip = (struct iphdr *)&nic.packet[ETH_HLEN]; + if ((ip->verhdrlen < 0x45) || (ip->verhdrlen > 0x4F)) + continue; + iplen = (ip->verhdrlen & 0xf) * 4; + if (ipchksum(ip, iplen) != 0) + continue; + if (ip->frags & htons(0x3FFF)) { + static int warned_fragmentation = 0; + if (!warned_fragmentation) { + printf("ALERT: got a fragmented packet - reconfigure your server\n"); + warned_fragmentation = 1; + } + continue; + } + if (ntohs(ip->len) > ETH_MAX_MTU) + continue; + + ipoptlen = iplen - sizeof(struct iphdr); + if (ipoptlen) { + /* Delete the ip options, to guarantee + * good alignment, and make etherboot simpler. + */ + memmove(&nic.packet[ETH_HLEN + sizeof(struct iphdr)], + &nic.packet[ETH_HLEN + iplen], + nic.packetlen - ipoptlen); + nic.packetlen -= ipoptlen; + } + } + udp = 0; + if (ip && (ip->protocol == IP_UDP) && + (nic.packetlen >= + ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr))) { + udp = (struct udphdr *)&nic.packet[ETH_HLEN + sizeof(struct iphdr)]; + + /* Make certain we have a reasonable packet length */ + if (ntohs(udp->len) > (ntohs(ip->len) - iplen)) + continue; + + if (udp->chksum && tcpudpchksum(ip)) { + printf("UDP checksum error\n"); + continue; + } + } + tcp = 0; +#ifdef DOWNLOAD_PROTO_HTTP + if (ip && (ip->protocol == IP_TCP) && + (nic.packetlen >= + ETH_HLEN + sizeof(struct iphdr) + sizeof(struct tcphdr))){ + tcp = (struct tcphdr *)&nic.packet[ETH_HLEN + + sizeof(struct iphdr)]; + /* Make certain we have a reasonable packet length */ + if (((ntohs(tcp->ctrl) >> 10) & 0x3C) > + ntohs(ip->len) - (int)iplen) + continue; + if (tcpudpchksum(ip)) { + printf("TCP checksum error\n"); + continue; + } + + } +#endif + result = reply(ival, ptr, ptype, ip, udp, tcp); + if (result > 0) { + return result; + } + + /* If it isn't a packet the upper layer wants see if there is a default + * action. This allows us reply to arp, igmp, and lacp queries. + */ + if ((ptype == ETH_P_ARP) && + (nic.packetlen >= ETH_HLEN + sizeof(struct arprequest))) { + struct arprequest *arpreply; + unsigned long tmp; + + arpreply = (struct arprequest *)&nic.packet[ETH_HLEN]; + memcpy(&tmp, arpreply->tipaddr, sizeof(in_addr)); + if ((arpreply->opcode == htons(ARP_REQUEST)) && + (tmp == arptable[ARP_CLIENT].ipaddr.s_addr)) { + arpreply->opcode = htons(ARP_REPLY); + memcpy(arpreply->tipaddr, arpreply->sipaddr, sizeof(in_addr)); + memcpy(arpreply->thwaddr, arpreply->shwaddr, ETH_ALEN); + memcpy(arpreply->sipaddr, &arptable[ARP_CLIENT].ipaddr, sizeof(in_addr)); + memcpy(arpreply->shwaddr, arptable[ARP_CLIENT].node, ETH_ALEN); + eth_transmit(arpreply->thwaddr, ETH_P_ARP, + sizeof(struct arprequest), + arpreply); +#ifdef MDEBUG + memcpy(&tmp, arpreply->tipaddr, sizeof(in_addr)); + printf("Sent ARP reply to: %@\n",tmp); +#endif /* MDEBUG */ + } + } + process_eth_slow(ptype, now); + process_igmp(ip, now); + } + return(0); +} + +#ifdef REQUIRE_VCI_ETHERBOOT +/************************************************************************** +FIND_VCI_ETHERBOOT - Looks for "Etherboot" in Vendor Encapsulated Identifiers +On entry p points to byte count of VCI options +**************************************************************************/ +static int find_vci_etherboot(unsigned char *p) +{ + unsigned char *end = p + 1 + *p; + + for (p++; p < end; ) { + if (*p == RFC2132_VENDOR_CLASS_ID) { + if (strncmp("Etherboot", p + 2, sizeof("Etherboot") - 1) == 0) + return (1); + } else if (*p == RFC1533_END) + return (0); + p += TAG_LEN(p) + 2; + } + return (0); +} +#endif /* REQUIRE_VCI_ETHERBOOT */ + +/************************************************************************** +DECODE_RFC1533 - Decodes RFC1533 header +**************************************************************************/ +int decode_rfc1533(unsigned char *p, unsigned int block, unsigned int len, int eof) +{ + static unsigned char *extdata = NULL, *extend = NULL; + unsigned char *extpath = NULL; + unsigned char *endp; + static unsigned char in_encapsulated_options = 0; + + if (eof == -1) { + /* Encapsulated option block */ + endp = p + len; + } + else if (block == 0) { +#ifdef REQUIRE_VCI_ETHERBOOT + vci_etherboot = 0; +#endif + end_of_rfc1533 = NULL; +#ifdef IMAGE_FREEBSD + /* yes this is a pain FreeBSD uses this for swap, however, + there are cases when you don't want swap and then + you want this set to get the extra features so lets + just set if dealing with FreeBSD. I haven't run into + any troubles with this but I have without it + */ + vendorext_isvalid = 1; +#ifdef FREEBSD_KERNEL_ENV + memcpy(freebsd_kernel_env, FREEBSD_KERNEL_ENV, + sizeof(FREEBSD_KERNEL_ENV)); + /* FREEBSD_KERNEL_ENV had better be a string constant */ +#else + freebsd_kernel_env[0]='\0'; +#endif +#else + vendorext_isvalid = 0; +#endif + if (memcmp(p, rfc1533_cookie, 4)) + return(0); /* no RFC 1533 header found */ + p += 4; + endp = p + len; + } else { + if (block == 1) { + if (memcmp(p, rfc1533_cookie, 4)) + return(0); /* no RFC 1533 header found */ + p += 4; + len -= 4; } + if (extend + len <= (unsigned char *)&(BOOTP_DATA_ADDR->bootp_extension[MAX_BOOTP_EXTLEN])) { + memcpy(extend, p, len); + extend += len; + } else { + printf("Overflow in vendor data buffer! Aborting...\n"); + *extdata = RFC1533_END; + return(0); + } + p = extdata; endp = extend; + } + if (!eof) + return 1; + while (p < endp) { + unsigned char c = *p; + if (c == RFC1533_PAD) { + p++; + continue; + } + else if (c == RFC1533_END) { + end_of_rfc1533 = endp = p; + continue; + } + else if (NON_ENCAP_OPT c == RFC1533_NETMASK) + memcpy(&netmask, p+2, sizeof(in_addr)); + else if (NON_ENCAP_OPT c == RFC1533_GATEWAY) { + /* This is a little simplistic, but it will + usually be sufficient. + Take only the first entry */ + if (TAG_LEN(p) >= sizeof(in_addr)) + memcpy(&arptable[ARP_GATEWAY].ipaddr, p+2, sizeof(in_addr)); + } + else if (c == RFC1533_EXTENSIONPATH) + extpath = p; +#ifndef NO_DHCP_SUPPORT +#ifdef REQUIRE_VCI_ETHERBOOT + else if (NON_ENCAP_OPT c == RFC1533_VENDOR) { + vci_etherboot = find_vci_etherboot(p+1); +#ifdef MDEBUG + printf("vci_etherboot %d\n", vci_etherboot); +#endif + } +#endif /* REQUIRE_VCI_ETHERBOOT */ + else if (NON_ENCAP_OPT c == RFC2132_MSG_TYPE) + dhcp_reply=*(p+2); + else if (NON_ENCAP_OPT c == RFC2132_SRV_ID) + memcpy(&dhcp_server, p+2, sizeof(in_addr)); +#endif /* NO_DHCP_SUPPORT */ + else if (NON_ENCAP_OPT c == RFC1533_HOSTNAME) { + hostname = p + 2; + hostnamelen = *(p + 1); + } + else if (ENCAP_OPT c == RFC1533_VENDOR_MAGIC + && TAG_LEN(p) >= 6 && + !memcmp(p+2,vendorext_magic,4) && + p[6] == RFC1533_VENDOR_MAJOR + ) + vendorext_isvalid++; + else if (NON_ENCAP_OPT c == RFC1533_VENDOR_ETHERBOOT_ENCAP) { + in_encapsulated_options = 1; + decode_rfc1533(p+2, 0, TAG_LEN(p), -1); + in_encapsulated_options = 0; + } +#ifdef IMAGE_FREEBSD + else if (NON_ENCAP_OPT c == RFC1533_VENDOR_HOWTO) + freebsd_howto = ((p[2]*256+p[3])*256+p[4])*256+p[5]; + else if (NON_ENCAP_OPT c == RFC1533_VENDOR_KERNEL_ENV){ + if(*(p + 1) < sizeof(freebsd_kernel_env)){ + memcpy(freebsd_kernel_env,p+2,*(p+1)); + }else{ + printf("Only support %ld bytes in Kernel Env\n", + sizeof(freebsd_kernel_env)); + } + } +#endif +#ifdef DNS_RESOLVER + else if (NON_ENCAP_OPT c == RFC1533_DNS) { + // TODO: Copy the DNS IP somewhere reasonable + if (TAG_LEN(p) >= sizeof(in_addr)) + memcpy(&arptable[ARP_NAMESERVER].ipaddr, p+2, sizeof(in_addr)); + } +#endif + else { +#if 0 + unsigned char *q; + printf("Unknown RFC1533-tag "); + for(q=p;q<p+2+TAG_LEN(p);q++) + printf("%hhX ",*q); + putchar('\n'); +#endif + } + p += TAG_LEN(p) + 2; + } + extdata = extend = endp; + if (block <= 0 && extpath != NULL) { + char fname[64]; + memcpy(fname, extpath+2, TAG_LEN(extpath)); + fname[(int)TAG_LEN(extpath)] = '\0'; + printf("Loading BOOTP-extension file: %s\n",fname); + tftp(fname, decode_rfc1533); + } + return 1; /* proceed with next block */ +} + + +/* FIXME double check TWO_SECOND_DIVISOR */ +#define TWO_SECOND_DIVISOR (RAND_MAX/TICKS_PER_SEC) +/************************************************************************** +RFC2131_SLEEP_INTERVAL - sleep for expotentially longer times (base << exp) +- 1 sec) +**************************************************************************/ +long rfc2131_sleep_interval(long base, int exp) +{ + unsigned long tmo; +#ifdef BACKOFF_LIMIT + if (exp > BACKOFF_LIMIT) + exp = BACKOFF_LIMIT; +#endif + tmo = (base << exp) + (TICKS_PER_SEC - (random()/TWO_SECOND_DIVISOR)); + return tmo; +} + +#ifdef MULTICAST_LEVEL2 +/************************************************************************** +RFC1112_SLEEP_INTERVAL - sleep for expotentially longer times, up to (base << exp) +**************************************************************************/ +long rfc1112_sleep_interval(long base, int exp) +{ + unsigned long divisor, tmo; +#ifdef BACKOFF_LIMIT + if (exp > BACKOFF_LIMIT) + exp = BACKOFF_LIMIT; +#endif + divisor = RAND_MAX/(base << exp); + tmo = random()/divisor; + return tmo; +} +#endif /* MULTICAST_LEVEL_2 */ |
