Created
June 14, 2016 20:04
-
-
Save goldsborough/275bacff18fbcd373a52a7fad07e3e77 to your computer and use it in GitHub Desktop.
Working IPv6 Neighbor Discovery
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <arpa/inet.h> | |
#include <asm/byteorder.h> | |
#include <assert.h> | |
#include <errno.h> | |
#include <net/ethernet.h> | |
#include <netinet/ether.h> | |
#include <netinet/icmp6.h> | |
#include <netinet/ip6.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/types.h> | |
#include <ctype.h> | |
#include "arguments.h" | |
#include "checksums.h" | |
#include "hexdump.h" | |
#include "raw.h" | |
#define ICMPv6_NEXT_HEADER 0x3A | |
#define IPv6_ETHER_TYPE htons(0x86DD) | |
#define IPv6_VERSION 0x06 | |
#define ICMPv6_ND_CODE 0x00 | |
#define ICMPv6_ND_SOLICIT_TYPE ND_NEIGHBOR_SOLICIT | |
#define ICMPv6_ND_ADVERT_TYPE ND_NEIGHBOR_ADVERT | |
#define ND_OPTION_LENGTH 0x01 | |
#define ND_HOP_LIMIT 0xFF | |
#define IPv6_ADDR_LEN 16 | |
#define RECEIVE_BUFFER_LENGTH 1514 | |
#define IPv6_ADDR_LENGTH sizeof(struct in6_addr) | |
#define MIN_PACKET_LENGTH sizeof(struct icmp6_neighbor_solicit) | |
#define MIN_ICMPv6_PAYLOAD_LENGTH sizeof(struct neighbor_solicit_payload) | |
#define MIN_IPv6_TOTAL_LENGTH \ | |
sizeof(struct ipv6_hdr) + sizeof(struct neighbor_solicit_payload) | |
#define ROUTER_FLAG 0x80 | |
#define SOLICITED_FLAG 0x40 | |
#define OVERRIDE_FLAG 0x20 | |
typedef enum { false, true } bool; | |
typedef uint8_t *ether_addr; | |
#define FAILURE false | |
#define SUCCESS true | |
/* Extracted from /usr/include/linux/if_ethernet.h (just for reference): | |
* #define ETH_ALEN 6 Octets in one ethernet addr | |
* | |
* Extracted from /usr/include/net/ethernet.h (just for reference): | |
* struct ether_header | |
* { | |
* u_int8_t ether_dhost[ETH_ALEN]; destination eth addr | |
* u_int8_t ether_shost[ETH_ALEN]; source ether addr | |
* u_int16_t ether_type; packet type ID field | |
* } | |
*/ | |
/* Extracted from /usr/include/netinet/in.h (just for reference): | |
* struct in6_addr | |
* { | |
* union | |
* { | |
* uint8_t __u6_addr8[16]; | |
* #ifdef __USE_MISC | |
* uint16_t __u6_addr16[8]; | |
* uint32_t __u6_addr32[4]; | |
* #endif | |
* } __in6_u; | |
* #define s6_addr __in6_u.__u6_addr8 | |
* #ifdef __USE_MISC | |
* # define s6_addr16 __in6_u.__u6_addr16 | |
* # define s6_addr32 __in6_u.__u6_addr32 | |
* #endif | |
* }; | |
* | |
* "hdr->s6_addr" will work as access. | |
*/ | |
/* | |
* We do not use the kernel's definition of the IPv6 header (struct ipv6hdr) | |
* because the definition there is slightly different from what we would | |
* expect | |
* (the problem is the 20bit flow label - 20bit is brain-damaged). | |
* | |
* Instead, we provide you struct that directly maps to the RFCs and lecture | |
* slides below. | |
*/ | |
struct ipv6_hdr { | |
#if defined(__LITTLE_ENDIAN_BITFIELD) | |
uint32_t tc1 : 4, version : 4, flow_label1 : 4, tc2 : 4, flow_label2 : 16; | |
#elif defined(__BIG_ENDIAN_BITFIELD) | |
uint32_t version : 4, tc1 : 4, tc2 : 4, flow_label1 : 4, flow_label2 : 16; | |
#else | |
#error "You did something wrong" | |
#endif | |
uint16_t plen; | |
uint8_t nxt; | |
uint8_t hlim; | |
struct in6_addr src; | |
struct in6_addr dst; | |
} __attribute__((packed)); | |
/*====================================TODO===================================*/ | |
/* First, declare struct useful for package dissection, i.e., a struct | |
* containing pointers to the various headers and header fields that will be | |
* set by your dissector (the function that tests whether or not a received | |
* frame/packet is valid). | |
*/ | |
/* | |
* It is very useful to look into the files at /usr/include/netinet for | |
* existing structs that may help to create these. Also have a look at | |
* assignment2.c, e.g. 'struct wol'. | |
*/ | |
struct icmp6_header { | |
uint8_t type; | |
uint8_t code; | |
uint16_t checksum; | |
} __attribute__((packed));; | |
struct icmp_option_header { | |
uint8_t type; | |
uint8_t length; | |
} __attribute__((packed));; | |
struct nd_option { | |
struct icmp_option_header header; | |
uint8_t link_address[ETH_ALEN]; | |
} __attribute__((packed));; | |
/* This struct should contain the icmp part of your message (ip-payload) */ | |
struct neighbor_solicit_payload { | |
struct icmp6_header icmp6; | |
uint32_t reserved; | |
struct in6_addr target_address; | |
struct nd_option option; | |
} __attribute__((packed)); | |
/* This struct should reserve enough space for the complete packet */ | |
struct icmp6_neighbor_solicit { | |
struct ether_header ether; | |
struct ipv6_hdr ip; | |
struct neighbor_solicit_payload payload; | |
} __attribute__((packed)); | |
/* This struct should contain the icmp part of the advertisement (ip-payload) */ | |
struct neighbor_advertise_payload { | |
struct icmp6_header icmp6; | |
uint8_t flags; | |
uint8_t reserved [3]; | |
struct in6_addr target_address; | |
uint8_t options []; | |
} __attribute__((packed)); | |
/* This struct should reserve enough space for the complete packet */ | |
struct icmp6_neighbor_advertise { | |
struct ether_header ether; | |
struct ipv6_hdr ip; | |
struct neighbor_advertise_payload payload; | |
} __attribute__((packed)); | |
/** | |
* Checks a flag in the ICMPv6 payload's flag set. | |
* | |
* @param payload The NDP advertisement payload. | |
* @param flag The flag to check. | |
* | |
* @return The boolean value of the flag. | |
* | |
*/ | |
bool is_set(const struct neighbor_advertise_payload* payload, int flag) { | |
return payload->flags & flag; | |
} | |
/** | |
* Determines the number of neighbor discovery options in an IMCPv6 Payload. | |
* | |
* @param packet The neighbor advertisement packet. | |
* | |
* @return The number of NDP options. | |
* | |
*/ | |
int get_nd_option_bytes(struct icmp6_neighbor_advertise* packet) { | |
// Note that the sizeof operator evaluated on a struct with a flexible | |
// array member (empty []) gives the size of the struct *up to the array* | |
// i.e. the offset of the array pointer. This is precisely what we want here, | |
// since plen is the whole size of the payload. | |
return ntohs(packet->ip.plen) - sizeof(struct neighbor_advertise_payload); | |
} | |
/** | |
* Tests whether a character is one of the allowed | |
* hexadecimal alphabetic characters ([a-f]). | |
* | |
* @param character The character to check. | |
* | |
* @return true if the character is in [a-f], else false. | |
*/ | |
bool ishexalpha(char character) { | |
return character >= 'a' && character <= 'z'; | |
} | |
/** | |
* Checks if a character is part of the hexadecimal alphabet ([0-9a-f]). | |
* | |
* @param character The character to check. | |
* | |
* @return true if the character is hexadecimal, else false. | |
*/ | |
bool ishex(char character) { | |
return isdigit(character) || ishexalpha(character); | |
} | |
/** | |
* Lowers both characters of an octet. | |
* | |
* @param A pointer to an array of two characters to lower. | |
*/ | |
void make_octet_lower(char *chars) { | |
chars[0] = tolower(chars[0]); | |
chars[1] = tolower(chars[1]); | |
} | |
/** | |
* Parses a hexadecimal ASCII digit into the | |
* corresponding hexadecimal number. | |
* | |
* E.g. 'a' => 10, '5' => 5 | |
* | |
* @param character The ASCII character to parse. | |
* | |
* @return The numeric value of the hexadecimal value | |
* represented by the character. | |
*/ | |
uint8_t parse_hex_digit(char character) { | |
if (isdigit(character)) { | |
return character - '0'; | |
} else { | |
return character - 'a' + 10; | |
} | |
} | |
/** | |
* Parses the two hex digits (as ASCII characters) pointed | |
* to by octet into a singe byte value. | |
* | |
* @param octet The two hex digits to conver to a byte. | |
* | |
* @return The corresponding byte. | |
*/ | |
int parse_octet(char *octet) { | |
uint8_t result; | |
make_octet_lower(octet); | |
if (!ishex(octet[0]) || !ishex(octet[1])) return -1; | |
// Grab lower nibble | |
result = parse_hex_digit(octet[1]); | |
// Grab upper nibble | |
result |= parse_hex_digit(octet[0]) << 4; | |
return result; | |
} | |
/* | |
* Counts the number of empty groups in an IPv6 address. | |
* E.g. in a::b the number of empty groups would be six. | |
* | |
* @param ip The IP address to count the number of empty groups in. | |
* | |
* @return The number of empty groups in the IP address. | |
*/ | |
int count_groups(const char* ip) { | |
int count = 0; | |
bool has_empty_groups = false; | |
for (; *ip != '\0'; ++ip) { | |
if(*ip == ':') { | |
if (*(ip + 1) == ':') { | |
// Should only come here once | |
if (has_empty_groups) return -1; | |
has_empty_groups = true; | |
} | |
++count; | |
} | |
} | |
// If we didn't count any empty groups, we expect 7 | |
if (!has_empty_groups && count != 7) return -1; | |
// For 8 groups we can have at most 7 delimiters | |
if (count > 7) return -1; | |
return count; | |
} | |
/* | |
* This function parses the ip address from a string into an network byte order | |
* representation | |
* | |
* @param dst_ip A pointer to a struct in6_addr where the ip will be written to | |
* @param ipaddr The ip address as null terminated string | |
* | |
* @return 1 on success | |
* 0 on error (NOTE: I changed this to have a uniform FAILURE code) | |
*/ | |
bool parse_ip(const char *original_ip_addr, struct in6_addr *dst_ip) { | |
size_t index = 0; | |
char ip[INET6_ADDRSTRLEN]; | |
char* token = ip; | |
char* colon; | |
int number_of_groups; | |
if (original_ip_addr == NULL || dst_ip == NULL) return -1; | |
// strok will modify our IP address | |
strcpy(ip, original_ip_addr); | |
// Set all to zero first so we only have | |
// to fill in the non-empty groups | |
memset(dst_ip->s6_addr, 0, IPv6_ADDR_LEN); | |
if ((number_of_groups = count_groups(original_ip_addr)) == -1) { | |
fprintf(stderr, "'%s' is not a valid IPv6 address!\n", original_ip_addr); | |
} | |
for (colon = strchr(ip, ':'); true; colon = strchr(colon, ':')) { | |
char group[4]; | |
int octet; | |
int length; | |
// If we just found an empty group and are not at the start | |
// then this is the empty group in the middle, i.e. the :: | |
if (token == colon && token != ip) { | |
// Skip all the empty groups (note we set everything to zero | |
// first so this is OK). It even deals well with the invalid | |
// case of compressing a single 0 field, i.e. a:b:c::e:f:g:h, | |
// which is actually invalid. That group would simply be skipped. | |
index += (7 - number_of_groups + 1) * 2; | |
} else { | |
if (colon) *colon = '\0'; | |
// Pad with zero (characters!) first | |
memset(group, '0', 4); | |
// Copy the rest of the group into the upper chars of | |
// the group string (so that lower zeros are taken into account) | |
// E.g. ff => 00ff | |
length = strlen(token); | |
strncpy(group + (4 - length), token, length); | |
// Parse each group of two hex digits into one octet | |
if ((octet = parse_octet(group)) == -1) { | |
return FAILURE; | |
} else { | |
dst_ip->s6_addr[index++] = (uint8_t)octet; | |
} | |
// Next octet in the group | |
if ((octet = parse_octet(group + 2)) == -1) { | |
return FAILURE; | |
} else { | |
dst_ip->s6_addr[index++] = (uint8_t)octet; | |
} | |
} | |
// colon was at the : (now the \0), so set | |
// it one passed that for the next token | |
// (only if colon is not null, then we break now) | |
if (colon) token = ++colon; | |
else break; | |
} | |
return SUCCESS; | |
} | |
/* | |
* This function initializes an ethernet header | |
* | |
* @param hdr A pointer to the ethernet header | |
* @param dst_mac A pointer to a buffer that contains the destination mac | |
* @param src_mac A pointer to a buffer that contains the mac of this host | |
* @param ethertype The ethernet type in host byte order | |
*/ | |
void init_ether_header(struct ether_header *hdr, | |
const ether_addr dst_mac, | |
const ether_addr src_mac, | |
uint16_t ethertype) { | |
memcpy(hdr->ether_dhost, dst_mac, ETH_ALEN); | |
memcpy(hdr->ether_shost, src_mac, ETH_ALEN); | |
hdr->ether_type = ethertype; | |
} | |
/* | |
* This function initializes an ipv6 header | |
* This encompasses the version and everything given as parameter | |
* | |
* @param hdr A pointer to the ip header | |
* @param dst_ip A pointer to a buffer that contains the destination ip | |
* @param src_ip A pointer to a buffer that contains the ip of this host | |
* @param next The next hop field for this header | |
* @param plen The size of the ip payload in host byte order | |
* @param hoplimit The hoplimit for the ip packet | |
*/ | |
void init_ip6_header(struct ipv6_hdr *hdr, | |
const struct in6_addr *dst_ip, | |
const struct in6_addr *src_ip, | |
uint8_t next, | |
uint16_t plen, | |
uint8_t hoplimit) { | |
hdr->version = IPv6_VERSION; | |
hdr->tc1 = hdr->tc2 = 0x00; | |
hdr->flow_label1 = hdr->flow_label2 = 0x00; | |
hdr->plen = plen; | |
hdr->nxt = next; | |
hdr->hlim = hoplimit; | |
hdr->src = *src_ip; | |
hdr->dst = *dst_ip; | |
} | |
void init_icmp6_header(struct icmp6_header *header) { | |
// ND_NEIGHBOR_SOLICIT = 0x87 (from netinet/icmp6.h) | |
header->type = ICMPv6_ND_SOLICIT_TYPE; | |
header->code = ICMPv6_ND_CODE; | |
} | |
void init_icmp6_checksum(struct ipv6_hdr *ip_header, | |
struct neighbor_solicit_payload* payload) { | |
payload->icmp6.checksum = icmp6_checksum((struct ip6_hdr*)ip_header, | |
(uint8_t*)payload, | |
sizeof(struct neighbor_solicit_payload)); | |
} | |
void init_nd_option(struct nd_option *option, | |
const ether_addr source_link_address) { | |
// ND_OPT_SOURCE_LINKADDR = 0x01 (from netinet/icmp6.h) | |
option->header.type = ND_OPT_SOURCE_LINKADDR; | |
// Length is set to 0x01, but measured in 8 bytes | |
option->header.length = ND_OPTION_LENGTH; | |
// The 48-bit/6-byte source link (MAC) address | |
memcpy(option->link_address, source_link_address, ETH_ALEN); | |
} | |
/* | |
* This function initializes the icmpv6 header and the neighbor discovery | |
* payload | |
* | |
* @param payload A pointer to the buffer in which to initialize | |
* @param dst_ip A pointer to a buffer that contains the destination ip | |
* @param src_mac A pointer to a buffer that contains the mac of this host | |
*/ | |
void init_icmp6_ndisc(struct neighbor_solicit_payload *payload, | |
const struct in6_addr *dst_ip, | |
const ether_addr src_mac) { | |
init_icmp6_header(&payload->icmp6); | |
payload->reserved = 0x00; | |
payload->target_address = *dst_ip; | |
init_nd_option(&payload->option, src_mac); | |
} | |
void init_packet(struct icmp6_neighbor_solicit *packet, | |
const ether_addr dst_mac, | |
const ether_addr src_mac, | |
const struct in6_addr *solicited_dst_ip, | |
const struct in6_addr *dst_ip, | |
const struct in6_addr *src_ip) { | |
memset(packet, 0, sizeof(*packet)); | |
init_ether_header(&packet->ether, dst_mac, src_mac, IPv6_ETHER_TYPE); | |
init_ip6_header(&packet->ip, | |
solicited_dst_ip, | |
src_ip, | |
ICMPv6_NEXT_HEADER, | |
htons(MIN_ICMPv6_PAYLOAD_LENGTH), | |
ND_HOP_LIMIT); | |
init_icmp6_ndisc(&packet->payload, dst_ip, src_mac); | |
init_icmp6_checksum(&packet->ip, &packet->payload); | |
} | |
/* | |
* This function checks whether the ethernet header may be for a neighbor | |
* advertisement which is sent as response to our neighbor discovery | |
* | |
* @param hdr A pointer to the header | |
* @param len The length of the packet reported by read | |
* @param mymac A pointer to a buffer which contains the mac of this host | |
* | |
* @return 0 on fail | |
* 1 on success | |
*/ | |
int is_my_ether_header(struct ether_header *hdr, | |
size_t len, | |
const ether_addr mymac) { | |
if (len < MIN_PACKET_LENGTH) return FAILURE; | |
// Should be an IPv6 ethertype | |
if (hdr->ether_type != IPv6_ETHER_TYPE) return FAILURE; | |
// Destination should be our MAC Address | |
if (memcmp(hdr->ether_dhost, mymac, ETH_ALEN) != 0) return FAILURE; | |
return SUCCESS; | |
} | |
/* | |
* This function checks whether the ip header is a valid ipv6 header and if | |
* it might be a neighbor advertisement sent in response to our neighbor | |
* discovery | |
* | |
* @param hdr A pointer to the ethernet payload | |
* @param len The length of the *packet* reported by read | |
* @param myip A pointer to a buffer containing the ip address of this host | |
* @param dst_ip A pointer to a buffer containing the ip address of our target | |
* | |
* @return 0 on fail | |
* 1 on success | |
*/ | |
int is_my_ip6_header(struct ipv6_hdr *hdr, | |
size_t len, | |
const struct in6_addr *myip, | |
const struct in6_addr *dst_ip) { | |
// Length of the whole IPv6 packet should match | |
if (len < MIN_IPv6_TOTAL_LENGTH) return FAILURE; | |
if (hdr->version != IPv6_VERSION) return FAILURE; | |
if (hdr->nxt != ICMPv6_NEXT_HEADER) return FAILURE; | |
// The payload length should match for an ICMPv6 + ND Option packet | |
if (ntohs(hdr->plen) < MIN_ICMPv6_PAYLOAD_LENGTH) return FAILURE; | |
// The icmp6 payload can have more than one option, but then | |
// the payload length minus all the non-option stuff (e.g. icmp6 header) | |
// should be in multiples of 8 byte. Note that sizeof() the advertise | |
// message does not measure the options, because they are a flexible array | |
if (ntohs(hdr->plen) - sizeof(struct neighbor_advertise_payload) % 8 != 0) | |
// Hop Limit is fixed as 0xFF for neighbor discovery | |
if (hdr->hlim != ND_HOP_LIMIT) return FAILURE; | |
// Check the IPv6 addresses | |
// Source should be the address of the host we wanted the MAC address for | |
if ((memcmp(&hdr->src, dst_ip, sizeof(struct in6_addr))) != 0) return FAILURE; | |
// Destination should be our address | |
if ((memcmp(&hdr->dst, myip, sizeof(struct in6_addr))) != 0) return FAILURE; | |
return SUCCESS; | |
} | |
/* | |
* Checks the ICMPv6 header of the packet's payload, taking care of | |
* the type, code and especially the checksum of the header. | |
* | |
* @param packet The ND advertisement packet. | |
* @param payload The advertisement payload (header + nd option). | |
* @param len The length of the *payload*. | |
* | |
* @return 0 on fail | |
* 1 on success | |
*/ | |
int is_my_icmp6_header(struct icmp6_neighbor_advertise* packet, | |
struct neighbor_advertise_payload* payload, | |
size_t len) { | |
int checksum; | |
int expected_checksum; | |
// Length of the ICMPv6 payload should be at least this | |
if (len < MIN_ICMPv6_PAYLOAD_LENGTH) return FAILURE; | |
// Type should be for an ND Advertisement | |
// ND_NEIGHBOR_ADVERT = 0x88 (from netinet/icmp6.h) | |
if (payload->icmp6.type != ICMPv6_ND_ADVERT_TYPE) return FAILURE; | |
if (payload->icmp6.code != ICMPv6_ND_CODE) return FAILURE; | |
// Need to store the checksum first, then set to null, then recompute it | |
checksum = payload->icmp6.checksum; | |
payload->icmp6.checksum = 0; | |
expected_checksum = icmp6_checksum((struct ip6_hdr*)&packet->ip, | |
(uint8_t*)payload, | |
len); | |
if (checksum != expected_checksum) return FAILURE; | |
return SUCCESS; | |
} | |
/** | |
* | |
* Checks the options of a received ICMPv6 payload. | |
* | |
* @param packet The ND advertisement packet. | |
* @param option An output pointer for the correct option (if there are many options). | |
* | |
*/ | |
int is_my_nd_option(struct icmp6_neighbor_advertise* packet, | |
struct nd_option** option) { | |
uint8_t* iterator; | |
uint8_t* end; | |
int option_bytes; | |
option_bytes = get_nd_option_bytes(packet); | |
// Should be multiples of 8 | |
if (option_bytes % 8 != 0) return FAILURE; | |
iterator = packet->payload.options; | |
end = iterator + option_bytes; | |
while (iterator < end) { | |
struct icmp_option_header* header = (struct icmp_option_header*)iterator; | |
// Won't be able to parse the other options if we don't | |
// know where they start because we don't know when this | |
// option ends | |
if ((header->length * 8) > option_bytes) return FAILURE; | |
// ND_OPT_TARGET_LINKADDR = 0x02 (from netinet/icmp6.h) | |
if (header->type == ND_OPT_TARGET_LINKADDR) { | |
// Length should 1, measured in 8 bytes. | |
if (header->length == ND_OPTION_LENGTH) { | |
// The advertised MAC address and the source MAC | |
// of the ethernet header should match | |
uint8_t* first = iterator + sizeof(struct icmp_option_header); | |
uint8_t* second = packet->ether.ether_shost; | |
if (memcmp(first, second, ETH_ALEN) == 0) { | |
*option = (struct nd_option*)iterator; | |
return SUCCESS; | |
} | |
} | |
} | |
iterator += (header->length * 8); | |
} | |
// No suitable option found | |
return FAILURE; | |
} | |
/** | |
* This function checks whether the ip payload is a | |
* neighbor advertisement sent to us. | |
* | |
* @param payload A pointer to the icmp payload | |
* @param len The length of the icmp payload | |
* @param dstip A pointer to a buffer containing the target ip | |
* @param option An output pointer for the correct option (if there are many options). | |
* | |
* @return 0 on fail | |
* 1 on success | |
*/ | |
int is_my_neighbor_advertisement(struct icmp6_neighbor_advertise* packet, | |
size_t len, | |
const struct in6_addr *dstip, | |
struct nd_option** option) { | |
struct neighbor_advertise_payload* payload = &packet->payload; | |
if (!is_my_icmp6_header(packet, payload, len)) return FAILURE; | |
// Ensure the advertisement is a response to our solicitation | |
if (!is_set(payload, SOLICITED_FLAG)) return FAILURE; | |
// Ensure the target IP Address is the one who's solicited node | |
// multicast group we sent the packet to. | |
if (memcmp(&payload->target_address, dstip, IPv6_ADDR_LENGTH) != 0) { | |
return FAILURE; | |
} | |
if (!is_my_nd_option(packet, option)) return FAILURE; | |
return SUCCESS; | |
} | |
/* | |
* This function checks if the frame received is a neighbor advertisement sent | |
*as | |
* response to our neighbor discovery. If it is, it returns a pointer to the mac | |
* address in the frame. | |
* It calls the more specialized functions in succession to check their parts | |
* and does tests that have to be with access to multiple layers | |
* (i.e. checksum, matching macs) | |
* | |
* @param buffer A pointer to the received frame | |
* @param len The size of the received packet | |
* @param mymac A pointer to a buffer which contains the mac of this host | |
* @param myip6 A pointer to a buffer which contains the ip of this host | |
* @param dstip A pointer to a buffer which contains the destination ip | |
* | |
* @return null on fail | |
* A pointer to the destination mac address sent with the neighbor | |
* advertisement | |
*/ | |
ether_addr retrieve_mac(uint8_t *buffer, | |
ssize_t len, | |
const ether_addr mymac, | |
const struct in6_addr *myip6, | |
const struct in6_addr *dstip) { | |
struct nd_option* option; | |
struct icmp6_neighbor_advertise *packet = | |
(struct icmp6_neighbor_advertise *)buffer; | |
if (!is_my_ether_header(&packet->ether, len, mymac)) { | |
return NULL; | |
} | |
len -= sizeof(struct ether_header); | |
if (!is_my_ip6_header(&packet->ip, len, myip6, dstip)) { | |
return NULL; | |
} | |
len -= sizeof(struct ipv6_hdr); | |
// Note that we pass the option as an output parameter, it will | |
// then store the option that contains the requested link address | |
// for the case that there are many options (and if the advertisement | |
// is valid at all). | |
if (!is_my_neighbor_advertisement(packet, len, dstip, &option)) { | |
return NULL; | |
} | |
// Wooh! | |
return option->link_address; | |
} | |
void get_solicited_ip(const struct in6_addr *ip, struct in6_addr *solicited) { | |
size_t index = 0; | |
// ff02: | |
solicited->s6_addr[index++] = 0xff; | |
solicited->s6_addr[index++] = 0x02; | |
// 8 bytes (4 groups) for the :: | |
memset(solicited->s6_addr + index, 0, 8); | |
index += 8; | |
// :0001 | |
solicited->s6_addr[index++] = 0x00; | |
solicited->s6_addr[index++] = 0x01; | |
// :ff | |
solicited->s6_addr[index++] = 0xff; | |
// Lower 24 bits (3 bytes) of the IPv6 Address | |
memcpy(solicited->s6_addr + index, ip->s6_addr + index, 3); | |
} | |
void get_solicited_mac(const struct in6_addr *ip, ether_addr mac) { | |
// IPv6 multicast prefix | |
mac[0] = mac[1] = 0x33; | |
// The last 4 bytes of the solicited node IP address | |
memcpy(mac + 2, ip->s6_addr + 12, 4); | |
} | |
void send_packet(int fd, const struct icmp6_neighbor_solicit *packet, size_t length) { | |
hexdump(packet, sizeof(*packet)); | |
if (grnvs_write(fd, packet, length) < 0) { | |
fprintf(stderr, "grnvs_write() failed: %s\n", strerror(errno)); | |
} | |
} | |
ether_addr | |
read_mac(int fd, uint8_t* recbuffer, const ether_addr mymac, const struct in6_addr *sip6, const struct in6_addr *dip6, unsigned int timeout) { | |
ssize_t ret; | |
ether_addr mac; | |
while ((ret = grnvs_read(fd, recbuffer, RECEIVE_BUFFER_LENGTH, &timeout))) { | |
if ((mac = retrieve_mac(recbuffer, ret, mymac, sip6, dip6))) { | |
break; | |
} | |
} | |
if (ret == 0) { | |
/* | |
* This should go to stdout because the tester uses this to | |
* determine a graceful shutdown. Do NOT change this | |
*/ | |
fprintf(stdout, "Timeout\n"); | |
return NULL; | |
} | |
return mac; | |
} | |
/*====================================TODO===================================*/ | |
/* | |
*TODO: Print the retrieved mac address. | |
* The format MUST strictly adhere to the following rule: | |
* | |
* <IP-Address> is at <mac> | |
* | |
* The IP-address MAY be shortened, the mac MUST be 2 characters for | |
* each byte. The result MUST be printed on stdout. Don't forget the | |
* newline! | |
* | |
* Example output: | |
* ::1 is at 01:02:03:04:05:06 | |
*/ | |
/*===========================================================================*/ | |
void print_mac(const char *ip, ether_addr mac) { | |
printf("%s is at %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", | |
ip, | |
(unsigned)mac[0], | |
(unsigned)mac[1], | |
(unsigned)mac[2], | |
(unsigned)mac[3], | |
(unsigned)mac[4], | |
(unsigned)mac[5]); | |
} | |
void assignment3(int fd, const char *ipaddr, const int timeoutval) { | |
// Buffer for receiving packets | |
uint8_t recbuffer[RECEIVE_BUFFER_LENGTH]; | |
// Returned target MAC | |
ether_addr mac; | |
struct icmp6_neighbor_solicit packet; | |
size_t length = sizeof(packet); | |
struct in6_addr dip6; | |
const struct in6_addr *sip6 = grnvs_get_ip6addr(fd); | |
struct in6_addr solicited_dip6; | |
// Buffer for solicited node multicast mac | |
uint8_t dstmac[ETH_ALEN]; | |
const ether_addr mymac = (const ether_addr)grnvs_get_hwaddr(fd); | |
if (parse_ip(ipaddr, &dip6) == FAILURE) { | |
fprintf(stderr, | |
"Wrong input format for destination address, " | |
"input should be in format: a:b:c::1:2:3\n"); | |
return; | |
} | |
get_solicited_ip(&dip6, &solicited_dip6); | |
get_solicited_mac(&solicited_dip6, dstmac); | |
init_packet(&packet, dstmac, mymac, &solicited_dip6, &dip6, sip6); | |
send_packet(fd, &packet, length); | |
mac = read_mac(fd, recbuffer, mymac, sip6, &dip6, timeoutval * 1000); | |
if (mac) print_mac(ipaddr, mac); | |
} | |
int main(int argc, char **argv) { | |
struct arguments args; | |
int sock; | |
if (parse_args(&args, argc, argv) < 0) { | |
fprintf(stderr, | |
"Failed to parse arguments, call with " | |
"--help for more information\n"); | |
return -1; | |
} | |
if ((sock = grnvs_open(args.interface, SOCK_RAW)) < 0) { | |
fprintf(stderr, "grnvs_open() failed: %s\n", strerror(errno)); | |
return -1; | |
} | |
assignment3(sock, args.dst, args.timeout); | |
grnvs_close(sock); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Is there a possibility to make the remaining files available?