diff options
Diffstat (limited to 'roms/SLOF/lib/libnet/ipv6.c')
-rw-r--r-- | roms/SLOF/lib/libnet/ipv6.c | 768 |
1 files changed, 768 insertions, 0 deletions
diff --git a/roms/SLOF/lib/libnet/ipv6.c b/roms/SLOF/lib/libnet/ipv6.c new file mode 100644 index 000000000..642000436 --- /dev/null +++ b/roms/SLOF/lib/libnet/ipv6.c @@ -0,0 +1,768 @@ +/****************************************************************************** + * Copyright (c) 2013 IBM Corporation + * All rights reserved. + * This program and the accompanying materials + * are made available under the terms of the BSD License + * which accompanies this distribution, and is available at + * http://www.opensource.org/licenses/bsd-license.php + * + * Contributors: + * IBM Corporation - initial implementation + *****************************************************************************/ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <time.h> +#include <ctype.h> +#include <sys/socket.h> +#include "ethernet.h" +#include "ipv6.h" +#include "icmpv6.h" +#include "ndp.h" +#include "udp.h" + +#undef IPV6_DEBUG +//#define IPV6_DEBUG +#ifdef IPV6_DEBUG +#define dprintf(_x ...) do { printf(_x); } while (0) +#else +#define dprintf(_x ...) +#endif + +/****************************** PROTOTYPES *******************************/ +static void ipv6_init(int fd); +static int ip6_is_multicast (ip6_addr_t * ip); + +/****************************** LOCAL VARIABLES **************************/ + +/* List of Ipv6 Addresses */ +static struct ip6addr_list_entry *first_ip6; +static struct ip6addr_list_entry *last_ip6; + +/* Own IPv6 address */ +static struct ip6addr_list_entry *own_ip6; + +/* All nodes link-local address */ +struct ip6addr_list_entry all_nodes_ll; + +/* Null IPv6 address */ +static ip6_addr_t null_ip6; + +/* helper variables */ +static uint8_t null_mac[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +struct ip6_config ip6_state; + +/****************************** IMPLEMENTATION ***************************/ + +/** + * IPv6: Set the own IPv6 address. + * + * @param fd Socket descriptor + * @param _own_ip client IPv6 address (e.g. ::1) + */ +void set_ipv6_address(int fd, ip6_addr_t *_own_ip6) +{ + struct ip6addr_list_entry *ile; + + ile = malloc(sizeof(struct ip6addr_list_entry)); + if (!ile) + return; + memset(ile, 0, sizeof(struct ip6addr_list_entry)); + own_ip6 = ile; + + /* If no address was passed as a parameter generate a link-local + * address from our MAC address.*/ + if (_own_ip6 == NULL) + ip6_create_ll_address(get_mac_address(), &own_ip6->addr); + else + memcpy (&(own_ip6->addr.addr), _own_ip6, 16); + + /* Add to our list of IPv6 addresses */ + ip6addr_add (own_ip6); + + ipv6_init(fd); + + /* + * Check whether we've got a non-link-local address during + * ipv6_init() and use that as preferred address if possible + */ + if (_own_ip6 == NULL) { + for (ile = first_ip6; ile != NULL ; ile = ile->next) { + if (!ip6_is_multicast(&ile->addr) && + !ip6_is_linklocal(&ile->addr)) { + own_ip6 = ile; + break; + } + } + } +} + +/** + * IPv6: Get pointer to own IPv6 address. + * + * @return pointer to client IPv6 address (e.g. ::1) + */ +ip6_addr_t *get_ipv6_address(void) +{ + return (ip6_addr_t *) &(own_ip6->addr); +} + +/** + * IPv6: Search for IPv6 address in list + * + * @return 0 - IPv6 address is not in list + * 1 - IPv6 address is in list + */ +static int8_t find_ip6addr(ip6_addr_t ip) +{ + struct ip6addr_list_entry *n = NULL; + + for (n = first_ip6; n != NULL ; n=n->next) + if (ip6_cmp(n->addr, ip)) + return 1; /* IPv6 address is in our list*/ + + return 0; /* not one of our IPv6 addresses*/ +} + +/** + * NET: Handles IPv6-packets + * + * @param fd - Socket descriptor + * @param ip6_packet - Pointer to IPv6 header + * @param packetsize - Size of Ipv6 packet + * @return ERROR - -1 if packet is too small or unknown protocol + * return value of handle_udp + * + * @see handle_udp + * @see ip6hdr + */ +int8_t handle_ipv6(int fd, uint8_t * ip6_packet, uint32_t packetsize) +{ + + struct ip6hdr *ip6 = NULL; + ip6 = (struct ip6hdr *) ip6_packet; + + /* Only handle packets which are for us */ + if (!find_ip6addr(ip6->dst)) + return -1; + + if (packetsize < sizeof(struct ip6hdr)) + return -1; // packet is too small + + switch (ip6->nh) { + case IPTYPE_UDP: + return handle_udp (fd, ip6_packet + sizeof (struct ip6hdr), + ip6->pl); + case IPTYPE_ICMPV6: + return handle_icmpv6 (fd, (struct ethhdr *) ip6_packet - sizeof(struct ethhdr), + ip6_packet); + } + + return -1; // unknown protocol +} + + /** + * NET: Creates IPv6-packet. Places IPv6-header in a packet and fills it + * with corresponding information. + * <p> + * Use this function with similar functions for other network layers + * (fill_ethhdr, fill_udphdr, fill_dnshdr, fill_btphdr). + * + * @param packet Points to the place where IPv6-header must be placed. + * @param packetsize Size of payload (i.e. excluding ethhdr and ip6hdr) + * @param ip_proto Type of the next level protocol (e.g. UDP). + * @param ip6_src Sender IPv6 address + * @param ip6_dst Receiver IPv6 address + * @see ip6hdr + * @see fill_iphdr + * @see fill_ethhdr + * @see fill_udphdr + * @see fill_dnshdr + * @see fill_btphdr + */ +void fill_ip6hdr(uint8_t * packet, uint16_t packetsize, + uint8_t ip_proto, ip6_addr_t *ip6_src, ip6_addr_t *ip6_dst) +{ + struct ip6hdr * ip6h = (struct ip6hdr *) packet; + + ip6h->ver_tc_fl = 6 << 28; // set version to 6 + ip6h->pl = packetsize; // IPv6 payload size + ip6h->nh = ip_proto; + ip6h->hl = 255; + memcpy (&(ip6h->src), ip6_src, IPV6_ADDR_LENGTH); + memcpy (&(ip6h->dst), ip6_dst, IPV6_ADDR_LENGTH); +} + +/** + * NET: For a given MAC calculates EUI64-Identifier. + * See RFC 4291 "IP Version 6 Addressing Architecture" + * + */ +uint64_t mac2eui64(const uint8_t *mac) +{ + uint8_t eui64id[8]; + uint64_t retid; + + memcpy (eui64id, mac, 3); + memcpy (eui64id + 5, mac + 3, 3); + eui64id[3] = 0xff; + eui64id[4] = 0xfe; + + memcpy(&retid, eui64id, 8); + return retid; +} + +/** + * NET: create link-local IPv6 address + * + * @param own_mac MAC of NIC + * @param ll_addr pointer to link-local address which should be created + */ +void ip6_create_ll_address(const uint8_t *own_mac, ip6_addr_t *ll_addr) +{ + ll_addr->part.prefix = IPV6_LL_PREFIX; + ll_addr->part.interface_id = mac2eui64((uint8_t *) own_mac); +} + +/* + * NET: check if we already have an address with the same prefix. + * @param struct ip6_addr_list_entry *ip6 + * @return true or false + */ +int8_t unknown_prefix(ip6_addr_t *ip) +{ + struct ip6addr_list_entry *node; + + for( node = first_ip6; node != NULL; node=node->next ) + if( node->addr.part.prefix == ip->part.prefix ) + return 0; /* address is one of ours */ + + return 1; /* prefix not yet in our list */ +} + +/* + * NET: Create empty element for prefix list and return a pointer to it; + * @return NULL - malloc failed + * ! NULL - pointer to new prefix_info + */ +struct prefix_info *ip6_create_prefix_info(void) +{ + struct prefix_info *prfx_info; + + prfx_info = malloc (sizeof(struct prefix_info)); + if (!prfx_info) + return NULL; + memset(prfx_info, 0, sizeof(struct prefix_info)); + + return prfx_info; +} + +/* + * NET: create a new IPv6 address with a given network prefix + * and add it to our IPv6 address list + * + * @param ip6_addr prefix (as received in RA) + * @return NULL - pointer to new ip6addr_list entry + */ +void *ip6_prefix2addr(ip6_addr_t prefix) +{ + struct ip6addr_list_entry *new_address; + uint64_t interface_id; + + new_address = malloc (sizeof(struct ip6addr_list_entry)); + if( !new_address ) + return NULL; + memset(new_address, 0, sizeof(struct ip6addr_list_entry)); + + /* fill new addr struct */ + /* extract prefix from Router Advertisement */ + memcpy (&(new_address->addr.part.prefix), &prefix, 8 ); + + /* interface id is generated from MAC address */ + interface_id = mac2eui64 (get_mac_address()); + memcpy (&(new_address->addr.part.interface_id), &interface_id, 8); + + return new_address; +} + +/** + * NET: add new IPv6 adress to list + * + * @param ip6_addr *new_address + * @return 0 - passed pointer = NULL; + * 1 - ok + */ +int8_t ip6addr_add(struct ip6addr_list_entry *new_address) +{ + struct ip6addr_list_entry *solicited_node; + + + if (new_address == NULL) + return 0; + + /* Don't add the same address twice */ + if (find_ip6addr(new_address->addr)) + return 0; + + /* If address is a unicast address, we also have to process packets + * for its solicited-node multicast address. + * See RFC 2373 - IP Version 6 Adressing Architecture */ + if (! ip6_is_multicast(&(new_address->addr))) { + solicited_node = malloc(sizeof(struct ip6addr_list_entry)); + if (! solicited_node) + return 0; + memset(solicited_node, 0, sizeof(struct ip6addr_list_entry)); + + solicited_node->addr.part.prefix = IPV6_SOLIC_NODE_PREFIX; + solicited_node->addr.part.interface_id = IPV6_SOLIC_NODE_IFACE_ID; + solicited_node->addr.addr[13] = new_address->addr.addr[13]; + solicited_node->addr.addr[14] = new_address->addr.addr[14]; + solicited_node->addr.addr[15] = new_address->addr.addr[15]; + ip6addr_add (solicited_node); + } + + if (first_ip6 == NULL) + first_ip6 = new_address; + else + last_ip6->next = new_address; + last_ip6 = new_address; + last_ip6->next = NULL; + + return 1; /* no error */ +} + +/** + * NET: Initialize IPv6 + * + * @param fd socket fd + */ +static void ipv6_init(int fd) +{ + int i = 0; + + send_ip = &send_ipv6; + + /* Address configuration parameters */ + ip6_state.managed_mode = 0; + + /* Null IPv6 address */ + null_ip6.part.prefix = 0; + null_ip6.part.interface_id = 0; + + /* Multicast addresses */ + all_nodes_ll.addr.part.prefix = 0xff02000000000000; + all_nodes_ll.addr.part.interface_id = 1; + ip6addr_add(&all_nodes_ll); + + ndp_init(); + + send_router_solicitation (fd); + for(i=0; i < 4 && !is_ra_received(); i++) { + set_timer(TICKS_SEC); + do { + receive_ether(fd); + if (is_ra_received()) + break; + } while (get_timer() > 0); + } +} + +/** + * NET: compare IPv6 adresses + * + * @param ip6_addr ip_1 + * @param ip6_addr ip_2 + */ +int8_t ip6_cmp(ip6_addr_t ip_1, ip6_addr_t ip_2) +{ + return !memcmp(ip_1.addr, ip_2.addr, IPV6_ADDR_LENGTH); +} + +/** + * NET: Calculate checksum over IPv6 header and upper-layer protocol + * (e.g. UDP or ICMPv6) + * + * @param *ip - pointer to IPv6 address + * @return true or false + */ +int ip6_is_multicast(ip6_addr_t * ip) +{ + return ip->addr[0] == 0xFF; +} + +/** + * NET: Generate multicast MAC address from IPv6 address + * (e.g. UDP or ICMPv6) + * + * @param *ip - pointer to IPv6 address + * @param *mc_mac pointer to an array with 6 bytes (for the MAC address) + * @return pointer to Multicast MAC address + */ +static uint8_t *ip6_to_multicast_mac(ip6_addr_t * ip, uint8_t *mc_mac) +{ + mc_mac[0] = 0x33; + mc_mac[1] = 0x33; + memcpy (mc_mac+2, (uint8_t *) &(ip->addr)+12, 4); + + return mc_mac; +} + +/** + * Check whether an IPv6 address is on the same network as we are + */ +static bool is_ip6addr_in_my_net(ip6_addr_t *ip) +{ + struct ip6addr_list_entry *n = NULL; + + for (n = first_ip6; n != NULL; n = n->next) { + if (n->addr.part.prefix == ip->part.prefix) + return true; /* IPv6 address is in our neighborhood */ + } + + return false; /* not in our neighborhood */ +} + +/** + * NET: calculate checksum over IPv6 header and upper-layer protocol + * (e.g. UDP or ICMPv6) + * + * @param struct ip6hdr *ip6h - pointer to IPv6 header + * @param unsigned char *packet - pointer to header of upper-layer + * protocol + * @param int bytes - number of bytes + * starting from *packet + * @return checksum + */ +static unsigned short ip6_checksum(struct ip6hdr *ip6h, unsigned char *packet, + int bytes) +{ + int i; + unsigned long checksum; + const int ip6size = sizeof(struct ip6hdr)/sizeof(unsigned short); + union { + struct ip6hdr ip6h; + unsigned short raw[ip6size]; + } pseudo; + + memcpy (&pseudo.ip6h, ip6h, sizeof(struct ip6hdr)); + pseudo.ip6h.hl = ip6h->nh; + pseudo.ip6h.ver_tc_fl = 0; + pseudo.ip6h.nh = 0; + + for (checksum = 0, i = 0; i < bytes; i += 2) + checksum += (packet[i] << 8) | packet[i + 1]; + + for (i = 0; i < ip6size; i++) + checksum += pseudo.raw[i]; + + checksum = (checksum >> 16) + (checksum & 0xffff); + checksum += (checksum >> 16); + + return ~checksum; +} + +/** + * NET: Handles IPv6-packets + * + * @param fd socket fd + * @param ip6_packet Pointer to IPv6 header in packet + * @param packetsize Size of IPv6 packet + * @return -1 : Some error occured + * 0 : packet stored (NDP request sent - packet will be sent if + * NDP response is received) + * >0 : packet sent (number of transmitted bytes is returned) + * + * @see receive_ether + * @see ip6hdr + */ +int send_ipv6(int fd, void* buffer, int len) +{ + struct ip6hdr *ip6h; + struct udphdr *udph; + struct icmp6hdr *icmp6h; + ip6_addr_t ip_dst; + uint8_t *mac_addr, mc_mac[6]; + static uint8_t ethframe[ETH_MTU_SIZE]; + + mac_addr = null_mac; + + ip6h = (struct ip6hdr *) buffer; + udph = (struct udphdr *) ((uint8_t *) ip6h + sizeof (struct ip6hdr)); + icmp6h = (struct icmp6hdr *) ((uint8_t *) ip6h + sizeof (struct ip6hdr)); + + memcpy(&ip_dst, &ip6h->dst, 16); + + if(len + sizeof(struct ethhdr) > ETH_MTU_SIZE) + return -1; + + if ( ip6_cmp(ip6h->src, null_ip6)) + memcpy (&(ip6h->src), get_ipv6_address(), IPV6_ADDR_LENGTH); + + if (ip6h->nh == 17) {//UDP + udph->uh_sum = ip6_checksum (ip6h, (unsigned char *) udph, + ip6h->pl); + /* As per RFC 768, if the computed checksum is zero, + * it is transmitted as all ones (the equivalent in + * one's complement arithmetic). + */ + if (udph->uh_sum == 0) + udph->uh_sum = ~udph->uh_sum; + } + else if (ip6h->nh == 0x3a) //ICMPv6 + icmp6h->checksum = ip6_checksum (ip6h, (unsigned char *) icmp6h, + ip6h->pl); + + if (ip6_is_multicast (&ip_dst)) { + /* If multicast, then create a proper multicast mac address */ + mac_addr = ip6_to_multicast_mac (&ip_dst, mc_mac); + } else if (!is_ip6addr_in_my_net(&ip_dst)) { + /* If server is not in same subnet, user MAC of the router */ + struct router *gw; + gw = ipv6_get_default_router(ip6h->src); + mac_addr = gw ? gw->mac : null_mac; + } else { + /* Normal unicast, so use neighbor cache to look up MAC */ + struct neighbor *n = find_neighbor(ip_dst); + if (n) { /* Already cached ? */ + if (memcmp(n->mac, null_mac, ETH_ALEN) != 0) + mac_addr = n->mac; /* found it */ + } else { + mac_addr = null_mac; + n = malloc(sizeof(struct neighbor)); + if (!n) + return -1; + memset(n, 0, sizeof(struct neighbor)); + memcpy(&(n->ip.addr[0]), &ip_dst, 16); + n->status = NB_PROBE; + n->times_asked = 1; + neighbor_add(n); + } + + if (! memcmp (mac_addr, &null_mac, 6)) { + if (n->eth_len == 0) { + send_neighbour_solicitation (fd, &ip_dst); + + // Store the packet until we know the MAC address + fill_ethhdr (n->eth_frame, + htons(ETHERTYPE_IPv6), + get_mac_address(), + mac_addr); + memcpy (&(n->eth_frame[sizeof(struct ethhdr)]), + buffer, len); + n->eth_len = len; + set_timer(TICKS_SEC); + do { + receive_ether(fd); + if (n->status == NB_REACHABLE) + return len; + } while (get_timer() > 0); + return 0; + } + } + } + + if (mac_addr == null_mac) + return -1; + + fill_ethhdr(ethframe, htons(ETHERTYPE_IPv6), get_mac_address(), + mac_addr); + memcpy(ðframe[sizeof(struct ethhdr)], buffer, len); + + return send_ether(fd, ethframe, len + sizeof(struct ethhdr)); +} + +static int check_colons(const char *str) +{ + char *pch, *prv; + int col = 0; + int dcol = 0; + + dprintf("str : %s\n",str); + pch = strchr(str, ':'); + while(pch != NULL){ + prv = pch; + pch = strchr(pch+1, ':'); + if((pch-prv) != 1) { + col++; + } else { + col--; /* Its part of double colon */ + dcol++; + } + } + + dprintf("The number of col : %d \n",col); + dprintf("The number of dcol : %d \n",dcol); + + if((dcol > 1) || /* Cannot have 2 "::" */ + ((dcol == 1) && (col > 5)) || /* Too many ':'s */ + ((dcol == 0) && (col != 7)) ) { /* Too few ':'s */ + dprintf(" exiting for check_colons \n"); + return 0; + } + + return (col+dcol); +} + +static int ipv6str_to_bytes(const char *str, char *ip) +{ + char block[5]; + int res; + char *pos; + uint32_t cnt = 0, len; + + dprintf("str : %s \n",str); + + while (*str != 0) { + if (cnt > 15 || !isxdigit(*str)){ + return 0; + } + if ((pos = strchr(str, ':')) != NULL) { + len = (int16_t) (pos - str); + dprintf("\t len is : %d \n",len); + if (len > 4) + return 0; + strncpy(block, str, len); + block[len] = 0; + dprintf("\t str : %s \n",str); + dprintf("\t block : %s \n",block); + str += len; + } else { + strncpy(block, str, 4); + block[4] = 0; + dprintf("\t str : %s \n",str); + dprintf("\t block : %s \n",block); + str += strlen(block); + } + res = strtol(block, NULL, 16); + dprintf("\t res : %x \n",res); + if ((res > 0xFFFF) || (res < 0)) + return 0; + ip[cnt++] = (res & 0xFF00) >> 8; + ip[cnt++] = (res & 0x00FF); + if (*str == ':'){ + str++; + } + } + + dprintf("cnt : %d\n",cnt); + return cnt; +} + +int str_to_ipv6(const char *str, uint8_t *ip) +{ + int i, k; + uint16_t len; + char *ptr; + char tmp[30], buf[16]; + + memset(ip,0,16); + + if(!check_colons(str)) + return 0; + + if ((ptr = strstr(str, "::")) != NULL) { + /* Handle the ::1 IPv6 loopback */ + if(!strcmp(str,"::1")) { + ip[15] = 1; + return 16; + } + len = (ptr-str); + dprintf(" len : %d \n",len); + if (len >= sizeof(tmp)) + return 0; + strncpy(tmp, str, len); + tmp[len] = 0; + ptr += 2; + + i = ipv6str_to_bytes(ptr, buf); + if(i == 0) + return i; + + #if defined(ARGS_DEBUG) + int j; + dprintf("=========== bottom part i : %d \n",i); + for(j=0; j<i; j++) + dprintf("%02x \t",buf[j]); + #endif + + /* Copy the bottom part i.e bytes following "::" */ + memcpy(ip+(16-i), buf, i); + + k = ipv6str_to_bytes(tmp, buf); + if(k == 0) + return k; + + #if defined(ARGS_DEBUG) + dprintf("=========== top part k : %d \n",k); + for(j=0; j<k; j++) + printf("%02x \t",buf[j]); + #endif + + /* Copy the top part i.e bytes before "::" */ + memcpy(ip, buf, k); + #if defined(ARGS_DEBUG) + dprintf("\n"); + for(j=0; j<16; j++) + dprintf("%02x \t",ip[j]); + #endif + + } else { + i = ipv6str_to_bytes(str, (char *)ip); + } + return i; +} + +void ipv6_to_str(const uint8_t *ip, char *str) +{ + int i, len; + uint8_t byte_even, byte_odd; + char *consec_zero, *strptr; + + *str = 0; + for (i = 0; i < 16; i+=2) { + byte_even = ip[i]; + byte_odd = ip[i+1]; + if (byte_even) + sprintf(str, "%s%x%02x", str, byte_even, byte_odd); + else if (byte_odd) + sprintf(str, "%s%x", str, byte_odd); + else + strcat(str, "0"); + if (i != 14) + strcat(str, ":"); + } + strptr = str; + do { + consec_zero = strstr(strptr, "0:0:"); + if (consec_zero) { + len = consec_zero - strptr; + if (!len) + break; + else if (strptr[len-1] == ':') + break; + else + strptr = consec_zero + 2; + } + } while (consec_zero); + if (consec_zero) { + len = consec_zero - str; + str[len] = 0; + if (len) + strcat(str, ":"); + else + strcat(str, "::"); + strptr = consec_zero + 4; + while (*strptr) { + if (!strncmp(strptr, "0:", 2)) + strptr += 2; + else + break; + } + strcat(str, strptr); + if (!strcmp(str, "::0")) + strcpy(str, "::"); + } +} |