diff --git a/Makefile.util.def b/Makefile.util.def index 093f448..a49707e 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -9,6 +9,7 @@ library = { common = grub-core/kern/command.c; common = grub-core/kern/device.c; common = grub-core/kern/disk.c; + common = grub-core/kern/net.c; common = grub-core/kern/emu/getroot.c; common = grub-core/kern/emu/hostdisk.c; common = grub-core/kern/emu/misc.c; diff --git a/grub-core/Makefile.am b/grub-core/Makefile.am index 94f7f3f..0012e59 100644 --- a/grub-core/Makefile.am +++ b/grub-core/Makefile.am @@ -61,6 +61,7 @@ include $(srcdir)/Makefile.core.am KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/cache.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/command.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/device.h +KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/net.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/disk.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/dl.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/env.h diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 6979369..2cdcc7b 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -56,6 +56,7 @@ kernel = { common = kern/corecmd.c; common = kern/device.c; common = kern/disk.c; + common = kern/net.c; common = kern/dl.c; common = kern/env.c; common = kern/err.c; @@ -948,6 +949,11 @@ module = { }; module = { + name = tftp; + common = fs/tftp.c; +}; + +module = { name = jfs; common = fs/jfs.c; }; diff --git a/grub-core/commands/boot.c b/grub-core/commands/boot.c index 7714011..c3dbe12 100644 --- a/grub-core/commands/boot.c +++ b/grub-core/commands/boot.c @@ -24,6 +24,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -148,6 +149,8 @@ grub_loader_boot (void) if (! grub_loader_loaded) return grub_error (GRUB_ERR_NO_KERNEL, "no loaded kernel"); + grub_net_shutdown(); + if (grub_loader_noreturn) grub_machine_fini (); diff --git a/grub-core/fs/tftp.c b/grub-core/fs/tftp.c new file mode 100644 index 0000000..01dbcb6 --- /dev/null +++ b/grub-core/fs/tftp.c @@ -0,0 +1,508 @@ +/* tftp.c - tftp filesystem */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2005,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define TFTPS_PORT 69 + +#define TFTP_BLOCK_SIZE 512 + +#define TFTP_OP_RRQ 1 +#define TFTP_OP_WRQ 2 +#define TFTP_OP_DATA 3 +#define TFTP_OP_ACK 4 +#define TFTP_OP_ERROR 5 + +GRUB_MOD_LICENSE ("GPLv2+"); + +static grub_dl_t my_mod; + +typedef struct __attribute__ ((packed)) +{ + grub_uint16_t op; + grub_uint8_t data[]; +} tftp_hdr; + +typedef struct __attribute__ ((packed)) +{ + grub_uint16_t op; + grub_uint16_t block; + grub_uint8_t data[0]; +} tftp_ackdata; + +typedef struct __attribute__ ((packed)) +{ + grub_uint16_t op; + grub_uint16_t errorcode; + grub_uint16_t errmsg[0]; +} tftp_err; + + +#define TFTP_TIMEOUT 2000 +#define TFTP_TRIES 3 + +enum tftp_state +{ + TFTP_STATE_IDLE = 0, + TFTP_STATE_SENT_REQ, + TFTP_STATE_DATA, + TFTP_STATE_TIMEDOUT, + TFTP_STATE_ERROR, + TFTP_STATE_DONE +}; +typedef enum tftp_state tftp_state_t; + +struct tftp_file_data +{ + char path[1024]; + tftp_state_t state; + grub_udp_socket_t *socket; + grub_net_t net; + + + grub_uint64_t timeout; + + int length; + int buf_size; + grub_uint8_t *buf; +}; +typedef struct tftp_file_data tftp_file_data_t; + + +static void +ack (grub_net_t net, grub_udp_socket_t * socket, int block, + grub_ipv4_addr * src, grub_uint16_t sport) +{ + tftp_ackdata ta; + + ta.op = grub_net_htons (TFTP_OP_ACK); + ta.block = grub_net_htons (block); + + grub_udp_send (net, socket->port, (grub_uint8_t *) & ta, sizeof (ta), src, + sport); +} + + + +static void +data (tftp_file_data_t * fd, grub_net_t net, grub_udp_socket_t * socket, + grub_uint8_t * packet, int len, grub_ipv4_addr * src, + grub_uint16_t sport) +{ + tftp_ackdata *td; + int block; + int offset; + + if ((fd->state != TFTP_STATE_DATA) && (fd->state != TFTP_STATE_SENT_REQ)) + return; + + if (len < (int) sizeof (tftp_ackdata)) + return; + + fd->state = TFTP_STATE_DATA; + + td = (tftp_ackdata *) packet; + len -= sizeof (tftp_ackdata); + + block = grub_net_ntohs (td->block); + + offset = (block - 1) * TFTP_BLOCK_SIZE; + + if ((offset + len) > fd->length) + fd->length = offset + len; + + if (len) + { + + if (fd->length > fd->buf_size) + { + while (fd->length > fd->buf_size) + fd->buf_size <<= 1; + + fd->buf = grub_realloc (fd->buf, fd->buf_size); + + } + if (!fd->buf) + { +#if 0 + grub_printf ("failed to realloc %d bytes\n", fd->buf_size); +#endif + grub_millisleep (5000); + } + + if (!fd->buf) + { + fd->state = TFTP_STATE_ERROR; + } + else + { + grub_memcpy (&fd->buf[offset], td->data, len); + } + } + + fd->timeout = grub_get_time_ms () + TFTP_TIMEOUT; + + if (len != TFTP_BLOCK_SIZE) + { + fd->state = TFTP_STATE_DONE; + } + + ack (net, socket, block, src, sport); +} + +static void +error (tftp_file_data_t * fd) +{ + fd->state = TFTP_STATE_ERROR; +} + + + +static void +rrq (tftp_file_data_t * fd) +{ + grub_uint8_t frame[GRUB_UDP_DATA_LEN]; + int len = 0; + char *fn = fd->path; + + tftp_hdr *t = (tftp_hdr *) frame; + grub_uint8_t *ptr; + + t->op = grub_net_htons (TFTP_OP_RRQ); + ptr = t->data; + len += sizeof (tftp_hdr); + + while (*fn) + { + *(ptr++) = *(fn++); + len++; + } + + *(ptr++) = 0; + *(ptr++) = 'o'; + *(ptr++) = 'c'; + *(ptr++) = 't'; + *(ptr++) = 'e'; + *(ptr++) = 't'; + *(ptr++) = 0; + len += 7; + + grub_udp_send (fd->net, fd->socket->port, frame, len, &fd->net->tftp_addr, + TFTPS_PORT); + + fd->state = TFTP_STATE_SENT_REQ; +} + + +static void +tftp_new_frame (grub_udp_socket_t * socket, grub_net_t net, + grub_uint8_t * msg, int len, grub_ipv4_addr * src, + grub_uint16_t src_port) +{ + tftp_file_data_t *fd = (tftp_file_data_t *) socket->data; + tftp_hdr *t; + + if (grub_memcmp (src, &fd->net->tftp_addr, GRUB_IPV4_ALEN)) + return; + if (len < (int) sizeof (tftp_hdr)) + return; + + t = (tftp_hdr *) msg; + + switch (grub_net_ntohs (t->op)) + { + case TFTP_OP_DATA: + data (fd, net, socket, msg, len, src, src_port); + break; + case TFTP_OP_ERROR: + error (fd); + break; + } +} + +static int +tftp (tftp_file_data_t * fd) +{ + int tries = 0; + + fd->state = TFTP_STATE_IDLE; + + fd->length = 0; + fd->buf_size = 1024; + fd->buf = grub_malloc (fd->buf_size); + if (!fd->buf) + goto fail; + + fd->socket = grub_malloc (sizeof (struct grub_udp_socket)); + if (!fd->socket) + goto fail; + + + fd->socket->data = fd; + fd->socket->new_frame = tftp_new_frame; + + do + { + fd->socket->port = 0; /* We want to be given a different port for each attempt */ + + if (tries) + grub_sleep (1); /* Back off */ + + if (grub_udp_bind (fd->socket)) + goto fail; + + fd->timeout = grub_get_time_ms () + TFTP_TIMEOUT; + + rrq (fd); + + while ((grub_get_time_ms () < fd->timeout) + && (fd->state == TFTP_STATE_SENT_REQ)) + grub_net_dispatch (); + + if (fd->state == TFTP_STATE_SENT_REQ) + grub_udp_unbind (fd->socket); + + } + while ((fd->state == TFTP_STATE_SENT_REQ) && (tries++ < TFTP_TRIES)); + + + if (fd->state == TFTP_STATE_SENT_REQ) + { + grub_printf ("tftp - timeout after rrq\n"); + //TIMEOUT + goto fail; + } + + fd->timeout = grub_get_time_ms () + TFTP_TIMEOUT; + + while ((grub_get_time_ms () < fd->timeout) + && (fd->state == TFTP_STATE_DATA)) + grub_net_dispatch (); + + + + if (fd->state == TFTP_STATE_DONE) + { + grub_dprintf ("tftp","tftp - success\n"); + } + else if (fd->state == TFTP_STATE_ERROR) + { + grub_dprintf ("tftp","tftp - remote reported error\n"); + } + else if (fd->state == TFTP_STATE_DATA) + { + grub_printf ("tftp - timeout in data phase\n"); + } + +fail: + + if (fd->socket) + { + grub_udp_unbind (fd->socket); + grub_free (fd->socket); + } + fd->socket = NULL; + + return (fd->state == TFTP_STATE_DONE) ? fd->length : -1; + +} + +static void +ip_to_x (grub_ipv4_addr * s, char *d) +{ + int i, j; + + for (i = 0; i < 4; ++i) + { + for (j = 4; j >= 0; j -= 4) + { + int k = (s->s_addr[i] >> j) & 0xf; + if (k < 0xA) + *(d++) = '0' + k; + else + *(d++) = 'A' + (k - 0xA); + } + } + *(d++) = 0; +} + + +static grub_err_t +grub_tftp_open (grub_file_t file, const char *name) +{ + char ipx[9]; + char *p; + tftp_file_data_t *fd; + int i; + int j; + int ret; + + grub_dl_ref (my_mod); + + grub_dprintf ("tftp","tftp_open_called\n"); + + fd = grub_malloc (sizeof (*fd)); + if (!fd) + goto fail; + file->data = fd; + + + fd->socket = NULL; + fd->net = file->device->net; + + ip_to_x (&fd->net->my_addr, ipx); + + for (j = 0; j < 2;++j) + { + for (i = 8; i >= 0; --i) + { + + grub_strcpy (fd->path, file->device->net->dhcp_file); + p = grub_strrchr (fd->path, '/'); + if (p) + { + p++; + } + else + { + p = fd->path; + } + if (!j) { +#ifdef __x86_64__ + *(p++)='x'; + *(p++)='8'; + *(p++)='6'; + *(p++)='_'; + *(p++)='6'; + *(p++)='4'; +#else + *(p++)='i'; + *(p++)='a'; + *(p++)='3'; + *(p++)='2'; +#endif + *(p++)='/'; + } + grub_strcpy (p, ipx); + p += i; + *(p++) = '/'; + grub_strcpy (p, name); + + grub_dprintf ("tftp","tftp_open_called for %s\n", fd->path); + + ret = tftp (fd); + if (ret >= 0) { + file->size = fd->length; + file->offset = 0; + + return GRUB_ERR_NONE; + } + } + } + + fail: + + if (fd && fd->buf) + grub_free (fd->buf); + + if (fd) + grub_free (fd); + grub_error (GRUB_ERR_FILE_NOT_FOUND, "file not found"); + + grub_dl_unref (my_mod); + + return grub_errno; +} + +static grub_ssize_t +grub_tftp_read (grub_file_t file, char *buf, grub_size_t len) +{ + tftp_file_data_t *fd = (tftp_file_data_t *) file->data; + + if (len > file->size - file->offset) + len = file->size - file->offset; + + if (len <= 0) + return 0; + + grub_memcpy (buf, &fd->buf[file->offset], len); + + return len; +} + + +static grub_err_t +grub_tftp_close (grub_file_t file) +{ + tftp_file_data_t *fd = (tftp_file_data_t *) file->data; + + if (fd && fd->buf) + grub_free (fd->buf); + if (fd) + grub_free (fd); + + grub_dl_unref (my_mod); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_tftp_dir (grub_device_t device, const char *path, + int (*hook) (const char *filename, + const struct grub_dirhook_info *info)) +{ + (void) device; + (void) path; + (void) hook; + + grub_error (GRUB_ERR_BAD_FILE_TYPE, "not a directory"); + return grub_errno; +} + +static struct grub_fs grub_fs_tftp = { + .name = "tftp", + .dir = grub_tftp_dir, + .open = grub_tftp_open, + .read = grub_tftp_read, + .close = grub_tftp_close, + .next = 0 +}; + +GRUB_MOD_INIT(tftp) +{ + grub_fs_register (&grub_fs_tftp); + my_mod = mod; +} + +GRUB_MOD_FINI(tftp) +{ + grub_fs_unregister (&grub_fs_tftp); +} diff --git a/grub-core/kern/device.c b/grub-core/kern/device.c index 3db14f5..2f02b83 100644 --- a/grub-core/kern/device.c +++ b/grub-core/kern/device.c @@ -30,6 +30,7 @@ grub_device_t grub_device_open (const char *name) { grub_disk_t disk = 0; + grub_net_t net = 0; grub_device_t dev = 0; if (! name) @@ -48,15 +49,22 @@ grub_device_open (const char *name) /* Try to open a disk. */ disk = grub_disk_open (name); - if (! disk) + if (! disk) { + grub_errno = GRUB_ERR_NONE; + net = grub_net_open(name); + } + + if (!disk && !net) goto fail; dev->disk = disk; - dev->net = 0; /* FIXME */ + dev->net = net; return dev; fail: + if (net) + grub_net_close (net); if (disk) grub_disk_close (disk); @@ -68,6 +76,8 @@ grub_device_open (const char *name) grub_err_t grub_device_close (grub_device_t device) { + if (device->net) + grub_net_close (device->net); if (device->disk) grub_disk_close (device->disk); diff --git a/grub-core/kern/net.c b/grub-core/kern/net.c new file mode 100644 index 0000000..b0b6b5d --- /dev/null +++ b/grub-core/kern/net.c @@ -0,0 +1,1422 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2004,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + + /*PORTS*/ +#define DHCPS_PORT 67 +#define DHCPC_PORT 68 +static int net_blocked = 0; + +static grub_net_dev_t grub_net_dev_list; +static grub_net_t grub_net_list; + +static void net_dispatch (grub_net_t); + +grub_udp_socket_t *udp_listeners = NULL; + +#if 0 +#undef grub_dprintf +#define grub_dprintf(a,b...) grub_printf(b) +#endif + +/*BYTE SEX*/ +static inline grub_uint16_t +net_maybeswap16 (grub_uint16_t i) +{ + grub_uint16_t ret; + + *((grub_uint8_t *) & ret) = i >> 8; + *(((grub_uint8_t *) & ret) + 1) = i & 0xff; + + return ret; +} + +grub_uint16_t +grub_net_htons (grub_uint16_t i) +{ + return net_maybeswap16 (i); +} + +grub_uint16_t +grub_net_ntohs (grub_uint16_t i) +{ + return net_maybeswap16 (i); +} + +static inline grub_uint32_t +net_maybeswap32 (grub_uint32_t i) +{ + grub_uint32_t ret; + + *((grub_uint8_t *) & ret) = i >> 24; + *(((grub_uint8_t *) & ret) + 1) = (i >> 16) & 0xff; + *(((grub_uint8_t *) & ret) + 2) = (i >> 8) & 0xff; + *(((grub_uint8_t *) & ret) + 3) = i & 0xff; + + return ret; +} + +grub_uint32_t +grub_net_htonl (grub_uint32_t i) +{ + return net_maybeswap32 (i); +} + +grub_uint32_t +grub_net_ntohl (grub_uint32_t i) +{ + return net_maybeswap32 (i); +} + +typedef struct __attribute__ ((packed)) +{ + grub_ether_addr dst; + grub_ether_addr src; + grub_uint16_t type; + grub_uint8_t data[0]; +} ether_hdr; + +#define ETH_P_IP 0x0800 +#define ETH_P_ARP 0x0806 + +static grub_ether_addr ether_broadcast = + { {0xff, 0xff, 0xff, 0xff, 0xff, 0xff} }; + + + + +static grub_err_t +ether_send (grub_net_t net, grub_uint16_t type, grub_uint8_t * buf, + int len, grub_ether_addr * dst) +{ + grub_uint8_t frame[GRUB_ETH_FRAME_LEN]; + ether_hdr *h; + + h = (ether_hdr *) frame; + + if (len > GRUB_ETH_DATA_LEN) + len = GRUB_ETH_DATA_LEN; + + grub_memcpy (&h->src, &net->my_eth_addr, GRUB_ETH_ALEN); + grub_memcpy (&h->dst, dst, GRUB_ETH_ALEN); + + h->type = grub_net_htons (type); + grub_memcpy (h->data, buf, len); + + return net->dev->send (net, frame, len + GRUB_ETH_HLEN); +} + +static void ip_new_frame (grub_net_t, grub_uint8_t *, int, grub_ether_addr *); +static void arp_new_frame (grub_net_t, grub_uint8_t *, int, + grub_ether_addr *); + +static void +ether_new_frame (grub_net_t net, grub_uint8_t * frame, int len) +{ + ether_hdr *h; + int type; + + if (len < GRUB_ETH_HLEN) + return; + if (len > GRUB_ETH_FRAME_LEN) + return; + + h = (ether_hdr *) frame; + + if (grub_memcmp (&h->dst, &net->my_eth_addr, GRUB_ETH_ALEN) && + grub_memcmp (&h->dst, ðer_broadcast, GRUB_ETH_ALEN)) + return; + + type = grub_net_ntohs (h->type); + + frame += GRUB_ETH_HLEN; + len -= GRUB_ETH_HLEN; + + + switch (type) + { + case ETH_P_IP: + ip_new_frame (net, frame, len, &h->src); + break; + case ETH_P_ARP: + arp_new_frame (net, frame, len, &h->src); + break; + } +} + + /* ARP */ +#define ARPHRD_ETHER 1 +#define ARPOP_REQUEST 1 +#define ARPOP_REPLY 2 +#define ARP_TABLE_SIZE 16 +typedef struct __attribute__ ((packed)) +{ + grub_uint16_t hwaddr_type; + grub_uint16_t paddr_type; + grub_uint8_t hwaddr_len; + grub_uint8_t paddr_len; + grub_uint16_t op; + grub_uint8_t data[0]; +} arp_hdr; + +typedef struct __attribute__ ((packed)) +{ + arp_hdr h; + grub_ether_addr hw_sender; + grub_ipv4_addr p_sender; + grub_ether_addr hw_target; + grub_ipv4_addr p_target; +} arp_hdr_eth_ipv4; + +typedef enum +{ + ARP_STATE_EMPTY = 0, + ARP_STATE_UNRESOLVED, + ARP_STATE_RESOLVED +} arp_state; + + +typedef struct +{ + arp_state state; + grub_ipv4_addr ip; + grub_ether_addr mac; +} arp_entry; + + +static arp_entry arp_table[ARP_TABLE_SIZE]; +static int arp_next_entry; + + +static void +arp_add_hw (grub_ipv4_addr * ip, grub_ether_addr * mac) +{ + int i; + + for (i = 0; i < ARP_TABLE_SIZE; ++i) + { + if (arp_table[i].state == ARP_STATE_EMPTY) + return; + if (!grub_memcmp (&arp_table[i].ip, ip, GRUB_IPV4_ALEN)) + { + grub_memcpy (&arp_table[i].mac, mac, GRUB_ETH_ALEN); + arp_table[i].state = ARP_STATE_RESOLVED; + return; + } + } +} + +static int +arp_add_ip (grub_ipv4_addr * ip) +{ + int i; + for (i = 0; i < ARP_TABLE_SIZE; ++i) + { + if (arp_table[i].state == ARP_STATE_EMPTY) + break; + if (!grub_memcmp (&arp_table[i].ip, ip, GRUB_IPV4_ALEN)) + return i; + } + + i = arp_next_entry; + arp_table[i].state = ARP_STATE_UNRESOLVED; + grub_memcpy (&arp_table[i].ip, ip, GRUB_IPV4_ALEN); + + arp_next_entry++; + if (arp_next_entry == ARP_TABLE_SIZE) + arp_next_entry = 0; + + return i; +} + +static void +arp_send_reply (grub_net_t net, grub_ipv4_addr * i, grub_ether_addr * e) +{ + + arp_hdr_eth_ipv4 ih; + + ih.h.hwaddr_type = grub_net_htons (ARPHRD_ETHER); + ih.h.paddr_type = grub_net_htons (ETH_P_IP); + ih.h.hwaddr_len = GRUB_ETH_ALEN; + ih.h.paddr_len = GRUB_IPV4_ALEN; + ih.h.op = grub_net_htons (ARPOP_REPLY); + + grub_memcpy (&ih.hw_sender, &net->my_eth_addr, GRUB_ETH_ALEN); + grub_memcpy (&ih.p_sender, &net->my_addr, GRUB_IPV4_ALEN); + grub_memcpy (&ih.hw_target, e, GRUB_ETH_ALEN); + grub_memcpy (&ih.p_target, i, GRUB_IPV4_ALEN); + + ether_send (net, ETH_P_ARP, (grub_uint8_t *) & ih, sizeof (ih), + ðer_broadcast); + +} + +static void +arp_send_request (grub_net_t net, grub_ipv4_addr * i) +{ + arp_hdr_eth_ipv4 ih; + + arp_add_ip (i); + + ih.h.hwaddr_type = grub_net_htons (ARPHRD_ETHER); + ih.h.paddr_type = grub_net_htons (ETH_P_IP); + ih.h.hwaddr_len = GRUB_ETH_ALEN; + ih.h.paddr_len = GRUB_IPV4_ALEN; + ih.h.op = grub_net_htons (ARPOP_REQUEST); + + grub_memcpy (&ih.hw_sender, &net->my_eth_addr, GRUB_ETH_ALEN); + grub_memcpy (&ih.p_sender, &net->my_addr, GRUB_IPV4_ALEN); + grub_memcpy (&ih.hw_target, ðer_broadcast, GRUB_ETH_ALEN); + grub_memcpy (&ih.p_target, i, GRUB_IPV4_ALEN); + + ether_send (net, ETH_P_ARP, (grub_uint8_t *) & ih, sizeof (ih), + ðer_broadcast); +} + +#if 0 /*-Wunused*/ +static void +arp_flush (void) +{ + int i; + for (i = 0; i < ARP_TABLE_SIZE; ++i) + { + arp_table[i].state = ARP_STATE_EMPTY; + } +} +#endif + +static void +arp_new_frame (grub_net_t net, grub_uint8_t * arp, int len, + grub_ether_addr * src) +{ + arp_hdr *h; + arp_hdr_eth_ipv4 *ih; + + (void) src; + + if (len < (int) sizeof (arp_hdr)) + return; + + h = (arp_hdr *) arp; + + if (grub_net_ntohs (h->hwaddr_type) != ARPHRD_ETHER) + return; + if (grub_net_ntohs (h->paddr_type) != ETH_P_IP) + return; + + if (h->hwaddr_len != GRUB_ETH_ALEN) + return; + if (h->paddr_len != GRUB_IPV4_ALEN) + return; + + ih = (arp_hdr_eth_ipv4 *) arp; + + arp_add_hw (&ih->p_sender, &ih->hw_sender); + + switch (grub_net_ntohs (h->op)) + { + case ARPOP_REQUEST: + if (grub_memcmp (&ih->p_target, &net->my_addr, GRUB_IPV4_ALEN)) + return; + arp_send_reply (net, &ih->p_sender, &ih->hw_sender); + break; + case ARPOP_REPLY: + arp_add_hw (&ih->p_target, &ih->hw_target); + break; + } +} + + +static grub_ether_addr * +arp_get_mac (grub_net_t net, grub_ipv4_addr * ip) +{ + int tries = 5; + int i; + grub_uint64_t t; + + i = arp_add_ip (ip); + + do + { + if (arp_table[i].state == ARP_STATE_RESOLVED) + { + return &arp_table[i].mac; + } + + t = grub_get_time_ms () + 1000; + + arp_send_request (net, ip); + + while ((arp_table[i].state == ARP_STATE_UNRESOLVED) && + (grub_get_time_ms () < t)) + { + net_dispatch (net); + } + } + while (tries--); + + return NULL; +} + + + + + /* IP */ +#define IPV4_ALEN 4 +#define IP_PROTO_IP 0x0 +#define IP_PROTO_ICMP 0x1 +#define IP_PROTO_TCP 0x6 +#define IP_PROTO_UDP 0x11 +typedef struct __attribute__ ((packed)) +{ + grub_uint8_t ihl:4, version:4; + grub_uint8_t tos; + grub_uint16_t total_len; + grub_uint16_t id; + grub_uint16_t frag_off; + grub_uint8_t ttl; + grub_uint8_t protocol; + grub_uint16_t header_sum; + grub_ipv4_addr src; + grub_ipv4_addr dst; + grub_uint8_t options[0]; +} ipv4_header; + + +#define IP_VERSION 4 +#define IP_DEFAULT_TTL 64 +#define IP_DEFAULT_TOS 0 + + +static grub_ipv4_addr ip_broadcast = { {0xff, 0xff, 0xff, 0xff} }; + +static int +ip_is_broadcast (grub_ipv4_addr * a) +{ + return !grub_memcmp (a, &ip_broadcast, GRUB_IPV4_ALEN); +} + + +static int +ip_is_local (grub_net_t net, grub_ipv4_addr * a) +{ + int i; + + for (i = 0; i < 4; ++i) + { + if ((a->s_addr[i] & net->my_mask.s_addr[i]) != + (net->my_addr.s_addr[i] & net->my_mask.s_addr[i])) + return 0; + } + + return 1; +} + +static grub_uint16_t +ip_checksum (grub_uint8_t * b, int len) +{ + grub_uint32_t ret = 0; + + while (len > 1) + { + ret += *(grub_uint16_t *) b; + b += 2; + if (ret & 0x80000000) + ret = (ret & 0xFFFF) + (ret >> 16); + len -= 2; + } + + if (len) + ret += (grub_uint16_t) * b; + + while (ret >> 16) + ret = (ret & 0xFFFF) + (ret >> 16); + + + return ~ret; +} +static void udp_new_frame (grub_net_t, grub_uint8_t *, int, grub_ipv4_addr *); +static void icmp_new_frame (grub_net_t, grub_uint8_t *, int, + grub_ipv4_addr *); + + + +static void +ip_new_frame (grub_net_t net, grub_uint8_t * ip, int len, + grub_ether_addr * src) +{ + ipv4_header *h = (ipv4_header *) ip; + + (void) src; + + int plen; + grub_uint8_t *payload; + + if (len < (int) sizeof (ipv4_header)) + return; + + + if (h->version != IP_VERSION) + return; + + if (len < 4 * h->ihl) + return; + + + if (grub_memcmp (&h->dst, &net->my_addr, GRUB_IPV4_ALEN) + && !ip_is_broadcast (&h->dst)) + return; + + if (ip_checksum (ip, 4 * h->ihl)) + return; + + /* FIXME: we don't do fragments */ + if (grub_net_ntohs (h->frag_off) & 0x3fff) + return; + + plen = grub_net_ntohs (h->total_len); + if (plen > len) + return; + + payload = ip; + + plen -= 4 * h->ihl; + payload += 4 * h->ihl; + + switch (h->protocol) + { + case IP_PROTO_ICMP: + icmp_new_frame (net, payload, plen, &h->src); + break; + case IP_PROTO_UDP: + udp_new_frame (net, payload, plen, &h->src); + break; + } + +} + + + + +static int +ip_send (grub_net_t net, grub_uint8_t protocol, grub_uint8_t * data, + int dlen, grub_ipv4_addr * dst) +{ + grub_uint8_t packet[GRUB_IP_PACKET_LEN]; + int len; + + ipv4_header *h = (ipv4_header *) packet; + grub_ether_addr *edst; + + if (ip_is_broadcast (dst)) + { + edst = ðer_broadcast; + } + else if (ip_is_local (net, dst)) + { + edst = arp_get_mac (net, dst); + } + else + { + edst = arp_get_mac (net, &net->my_router); + } + + if (!edst) + return -1; + + + if (dlen > GRUB_IP_DATA_LEN) + dlen = GRUB_IP_DATA_LEN; + + len = dlen + sizeof (ipv4_header); + + h->version = IP_VERSION; + h->ihl = sizeof (ipv4_header) / 4; + h->tos = IP_DEFAULT_TOS; + h->total_len = grub_net_htons (len); + h->id = 0; + h->frag_off = grub_net_htons (0x4000); + h->ttl = IP_DEFAULT_TTL; + h->protocol = protocol; + h->header_sum = 0; + grub_memcpy (&h->src, &net->my_addr, GRUB_IPV4_ALEN); + grub_memcpy (&h->dst, dst, GRUB_IPV4_ALEN); + + h->header_sum = ip_checksum (packet, sizeof (ipv4_header)); + + + grub_memcpy (packet + sizeof (ipv4_header), data, dlen); + + return ether_send (net, ETH_P_IP, packet, len, edst); +} + +/* ICMP */ + +#define ICMP_TYPE_ECHO 8 +#define ICMP_TYPE_ECHO_REPLY 0 + + +typedef struct __attribute__ ((packed)) +{ + grub_uint8_t type; + grub_uint8_t code; + grub_uint16_t checksum; + grub_uint16_t id; + grub_uint16_t seq; + grub_uint8_t data[0]; +} icmp_echo; + + + +static void +icmp_new_frame (grub_net_t net, grub_uint8_t * icmp, int len, + grub_ipv4_addr * src) +{ + icmp_echo *e, *r; + grub_uint8_t reply[GRUB_IP_DATA_LEN]; + + if (len < (int) sizeof (icmp_echo)) + return; + + e = (icmp_echo *) icmp; + + if (e->type != ICMP_TYPE_ECHO) + return; + + grub_memcpy (reply, icmp, len); + + r = (icmp_echo *) reply; + r->type = ICMP_TYPE_ECHO_REPLY; + + ip_send (net, IP_PROTO_ICMP, reply, len, src); +} + + + +/* UDP */ + + + +typedef struct __attribute__ ((packed)) +{ + grub_uint16_t src_port; + grub_uint16_t dst_port; + grub_uint16_t length; + grub_uint16_t checksum; + grub_uint8_t data[0]; +} udp_hdr; + +static void dhcp_new_frame (grub_net_t, grub_uint8_t *, + int, grub_ipv4_addr *, grub_uint16_t); + + +static void +udp_new_frame (grub_net_t net, grub_uint8_t * udp, int len, + grub_ipv4_addr * src) +{ + grub_udp_socket_t *s; + udp_hdr *h; + grub_uint8_t *payload; + int plen; + int port; + + + if (len < (int) sizeof (udp_hdr)) + return; + + h = (udp_hdr *) udp; + + plen = len - sizeof (udp_hdr); + payload = udp + sizeof (udp_hdr); + + port = grub_net_ntohs (h->dst_port); + + + switch (port) + { + case DHCPC_PORT: + dhcp_new_frame (net, payload, plen, src, grub_net_ntohs (h->src_port)); + break; + default: + + for (s = udp_listeners; s; s = s->next) + { + if (port == s->port) + { + s->new_frame (s, net, payload, plen, src, + grub_net_ntohs (h->src_port)); + } + } + } + +} + +int +grub_udp_send (grub_net_t net, grub_uint16_t sport, grub_uint8_t * data, + int dlen, grub_ipv4_addr * dst, grub_uint16_t dport) +{ + grub_uint8_t packet[GRUB_UDP_PACKET_LEN]; + udp_hdr *h = (udp_hdr *) packet; + int len; + + len = dlen + sizeof (udp_hdr); + + h->src_port = grub_net_htons (sport); + h->dst_port = grub_net_htons (dport); + h->length = grub_net_htons (len); + h->checksum = 0; + + grub_memcpy (&h->data, data, dlen); + + return ip_send (net, IP_PROTO_UDP, packet, len, dst); + +} + + +/* DHCP */ + +#define DHCP_MAX_MESSAGE_LEN ((GRUB_UDP_DATA_LEN)-256) +#define DHCP_DEFAULT_MESSAGE_LEN 548 +#define DHCP_MAGIC_COOKIE_LEN 4 + +#define DHCP_OP_BOOTREQUEST 1 +#define DHCP_OP_BOOTREPLY 2 + +#define DHCP_FLAGS_BROADCAST 0x8000 + +#define DHCP_MSG_TYPE_DISCOVER 1 +#define DHCP_MSG_TYPE_OFFER 2 +#define DHCP_MSG_TYPE_REQUEST 3 +#define DHCP_MSG_TYPE_DECLINE 4 +#define DHCP_MSG_TYPE_ACK 5 +#define DHCP_MSG_TYPE_NAK 6 +#define DHCP_MSG_TYPE_RELEASE 7 +#define DHCP_MSG_TYPE_INFORM 8 + + +#define DHCP_OPTION_SUBNET 1 +#define DHCP_OPTION_ROUTER 3 +#define DHCP_OPTION_BROADCAST 28 +#define DHCP_OPTION_REQUESTED_IP_ADDR 50 +#define DHCP_OPTION_TAG_DHCP 53 +#define DHCP_OPTION_SERVER_ID 54 +#define DHCP_OPTION_TAG_PARAMETER_REQUEST 55 +#define DHCP_OPTION_DHCP_MAX_MESSAGE_LEN 57 +#define DHCP_OPTION_TFTP_SERVER_NAME 66 +#define DHCP_OPTION_BOOTFILE_NAME 67 +#define DHCP_OPTION_END 255 + + +typedef struct PACKED +{ + grub_uint8_t op; + grub_uint8_t htype; + grub_uint8_t hlen; + grub_uint8_t hops; + grub_uint32_t xid; + grub_uint16_t secs; + grub_uint16_t flags; + grub_ipv4_addr ciaddr; + grub_ipv4_addr yiaddr; + grub_ipv4_addr siaddr; + grub_ipv4_addr giaddr; + grub_uint8_t chaddr[16]; + grub_uint8_t sname[64]; + grub_uint8_t file[128]; + grub_uint8_t options[0]; +} dhcp_packet; + + + +#define DHCP_TRIES 5 +#define DHCP_WAIT_FOR_OFFERS 5000 +#define DHCP_WAIT_FOR_ACK 5000 + + +static grub_uint8_t dhcp_magic_cookie[DHCP_MAGIC_COOKIE_LEN] = + { 99, 130, 83, 99 }; + + +static grub_uint32_t dhcp_xid = 0xdeadbeef; + + + + +static grub_uint8_t * +dhcp_find_option (dhcp_packet * p, int len, int option) +{ + grub_uint8_t *optr = p->options; + int ol; + + optr += DHCP_MAGIC_COOKIE_LEN; + len -= sizeof (dhcp_packet) + DHCP_MAGIC_COOKIE_LEN; + + while (len > 0) + { + if (*optr == option) + return optr; + if (*optr == DHCP_OPTION_END) + return NULL; + + optr++; + ol = *(optr++); + len -= 2; + optr += ol; + len -= ol; + } + + return NULL; +} + +static grub_uint8_t * +dhcp_copy_option (grub_uint8_t * out, dhcp_packet * p, int len, int option) +{ + grub_uint8_t *in; + int n; + + in = dhcp_find_option (p, len, option); + if (!in) + return out; + + *(out++) = *(in++); + n = *(out++) = *(in++); + while (n--) + { + *(out++) = *(in++); + } + return out; +} + +static void +dhcp_find_ip_option (dhcp_packet * p, int len, int option, grub_ipv4_addr * a) +{ + grub_uint8_t *opt; + + opt = dhcp_find_option (p, len, option); + if (!opt) + return; + + opt++; + if (*opt != GRUB_IPV4_ALEN) + return; + + opt++; + + grub_memcpy (a, opt, GRUB_IPV4_ALEN); +} + +static grub_uint8_t * +dhcp_add_generic_options (grub_uint8_t * optr, int dhcp_type) +{ + grub_uint8_t *lptr; + grub_memcpy (optr, &dhcp_magic_cookie, DHCP_MAGIC_COOKIE_LEN); + optr += DHCP_MAGIC_COOKIE_LEN; + + *(optr++) = DHCP_OPTION_TAG_DHCP; + *(optr++) = 1; + *(optr++) = dhcp_type; + *(optr++) = DHCP_OPTION_TAG_PARAMETER_REQUEST; + lptr = optr++; + *(optr++) = DHCP_OPTION_SUBNET; + *(optr++) = DHCP_OPTION_ROUTER; + *(optr++) = DHCP_OPTION_BROADCAST; + *(optr++) = DHCP_OPTION_REQUESTED_IP_ADDR; + *(optr++) = DHCP_OPTION_SERVER_ID; + *(optr++) = DHCP_OPTION_TFTP_SERVER_NAME; + *(optr++) = DHCP_OPTION_BOOTFILE_NAME; + *lptr = (grub_uint8_t) (optr - lptr) - 1; + *(optr++) = DHCP_OPTION_DHCP_MAX_MESSAGE_LEN; + *(optr++) = 2; + *(grub_uint16_t *) optr = grub_net_htons (DHCP_MAX_MESSAGE_LEN); + optr += 2; + + return optr; +} + + +static void +dhcp_acknak (grub_net_t net, dhcp_packet * an, int len) +{ + grub_uint8_t *opt; + + opt = dhcp_find_option (an, len, DHCP_OPTION_TAG_DHCP); + if (!opt) + return; + + switch (*(opt + 2)) + { + case DHCP_MSG_TYPE_NAK: + net->dhcp_state = GRUB_DHCP_STATE_OFFERS; + return; + case DHCP_MSG_TYPE_ACK: + break; + default: + return; + } + +/* May the lord be praised we finally have an ACK */ +/* Set all the rubbish */ + + grub_memcpy (net->dhcp_file, &an->file, sizeof (net->dhcp_file)); + grub_memcpy (&net->tftp_addr, &an->siaddr, GRUB_IPV4_ALEN); + grub_memcpy (&net->my_addr, &an->yiaddr, GRUB_IPV4_ALEN); + + dhcp_find_ip_option (an, len, DHCP_OPTION_SUBNET, &net->my_mask); + dhcp_find_ip_option (an, len, DHCP_OPTION_ROUTER, &net->my_router); + dhcp_find_ip_option (an, len, DHCP_OPTION_SERVER_ID, &net->dhcp_addr); + + net->dhcp_state = GRUB_DHCP_STATE_BOUND; + +} + + + +static void +dhcp_offer (grub_net_t net, dhcp_packet * off, int len) +{ + grub_uint8_t packet[DHCP_DEFAULT_MESSAGE_LEN]; + dhcp_packet *req = (dhcp_packet *) packet; + grub_uint8_t *optr; + + grub_memset (packet, 0, DHCP_DEFAULT_MESSAGE_LEN); + + req->op = DHCP_OP_BOOTREQUEST; + req->htype = ARPHRD_ETHER; + req->hlen = GRUB_ETH_ALEN; + req->xid = dhcp_xid; + + req->flags = grub_net_htons (DHCP_FLAGS_BROADCAST); + + grub_memcpy (req->chaddr, &net->my_eth_addr, GRUB_ETH_ALEN); + + optr = dhcp_add_generic_options (req->options, DHCP_MSG_TYPE_REQUEST); + + optr = dhcp_copy_option (optr, off, len, DHCP_OPTION_SERVER_ID); + + *(optr++) = DHCP_OPTION_REQUESTED_IP_ADDR; + *(optr++) = GRUB_IPV4_ALEN; + grub_memcpy (optr, &off->yiaddr, GRUB_IPV4_ALEN); + optr += 4; + + *(optr++) = DHCP_OPTION_END; + + net->dhcp_state = GRUB_DHCP_STATE_ACKNAK; + + grub_udp_send (net, DHCPC_PORT, packet, len, &ip_broadcast, DHCPS_PORT); +} + + + +static int +dhcp_discover (grub_net_t net) +{ + grub_uint8_t packet[DHCP_DEFAULT_MESSAGE_LEN]; + grub_uint8_t *optr; + dhcp_packet *dis = (dhcp_packet *) packet; + + grub_memset (packet, 0, DHCP_DEFAULT_MESSAGE_LEN); + + + dis->op = DHCP_OP_BOOTREQUEST; + dis->htype = ARPHRD_ETHER; + dis->hlen = GRUB_ETH_ALEN; + dis->xid = dhcp_xid; + + dis->flags = grub_net_htons (DHCP_FLAGS_BROADCAST); + + grub_memcpy (dis->chaddr, &net->my_eth_addr, GRUB_ETH_ALEN); + + optr = dhcp_add_generic_options (dis->options, DHCP_MSG_TYPE_DISCOVER); + *(optr++) = DHCP_OPTION_END; + + + return grub_udp_send (net, DHCPC_PORT, packet, sizeof (packet), + &ip_broadcast, DHCPS_PORT); +} + + +static void +dhcp_new_frame (grub_net_t net, grub_uint8_t * data, + int len, grub_ipv4_addr * src, grub_uint16_t sport) +{ + dhcp_packet *d; + + (void) src; + + if (sport != DHCPS_PORT) + return; + if (len > DHCP_MAX_MESSAGE_LEN) + return; + + if (len < (int) (sizeof (dhcp_packet) + 4)) + return; + + d = (dhcp_packet *) data; + + if (d->op != DHCP_OP_BOOTREPLY) + return; + if (d->htype != ARPHRD_ETHER) + return; + if (d->hlen != GRUB_ETH_ALEN) + return; + if (d->xid != dhcp_xid) + return; + if (grub_memcmp (d->chaddr, &net->my_eth_addr, GRUB_ETH_ALEN)) + return; + if (grub_memcmp (d->options, &dhcp_magic_cookie, DHCP_MAGIC_COOKIE_LEN)) + return; + + switch (net->dhcp_state) + { + case GRUB_DHCP_STATE_OFFERS: + + if (len > DHCP_MAX_MESSAGE_LEN) + return; + dhcp_offer (net, d, len); + return; + + case GRUB_DHCP_STATE_ACKNAK: + dhcp_acknak (net, d, len); + return; + } +} + + + + +/* slight wizzardry, fails go to STATE_OFFERS so it times out and */ +/* decrements the try counter before returning to tu_dhcp_state discover */ +/* this gives a back off and makes the logic simpler */ + + +static int +dhcp (grub_net_t net) +{ + grub_uint64_t c; + int tries = DHCP_TRIES; + + while (net->dhcp_state != GRUB_DHCP_STATE_BOUND) + { + c = grub_get_time_ms (); + + switch (net->dhcp_state) + { + case GRUB_DHCP_STATE_DISCOVER: + net->dhcp_state = GRUB_DHCP_STATE_OFFERS; + dhcp_discover (net); + break; + case GRUB_DHCP_STATE_OFFERS: + while (((c + DHCP_WAIT_FOR_OFFERS) > grub_get_time_ms ()) + && (net->dhcp_state == GRUB_DHCP_STATE_OFFERS)) + net_dispatch (net); + if (net->dhcp_state == GRUB_DHCP_STATE_OFFERS) + { + if (!(tries--)) + { + net->dhcp_state = GRUB_DHCP_STATE_GAVE_UP; + return -1; + } + else + { + net->dhcp_state = GRUB_DHCP_STATE_DISCOVER; + } + } + break; + case GRUB_DHCP_STATE_REQUEST: + break; + case GRUB_DHCP_STATE_ACKNAK: + while (((c + DHCP_WAIT_FOR_ACK) > grub_get_time_ms ()) + && (net->dhcp_state == GRUB_DHCP_STATE_ACKNAK)) + net_dispatch (net); + + if (net->dhcp_state == GRUB_DHCP_STATE_ACKNAK) + { + net->dhcp_state = GRUB_DHCP_STATE_OFFERS; /* To decrement tries */ + } + break; + case GRUB_DHCP_STATE_BOUND: + return 0; + case GRUB_DHCP_STATE_GAVE_UP: + return -1; + default: + net->dhcp_state = GRUB_DHCP_STATE_DISCOVER; + } + } + return 0; +} + + +/* Network */ + +static void +net_dispatch (grub_net_t net) +{ + grub_uint8_t frame[GRUB_ETH_FRAME_LEN]; + int len; + + len = sizeof (frame); + while (net->dev->recv (net, frame, &len) == GRUB_ERR_NONE) + { + if (!len) break; + ether_new_frame (net, frame, len); + len = sizeof (frame); + } + +} + +static int +net_init (grub_net_t net) +{ + if (net_blocked) + return 0; + + if ((!net->is_running) && (net->dev->start (net))) + return -1; + + if (net->is_up) + return 0; + + if (net->has_failed) + return -1; + + if (!net->is_up) + { + if (dhcp (net)) + { + net->has_failed++; + return -1; + } + net->is_up++; + } + + return 0; +} + + +void +grub_net_dev_register (grub_net_dev_t dev) +{ + dev->next = grub_net_dev_list; + grub_net_dev_list = dev; +#if 1 + grub_net_syslog ("grub says: hello world"); +#endif +} + +void +grub_net_dev_unregister (grub_net_dev_t dev) +{ + grub_net_dev_t *p, q; + + /* FIXME: XXX: we need to tear down active interfaces if their */ + /* drivers are being de-registered */ + + for (p = &grub_net_dev_list, q = *p; q; p = &(q->next), q = q->next) + if (q == dev) + { + *p = q->next; + break; + } +} + +static grub_fs_t find_tftp_fs(void) +{ + grub_fs_t p; + + for (p = grub_fs_list; p; p = p->next) + { + if (!grub_strcmp(p->name,"tftp")) + return p; + } + + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't find tftp filesystem to bind to net device"); + + return NULL; +} + +grub_net_t +grub_net_open (const char *name) +{ + grub_net_t net; + grub_net_dev_t dev; + grub_fs_t fs=find_tftp_fs(); + + if (!fs) + return 0; + + if (net_blocked) + return NULL; + + if (name && !grub_strcmp (name, "net")) + { + name = NULL; + } + + /* See if we already have this device open */ + for (net = grub_net_list; net; net = net->next) + { + if (!name || !grub_strcmp (net->name, name)) + { + net_init (net); + return net->has_failed ? NULL:net; + } + } + + for (dev = grub_net_dev_list; dev; dev = dev->next) + { + int ret; + grub_errno = GRUB_ERR_NONE; + + + net = (grub_net_t) grub_malloc (sizeof (*net)); + if (!net) + return NULL; + + + net->name = NULL; + net->dhcp_state = GRUB_DHCP_STATE_DISCOVER; + net->is_running = 0; + net->is_up = 0; + net->has_failed = 0; + net->fs = fs; + grub_memcpy (&net->my_addr, &ip_broadcast, GRUB_IPV4_ALEN); + + /*Try opening the name with this device sets net->name */ + ret=dev->open (name, net); + +#if 0 + grub_dprintf ("net", "opening `%s' gave %d.\n", name, ret); +#endif + + if (ret!=GRUB_ERR_NONE) { /*There was a problem*/ + + /*Fail - free*/ + if (net->name) + grub_free ((char *) net->name); + + grub_free(net); + net=NULL; + if (grub_errno == GRUB_ERR_UNKNOWN_DEVICE) { + /*name made no sense - try next driver*/ + grub_errno=GRUB_ERR_NONE; + continue; + } else { + /*There was a problem - if we have a name, fail*/ + if (name) + goto fail; + /*we don't so try another device*/ + grub_errno=GRUB_ERR_NONE; + continue; + } + } + + /*The open worked add the opened device to the list*/ + net->dev = dev; + + net->next = grub_net_list; + grub_net_list = net; + + /*Attempt initalization*/ + net_init (net); + + /*Do we have a working network - yes hurrah all done*/ + if (net->is_up) + break; + + /*no - try others*/ + net=NULL; + } + + if (!dev) + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device"); + } + + +fail: + +#if 0 + if (!net) + { + grub_dprintf ("net", "opening `%s' failed.\n", name); + } +#endif + + return net; +} + +static void +net_open_default (void) +{ + if (grub_net_list) + { + grub_net_open (grub_net_list->name); + } + else + { + grub_net_open (NULL); + } +} + + +void +grub_net_close (grub_net_t net) +{ +#if 0 + grub_dprintf ("net", "closing `%s'.\n", net->name); +#else + (void) net; +#endif + + /* we don't actually close the network device as we're bound to want it later */ +} + + +void +grub_net_dispatch (void) +{ + grub_net_t net; + + for (net = grub_net_list; net; net = net->next) + { + if (net->is_running) + net_dispatch (net); + } +} + +void +grub_net_shutdown (void) +{ + grub_net_t net; + + while ((net = grub_net_list)) + { + grub_net_list = net->next; + + if (net->is_running) + { + net->dev->stop (net); + } + net->dev->close (net); + grub_free (net); + } + +} + + + + +/* syslog */ + +#define SYSLOG_HDR_LEN 5 +#define SYSLOG_PORT 514 + +void +grub_net_syslog (const char *msg) +{ + grub_uint8_t buf[GRUB_UDP_DATA_LEN]; + grub_uint8_t *ptr; + int len; + int plen; + + grub_net_t net; + + if (net_blocked) + return; + + grub_error_push (); + + net_open_default (); + + len = sizeof (buf) - 1; + ptr = buf; + plen = 0; + + grub_memcpy (ptr, "<133>", SYSLOG_HDR_LEN); + ptr += SYSLOG_HDR_LEN; + len -= SYSLOG_HDR_LEN; + plen += SYSLOG_HDR_LEN; + + + while ((*msg) && len) + { + *(ptr++) = *(msg++); + len--; + plen++; + } + + *(ptr++) = 0; + plen++; + + for (net = grub_net_list; net; net = net->next) + { + if (net->is_running) + grub_udp_send (net, SYSLOG_PORT, buf, plen, &ip_broadcast, + SYSLOG_PORT); + } + grub_error_pop (); + +} + + +static grub_uint16_t udp_port = 32000; + +grub_err_t +grub_udp_bind (grub_udp_socket_t * socket) +{ + grub_udp_socket_t *p; + + if (!socket->port) + socket->port = udp_port++; + socket->next = 0; + + for (p = udp_listeners; p; p = p->next) + { + if (p->port == socket->port) + return GRUB_ERR_BAD_ARGUMENT; + } + + + socket->next = udp_listeners; + udp_listeners = socket; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_udp_unbind (grub_udp_socket_t * socket) +{ + grub_udp_socket_t **p, *q; + + for (p = &udp_listeners, q = *p; q; p = &(q->next), q = q->next) + if (q == socket) + { + *p = q->next; + return GRUB_ERR_NONE; + } + + return GRUB_ERR_BAD_ARGUMENT; +} + diff --git a/include/grub/net.h b/include/grub/net.h index c6d71d5..e056718 100644 --- a/include/grub/net.h +++ b/include/grub/net.h @@ -23,50 +23,193 @@ #include #include +#define GRUB_ETH_FRAME_LEN 1514 +#define GRUB_ETH_HLEN 14 +#define GRUB_ETH_DATA_LEN (GRUB_ETH_FRAME_LEN - GRUB_ETH_HLEN) +#define GRUB_IP_PACKET_LEN (GRUB_ETH_DATA_LEN) +#define GRUB_IP_DATA_LEN ((GRUB_IP_PACKET_LEN) - 32) +#define GRUB_UDP_PACKET_LEN (GRUB_IP_PACKET_LEN) +#define GRUB_UDP_DATA_LEN ((GRUB_UDP_PACKET_LEN) - 8) + struct grub_net; +#define GRUB_ETH_ALEN 6 + +typedef struct +{ + grub_uint8_t s_addr[GRUB_ETH_ALEN]; +} grub_ether_addr; + +#define GRUB_IPV4_ALEN 4 + +typedef struct +{ + grub_uint8_t s_addr[GRUB_IPV4_ALEN]; +} grub_ipv4_addr; + + +typedef enum +{ + GRUB_DHCP_STATE_DISCOVER, + GRUB_DHCP_STATE_OFFERS, + GRUB_DHCP_STATE_REQUEST, + GRUB_DHCP_STATE_ACKNAK, + GRUB_DHCP_STATE_BOUND, + GRUB_DHCP_STATE_GAVE_UP +} grub_dhcp_state; + +/* The usual form for a network device is that it should + scan for compatible hardware upon module startup and + make an internal list of such devices. When the network + layer calls open it will either be with some user + specificed device name eg (rtl2) in which case the driver + should see if it recognises it (here the rtl driver should + look for the 3rd device it recognises) or NULL it which case + the driver is to look for the first device it recognises. + Open will then fill in the relevant parts of the struct grub_net + that it has been passed. + + start will then initalize and bring up the interface + setting net->is_running + + send and recv will then send and receive packets from the network + note that the recv function may not be called sufficiently often + to prevent an overflow in the inbound buffer of the card and the + driver should cope with this. A call to recv, should flush any + pending packets in the transmit queue before returning + + stop will prevent the network card from accessing host memory + until the next start making it safe for an operating system + boot or some other driver to interact with the card. + + close will free resources allocated in the open + (if net->name is freed, it must be set yo NULL) + + and the module unload function should tear down the list of + devices. NB the network stack MUST always call stop and close + before the the module unloads. + +*/ + + struct grub_net_dev { - /* The device name. */ - const char *name; - - /* FIXME: Just a template. */ - int (*probe) (struct grub_net *net, const void *addr); - void (*reset) (struct grub_net *net); - int (*poll) (struct grub_net *net); - void (*transmit) (struct grub_net *net, const void *destip, - unsigned srcsock, unsigned destsock, const void *packet); - void (*disable) (struct grub_net *net); - - /* The next net device. */ - struct grub_net_dev *next; + const char *name; /* Name of driver */ + + /* Open routine, name is either a specific + device name eg "rtl3" or is NULL to indicate + that the driver should attempt to configure + the first device in the system it supports + will return GRUB_ERR_NONE upon success and + GRUB_ERR_UNKNOWN_DEVICE if no such deivce + is found, the open routine shall not start + any DMA transactions. open shall fill in + net->name */ + grub_err_t (*open) (const char *name, struct grub_net * net); + + + + /* put the network card into a state + where it may receive and transmit. + start shall set net->is_running + on success and net->my_eth_addr + shall be set */ + grub_err_t (*start) (struct grub_net * net); + + /* put the network card into a state + where it shall not attempt to read + or write from host memory. stop shall + clear net->is_running */ + void (*stop) (struct grub_net * net); + + /* release the resources consumed by + open. If close frees net->name, it + MUST set net->name to NULL */ + void (*close) (struct grub_net * net); + + /* transmit one ethernet frame */ + grub_err_t (*send) (struct grub_net * net, const void *buf, int len); + + /* receive one ethernet frame, if no + frame is available recv shall return + GRUB_ERR_TIMEOUT and set *len to zero */ + grub_err_t (*recv) (struct grub_net * net, void *buf, int *len); + + /* The next net driver. */ + struct grub_net_dev *next; }; typedef struct grub_net_dev *grub_net_dev_t; struct grub_fs; +/* We keep a list of open network devices, because doing otherwise */ +/* would require starting and stopping the network for each request */ struct grub_net { - /* The net name. */ - const char *name; + const char *name; /* Name of device, should be set with + grub_malloc-ed storage by the driver's + open routine */ - /* The underlying disk device. */ - grub_net_dev_t dev; + grub_ether_addr my_eth_addr; + grub_ipv4_addr my_addr; + grub_ipv4_addr my_mask; + grub_ipv4_addr my_router; + grub_ipv4_addr dhcp_addr; + grub_ipv4_addr tftp_addr; - /* The binding filesystem. */ - struct grub_fs *fs; + char dhcp_file[128]; - /* FIXME: More data would be required, such as an IP address, a mask, - a gateway, etc. */ + int dhcp_state; - /* Device-specific data. */ - void *data; + int is_running; /*Interface has been started - set + by dev->start */ + int is_up; /*Interface is up - dhcp has + succeded in getting an address */ + int has_failed; /*Interface has failed to come up - + used to prevent endless dhcp timeouts */ + + grub_net_dev_t dev; /* The underlying net device. */ + + struct grub_fs *fs; /* The binding filesystem. */ + + + void *data; /* Device-instance-specific data. */ + + struct grub_net *next; }; typedef struct grub_net *grub_net_t; -/* FIXME: How to abstract networks? More consideration is necessary. */ -/* Note: Networks are very different from disks, because networks must - be initialized before used, and the status is persistent. */ +struct grub_udp_socket +{ + int port; + void (*new_frame) (struct grub_udp_socket * socket, grub_net_t net, + grub_uint8_t * msg, int len, grub_ipv4_addr * src, + grub_uint16_t src_port); + void *data; + struct grub_udp_socket *next; +}; +typedef struct grub_udp_socket grub_udp_socket_t; + +void EXPORT_FUNC (grub_net_dev_register) (grub_net_dev_t dev); +void EXPORT_FUNC (grub_net_dev_unregister) (grub_net_dev_t dev); +grub_net_t EXPORT_FUNC (grub_net_open) (const char *name); +void EXPORT_FUNC (grub_net_close) (grub_net_t net); +void EXPORT_FUNC (grub_net_shutdown) (void); +void EXPORT_FUNC (grub_net_dispatch) (void); +void EXPORT_FUNC (grub_net_syslog) (const char *msg); + +void EXPORT_FUNC (grub_net_dispatch) (void); +int EXPORT_FUNC (grub_udp_send) (grub_net_t net, grub_uint16_t sport, + grub_uint8_t * data, int dlen, + grub_ipv4_addr * dst, grub_uint16_t dport); +grub_err_t EXPORT_FUNC (grub_udp_bind) (grub_udp_socket_t * socket); +grub_err_t EXPORT_FUNC (grub_udp_unbind) (grub_udp_socket_t * socket); + +grub_uint32_t EXPORT_FUNC (grub_net_htonl) (grub_uint32_t); +grub_uint32_t EXPORT_FUNC (grub_net_ntohl) (grub_uint32_t); +grub_uint16_t EXPORT_FUNC (grub_net_htons) (grub_uint16_t); +grub_uint16_t EXPORT_FUNC (grub_net_ntohs) (grub_uint16_t); + #endif /* ! GRUB_NET_HEADER */