#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, };