407 lines
11 KiB
C
407 lines
11 KiB
C
/******************************************************************************
|
|
* 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 <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include "ethernet.h"
|
|
#include "ipv6.h"
|
|
#include "icmpv6.h"
|
|
#include "ndp.h"
|
|
#include "dhcpv6.h"
|
|
|
|
static int ra_received = 0;
|
|
|
|
/**
|
|
* NET:
|
|
* @param fd socket fd
|
|
*/
|
|
void
|
|
send_router_solicitation (int fd)
|
|
{
|
|
ip6_addr_t dest_addr;
|
|
uint8_t *ether_packet;
|
|
struct packeth headers;
|
|
|
|
ether_packet = malloc(ETH_MTU_SIZE);
|
|
if (!ether_packet) {
|
|
fprintf(stderr, "send_router_solicitation: Out of memory\n");
|
|
return;
|
|
}
|
|
|
|
headers.ip6h = (struct ip6hdr *) (ether_packet + sizeof(struct ethhdr));
|
|
headers.icmp6h = (struct icmp6hdr *) (ether_packet +
|
|
sizeof(struct ethhdr) +
|
|
sizeof(struct ip6hdr));
|
|
|
|
/* Destination is "All routers multicast address" (link-local) */
|
|
dest_addr.part.prefix = 0xff02000000000000ULL;
|
|
dest_addr.part.interface_id = 2;
|
|
|
|
/* Fill IPv6 header */
|
|
fill_ip6hdr (ether_packet + sizeof(struct ethhdr),
|
|
ICMPv6_HEADER_SIZE + sizeof(struct router_solicitation),
|
|
0x3a, //ICMPV6
|
|
get_ipv6_address(), &dest_addr);
|
|
|
|
/* Fill ICMPv6 message */
|
|
headers.icmp6h->type = ICMPV6_ROUTER_SOLICITATION;
|
|
headers.icmp6h->code = 0;
|
|
headers.icmp6h->icmp6body.router_solicit.lladdr.type = 1;
|
|
headers.icmp6h->icmp6body.router_solicit.lladdr.length = 1;
|
|
memcpy( &(headers.icmp6h->icmp6body.router_solicit.lladdr.mac),
|
|
get_mac_address(), 6);
|
|
|
|
send_ip (fd, headers.ip6h, sizeof(struct ip6hdr) +
|
|
ICMPv6_HEADER_SIZE + sizeof(struct router_solicitation));
|
|
|
|
free(ether_packet);
|
|
}
|
|
|
|
/**
|
|
* NET: Process prefix option in Router Advertisements
|
|
*
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
*/
|
|
static void
|
|
handle_prefixoption (uint8_t *option)
|
|
{
|
|
ip6_addr_t prefix;
|
|
struct ip6addr_list_entry *new_address;
|
|
struct option_prefix *prefix_option;
|
|
struct prefix_info *prfx_info;
|
|
|
|
prefix_option = (struct option_prefix *) option;
|
|
memcpy( &(prefix.addr), &(prefix_option->prefix.addr), IPV6_ADDR_LENGTH);
|
|
|
|
/* Link-local adresses in RAs are nonsense */
|
|
if (ip6_is_linklocal(&prefix))
|
|
return;
|
|
|
|
if (prefix_option->preferred_lifetime > prefix_option->valid_lifetime)
|
|
return;
|
|
|
|
/* Add address created from prefix to IPv6 address list */
|
|
new_address = ip6_prefix2addr (prefix);
|
|
if (!new_address)
|
|
return;
|
|
|
|
/* Process only prefixes we don't already have an adress from */
|
|
if (!unknown_prefix (&new_address->addr)) {
|
|
return;
|
|
}
|
|
|
|
/* Fill struct prefix_info from data in RA and store it in new_address */
|
|
prfx_info = ip6_create_prefix_info();
|
|
if (!prfx_info)
|
|
return;
|
|
memcpy (&(new_address->prfx_info), prfx_info, sizeof(struct prefix_info));
|
|
|
|
/* Add prefix received in RA to list of known prefixes */
|
|
ip6addr_add (new_address);
|
|
}
|
|
|
|
/**
|
|
* NET: Process source link layer addresses in Router Advertisements
|
|
*
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
*/
|
|
static void
|
|
handle_source_lladdr ( struct option_ll_address *option, struct router *rtr)
|
|
{
|
|
memcpy (&(rtr->mac), &(option->mac), 6);
|
|
}
|
|
|
|
/**
|
|
* NET: Process ICMPv6 options in Router Advertisements
|
|
*
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
*/
|
|
static void
|
|
process_ra_options (uint8_t *option, int32_t option_length, struct router *r)
|
|
{
|
|
while (option_length > 0) {
|
|
switch (*option) {
|
|
case ND_OPTION_SOURCE_LL_ADDR:
|
|
handle_source_lladdr ((struct option_ll_address *) option, r);
|
|
break;
|
|
case ND_OPTION_PREFIX_INFO:
|
|
handle_prefixoption(option);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
//option+1 is the length field. length is in units of 8 bytes
|
|
option_length = option_length - (*(option+1) * 8);
|
|
option = option + (*(option+1) * 8);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* NET: Process Router Advertisements
|
|
*
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
*/
|
|
static void
|
|
handle_ra (struct icmp6hdr *icmp6h, uint8_t *ip6_packet)
|
|
{
|
|
uint8_t *first_option;
|
|
int32_t option_length;
|
|
struct ip6hdr *ip6h;
|
|
struct router_advertisement *ra;
|
|
struct router *rtr;
|
|
uint8_t rtr_mac[] = {0, 0, 0, 0, 0, 0};
|
|
|
|
ip6h = (struct ip6hdr *) ip6_packet;
|
|
ra = (struct router_advertisement *) &icmp6h->icmp6body.ra;
|
|
|
|
rtr = find_router(ip6h->src);
|
|
if (!rtr) {
|
|
rtr = router_create (rtr_mac, ip6h->src);
|
|
router_add (rtr);
|
|
}
|
|
|
|
/* store info from router advertisement in router struct */
|
|
rtr->lifetime = ra->router_lifetime;
|
|
rtr->reachable_time = ra->reachable_time;
|
|
rtr->retrans_timer = ra->retrans_timer;
|
|
|
|
/* save flags concerning address (auto-) configuration */
|
|
ip6_state.managed_mode = ra->flags.managed;
|
|
ip6_state.other_config = ra->flags.other;
|
|
|
|
/* Process ICMPv6 options in Router Advertisement */
|
|
first_option = (uint8_t *) icmp6h + ICMPv6_HEADER_SIZE + 12;
|
|
option_length = (uint8_t *) icmp6h + ip6h->pl - first_option;
|
|
process_ra_options( (uint8_t *) first_option, option_length, rtr);
|
|
|
|
ra_received = 1;
|
|
}
|
|
|
|
int is_ra_received(void)
|
|
{
|
|
return ra_received;
|
|
}
|
|
|
|
/**
|
|
* NET:
|
|
*
|
|
* @param fd socket fd
|
|
* @param ip6_addr_t *dest_ip6
|
|
*/
|
|
void
|
|
send_neighbour_solicitation (int fd, ip6_addr_t *dest_ip6)
|
|
{
|
|
ip6_addr_t snma;
|
|
uint8_t *ether_packet;
|
|
struct packeth headers;
|
|
|
|
ether_packet = malloc(ETH_MTU_SIZE);
|
|
if (!ether_packet) {
|
|
fprintf(stderr, "send_neighbour_solicitation: Out of memory\n");
|
|
return;
|
|
}
|
|
|
|
memset(ether_packet, 0, ETH_MTU_SIZE);
|
|
headers.ethh = (struct ethhdr *) ether_packet;
|
|
headers.ip6h = (struct ip6hdr *) (ether_packet + sizeof(struct ethhdr));
|
|
headers.icmp6h = (struct icmp6hdr *) (ether_packet +
|
|
sizeof(struct ethhdr) +
|
|
sizeof(struct ip6hdr));
|
|
|
|
/* Fill IPv6 header */
|
|
snma.part.prefix = IPV6_SOLIC_NODE_PREFIX;
|
|
snma.part.interface_id = IPV6_SOLIC_NODE_IFACE_ID;
|
|
snma.addr[13] = dest_ip6->addr[13];
|
|
snma.addr[14] = dest_ip6->addr[14];
|
|
snma.addr[15] = dest_ip6->addr[15];
|
|
fill_ip6hdr((uint8_t *) headers.ip6h,
|
|
ICMPv6_HEADER_SIZE + sizeof(struct neighbour_solicitation),
|
|
0x3a, //ICMPv6
|
|
get_ipv6_address(), &snma);
|
|
|
|
/* Fill ICMPv6 message */
|
|
headers.icmp6h->type = ICMPV6_NEIGHBOUR_SOLICITATION;
|
|
headers.icmp6h->code = 0;
|
|
memcpy( &(headers.icmp6h->icmp6body.nghb_solicit.target),
|
|
dest_ip6, IPV6_ADDR_LENGTH );
|
|
headers.icmp6h->icmp6body.nghb_solicit.lladdr.type = 1;
|
|
headers.icmp6h->icmp6body.nghb_solicit.lladdr.length = 1;
|
|
memcpy( &(headers.icmp6h->icmp6body.nghb_solicit.lladdr.mac),
|
|
get_mac_address(), 6);
|
|
|
|
send_ip (fd, ether_packet + sizeof(struct ethhdr),
|
|
sizeof(struct ip6hdr) + ICMPv6_HEADER_SIZE +
|
|
sizeof(struct neighbour_solicitation));
|
|
|
|
free(ether_packet);
|
|
}
|
|
|
|
/**
|
|
* NET:
|
|
*
|
|
* @param fd socket fd
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
* @param icmp6hdr pointer to the icmp6 header in ip6_packet
|
|
* @param na_flags Neighbour advertisment flags
|
|
*/
|
|
static void
|
|
send_neighbour_advertisement (int fd, struct neighbor *target)
|
|
{
|
|
struct na_flags na_adv_flags;
|
|
uint8_t *ether_packet;
|
|
struct packeth headers;
|
|
|
|
ether_packet = malloc(ETH_MTU_SIZE);
|
|
if (!ether_packet) {
|
|
fprintf(stderr, "send_neighbour_advertisement: Out of memory\n");
|
|
return;
|
|
}
|
|
|
|
headers.ip6h = (struct ip6hdr *) (ether_packet + sizeof(struct ethhdr));
|
|
headers.icmp6h = (struct icmp6hdr *) (ether_packet +
|
|
sizeof(struct ethhdr) +
|
|
sizeof(struct ip6hdr));
|
|
|
|
/* Fill IPv6 header */
|
|
fill_ip6hdr(ether_packet + sizeof(struct ethhdr),
|
|
ICMPv6_HEADER_SIZE + sizeof(struct neighbour_advertisement),
|
|
0x3a, //ICMPv6
|
|
get_ipv6_address(), (ip6_addr_t *) &(target->ip.addr));
|
|
|
|
/* Fill ICMPv6 message */
|
|
memcpy( &(headers.icmp6h->icmp6body.nghb_adv.target),
|
|
&(target->ip.addr), IPV6_ADDR_LENGTH );
|
|
headers.icmp6h->icmp6body.nghb_adv.lladdr.type = 1;
|
|
headers.icmp6h->icmp6body.nghb_adv.lladdr.length = 1;
|
|
memcpy( &(headers.icmp6h->icmp6body.nghb_adv.lladdr.mac),
|
|
get_mac_address(), 6);
|
|
|
|
na_adv_flags.is_router = 0;
|
|
na_adv_flags.na_is_solicited = 1;
|
|
na_adv_flags.override = 1;
|
|
|
|
headers.icmp6h->type = ICMPV6_NEIGHBOUR_ADVERTISEMENT;
|
|
headers.icmp6h->code = 0;
|
|
headers.icmp6h->icmp6body.nghb_adv.router = na_adv_flags.is_router;
|
|
|
|
headers.icmp6h->icmp6body.nghb_adv.solicited = na_adv_flags.na_is_solicited;
|
|
headers.icmp6h->icmp6body.nghb_adv.override = na_adv_flags.override;
|
|
headers.icmp6h->icmp6body.nghb_adv.lladdr.type = 2;
|
|
headers.icmp6h->icmp6body.nghb_adv.lladdr.length = 1;
|
|
|
|
memset( &(headers.icmp6h->icmp6body.nghb_adv.target), 0,
|
|
IPV6_ADDR_LENGTH );
|
|
|
|
if( na_adv_flags.na_is_solicited ) {
|
|
memcpy( &(headers.icmp6h->icmp6body.nghb_adv.target),
|
|
get_ipv6_address(), IPV6_ADDR_LENGTH);
|
|
}
|
|
|
|
memcpy( &(headers.icmp6h->icmp6body.nghb_adv.lladdr.mac),
|
|
get_mac_address(), 6);
|
|
|
|
send_ip (fd, ether_packet + sizeof(struct ethhdr),
|
|
sizeof(struct ip6hdr) + ICMPv6_HEADER_SIZE +
|
|
sizeof(struct neighbour_advertisement));
|
|
|
|
free(ether_packet);
|
|
}
|
|
|
|
/**
|
|
* NET:
|
|
*
|
|
* @param fd socket fd
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
*/
|
|
static int8_t
|
|
handle_na (int fd, uint8_t *packet)
|
|
{
|
|
struct neighbor *n = NULL;
|
|
struct packeth headers;
|
|
ip6_addr_t ip;
|
|
|
|
headers.ethh = (struct ethhdr *) packet;
|
|
headers.ip6h = (struct ip6hdr *) (packet + sizeof(struct ethhdr));
|
|
headers.icmp6h = (struct icmp6hdr *) (packet +
|
|
sizeof(struct ethhdr) +
|
|
sizeof(struct ip6hdr));
|
|
|
|
memcpy(&(ip.addr), &(headers.ip6h->src), IPV6_ADDR_LENGTH);
|
|
|
|
n = find_neighbor(ip);
|
|
|
|
if (!n) {
|
|
n= (struct neighbor *)
|
|
neighbor_create( packet, &headers );
|
|
if (!n)
|
|
return 0;
|
|
if (!neighbor_add(n))
|
|
return 0;
|
|
} else {
|
|
memcpy (&(n->mac), &(headers.ethh->src_mac[0]), 6);
|
|
n->status = NB_REACHABLE;
|
|
if (n->eth_len > 0) {
|
|
struct ethhdr * ethh = (struct ethhdr *) &(n->eth_frame);
|
|
memcpy(ethh->dest_mac, &(n->mac), 6);
|
|
send_ether (fd, &(n->eth_frame), n->eth_len + sizeof(struct ethhdr));
|
|
n->eth_len = 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* NET: Handles ICMPv6 messages
|
|
*
|
|
* @param fd socket fd
|
|
* @param ip6_packet pointer to an IPv6 packet
|
|
* @param packetsize size of ipv6_packet
|
|
*/
|
|
int8_t
|
|
handle_icmpv6 (int fd, struct ethhdr *etherhdr,
|
|
uint8_t *ip6_packet)
|
|
{
|
|
|
|
struct icmp6hdr *received_icmp6 = NULL;
|
|
struct ip6hdr *received_ip6 = NULL;
|
|
struct neighbor target;
|
|
|
|
received_ip6 = (struct ip6hdr *) ip6_packet;
|
|
received_icmp6 = (struct icmp6hdr *) (ip6_packet +
|
|
sizeof(struct ip6hdr));
|
|
memcpy( &(target.ip.addr), &(received_ip6->src),
|
|
IPV6_ADDR_LENGTH );
|
|
memcpy( &(target.mac), etherhdr->src_mac, 6);
|
|
|
|
/* process ICMPv6 types */
|
|
switch(received_icmp6->type) {
|
|
case ICMPV6_NEIGHBOUR_SOLICITATION:
|
|
send_neighbour_advertisement(fd, &target);
|
|
break;
|
|
case ICMPV6_NEIGHBOUR_ADVERTISEMENT:
|
|
handle_na(fd, (uint8_t *) ip6_packet - sizeof(struct ethhdr));
|
|
break;
|
|
case ICMPV6_ROUTER_ADVERTISEMENT:
|
|
handle_ra(received_icmp6, (uint8_t *) received_ip6);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|