diff options
-rw-r--r-- | arch/sandbox/cpu/eth-raw-os.c | 113 | ||||
-rw-r--r-- | arch/sandbox/dts/sandbox.dts | 7 | ||||
-rw-r--r-- | arch/sandbox/include/asm/eth-raw-os.h | 10 | ||||
-rw-r--r-- | board/sandbox/README.sandbox | 22 | ||||
-rw-r--r-- | drivers/net/sandbox-raw.c | 71 |
5 files changed, 218 insertions, 5 deletions
diff --git a/arch/sandbox/cpu/eth-raw-os.c b/arch/sandbox/cpu/eth-raw-os.c index 601205a60b..b76a7319ae 100644 --- a/arch/sandbox/cpu/eth-raw-os.c +++ b/arch/sandbox/cpu/eth-raw-os.c @@ -12,6 +12,8 @@ #include <fcntl.h> #include <net/if.h> #include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/udp.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -20,10 +22,11 @@ #include <sys/socket.h> #include <unistd.h> +#include <arpa/inet.h> #include <linux/if_ether.h> #include <linux/if_packet.h> -int sandbox_eth_raw_os_start(const char *ifname, unsigned char *ethmac, +static int _raw_packet_start(const char *ifname, unsigned char *ethmac, struct eth_sandbox_raw_priv *priv) { struct sockaddr_ll *device; @@ -89,14 +92,114 @@ int sandbox_eth_raw_os_start(const char *ifname, unsigned char *ethmac, return 0; } +static int _local_inet_start(struct eth_sandbox_raw_priv *priv) +{ + struct sockaddr_in *device; + int ret; + int flags; + int one = 1; + + /* Prepare device struct */ + priv->device = malloc(sizeof(struct sockaddr_in)); + if (priv->device == NULL) + return -ENOMEM; + device = priv->device; + memset(device, 0, sizeof(struct sockaddr_in)); + device->sin_family = AF_INET; + device->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + /** + * Open socket + * Since we specify UDP here, any incoming ICMP packets will + * not be received, so things like ping will not work on this + * localhost interface. + */ + priv->sd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + if (priv->sd < 0) { + printf("Failed to open socket: %d %s\n", errno, + strerror(errno)); + return -errno; + } + + /* Make the socket non-blocking */ + flags = fcntl(priv->sd, F_GETFL, 0); + fcntl(priv->sd, F_SETFL, flags | O_NONBLOCK); + + /* Include the UDP/IP headers on send and receive */ + ret = setsockopt(priv->sd, IPPROTO_IP, IP_HDRINCL, &one, + sizeof(one)); + if (ret < 0) { + printf("Failed to set header include option: %d %s\n", errno, + strerror(errno)); + return -errno; + } + priv->local_bind_sd = -1; + priv->local_bind_udp_port = 0; + return 0; +} + +int sandbox_eth_raw_os_start(const char *ifname, unsigned char *ethmac, + struct eth_sandbox_raw_priv *priv) +{ + if (priv->local) + return _local_inet_start(priv); + else + return _raw_packet_start(ifname, ethmac, priv); +} + int sandbox_eth_raw_os_send(void *packet, int length, - const struct eth_sandbox_raw_priv *priv) + struct eth_sandbox_raw_priv *priv) { int retval; + struct udphdr *udph = packet + sizeof(struct iphdr); if (!priv->sd || !priv->device) return -EINVAL; + /* + * This block of code came about when testing tftp on the localhost + * interface. When using the RAW AF_INET API, the network stack is still + * in play responding to incoming traffic based on open "ports". Since + * it is raw (at the IP layer, no Ethernet) the network stack tells the + * TFTP server that the port it responded to is closed. This causes the + * TFTP transfer to be aborted. This block of code inspects the outgoing + * packet as formulated by the u-boot network stack to determine the + * source port (that the TFTP server will send packets back to) and + * opens a typical UDP socket on that port, thus preventing the network + * stack from sending that ICMP message claiming that the port has no + * bound socket. + */ + if (priv->local && (priv->local_bind_sd == -1 || + priv->local_bind_udp_port != udph->source)) { + struct iphdr *iph = packet; + struct sockaddr_in addr; + + if (priv->local_bind_sd != -1) + close(priv->local_bind_sd); + + /* A normal UDP socket is required to bind */ + priv->local_bind_sd = socket(AF_INET, SOCK_DGRAM, 0); + if (priv->local_bind_sd < 0) { + printf("Failed to open bind sd: %d %s\n", errno, + strerror(errno)); + return -errno; + } + priv->local_bind_udp_port = udph->source; + + /** + * Bind the UDP port that we intend to use as our source port + * so that the kernel will not send an ICMP port unreachable + * message to the server + */ + addr.sin_family = AF_INET; + addr.sin_port = udph->source; + addr.sin_addr.s_addr = iph->saddr; + retval = bind(priv->local_bind_sd, &addr, sizeof(addr)); + if (retval < 0) + printf("Failed to bind: %d %s\n", errno, + strerror(errno)); + } + retval = sendto(priv->sd, packet, length, 0, (struct sockaddr *)priv->device, sizeof(struct sockaddr_ll)); @@ -137,4 +240,10 @@ void sandbox_eth_raw_os_stop(struct eth_sandbox_raw_priv *priv) priv->device = NULL; close(priv->sd); priv->sd = -1; + if (priv->local) { + if (priv->local_bind_sd != -1) + close(priv->local_bind_sd); + priv->local_bind_sd = -1; + priv->local_bind_udp_port = 0; + } } diff --git a/arch/sandbox/dts/sandbox.dts b/arch/sandbox/dts/sandbox.dts index 553bfbee32..7d050d92af 100644 --- a/arch/sandbox/dts/sandbox.dts +++ b/arch/sandbox/dts/sandbox.dts @@ -5,6 +5,7 @@ #size-cells = <1>; aliases { + eth5 = "/eth@90000000"; pci0 = &pci; }; @@ -212,4 +213,10 @@ reg = <0x80000000 0x1000>; host-raw-interface = "eth0"; }; + + eth@90000000 { + compatible = "sandbox,eth-raw"; + reg = <0x90000000 0x1000>; + host-raw-interface = "lo"; + }; }; diff --git a/arch/sandbox/include/asm/eth-raw-os.h b/arch/sandbox/include/asm/eth-raw-os.h index df60c4f3b8..ed4b2e2649 100644 --- a/arch/sandbox/include/asm/eth-raw-os.h +++ b/arch/sandbox/include/asm/eth-raw-os.h @@ -15,16 +15,24 @@ * * sd: socket descriptor - the open socket during a session * device: struct sockaddr_ll - the host interface packets move to/from + * local: 1 or 0 to select the local interface ('lo') or not + * local_bindsd: socket descriptor to prevent the kernel from sending + * a message to the server claiming the port is + * unreachable + * local_bind_udp_port: The UDP port number that we bound to */ struct eth_sandbox_raw_priv { int sd; void *device; + int local; + int local_bind_sd; + unsigned short local_bind_udp_port; }; int sandbox_eth_raw_os_start(const char *ifname, unsigned char *ethmac, struct eth_sandbox_raw_priv *priv); int sandbox_eth_raw_os_send(void *packet, int length, - const struct eth_sandbox_raw_priv *priv); + struct eth_sandbox_raw_priv *priv); int sandbox_eth_raw_os_recv(void *packet, int *length, const struct eth_sandbox_raw_priv *priv); void sandbox_eth_raw_os_stop(struct eth_sandbox_raw_priv *priv); diff --git a/board/sandbox/README.sandbox b/board/sandbox/README.sandbox index 9f5d3f71d5..08489e3880 100644 --- a/board/sandbox/README.sandbox +++ b/board/sandbox/README.sandbox @@ -241,6 +241,28 @@ dhcp set serverip WWW.XXX.YYY.ZZZ tftpboot u-boot.bin +The bridge also support (to a lesser extent) the localhost inderface, 'lo'. + +The 'lo' interface cannot use the RAW AF_PACKET API because the lo interface +doesn't support Ethernet-level traffic. It is a higher-level interface that is +expected only to be used at the AF_INET level of the API. As such, the most raw +we can get on that interface is the RAW AF_INET API on UDP. This allows us to +set the IP_HDRINCL option to include everything except the Ethernet header in +the packets we send and receive. + +Because only UDP is supported, ICMP traffic will not work, so expect that ping +commands will time out. + +The default device tree for sandbox includes an entry for lo on the sandbox +host machine whose alias is "eth5". The following is an example of a network +operation being tested on the lo interface. + +TFTP +.... + +set ethact eth5 +tftpboot u-boot.bin + SPI Emulation ------------- diff --git a/drivers/net/sandbox-raw.c b/drivers/net/sandbox-raw.c index 435b8745c3..91da5f55ca 100644 --- a/drivers/net/sandbox-raw.c +++ b/drivers/net/sandbox-raw.c @@ -15,6 +15,8 @@ DECLARE_GLOBAL_DATA_PTR; +static int reply_arp; +static IPaddr_t arp_ip; static int sb_eth_raw_start(struct udevice *dev) { @@ -29,6 +31,11 @@ static int sb_eth_raw_start(struct udevice *dev) if (interface == NULL) return -EINVAL; + if (strcmp(interface, "lo") == 0) { + priv->local = 1; + setenv("ipaddr", "127.0.0.1"); + setenv("serverip", "127.0.0.1"); + } return sandbox_eth_raw_os_start(interface, pdata->enetaddr, priv); } @@ -38,18 +45,78 @@ static int sb_eth_raw_send(struct udevice *dev, void *packet, int length) debug("eth_sandbox_raw: Send packet %d\n", length); + if (priv->local) { + struct ethernet_hdr *eth = packet; + + if (ntohs(eth->et_protlen) == PROT_ARP) { + struct arp_hdr *arp = packet + ETHER_HDR_SIZE; + + /** + * localhost works on a higher-level API in Linux than + * ARP packets, so fake it + */ + arp_ip = NetReadIP(&arp->ar_tpa); + reply_arp = 1; + return 0; + } + packet += ETHER_HDR_SIZE; + length -= ETHER_HDR_SIZE; + } return sandbox_eth_raw_os_send(packet, length, priv); } static int sb_eth_raw_recv(struct udevice *dev, uchar **packetp) { + struct eth_pdata *pdata = dev_get_platdata(dev); struct eth_sandbox_raw_priv *priv = dev_get_priv(dev); - int retval; + int retval = 0; int length; - retval = sandbox_eth_raw_os_recv(net_rx_packets[0], &length, priv); + if (reply_arp) { + struct arp_hdr *arp = (void *)net_rx_packets[0] + + ETHER_HDR_SIZE; + + /* + * Fake an ARP response. The u-boot network stack is sending an + * ARP request (to find the MAC address to address the actual + * packet to) and requires an ARP response to continue. Since + * this is the localhost interface, there is no Etherent level + * traffic at all, so there is no way to send an ARP request or + * to get a response. For this reason we fake the response to + * make the u-boot network stack happy. + */ + arp->ar_hrd = htons(ARP_ETHER); + arp->ar_pro = htons(PROT_IP); + arp->ar_hln = ARP_HLEN; + arp->ar_pln = ARP_PLEN; + arp->ar_op = htons(ARPOP_REPLY); + /* Any non-zero MAC address will work */ + memset(&arp->ar_sha, 0x01, ARP_HLEN); + /* Use whatever IP we were looking for (always 127.0.0.1?) */ + NetWriteIP(&arp->ar_spa, arp_ip); + memcpy(&arp->ar_tha, pdata->enetaddr, ARP_HLEN); + NetWriteIP(&arp->ar_tpa, NetOurIP); + length = ARP_HDR_SIZE; + } else { + /* If local, the Ethernet header won't be included; skip it */ + uchar *pktptr = priv->local ? + net_rx_packets[0] + ETHER_HDR_SIZE : net_rx_packets[0]; + + retval = sandbox_eth_raw_os_recv(pktptr, &length, priv); + } if (!retval && length) { + if (priv->local) { + struct ethernet_hdr *eth = (void *)net_rx_packets[0]; + + /* Fill in enough of the missing Ethernet header */ + memcpy(eth->et_dest, pdata->enetaddr, ARP_HLEN); + memset(eth->et_src, 0x01, ARP_HLEN); + eth->et_protlen = htons(reply_arp ? PROT_ARP : PROT_IP); + reply_arp = 0; + length += ETHER_HDR_SIZE; + } + debug("eth_sandbox_raw: received packet %d\n", length); *packetp = net_rx_packets[0]; |