488 lines
16 KiB
C
488 lines
16 KiB
C
|
#include "stack-ndpv6.h"
|
||
|
#include "proto-preprocess.h"
|
||
|
#include "stack-src.h"
|
||
|
#include "util-checksum.h"
|
||
|
#include "rawsock-adapter.h"
|
||
|
#include "rawsock.h"
|
||
|
#include "util-logger.h"
|
||
|
#include <string.h>
|
||
|
|
||
|
|
||
|
|
||
|
static inline void _append(unsigned char *buf, size_t *r_offset, size_t max, unsigned x)
|
||
|
{
|
||
|
if (*r_offset >= max)
|
||
|
return;
|
||
|
buf[(*r_offset)++] = (unsigned char)x;
|
||
|
}
|
||
|
static inline void _append_bytes(unsigned char *buf, size_t *r_offset, size_t max, const void *v_bytes, size_t len)
|
||
|
{
|
||
|
const unsigned char *bytes = (const unsigned char *)v_bytes;
|
||
|
if (*r_offset + len >= max)
|
||
|
return;
|
||
|
memcpy(buf + *r_offset, bytes, len);
|
||
|
*r_offset += len;
|
||
|
}
|
||
|
|
||
|
static inline void
|
||
|
_append_short(unsigned char *buf, size_t *offset, size_t max, unsigned num)
|
||
|
{
|
||
|
if (2 > max - *offset) {
|
||
|
*offset = max;
|
||
|
return;
|
||
|
}
|
||
|
buf[(*offset)++] = (unsigned char)(num>>8);
|
||
|
buf[(*offset)++] = (unsigned char)(num & 0xFF);
|
||
|
}
|
||
|
|
||
|
static inline unsigned
|
||
|
_read_byte(const unsigned char *buf, size_t *offset, size_t max)
|
||
|
{
|
||
|
if (*offset + 1 < max) {
|
||
|
return buf[(*offset)++];
|
||
|
} else
|
||
|
return (unsigned)~0;
|
||
|
}
|
||
|
static inline unsigned
|
||
|
_read_short(const unsigned char *buf, size_t *offset, size_t max)
|
||
|
{
|
||
|
if (*offset + 2 <= max) {
|
||
|
unsigned result;
|
||
|
result = buf[(*offset)++] << 8;
|
||
|
result |= buf[(*offset)++];
|
||
|
return result;
|
||
|
} else
|
||
|
return (unsigned)~0;
|
||
|
}
|
||
|
|
||
|
static inline unsigned
|
||
|
_read_number(const unsigned char *buf, size_t *offset, size_t max)
|
||
|
{
|
||
|
if (*offset + 4 <= max) {
|
||
|
unsigned result;
|
||
|
result = buf[(*offset)++] << 24;
|
||
|
result |= buf[(*offset)++] << 16;
|
||
|
result |= buf[(*offset)++] << 8;
|
||
|
result |= buf[(*offset)++];
|
||
|
return result;
|
||
|
} else
|
||
|
return (unsigned)~0;
|
||
|
}
|
||
|
|
||
|
static inline ipv6address_t
|
||
|
_read_ipv6(const unsigned char *buf, size_t *offset, size_t max)
|
||
|
{
|
||
|
ipv6address_t result = {0,0};
|
||
|
|
||
|
if (*offset + 16 <= max) {
|
||
|
result = ipv6address_from_bytes(buf + *offset);
|
||
|
*offset += 16;
|
||
|
} else {
|
||
|
*offset = max;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Handle the IPv6 Neighbor Solicitation request.
|
||
|
* This happens after we've transmitted a packet, a response is on
|
||
|
* it's way back, and the router needs to give us the response
|
||
|
* packet. The router sends us a solicitation, like an ARP request,
|
||
|
* to which we must respond.
|
||
|
*/
|
||
|
int
|
||
|
stack_ndpv6_incoming_request(struct stack_t *stack, struct PreprocessedInfo *parsed, const unsigned char *px, size_t length)
|
||
|
{
|
||
|
struct PacketBuffer *response = 0;
|
||
|
size_t offset;
|
||
|
size_t remaining;
|
||
|
ipaddress target_ip;
|
||
|
const unsigned char *target_ip_buf;
|
||
|
macaddress_t target_mac = stack->source_mac;
|
||
|
unsigned xsum;
|
||
|
unsigned char *buf2;
|
||
|
static const size_t max = sizeof(response->px);
|
||
|
size_t offset_ip = parsed->ip_offset;
|
||
|
size_t offset_ip_src = offset_ip + 8; /* offset in packet to the source IPv6 address */
|
||
|
size_t offset_ip_dst = offset_ip + 24;
|
||
|
size_t offset_icmpv6 = parsed->transport_offset;
|
||
|
|
||
|
/* Verify it's a "Neighbor Solitication" opcode */
|
||
|
if (parsed->opcode != 135)
|
||
|
return -1;
|
||
|
|
||
|
/* Make sure there's at least a full header */
|
||
|
offset = parsed->transport_offset;
|
||
|
remaining = length - offset;
|
||
|
if (remaining < 24)
|
||
|
return -1;
|
||
|
|
||
|
/* Make sure it's looking for our own address */
|
||
|
target_ip_buf = px + offset + 8;
|
||
|
target_ip.version = 6;
|
||
|
target_ip.ipv6 = ipv6address_from_bytes(target_ip_buf);
|
||
|
if (!is_my_ip(stack->src, target_ip))
|
||
|
return -1;
|
||
|
|
||
|
/* Print a log message */
|
||
|
{
|
||
|
ipv6address_t a = ipv6address_from_bytes(px + offset_ip_src);
|
||
|
ipaddress_formatted_t fmt1 = ipv6address_fmt(a);
|
||
|
ipaddress_formatted_t fmt2 = ipaddress_fmt(target_ip);
|
||
|
LOG(1, "[+] received NDP request from %s for %s\n", fmt1.string, fmt2.string);
|
||
|
}
|
||
|
|
||
|
/* Get a buffer for sending the response packet. This thread doesn't
|
||
|
* send the packet itself. Instead, it formats a packet, then hands
|
||
|
* that packet off to a transmit thread for later transmission. */
|
||
|
response = stack_get_packetbuffer(stack);
|
||
|
if (response == NULL)
|
||
|
return -1;
|
||
|
|
||
|
|
||
|
/* Use the request packet as a template for the response */
|
||
|
memcpy(response->px, px, length);
|
||
|
buf2 = response->px;
|
||
|
|
||
|
/* Set the destination MAC address and destination IPv6 address*/
|
||
|
memcpy(buf2 + 0, px + 6, 6);
|
||
|
memcpy(buf2 + offset_ip_dst, px + offset_ip_src, 16);
|
||
|
|
||
|
/* Set the source MAC address and source IPv6 address */
|
||
|
memcpy(buf2 + offset_ip_src, target_ip_buf, 16);
|
||
|
memcpy(buf2 + 6, target_mac.addr, 6);
|
||
|
|
||
|
/* Format the response */
|
||
|
_append(buf2, &offset, max, 136); /* type */
|
||
|
_append(buf2, &offset, max, 0); /* code */
|
||
|
_append(buf2, &offset, max, 0); /*checksum[hi] */
|
||
|
_append(buf2, &offset, max, 0); /*checksum[lo] */
|
||
|
_append(buf2, &offset, max, 0x60); /* flags*/
|
||
|
_append(buf2, &offset, max, 0);
|
||
|
_append(buf2, &offset, max, 0);
|
||
|
_append(buf2, &offset, max, 0);
|
||
|
_append_bytes(buf2, &offset, max, target_ip_buf, 16);
|
||
|
_append(buf2, &offset, max, 2);
|
||
|
_append(buf2, &offset, max, 1);
|
||
|
_append_bytes(buf2, &offset, max, target_mac.addr, 6);
|
||
|
|
||
|
xsum = checksum_ipv6( buf2 + offset_ip_src,
|
||
|
buf2 + offset_ip_dst,
|
||
|
58,
|
||
|
offset - offset_icmpv6,
|
||
|
buf2 +offset_icmpv6);
|
||
|
buf2[offset_icmpv6 + 2] = (unsigned char)(xsum >> 8);
|
||
|
buf2[offset_icmpv6 + 3] = (unsigned char)(xsum >> 0);
|
||
|
|
||
|
/* Transmit the packet-buffer */
|
||
|
response->length = offset;
|
||
|
stack_transmit_packetbuffer(stack, response);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static int
|
||
|
_extract_router_advertisement(
|
||
|
const unsigned char *buf,
|
||
|
size_t length,
|
||
|
struct PreprocessedInfo *parsed,
|
||
|
ipv6address my_ipv6,
|
||
|
ipv6address *router_ip,
|
||
|
macaddress_t *router_mac)
|
||
|
{
|
||
|
size_t offset;
|
||
|
int is_same_prefix = 1;
|
||
|
int is_mac_explicit = 0;
|
||
|
|
||
|
if (parsed->ip_version != 6)
|
||
|
return 1;
|
||
|
|
||
|
*router_ip = parsed->src_ip.ipv6;
|
||
|
|
||
|
if (parsed->ip_protocol != 58)
|
||
|
return 1;
|
||
|
offset = parsed->transport_offset;
|
||
|
|
||
|
/* type = Router Advertisement */
|
||
|
if (_read_byte(buf, &offset, length) != 134)
|
||
|
return 1;
|
||
|
|
||
|
/* code = 0 */
|
||
|
if (_read_byte(buf, &offset, length) != 0)
|
||
|
return 1;
|
||
|
|
||
|
/* checksum */
|
||
|
_read_short(buf, &offset, length);
|
||
|
|
||
|
/* hop limit */
|
||
|
_read_byte(buf, &offset, length);
|
||
|
|
||
|
/* flags */
|
||
|
_read_byte(buf, &offset, length);
|
||
|
|
||
|
/* router life time */
|
||
|
_read_short(buf, &offset, length);
|
||
|
|
||
|
/* reachable time */
|
||
|
_read_number(buf, &offset, length);
|
||
|
|
||
|
/* retrans timer */
|
||
|
_read_number(buf, &offset, length);
|
||
|
|
||
|
while (offset + 8 <= length) {
|
||
|
unsigned type = buf[offset + 0];
|
||
|
size_t len2 = buf[offset + 1] * 8;
|
||
|
size_t off2 = 2;
|
||
|
const unsigned char *buf2 = buf + offset;
|
||
|
|
||
|
switch (type) {
|
||
|
case 3: /* prefix info */
|
||
|
{
|
||
|
unsigned prefix_len;
|
||
|
ipv6address prefix;
|
||
|
ipaddress_formatted_t fmt;
|
||
|
|
||
|
prefix_len = _read_byte(buf2, &off2, len2);
|
||
|
_read_byte(buf2, &off2, len2); /* flags */
|
||
|
_read_number(buf2, &off2, len2); /* valid lifetime */
|
||
|
_read_number(buf2, &off2, len2); /* preferred lifetime */
|
||
|
_read_number(buf2, &off2, len2); /* reserved */
|
||
|
prefix = _read_ipv6(buf2, &off2, len2);
|
||
|
|
||
|
fmt = ipv6address_fmt(prefix);
|
||
|
LOG(1, "[+] IPv6.prefix = %s/%u\n", fmt.string, prefix_len);
|
||
|
if (ipv6address_is_equal_prefixed(my_ipv6, prefix, prefix_len)) {
|
||
|
is_same_prefix = 1;
|
||
|
} else {
|
||
|
ipaddress_formatted_t fmt1 = ipv6address_fmt(my_ipv6);
|
||
|
ipaddress_formatted_t fmt2 = ipv6address_fmt(prefix);
|
||
|
|
||
|
LOG(0, "[-] WARNING: our source-ip is %s, but router prefix announces %s/%u\n",
|
||
|
fmt1.string, fmt2.string, prefix_len);
|
||
|
is_same_prefix = 0;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
break;
|
||
|
case 25: /* recursive DNS server */
|
||
|
_read_short(buf2, &off2, len2);
|
||
|
_read_number(buf2, &off2, len2);
|
||
|
|
||
|
while (off2 + 16 <= len2) {
|
||
|
ipv6address resolver = _read_ipv6(buf2, &off2, len2);
|
||
|
ipaddress_formatted_t fmt = ipv6address_fmt(resolver);
|
||
|
LOG(1, "[+] IPv6.DNS = %s\n", fmt.string);
|
||
|
}
|
||
|
break;
|
||
|
case 1:
|
||
|
if (len2 == 8) {
|
||
|
memcpy(router_mac->addr, buf2 + 2, 6);
|
||
|
is_mac_explicit = 1;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
offset += len2;
|
||
|
}
|
||
|
|
||
|
if (!is_mac_explicit) {
|
||
|
/* The router advertisement didn't include an explicit
|
||
|
* source address. Therefore, pull the response from
|
||
|
* the Ethernet header of the packet instead */
|
||
|
memcpy(router_mac->addr, parsed->mac_src, 6);
|
||
|
}
|
||
|
|
||
|
if (!is_same_prefix) {
|
||
|
/* We had a valid router advertisement, but it didn't
|
||
|
* match the IPv6 address we are using. This presumably
|
||
|
* means there are multiple possible IPv6 routers on the
|
||
|
* network. Therefore, we are going to discard this
|
||
|
* packet and wait for another one */
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/****************************************************************************
|
||
|
****************************************************************************/
|
||
|
int
|
||
|
stack_ndpv6_resolve(struct Adapter *adapter,
|
||
|
ipv6address my_ipv6,
|
||
|
macaddress_t my_mac_address,
|
||
|
macaddress_t *router_mac)
|
||
|
{
|
||
|
unsigned char buf[128];
|
||
|
size_t max = sizeof(buf);
|
||
|
size_t offset = 0;
|
||
|
unsigned i;
|
||
|
time_t start;
|
||
|
unsigned is_arp_notice_given = 0;
|
||
|
int is_delay_reported = 0;
|
||
|
size_t offset_ip;
|
||
|
size_t offset_ip_src;
|
||
|
size_t offset_ip_dst;
|
||
|
size_t offset_icmpv6;
|
||
|
unsigned xsum;
|
||
|
struct PreprocessedInfo parsed = {0};
|
||
|
|
||
|
/*
|
||
|
* [KLUDGE]
|
||
|
* If this is a VPN connection, then there is no answer
|
||
|
*/
|
||
|
if (stack_if_datalink(adapter) == 12) {
|
||
|
memcpy(router_mac->addr, "\0\0\0\0\0\2", 6);
|
||
|
return 0; /* success */
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Ethernet header
|
||
|
*/
|
||
|
_append_bytes(buf, &offset, max, "\x33\x33\x00\x00\x00\x02", 6);
|
||
|
_append_bytes(buf, &offset, max, my_mac_address.addr, 6);
|
||
|
|
||
|
if (adapter->is_vlan) {
|
||
|
_append_short(buf, &offset, max, 0x8100);
|
||
|
_append_short(buf, &offset, max, adapter->vlan_id);
|
||
|
}
|
||
|
_append_short(buf, &offset, max, 0x86dd);
|
||
|
|
||
|
/*
|
||
|
* Create IPv6 header
|
||
|
*/
|
||
|
offset_ip = offset;
|
||
|
_append(buf, &offset, max, 0x60); /* version = 6 */
|
||
|
_append(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0); /* length = 0 */
|
||
|
_append(buf, &offset, max, 58); /* proto = ICMPv6 */
|
||
|
_append(buf, &offset, max, 255); /*hop limit = 255 */
|
||
|
|
||
|
/* Link local source address based on MAC address */
|
||
|
offset_ip_src = offset;
|
||
|
_append_short(buf, &offset, max, 0xfe80);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_bytes(buf, &offset, max, my_mac_address.addr, 3);
|
||
|
buf[offset-3] |= 2;
|
||
|
_append_short(buf, &offset, max, 0xfffe);
|
||
|
_append_bytes(buf, &offset, max, my_mac_address.addr+3, 3);
|
||
|
|
||
|
/* All-routers link local address */
|
||
|
offset_ip_dst = offset;
|
||
|
_append_short(buf, &offset, max, 0xff02);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 2);
|
||
|
|
||
|
/* ICMPv6 Router Solicitation */
|
||
|
offset_icmpv6 = offset;
|
||
|
_append(buf, &offset, max, 133); /* type = Router Solicitation */
|
||
|
_append(buf, &offset, max, 0);
|
||
|
_append_short(buf, &offset, max, 0); /* checksum = 0 (for the moment) */
|
||
|
_append_short(buf, &offset, max, 0); /* reserved */
|
||
|
_append_short(buf, &offset, max, 0); /* reserved */
|
||
|
_append(buf, &offset, max, 1); /* option = source link layer address */
|
||
|
_append(buf, &offset, max, 1); /* length = 2 + 6 / 8*/
|
||
|
_append_bytes(buf, &offset, max, my_mac_address.addr, 6);
|
||
|
|
||
|
buf[offset_ip + 4] = (unsigned char)( (offset - offset_icmpv6) >> 8);
|
||
|
buf[offset_ip + 5] = (unsigned char)( (offset - offset_icmpv6) & 0xFF);
|
||
|
xsum = checksum_ipv6( buf + offset_ip_src,
|
||
|
buf + offset_ip_dst,
|
||
|
58,
|
||
|
offset - offset_icmpv6,
|
||
|
buf + offset_icmpv6);
|
||
|
buf[offset_icmpv6 + 2] = (unsigned char)(xsum >> 8);
|
||
|
buf[offset_icmpv6 + 3] = (unsigned char)(xsum >> 0);
|
||
|
rawsock_send_packet(adapter, buf, (unsigned)offset, 1);
|
||
|
|
||
|
/*
|
||
|
* Send a shorter version after the long version. I don't know
|
||
|
* why, but some do this on the Internet.
|
||
|
*/
|
||
|
offset -= 8;
|
||
|
buf[offset_ip + 4] = (unsigned char)( (offset - offset_icmpv6) >> 8);
|
||
|
buf[offset_ip + 5] = (unsigned char)( (offset - offset_icmpv6) & 0xFF);
|
||
|
xsum = checksum_ipv6( buf + offset_ip_src,
|
||
|
buf + offset_ip_dst,
|
||
|
58,
|
||
|
offset - offset_icmpv6,
|
||
|
buf + offset_icmpv6);
|
||
|
buf[offset_icmpv6 + 2] = (unsigned char)(xsum >> 8);
|
||
|
buf[offset_icmpv6 + 3] = (unsigned char)(xsum >> 0);
|
||
|
rawsock_send_packet(adapter, buf, (unsigned)offset, 1);
|
||
|
|
||
|
|
||
|
start = time(0);
|
||
|
i = 0;
|
||
|
for (;;) {
|
||
|
unsigned length2;
|
||
|
unsigned secs;
|
||
|
unsigned usecs;
|
||
|
const unsigned char *buf2;
|
||
|
int err;
|
||
|
ipv6address router_ip;
|
||
|
|
||
|
/* Resend every so often */
|
||
|
if (time(0) != start) {
|
||
|
start = time(0);
|
||
|
rawsock_send_packet(adapter, buf, (unsigned)offset, 1);
|
||
|
if (i++ >= 10)
|
||
|
break; /* timeout */
|
||
|
|
||
|
/* It's taking too long, so notify the user */
|
||
|
if (!is_delay_reported) {
|
||
|
fprintf(stderr, "[ ] resolving IPv6 router MAC address (may take some time)...\n");
|
||
|
is_delay_reported = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If we aren't getting a response back to our ARP, then print a
|
||
|
* status message */
|
||
|
if (time(0) > start+1 && !is_arp_notice_given) {
|
||
|
fprintf(stderr, "[ ] resolving local IPv6 router\n");
|
||
|
is_arp_notice_given = 1;
|
||
|
}
|
||
|
|
||
|
err = rawsock_recv_packet(
|
||
|
adapter,
|
||
|
&length2,
|
||
|
&secs,
|
||
|
&usecs,
|
||
|
&buf2);
|
||
|
|
||
|
if (err != 0)
|
||
|
continue;
|
||
|
|
||
|
/*
|
||
|
* Parse the packet. We'll get lots of packets we aren't interested
|
||
|
* in,so we'll just loop around and keep searching until we find
|
||
|
* one.
|
||
|
*/
|
||
|
err = preprocess_frame(buf2, length2, 1, &parsed);
|
||
|
if (err != 1)
|
||
|
continue;
|
||
|
if (parsed.found != FOUND_NDPv6)
|
||
|
continue;
|
||
|
|
||
|
/* We've found a packet that may be the one we want, so parse it */
|
||
|
err = _extract_router_advertisement(buf2, length2, &parsed, my_ipv6, &router_ip, router_mac);
|
||
|
if (err)
|
||
|
continue;
|
||
|
|
||
|
/* The previous call found 'router_mac", so now return */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|