diff options
Diffstat (limited to 'contrib/syslinux-4.02/gpxe/src/net/ipv6.c')
-rw-r--r-- | contrib/syslinux-4.02/gpxe/src/net/ipv6.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/gpxe/src/net/ipv6.c b/contrib/syslinux-4.02/gpxe/src/net/ipv6.c new file mode 100644 index 0000000..f7308bb --- /dev/null +++ b/contrib/syslinux-4.02/gpxe/src/net/ipv6.c @@ -0,0 +1,381 @@ +#include <errno.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <byteswap.h> +#include <gpxe/in.h> +#include <gpxe/ip6.h> +#include <gpxe/ndp.h> +#include <gpxe/list.h> +#include <gpxe/icmp6.h> +#include <gpxe/tcpip.h> +#include <gpxe/socket.h> +#include <gpxe/iobuf.h> +#include <gpxe/netdevice.h> +#include <gpxe/if_ether.h> + +struct net_protocol ipv6_protocol; + +/* Unspecified IP6 address */ +static struct in6_addr ip6_none = { + .in6_u.u6_addr32 = { 0,0,0,0 } +}; + +/** An IPv6 routing table entry */ +struct ipv6_miniroute { + /* List of miniroutes */ + struct list_head list; + + /* Network device */ + struct net_device *netdev; + + /* Destination prefix */ + struct in6_addr prefix; + /* Prefix length */ + int prefix_len; + /* IPv6 address of interface */ + struct in6_addr address; + /* Gateway address */ + struct in6_addr gateway; +}; + +/** List of IPv6 miniroutes */ +static LIST_HEAD ( miniroutes ); + +/** + * Add IPv6 minirouting table entry + * + * @v netdev Network device + * @v prefix Destination prefix + * @v address Address of the interface + * @v gateway Gateway address (or ::0 for no gateway) + * @ret miniroute Routing table entry, or NULL + */ +static struct ipv6_miniroute * __malloc +add_ipv6_miniroute ( struct net_device *netdev, struct in6_addr prefix, + int prefix_len, struct in6_addr address, + struct in6_addr gateway ) { + struct ipv6_miniroute *miniroute; + + miniroute = malloc ( sizeof ( *miniroute ) ); + if ( miniroute ) { + /* Record routing information */ + miniroute->netdev = netdev_get ( netdev ); + miniroute->prefix = prefix; + miniroute->prefix_len = prefix_len; + miniroute->address = address; + miniroute->gateway = gateway; + + /* Add miniroute to list of miniroutes */ + if ( !IP6_EQUAL ( gateway, ip6_none ) ) { + list_add_tail ( &miniroute->list, &miniroutes ); + } else { + list_add ( &miniroute->list, &miniroutes ); + } + } + + return miniroute; +} + +/** + * Delete IPv6 minirouting table entry + * + * @v miniroute Routing table entry + */ +static void del_ipv6_miniroute ( struct ipv6_miniroute *miniroute ) { + netdev_put ( miniroute->netdev ); + list_del ( &miniroute->list ); + free ( miniroute ); +} + +/** + * Add IPv6 interface + * + * @v netdev Network device + * @v prefix Destination prefix + * @v address Address of the interface + * @v gateway Gateway address (or ::0 for no gateway) + */ +int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix, + int prefix_len, struct in6_addr address, + struct in6_addr gateway ) { + struct ipv6_miniroute *miniroute; + + /* Clear any existing address for this net device */ + del_ipv6_address ( netdev ); + + /* Add new miniroute */ + miniroute = add_ipv6_miniroute ( netdev, prefix, prefix_len, address, + gateway ); + if ( ! miniroute ) + return -ENOMEM; + + return 0; +} + +/** + * Remove IPv6 interface + * + * @v netdev Network device + */ +void del_ipv6_address ( struct net_device *netdev ) { + struct ipv6_miniroute *miniroute; + + list_for_each_entry ( miniroute, &miniroutes, list ) { + if ( miniroute->netdev == netdev ) { + del_ipv6_miniroute ( miniroute ); + break; + } + } +} + +/** + * Calculate TCPIP checksum + * + * @v iobuf I/O buffer + * @v tcpip TCP/IP protocol + * + * This function constructs the pseudo header and completes the checksum in the + * upper layer header. + */ +static uint16_t ipv6_tx_csum ( struct io_buffer *iobuf, uint16_t csum ) { + struct ip6_header *ip6hdr = iobuf->data; + struct ipv6_pseudo_header pshdr; + + /* Calculate pseudo header */ + memset ( &pshdr, 0, sizeof ( pshdr ) ); + pshdr.src = ip6hdr->src; + pshdr.dest = ip6hdr->dest; + pshdr.len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) ); + pshdr.nxt_hdr = ip6hdr->nxt_hdr; + + /* Update checksum value */ + return tcpip_continue_chksum ( csum, &pshdr, sizeof ( pshdr ) ); +} + +/** + * Dump IP6 header for debugging + * + * ip6hdr IPv6 header + */ +void ipv6_dump ( struct ip6_header *ip6hdr ) { + DBG ( "IP6 %p src %s dest %s nxt_hdr %d len %d\n", ip6hdr, + inet6_ntoa ( ip6hdr->src ), inet6_ntoa ( ip6hdr->dest ), + ip6hdr->nxt_hdr, ntohs ( ip6hdr->payload_len ) ); +} + +/** + * Transmit IP6 packet + * + * iobuf I/O buffer + * tcpip TCP/IP protocol + * st_dest Destination socket address + * + * This function prepends the IPv6 headers to the payload an transmits it. + */ +static int ipv6_tx ( struct io_buffer *iobuf, + struct tcpip_protocol *tcpip, + struct sockaddr_tcpip *st_src __unused, + struct sockaddr_tcpip *st_dest, + struct net_device *netdev, + uint16_t *trans_csum ) { + struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest; + struct in6_addr next_hop; + struct ipv6_miniroute *miniroute; + uint8_t ll_dest_buf[MAX_LL_ADDR_LEN]; + const uint8_t *ll_dest = ll_dest_buf; + int rc; + + /* Construct the IPv6 packet */ + struct ip6_header *ip6hdr = iob_push ( iobuf, sizeof ( *ip6hdr ) ); + memset ( ip6hdr, 0, sizeof ( *ip6hdr) ); + ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );//IP6_VERSION; + ip6hdr->payload_len = htons ( iob_len ( iobuf ) - sizeof ( *ip6hdr ) ); + ip6hdr->nxt_hdr = tcpip->tcpip_proto; + ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255 + + /* Determine the next hop address and interface + * + * TODO: Implement the routing table. + */ + next_hop = dest->sin6_addr; + list_for_each_entry ( miniroute, &miniroutes, list ) { + if ( ( memcmp ( &ip6hdr->dest, &miniroute->prefix, + miniroute->prefix_len ) == 0 ) || + ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) { + netdev = miniroute->netdev; + ip6hdr->src = miniroute->address; + if ( ! ( IS_UNSPECIFIED ( miniroute->gateway ) ) ) { + next_hop = miniroute->gateway; + } + break; + } + } + /* No network interface identified */ + if ( !netdev ) { + DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) ); + rc = -ENETUNREACH; + goto err; + } + + /* Complete the transport layer checksum */ + if ( trans_csum ) + *trans_csum = ipv6_tx_csum ( iobuf, *trans_csum ); + + /* Print IPv6 header */ + ipv6_dump ( ip6hdr ); + + /* Resolve link layer address */ + if ( next_hop.in6_u.u6_addr8[0] == 0xff ) { + ll_dest_buf[0] = 0x33; + ll_dest_buf[1] = 0x33; + ll_dest_buf[2] = next_hop.in6_u.u6_addr8[12]; + ll_dest_buf[3] = next_hop.in6_u.u6_addr8[13]; + ll_dest_buf[4] = next_hop.in6_u.u6_addr8[14]; + ll_dest_buf[5] = next_hop.in6_u.u6_addr8[15]; + } else { + /* Unicast address needs to be resolved by NDP */ + if ( ( rc = ndp_resolve ( netdev, &next_hop, &ip6hdr->src, + ll_dest_buf ) ) != 0 ) { + DBG ( "No entry for %s\n", inet6_ntoa ( next_hop ) ); + goto err; + } + } + + /* Transmit packet */ + return net_tx ( iobuf, netdev, &ipv6_protocol, ll_dest ); + + err: + free_iob ( iobuf ); + return rc; +} + +/** + * Process next IP6 header + * + * @v iobuf I/O buffer + * @v nxt_hdr Next header number + * @v src Source socket address + * @v dest Destination socket address + * + * Refer http://www.iana.org/assignments/ipv6-parameters for the numbers + */ +static int ipv6_process_nxt_hdr ( struct io_buffer *iobuf, uint8_t nxt_hdr, + struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest ) { + switch ( nxt_hdr ) { + case IP6_HOPBYHOP: + case IP6_ROUTING: + case IP6_FRAGMENT: + case IP6_AUTHENTICATION: + case IP6_DEST_OPTS: + case IP6_ESP: + DBG ( "Function not implemented for header %d\n", nxt_hdr ); + return -ENOSYS; + case IP6_ICMP6: + break; + case IP6_NO_HEADER: + DBG ( "No next header\n" ); + return 0; + } + /* Next header is not a IPv6 extension header */ + return tcpip_rx ( iobuf, nxt_hdr, src, dest, 0 /* fixme */ ); +} + +/** + * Process incoming IP6 packets + * + * @v iobuf I/O buffer + * @v netdev Network device + * @v ll_source Link-layer source address + * + * This function processes a IPv6 packet + */ +static int ipv6_rx ( struct io_buffer *iobuf, + __unused struct net_device *netdev, + __unused const void *ll_source ) { + + struct ip6_header *ip6hdr = iobuf->data; + union { + struct sockaddr_in6 sin6; + struct sockaddr_tcpip st; + } src, dest; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *ip6hdr ) ) { + DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) ); + goto drop; + } + + /* TODO: Verify checksum */ + + /* Print IP6 header for debugging */ + ipv6_dump ( ip6hdr ); + + /* Check header version */ + if ( ( ip6hdr->ver_traffic_class_flow_label & 0xf0000000 ) != 0x60000000 ) { + DBG ( "Invalid protocol version\n" ); + goto drop; + } + + /* Check the payload length */ + if ( ntohs ( ip6hdr->payload_len ) > iob_len ( iobuf ) ) { + DBG ( "Inconsistent packet length (%d bytes)\n", + ip6hdr->payload_len ); + goto drop; + } + + /* Ignore the traffic class and flow control values */ + + /* Construct socket address */ + memset ( &src, 0, sizeof ( src ) ); + src.sin6.sin_family = AF_INET6; + src.sin6.sin6_addr = ip6hdr->src; + memset ( &dest, 0, sizeof ( dest ) ); + dest.sin6.sin_family = AF_INET6; + dest.sin6.sin6_addr = ip6hdr->dest; + + /* Strip header */ + iob_unput ( iobuf, iob_len ( iobuf ) - ntohs ( ip6hdr->payload_len ) - + sizeof ( *ip6hdr ) ); + iob_pull ( iobuf, sizeof ( *ip6hdr ) ); + + /* Send it to the transport layer */ + return ipv6_process_nxt_hdr ( iobuf, ip6hdr->nxt_hdr, &src.st, &dest.st ); + + drop: + DBG ( "Packet dropped\n" ); + free_iob ( iobuf ); + return -1; +} + +/** + * Print a IP6 address as xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + */ +char * inet6_ntoa ( struct in6_addr in6 ) { + static char buf[40]; + uint16_t *bytes = ( uint16_t* ) &in6; + sprintf ( buf, "%x:%x:%x:%x:%x:%x:%x:%x", bytes[0], bytes[1], bytes[2], + bytes[3], bytes[4], bytes[5], bytes[6], bytes[7] ); + return buf; +} + +static const char * ipv6_ntoa ( const void *net_addr ) { + return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) ); +} + +/** IPv6 protocol */ +struct net_protocol ipv6_protocol __net_protocol = { + .name = "IPv6", + .net_proto = htons ( ETH_P_IPV6 ), + .net_addr_len = sizeof ( struct in6_addr ), + .rx = ipv6_rx, + .ntoa = ipv6_ntoa, +}; + +/** IPv6 TCPIP net protocol */ +struct tcpip_net_protocol ipv6_tcpip_protocol __tcpip_net_protocol = { + .name = "IPv6", + .sa_family = AF_INET6, + .tx = ipv6_tx, +}; |