diff --git a/headers/private/net/net_buffer.h b/headers/private/net/net_buffer.h index cd5b072a8d..2748f85ff6 100644 --- a/headers/private/net/net_buffer.h +++ b/headers/private/net/net_buffer.h @@ -36,6 +36,7 @@ typedef struct net_buffer { uint32 flags; uint32 size; uint8 protocol; + uint8 hoplimit; } net_buffer; struct ancillary_data_container; diff --git a/headers/private/net/net_datalink.h b/headers/private/net/net_datalink.h index b475e8860f..cee52de71f 100644 --- a/headers/private/net/net_datalink.h +++ b/headers/private/net/net_datalink.h @@ -100,6 +100,7 @@ struct net_datalink_module_info { struct net_address_module_info { module_info info; + bool has_broadcast_address; status_t (*copy_address)(const sockaddr *from, sockaddr **to, bool replaceWithZeros, const sockaddr *mask); diff --git a/headers/private/net/net_datalink_protocol.h b/headers/private/net/net_datalink_protocol.h index edaa94b4d4..48f500ca13 100644 --- a/headers/private/net/net_datalink_protocol.h +++ b/headers/private/net/net_datalink_protocol.h @@ -24,6 +24,7 @@ struct net_datalink_protocol_module_info { status_t (*send_data)(net_datalink_protocol *self, net_buffer *buffer); + status_t (*receive_data)(net_buffer *buffer); status_t (*interface_up)(net_datalink_protocol *self); void (*interface_down)(net_datalink_protocol *self); diff --git a/src/add-ons/kernel/network/datalink_protocols/Jamfile b/src/add-ons/kernel/network/datalink_protocols/Jamfile index e6abcb0297..177fc9cc71 100644 --- a/src/add-ons/kernel/network/datalink_protocols/Jamfile +++ b/src/add-ons/kernel/network/datalink_protocols/Jamfile @@ -3,4 +3,5 @@ SubDir HAIKU_TOP src add-ons kernel network datalink_protocols ; SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols arp ; SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols ethernet_frame ; SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols ipv4_datagram ; +SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols ipv6_datagram ; SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols loopback_frame ; diff --git a/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp b/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp index 6c3ad9fdd8..a1c5584204 100644 --- a/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp +++ b/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp @@ -1095,6 +1095,7 @@ static net_datalink_protocol_module_info sARPModule = { arp_init_protocol, arp_uninit_protocol, arp_send_data, + NULL, // receive_data arp_up, arp_down, arp_control, diff --git a/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp b/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp index 52c15da78b..c709b8bf24 100644 --- a/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp +++ b/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp @@ -210,6 +210,7 @@ static net_datalink_protocol_module_info sEthernetFrameModule = { ethernet_frame_init, ethernet_frame_uninit, ethernet_frame_send_data, + NULL, // receive_data ethernet_frame_up, ethernet_frame_down, ethernet_frame_control, diff --git a/src/add-ons/kernel/network/datalink_protocols/ipv4_datagram/ipv4_datagram.cpp b/src/add-ons/kernel/network/datalink_protocols/ipv4_datagram/ipv4_datagram.cpp index 3f163ee3e4..c0804ff15e 100644 --- a/src/add-ons/kernel/network/datalink_protocols/ipv4_datagram/ipv4_datagram.cpp +++ b/src/add-ons/kernel/network/datalink_protocols/ipv4_datagram/ipv4_datagram.cpp @@ -136,6 +136,7 @@ net_datalink_protocol_module_info gIPv4DataLinkModule = { ipv4_datalink_init, ipv4_datalink_uninit, ipv4_datalink_send_data, + NULL, // receive_data ipv4_datalink_up, ipv4_datalink_down, ipv4_datalink_control, diff --git a/src/add-ons/kernel/network/datalink_protocols/ipv6_datagram/Jamfile b/src/add-ons/kernel/network/datalink_protocols/ipv6_datagram/Jamfile new file mode 100644 index 0000000000..1daaf0c336 --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/ipv6_datagram/Jamfile @@ -0,0 +1,26 @@ +SubDir HAIKU_TOP src add-ons kernel network datalink_protocols ipv6_datagram ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateKernelHeaders ; +UsePrivateHeaders kernel net ; + +KernelAddon ipv6_datagram : + ipv6_datagram.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/datalink_protocols + : ipv6_datagram ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network datalink_protocols ; diff --git a/src/add-ons/kernel/network/datalink_protocols/ipv6_datagram/ipv6_datagram.cpp b/src/add-ons/kernel/network/datalink_protocols/ipv6_datagram/ipv6_datagram.cpp new file mode 100644 index 0000000000..e757c35367 --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/ipv6_datagram/ipv6_datagram.cpp @@ -0,0 +1,1089 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Atis Elsts, the.kfx@gmail.com + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../../protocols/ipv6/jenkins.h" // TODO: move this file +#include "../../protocols/ipv6/ipv6_address.h" + + +#define TRACE_NDP +#ifdef TRACE_NDP +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +struct ipv6_datalink_protocol : net_datalink_protocol { + sockaddr_dl hardware_address; +}; + + +static void ndp_timer(struct net_timer* timer, void* data); + + +net_buffer_module_info* gBufferModule; +static net_stack_module_info* sStackModule; +static net_protocol_module_info* sIPv6Module; +net_protocol* sIPv6Protocol; +static hash_table* sCache; +static mutex sCacheLock; +// TODO ETHER_FRAME_TYPE doesn't belong there, we need Layer 2 +// independence. +static const int32 kIPv6FrameType = ETHER_FRAME_TYPE | ETHER_TYPE_IPV6; +static const net_buffer* kDeletedBuffer = (net_buffer*)~0; + +// needed for IN6_IS_ADDR_UNSPECIFIED() macro +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; + + +// #pragma mark - + + +struct neighbor_discovery_header { + uint8 icmp6_type; + uint8 icmp6_code; + uint16 icmp6_checksum; + uint32 flags; + in6_addr target_address; + + // This part is specific for Ethernet; + // also, the could be more than one option in theory. + uint8 option_type; + uint8 option_length; + uint8 link_address[ETHER_ADDRESS_LENGTH]; +} _PACKED; + + +struct ndp_entry { + ndp_entry* next; + in6_addr protocol_address; + sockaddr_dl hardware_address; + uint32 flags; + net_buffer* request_buffer; + net_timer timer; + uint32 timer_state; + bigtime_t timestamp; + net_datalink_protocol* protocol; + + typedef DoublyLinkedListCLink NetBufferLink; + typedef DoublyLinkedList BufferList; + + BufferList queue; + + static int Compare(void* _entry, const void* _key); + static uint32 Hash(void* _entry, const void* _key, uint32 range); + static ndp_entry* Lookup(const in6_addr& protocolAddress); + static ndp_entry* Add(const in6_addr& protocolAddress, + sockaddr_dl* hardwareAddress, uint32 flags); + + ~ndp_entry(); + + void ClearQueue(); + void MarkFailed(); + void MarkValid(); + void ScheduleRemoval(); +}; + +#define NDP_FLAG_LOCAL 0x01 +#define NDP_FLAG_REJECT 0x02 +#define NDP_FLAG_PERMANENT 0x04 +#define NDP_FLAG_PUBLISH 0x08 +#define NDP_FLAG_VALID 0x10 + +#define NDP_FLAG_REMOVED 0x00010000 +#define NDP_PUBLIC_FLAG_MASK 0x0000ffff + +#define NDP_NO_STATE 0 +#define NDP_STATE_REQUEST 1 +#define NDP_STATE_LAST_REQUEST 5 +#define NDP_STATE_REQUEST_FAILED 6 +#define NDP_STATE_REMOVE_FAILED 7 +#define NDP_STATE_STALE 8 + +#define NDP_STALE_TIMEOUT 30 * 60000000LL // 30 minutes +#define NDP_REJECT_TIMEOUT 20000000LL // 20 seconds +#define NDP_REQUEST_TIMEOUT 1000000LL // 1 second + + +// #pragma mark - + + +static void +ipv6_to_ether_multicast(sockaddr_dl* destination, const sockaddr_in6* source) +{ + // To send an IPv6 multicast packet over Ethernet, + // take the last 32 bits of the destination IPv6 address, + // prepend 33-33- and use that as the destination Ethernet address. + + destination->sdl_len = sizeof(sockaddr_dl); + destination->sdl_family = AF_LINK; + destination->sdl_index = 0; + destination->sdl_type = IFT_ETHER; + destination->sdl_e_type = ETHER_TYPE_IPV6; + destination->sdl_nlen = destination->sdl_slen = 0; + destination->sdl_alen = ETHER_ADDRESS_LENGTH; + + destination->sdl_data[0] = 0x33; + destination->sdl_data[1] = 0x33; + memcpy(&destination->sdl_data[2], &source->sin6_addr.s6_addr[12], 4); +} + + +static inline sockaddr* +ipv6_to_sockaddr(sockaddr_in6* target, const in6_addr& address) +{ + target->sin6_family = AF_INET6; + target->sin6_len = sizeof(sockaddr_in6); + target->sin6_port = 0; + target->sin6_flowinfo = 0; + target->sin6_scope_id = 0; + memcpy(target->sin6_addr.s6_addr, address.s6_addr, sizeof(in6_addr)); + return (sockaddr*)target; +} + + +static inline sockaddr* +ipv6_to_solicited_multicast(sockaddr_in6* target, const in6_addr& address) +{ + // The solicited-node multicast address for a given unicast address + // is constructed by taking the last three octets of the unicast address + // and prepending FF02::1:FF00:0000/104. + + target->sin6_family = AF_INET6; + target->sin6_len = sizeof(sockaddr_in6); + target->sin6_port = 0; + target->sin6_flowinfo = 0; + target->sin6_scope_id = 0; + + uint8* targetIPv6 = target->sin6_addr.s6_addr; + memset(targetIPv6, 0, sizeof(in6_addr)); + targetIPv6[0] = 0xff; + targetIPv6[1] = 0x02; + targetIPv6[11] = 0x01; + targetIPv6[12] = 0xff; + memcpy(&targetIPv6[13], &address.s6_addr[13], 3); + + return (sockaddr*)target; +} + + +static inline uint32 +hash_ipv6_address(const in6_addr& address, uint32 range) +{ + return jenkins_hashword((const uint32*)&address, + sizeof(in6_addr) / sizeof(uint32), 0) % range; +} + + +// #pragma mark - + + +static net_buffer* +get_request_buffer(ndp_entry* entry) +{ + net_buffer* buffer = entry->request_buffer; + if (buffer == NULL || buffer == kDeletedBuffer) + return NULL; + + buffer = atomic_pointer_test_and_set(&entry->request_buffer, + (net_buffer*)NULL, buffer); + if (buffer == kDeletedBuffer) + return NULL; + + return buffer; +} + + +static void +put_request_buffer(ndp_entry* entry, net_buffer* buffer) +{ + net_buffer* requestBuffer = atomic_pointer_test_and_set( + &entry->request_buffer, buffer, (net_buffer*)NULL); + if (requestBuffer != NULL) { + // someone else took over ownership of the request buffer + gBufferModule->free(buffer); + } +} + + +static void +delete_request_buffer(ndp_entry* entry) +{ + net_buffer* buffer = atomic_pointer_set(&entry->request_buffer, + kDeletedBuffer); + if (buffer != NULL && buffer != kDeletedBuffer) + gBufferModule->free(buffer); +} + + +int +ndp_entry::Compare(void* _entry, const void* _key) +{ + ndp_entry* entry = (ndp_entry*)_entry; + in6_addr* key = (in6_addr*)_key; + + if (entry->protocol_address == *key) + return 0; + + return 1; +} + + +uint32 +ndp_entry::Hash(void* _entry, const void* _key, uint32 range) +{ + ndp_entry* entry = (ndp_entry*)_entry; + const in6_addr* key = (const in6_addr*)_key; + + if (entry != NULL) + return hash_ipv6_address(entry->protocol_address, range); + + return hash_ipv6_address(*key, range); +} + + +ndp_entry* +ndp_entry::Lookup(const in6_addr& address) +{ + return (ndp_entry*)hash_lookup(sCache, &address); +} + + +ndp_entry* +ndp_entry::Add(const in6_addr& protocolAddress, sockaddr_dl* hardwareAddress, + uint32 flags) +{ + ASSERT_LOCKED_MUTEX(&sCacheLock); + + ndp_entry* entry = new (std::nothrow) ndp_entry; + if (entry == NULL) + return NULL; + + entry->protocol_address = protocolAddress; + entry->flags = flags; + entry->timestamp = system_time(); + entry->protocol = NULL; + entry->request_buffer = NULL; + entry->timer_state = NDP_NO_STATE; + sStackModule->init_timer(&entry->timer, ndp_timer, entry); + + if (hardwareAddress != NULL) { + // this entry is already resolved + entry->hardware_address = *hardwareAddress; + entry->hardware_address.sdl_e_type = ETHER_TYPE_IPV6; + } else { + // this entry still needs to be resolved + entry->hardware_address.sdl_alen = 0; + } + if (entry->hardware_address.sdl_len != sizeof(sockaddr_dl)) { + // explicitly set correct length in case our caller hasn't... + entry->hardware_address.sdl_len = sizeof(sockaddr_dl); + } + + if (hash_insert(sCache, entry) != B_OK) { + // We can delete the entry here with the sCacheLock held, since it's + // guaranteed there are no timers pending. + delete entry; + return NULL; + } + + return entry; +} + + +ndp_entry::~ndp_entry() +{ + // make sure there is no active timer left for us + sStackModule->cancel_timer(&timer); + sStackModule->wait_for_timer(&timer); + + ClearQueue(); +} + + +void +ndp_entry::ClearQueue() +{ + BufferList::Iterator iterator = queue.GetIterator(); + while (iterator.HasNext()) { + net_buffer* buffer = iterator.Next(); + iterator.Remove(); + gBufferModule->free(buffer); + } +} + + +void +ndp_entry::MarkFailed() +{ + TRACE(("NDP entry %p Marked as FAILED\n", this)); + + flags = (flags & ~NDP_FLAG_VALID) | NDP_FLAG_REJECT; + ClearQueue(); +} + + +void +ndp_entry::MarkValid() +{ + TRACE(("NDP entry %p Marked as VALID\n", this)); + + flags = (flags & ~NDP_FLAG_REJECT) | NDP_FLAG_VALID; + + BufferList::Iterator iterator = queue.GetIterator(); + while (iterator.HasNext()) { + net_buffer* buffer = iterator.Next(); + iterator.Remove(); + + TRACE((" NDP Dequeing packet %p...\n", buffer)); + + memcpy(buffer->destination, &hardware_address, + hardware_address.sdl_len); + protocol->next->module->send_data(protocol->next, buffer); + } +} + + +void +ndp_entry::ScheduleRemoval() +{ + // schedule a timer to remove this entry + timer_state = NDP_STATE_REMOVE_FAILED; + sStackModule->set_timer(&timer, 0); +} + + +// #pragma mark - + + +static status_t +ndp_init() +{ + sIPv6Protocol = sIPv6Module->init_protocol(NULL); + if (sIPv6Protocol == NULL) + return B_NO_MEMORY; + sIPv6Protocol->module = sIPv6Module; + sIPv6Protocol->socket = NULL; + sIPv6Protocol->next = NULL; + + int value = 255; + sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &value, sizeof(value)); + + mutex_init(&sCacheLock, "ndp cache"); + + sCache = hash_init(64, offsetof(struct ndp_entry, next), + &ndp_entry::Compare, &ndp_entry::Hash); + if (sCache == NULL) { + mutex_destroy(&sCacheLock); + return B_NO_MEMORY; + } + + return B_OK; +} + + +static status_t +ndp_uninit() +{ + if (sIPv6Protocol) + sIPv6Module->uninit_protocol(sIPv6Protocol); + + return B_OK; +} + + +// #pragma mark - + + +/*! Updates the entry determined by \a protocolAddress with the specified + \a hardwareAddress. + If such an entry does not exist yet, a new entry is added. If you try + to update a local existing entry but didn't ask for it (by setting + \a flags to NDP_FLAG_LOCAL), an error is returned. + + This function does not lock the cache - you have to do it yourself + before calling it. +*/ +status_t +ndp_update_entry(const in6_addr& protocolAddress, sockaddr_dl* hardwareAddress, + uint32 flags, ndp_entry** _entry = NULL) +{ + ASSERT_LOCKED_MUTEX(&sCacheLock); + + ndp_entry* entry = ndp_entry::Lookup(protocolAddress); + if (entry != NULL) { + // We disallow updating of entries that had been resolved before, + // but to a different address (only for those that belong to a + // specific address - redefining INADDR_ANY is always allowed). + // Right now, you have to manually purge the NDP entries (or wait some + // time) to let us switch to the new address. + if (!IN6_IS_ADDR_UNSPECIFIED(&protocolAddress) + && entry->hardware_address.sdl_alen != 0 + && memcmp(LLADDR(&entry->hardware_address), + LLADDR(hardwareAddress), ETHER_ADDRESS_LENGTH)) { + // TODO: also printf the address + dprintf("NDP host updated with different hardware address " + "%02x:%02x:%02x:%02x:%02x:%02x.\n", + hardwareAddress->sdl_data[0], hardwareAddress->sdl_data[1], + hardwareAddress->sdl_data[2], hardwareAddress->sdl_data[3], + hardwareAddress->sdl_data[4], hardwareAddress->sdl_data[5]); + return B_ERROR; + } + + entry->hardware_address = *hardwareAddress; + entry->timestamp = system_time(); + } else { + entry = ndp_entry::Add(protocolAddress, hardwareAddress, flags); + if (entry == NULL) + return B_NO_MEMORY; + } + + delete_request_buffer(entry); + + if ((entry->flags & NDP_FLAG_PERMANENT) == 0) { + // (re)start the stale timer + entry->timer_state = NDP_STATE_STALE; + sStackModule->set_timer(&entry->timer, NDP_STALE_TIMEOUT); + } + + if ((entry->flags & NDP_FLAG_REJECT) != 0) + entry->MarkFailed(); + else + entry->MarkValid(); + + if (_entry) + *_entry = entry; + + return B_OK; +} + + +/*! Creates a permanent local entry for the interface belonging to this protocol. + You need to hold the cache lock when calling this function. +*/ +static status_t +ndp_update_local(ipv6_datalink_protocol* protocol) +{ + ASSERT_LOCKED_MUTEX(&sCacheLock); + + net_interface* interface = protocol->interface; + in6_addr inet6Address; + + if (interface->address == NULL) { + // interface has not yet been set + memset(&inet6Address, 0, sizeof(in6_addr)); + } else { + memcpy(&inet6Address, + &((sockaddr_in6*)interface->address)->sin6_addr, sizeof(in6_addr)); + } + + sockaddr_dl address; + address.sdl_len = sizeof(sockaddr_dl); + address.sdl_family = AF_LINK; + address.sdl_type = IFT_ETHER; + address.sdl_e_type = ETHER_TYPE_IPV6; + address.sdl_nlen = 0; + address.sdl_slen = 0; + address.sdl_alen = interface->device->address.length; + memcpy(LLADDR(&address), interface->device->address.data, address.sdl_alen); + + memcpy(&protocol->hardware_address, &address, sizeof(sockaddr_dl)); + // cache the address in our protocol + + ndp_entry* entry; + status_t status = ndp_update_entry(inet6Address, &address, + NDP_FLAG_LOCAL | NDP_FLAG_PERMANENT, &entry); + if (status == B_OK) + entry->protocol = protocol; + + return status; +} + + +static status_t +handle_neighbor_solicitation(net_buffer* buffer) +{ + NetBufferHeaderReader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + neighbor_discovery_header& header = bufferHeader.Data(); + if (header.option_type != ND_OPT_SOURCE_LINKADDR + || header.option_length != 1) + return B_OK; + + { + MutexLocker locker(sCacheLock); + + // remember the address of the sender as we might need it later + sockaddr_dl hardwareAddress; + hardwareAddress.sdl_len = sizeof(sockaddr_dl); + hardwareAddress.sdl_family = AF_LINK; + hardwareAddress.sdl_index = 0; + hardwareAddress.sdl_type = IFT_ETHER; + hardwareAddress.sdl_e_type = ETHER_TYPE_IPV6; + hardwareAddress.sdl_nlen = hardwareAddress.sdl_slen = 0; + hardwareAddress.sdl_alen = ETHER_ADDRESS_LENGTH; + memcpy(LLADDR(&hardwareAddress), header.link_address, + ETHER_ADDRESS_LENGTH); + + ndp_update_entry(header.target_address, &hardwareAddress, 0); + + // check if this request is for us + + ndp_entry* entry = ndp_entry::Lookup(header.target_address); + if (entry == NULL + || (entry->flags & (NDP_FLAG_LOCAL | NDP_FLAG_PUBLISH)) == 0) { + // We're not the one to answer this request + // TODO: instead of letting the other's request time-out, can we + // reply failure somehow? + TRACE((" not for us\n")); + return B_ERROR; + } + + // send a reply (by reusing the buffer we got) + gBufferModule->trim(buffer, sizeof(neighbor_discovery_header)); + + header.icmp6_type = ND_NEIGHBOR_SOLICIT; + header.icmp6_code = 0; + header.icmp6_checksum = 0; + header.flags = ND_NA_FLAG_SOLICITED; + header.option_type = ND_OPT_TARGET_LINKADDR; + memcpy(&header.link_address, LLADDR(&entry->hardware_address), + ETHER_ADDRESS_LENGTH); + bufferHeader.Sync(); + } + + // fix source and destination address + sockaddr_in6* source = (sockaddr_in6*)buffer->source; + sockaddr_in6* destination = (sockaddr_in6*)buffer->destination; + memcpy(&destination->sin6_addr, &source->sin6_addr, sizeof(in6_addr)); + memcpy(&source->sin6_addr, &header.target_address, sizeof(in6_addr)); + + buffer->flags = 0; + // make sure this won't be a broadcast message + + // TODO: there is not need to clone, could reuse old buffer + net_buffer* clone = gBufferModule->clone(buffer, true); + if (clone == NULL) + return B_NO_MEMORY; + + if (sIPv6Protocol == NULL) + return B_ERROR; + + // send the ICMPv6 packet out + TRACE(("Sending Neighbor Advertisement\n")); + return sIPv6Module->send_data(sIPv6Protocol, clone); +} + + +static void +handle_neighbor_advertisement(net_buffer* buffer) +{ + // TODO: also process unsolicited advertisments? + if ((buffer->flags & MSG_MCAST) != 0) + return; + + NetBufferHeaderReader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return; + + neighbor_discovery_header& header = bufferHeader.Data(); + if (header.option_type != ND_OPT_TARGET_LINKADDR + || header.option_length != 1) { + return; + } + + sockaddr_dl hardwareAddress; + hardwareAddress.sdl_len = sizeof(sockaddr_dl); + hardwareAddress.sdl_family = AF_LINK; + hardwareAddress.sdl_index = 0; + hardwareAddress.sdl_type = IFT_ETHER; + hardwareAddress.sdl_e_type = ETHER_TYPE_IPV6; + hardwareAddress.sdl_nlen = hardwareAddress.sdl_slen = 0; + hardwareAddress.sdl_alen = ETHER_ADDRESS_LENGTH; + memcpy(LLADDR(&hardwareAddress), header.link_address, ETHER_ADDRESS_LENGTH); + + MutexLocker locker(sCacheLock); + // TODO: take in account ND_NA_FLAGs + ndp_update_entry(header.target_address, &hardwareAddress, 0); +} + + +static void +ndp_timer(struct net_timer* timer, void* data) +{ + ndp_entry* entry = (ndp_entry*)data; + TRACE(("NDP timer %ld, entry %p!\n", entry->timer_state, entry)); + + switch (entry->timer_state) { + case NDP_NO_STATE: + // who are you kidding? + break; + + case NDP_STATE_REQUEST_FAILED: + // Requesting the NDP entry failed, we keep it around for a while, + // though, so that we won't try to request the same address again + // too soon. + TRACE((" requesting NDP entry %p failed!\n", entry)); + entry->timer_state = NDP_STATE_REMOVE_FAILED; + entry->MarkFailed(); + sStackModule->set_timer(&entry->timer, NDP_REJECT_TIMEOUT); + break; + + case NDP_STATE_REMOVE_FAILED: + case NDP_STATE_STALE: + // the entry has aged so much that we're going to remove it + TRACE((" remove NDP entry %p!\n", entry)); + + mutex_lock(&sCacheLock); + if ((entry->flags & NDP_FLAG_REMOVED) != 0) { + // The entry has already been removed, and is about to be deleted + mutex_unlock(&sCacheLock); + break; + } + + hash_remove(sCache, entry); + mutex_unlock(&sCacheLock); + + delete entry; + break; + + default: + { + if (entry->timer_state > NDP_STATE_LAST_REQUEST) + break; + + TRACE((" send request for NDP entry %p!\n", entry)); + + net_buffer* request = get_request_buffer(entry); + if (request == NULL) + break; + + if (entry->timer_state < NDP_STATE_LAST_REQUEST) { + // we'll still need our buffer, so in order to prevent it being + // freed by a successful send, we need to clone it + net_buffer* clone = gBufferModule->clone(request, true); + if (clone == NULL) { + // cloning failed - that means we won't be able to send as + // many requests as originally planned + entry->timer_state = NDP_STATE_LAST_REQUEST; + } else { + put_request_buffer(entry, request); + request = clone; + } + } + + if (sIPv6Protocol == NULL) + break; + + // we're trying to resolve the address, so keep sending requests + status_t status = sIPv6Module->send_data(sIPv6Protocol, request); + if (status < B_OK) + gBufferModule->free(request); + + entry->timer_state++; + sStackModule->set_timer(&entry->timer, NDP_REQUEST_TIMEOUT); + break; + } + } +} + + +static status_t +ndp_start_resolve(net_datalink_protocol* protocol, const in6_addr& address, + ndp_entry** _entry) +{ + ASSERT_LOCKED_MUTEX(&sCacheLock); + + // create an unresolved entry as a placeholder + ndp_entry* entry = ndp_entry::Add(address, NULL, 0); + if (entry == NULL) + return B_NO_MEMORY; + + // prepare NDP request + + net_buffer* buffer = entry->request_buffer = gBufferModule->create(256); + if (entry->request_buffer == NULL) { + entry->ScheduleRemoval(); + return B_NO_MEMORY; + } + + NetBufferPrepend header(buffer); + status_t status = header.Status(); + if (status < B_OK) { + entry->ScheduleRemoval(); + return status; + } + + net_interface* interface = protocol->interface; + net_device* device = interface->device; + + // prepare source and target addresses + + sockaddr_in6* source = (sockaddr_in6*)buffer->source; + ipv6_to_sockaddr(source, ((sockaddr_in6*)interface->address)->sin6_addr); + + sockaddr_in6* destination = (sockaddr_in6*)buffer->destination; + ipv6_to_solicited_multicast(destination, address); + + buffer->protocol = IPPROTO_ICMPV6; + + // prepare Neighbor Solicitation header + + header->icmp6_type = ND_NEIGHBOR_SOLICIT; + header->icmp6_code = 0; + header->icmp6_checksum = 0; + header->flags = 0; + memcpy(&header->target_address, &address, sizeof(in6_addr)); + header->option_type = ND_OPT_SOURCE_LINKADDR; + header->option_length = (sizeof(nd_opt_hdr) + ETHER_ADDRESS_LENGTH) >> 3; + memcpy(&header->link_address, device->address.data, ETHER_ADDRESS_LENGTH); + header.Sync(); + + if (sIPv6Protocol == NULL) { + entry->ScheduleRemoval(); + return B_NO_MEMORY; + } + + // this does not work, because multicast for now is only looped back! +#if FIXME + // hack: set to use the correct interface by setting socket option + sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &source->sin6_addr, sizeof(in6_addr)); +#endif + + net_buffer* clone = gBufferModule->clone(buffer, true); + if (clone == NULL) { + entry->ScheduleRemoval(); + return B_NO_MEMORY; + } + + // send the ICMPv6 packet out + TRACE(("Sending Neighbor Solicitation\n")); + status = sIPv6Module->send_data(sIPv6Protocol, clone); + if (status < B_OK) { + entry->ScheduleRemoval(); + return status; + } + + entry->protocol = protocol; + entry->timer_state = NDP_STATE_REQUEST; + sStackModule->set_timer(&entry->timer, 0); + // start request timer + + *_entry = entry; + return B_OK; +} + + +// #pragma mark - + + +static status_t +ipv6_datalink_init(net_interface* interface, net_datalink_protocol** _protocol) +{ + if (interface->domain->family != AF_INET6) + return B_BAD_TYPE; + + // While the loopback doesn't get a header to mux protocols, + // we let it do all of the registration work. + if (interface->device->type == IFT_LOOP) + return B_BAD_TYPE; + + ipv6_datalink_protocol* protocol = new(std::nothrow) ipv6_datalink_protocol; + if (protocol == NULL) + return B_NO_MEMORY; + + memset(&protocol->hardware_address, 0, sizeof(sockaddr_dl)); + + // We register ETHER_TYPE_IPV6 as most datalink protocols use it + // to identify IPv6 datagrams. In the future we may limit this. + + status_t status = sStackModule->register_domain_device_handler( + interface->device, kIPv6FrameType, interface->domain); + if (status < B_OK) + delete protocol; + else + *_protocol = protocol; + + return status; +} + + +static status_t +ipv6_datalink_uninit(net_datalink_protocol* protocol) +{ + sStackModule->unregister_device_handler(protocol->interface->device, + kIPv6FrameType); + delete protocol; + return B_OK; +} + + +static status_t +ipv6_datalink_send_data(net_datalink_protocol* _protocol, net_buffer* buffer) +{ + ipv6_datalink_protocol* protocol = (ipv6_datalink_protocol*)_protocol; + + memcpy(buffer->source, &protocol->hardware_address, + protocol->hardware_address.sdl_len); + + if ((buffer->flags & MSG_MCAST) != 0) { + sockaddr_dl multicastDestination; + ipv6_to_ether_multicast(&multicastDestination, + (sockaddr_in6*)buffer->destination); + memcpy(buffer->destination, &multicastDestination, + sizeof(sockaddr_dl)); + } else { + MutexLocker locker(sCacheLock); + + // Lookup destination (we may need to wait for this) + ndp_entry* entry = ndp_entry::Lookup( + ((struct sockaddr_in6*)buffer->destination)->sin6_addr); + if (entry == NULL) { + status_t status = ndp_start_resolve(protocol, + ((struct sockaddr_in6*)buffer->destination)->sin6_addr, &entry); + if (status < B_OK) + return status; + } + + if ((entry->flags & NDP_FLAG_REJECT) != 0) + return EHOSTUNREACH; + if (!(entry->flags & NDP_FLAG_VALID)) { + // entry is still being resolved. + TRACE(("NDP Queuing packet %p, entry still being resolved.\n", + buffer)); + entry->queue.Add(buffer); + return B_OK; + } + + memcpy(buffer->destination, &entry->hardware_address, + entry->hardware_address.sdl_len); + } + + return protocol->next->module->send_data(protocol->next, buffer); +} + + +static status_t +ipv6_datalink_receive_data(net_buffer* buffer) +{ + NetBufferHeaderReader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + switch (bufferHeader->icmp6_type) { + case ND_NEIGHBOR_SOLICIT: + TRACE((" received Neighbor Solicitation\n")); + handle_neighbor_solicitation(buffer); + break; + + case ND_NEIGHBOR_ADVERT: + TRACE((" received Neighbor Advertisement\n")); + handle_neighbor_advertisement(buffer); + break; + } + + gBufferModule->free(buffer); + return B_OK; +} + + +static status_t +ipv6_datalink_up(net_datalink_protocol* _protocol) +{ + ipv6_datalink_protocol* protocol = (ipv6_datalink_protocol*)_protocol; + status_t status = protocol->next->module->interface_up(protocol->next); + if (status < B_OK) + return status; + + // join multicast address for listening to NS packets + + // REVIEWME: can I rely on this function being called every time + // when interfae address changes? + + if (protocol->interface->address != NULL) { + sockaddr_in6& address = *(sockaddr_in6*)protocol->interface->address; + sockaddr_in6 multicast; + ipv6_to_solicited_multicast(&multicast, address.sin6_addr); + + struct ipv6_mreq mreq; + memcpy(&mreq.ipv6mr_multiaddr, &multicast.sin6_addr, sizeof(in6_addr)); + mreq.ipv6mr_interface = protocol->interface->index; + + if (sIPv6Protocol != NULL) { + sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, + IPV6_JOIN_GROUP, &mreq, sizeof(mreq)); + } + } + + // cache this device's address for later use + + mutex_lock(&sCacheLock); + status = ndp_update_local(protocol); + mutex_unlock(&sCacheLock); + + if (status < B_OK) { + protocol->next->module->interface_down(protocol->next); + return status; + } +} + + +static void +ipv6_datalink_down(net_datalink_protocol* protocol) +{ + // leave the NS multicast address + + // REVIEWME: can I rely on this function being called every time + // when interface address changes? + + if (protocol->interface->address != NULL) { + sockaddr_in6& address = *(sockaddr_in6*)protocol->interface->address; + sockaddr_in6 multicast; + ipv6_to_solicited_multicast(&multicast, address.sin6_addr); + + struct ipv6_mreq mreq; + memcpy(&mreq.ipv6mr_multiaddr, &multicast.sin6_addr, sizeof(in6_addr)); + mreq.ipv6mr_interface = protocol->interface->index; + + if (sIPv6Protocol != NULL) { + sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, + IPV6_LEAVE_GROUP, &mreq, sizeof(mreq)); + } + } + + // remove local NDP entry from the cache + + if (protocol->interface->address != NULL) { + MutexLocker locker(sCacheLock); + + ndp_entry* entry = ndp_entry::Lookup( + ((sockaddr_in6*)protocol->interface->address)->sin6_addr); + if (entry != NULL) { + hash_remove(sCache, entry); + entry->flags |= NDP_FLAG_REMOVED; + locker.Unlock(); + + delete entry; + } + } + + protocol->next->module->interface_down(protocol->next); +} + + +static status_t +ipv6_datalink_control(net_datalink_protocol* protocol, int32 op, void* argument, + size_t length) +{ + return protocol->next->module->control(protocol->next, op, argument, + length); +} + + +static status_t +ipv6_datalink_join_multicast(net_datalink_protocol* protocol, + const sockaddr* address) +{ + if (address->sa_family != AF_INET6) + return EINVAL; + + sockaddr_dl multicastAddress; + ipv6_to_ether_multicast(&multicastAddress, (const sockaddr_in6*)address); + + return protocol->next->module->join_multicast(protocol->next, + (sockaddr*)&multicastAddress); +} + + +static status_t +ipv6_datalink_leave_multicast(net_datalink_protocol* protocol, + const sockaddr* address) +{ + if (address->sa_family != AF_INET6) + return EINVAL; + + sockaddr_dl multicastAddress; + ipv6_to_ether_multicast(&multicastAddress, (const sockaddr_in6*)address); + + return protocol->next->module->leave_multicast(protocol->next, + (sockaddr*)&multicastAddress); +} + + +static status_t +ipv6_datalink_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return ndp_init(); + + case B_MODULE_UNINIT: + return ndp_uninit(); + } + + return B_ERROR; +} + +net_datalink_protocol_module_info gIPv6DataLinkModule = { + { + "network/datalink_protocols/ipv6_datagram/v1", + 0, + ipv6_datalink_std_ops + }, + ipv6_datalink_init, + ipv6_datalink_uninit, + ipv6_datalink_send_data, + ipv6_datalink_receive_data, + ipv6_datalink_up, + ipv6_datalink_down, + ipv6_datalink_control, + ipv6_datalink_join_multicast, + ipv6_datalink_leave_multicast, +}; + + +module_dependency module_dependencies[] = { + {NET_STACK_MODULE_NAME, (module_info**)&sStackModule}, + {NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule}, + {"network/protocols/ipv6/v1", (module_info**)&sIPv6Module}, + {} +}; + +module_info* modules[] = { + (module_info*)&gIPv6DataLinkModule, + NULL +}; diff --git a/src/add-ons/kernel/network/datalink_protocols/loopback_frame/loopback_frame.cpp b/src/add-ons/kernel/network/datalink_protocols/loopback_frame/loopback_frame.cpp index 2f23a47fde..56a97a7f15 100644 --- a/src/add-ons/kernel/network/datalink_protocols/loopback_frame/loopback_frame.cpp +++ b/src/add-ons/kernel/network/datalink_protocols/loopback_frame/loopback_frame.cpp @@ -171,6 +171,7 @@ static net_datalink_protocol_module_info sLoopbackFrameModule = { loopback_frame_init, loopback_frame_uninit, loopback_frame_send_data, + NULL, // receive_data loopback_frame_up, loopback_frame_down, loopback_frame_control, diff --git a/src/add-ons/kernel/network/protocols/Jamfile b/src/add-ons/kernel/network/protocols/Jamfile index f9ebdd0406..f7f6c22cfa 100644 --- a/src/add-ons/kernel/network/protocols/Jamfile +++ b/src/add-ons/kernel/network/protocols/Jamfile @@ -1,7 +1,9 @@ SubDir HAIKU_TOP src add-ons kernel network protocols ; SubInclude HAIKU_TOP src add-ons kernel network protocols icmp ; +SubInclude HAIKU_TOP src add-ons kernel network protocols icmp6 ; SubInclude HAIKU_TOP src add-ons kernel network protocols ipv4 ; +SubInclude HAIKU_TOP src add-ons kernel network protocols ipv6 ; SubInclude HAIKU_TOP src add-ons kernel network protocols l2cap ; SubInclude HAIKU_TOP src add-ons kernel network protocols tcp ; SubInclude HAIKU_TOP src add-ons kernel network protocols udp ; diff --git a/src/add-ons/kernel/network/protocols/icmp6/Jamfile b/src/add-ons/kernel/network/protocols/icmp6/Jamfile new file mode 100644 index 0000000000..30199a7e0e --- /dev/null +++ b/src/add-ons/kernel/network/protocols/icmp6/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols icmp6 ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders kernel net ; + +KernelAddon icmp6 : + icmp6.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/protocols + : icmp6 ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/protocols/icmp6/icmp6.cpp b/src/add-ons/kernel/network/protocols/icmp6/icmp6.cpp new file mode 100644 index 0000000000..151b220b0a --- /dev/null +++ b/src/add-ons/kernel/network/protocols/icmp6/icmp6.cpp @@ -0,0 +1,394 @@ +/* + * Copyright 2006-2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "../ipv6/ipv6_utils.h" // ipv6_checksum() + + +#define TRACE_ICMP6 +#ifdef TRACE_ICMP6 +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +typedef NetBufferField ICMP6ChecksumField; + + +net_buffer_module_info *gBufferModule; +static net_stack_module_info *sStackModule; +static net_datalink_protocol_module_info *sIPv6DatalinkModule; + + +net_protocol * +icmp6_init_protocol(net_socket *socket) +{ + net_protocol *protocol = new (std::nothrow) net_protocol; + if (protocol == NULL) + return NULL; + + return protocol; +} + + +status_t +icmp6_uninit_protocol(net_protocol *protocol) +{ + delete protocol; + return B_OK; +} + + +status_t +icmp6_open(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +icmp6_close(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +icmp6_free(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +icmp6_connect(net_protocol *protocol, const struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +icmp6_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) +{ + return EOPNOTSUPP; +} + + +status_t +icmp6_control(net_protocol *protocol, int level, int option, void *value, + size_t *_length) +{ + return protocol->next->module->control(protocol->next, level, option, + value, _length); +} + + +status_t +icmp6_getsockopt(net_protocol *protocol, int level, int option, + void *value, int *length) +{ + return protocol->next->module->getsockopt(protocol->next, level, option, + value, length); +} + + +status_t +icmp6_setsockopt(net_protocol *protocol, int level, int option, + const void *value, int length) +{ + return protocol->next->module->setsockopt(protocol->next, level, option, + value, length); +} + + +status_t +icmp6_bind(net_protocol *protocol, const struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +icmp6_unbind(net_protocol *protocol, struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +icmp6_listen(net_protocol *protocol, int count) +{ + return EOPNOTSUPP; +} + + +status_t +icmp6_shutdown(net_protocol *protocol, int direction) +{ + return EOPNOTSUPP; +} + + +status_t +icmp6_send_data(net_protocol *protocol, net_buffer *buffer) +{ + return protocol->next->module->send_data(protocol->next, buffer); +} + + +status_t +icmp6_send_routed_data(net_protocol *protocol, struct net_route *route, + net_buffer *buffer) +{ + return protocol->next->module->send_routed_data(protocol->next, route, buffer); +} + + +ssize_t +icmp6_send_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +status_t +icmp6_read_data(net_protocol *protocol, size_t numBytes, uint32 flags, + net_buffer **_buffer) +{ + return B_ERROR; +} + + +ssize_t +icmp6_read_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +struct net_domain * +icmp6_get_domain(net_protocol *protocol) +{ + return protocol->next->module->get_domain(protocol->next); +} + + +size_t +icmp6_get_mtu(net_protocol *protocol, const struct sockaddr *address) +{ + return protocol->next->module->get_mtu(protocol->next, address); +} + + +status_t +icmp6_receive_data(net_buffer *buffer) +{ + TRACE(("ICMPv6 received some data, buffer length %lu\n", buffer->size)); + + NetBufferHeaderReader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + icmp6_hdr &header = bufferHeader.Data(); + + TRACE((" got type %u, code %u, checksum 0x%x\n", header.icmp6_type, + header.icmp6_code, header.icmp6_cksum)); + + // compute and check the checksum + uint16 checksum; + checksum = gBufferModule->checksum(buffer, 0, buffer->size, false); + checksum = ipv6_checksum(&((sockaddr_in6*)buffer->source)->sin6_addr, + &((sockaddr_in6*)buffer->destination)->sin6_addr, + buffer->size, IPPROTO_ICMPV6, checksum); + + TRACE((" computed checksum: %ld\n", checksum)); + + if (checksum != 0) + return B_BAD_DATA; + + switch (header.icmp6_type) { + case ICMP6_ECHO_REPLY: + break; + + case ICMP6_ECHO_REQUEST: + { + net_domain *domain; + if (buffer->interface != NULL) { + domain = buffer->interface->domain; + + // We only reply to echo requests of our local interface; we + // don't reply to broadcast requests + if (!domain->address_module->equal_addresses( + buffer->interface->address, buffer->destination)) + break; + } else + domain = sStackModule->get_domain(buffer->source->sa_family); + + if (domain == NULL || domain->module == NULL) + break; + + net_buffer *reply = gBufferModule->duplicate(buffer); + if (reply == NULL) + return B_NO_MEMORY; + + gBufferModule->swap_addresses(reply); + + // There already is an ICMP header, and we'll reuse it + NetBufferHeaderReader header(reply); + + header->icmp6_type = ICMP6_ECHO_REPLY; + header->icmp6_code = 0; + header->icmp6_cksum = 0; + + header.Sync(); + + checksum = gBufferModule->checksum(buffer, 0, buffer->size, false); + *ICMP6ChecksumField(reply) = + ipv6_checksum(&((sockaddr_in6*)buffer->source)->sin6_addr, + &((sockaddr_in6*)buffer->destination)->sin6_addr, + buffer->size, IPPROTO_ICMPV6, checksum); + + status_t status = domain->module->send_data(NULL, reply); + if (status < B_OK) { + gBufferModule->free(reply); + return status; + } + } + + default: + // forward unrecognized messages to datalink layer + return sIPv6DatalinkModule->receive_data(buffer); + } + + gBufferModule->free(buffer); + return B_OK; +} + + +status_t +icmp6_deliver_data(net_protocol *protocol, net_buffer *buffer) +{ + // TODO: does this look OK? + return icmp6_receive_data(buffer); +} + + +status_t +icmp6_error(uint32 code, net_buffer *data) +{ + return B_ERROR; +} + + +status_t +icmp6_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, + void *errorData) +{ + return B_ERROR; +} + + +// #pragma mark - + + +static status_t +icmp6_init() +{ + sStackModule->register_domain_protocols(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6, + "network/protocols/icmp6/v1", + "network/protocols/ipv6/v1", + NULL); + + sStackModule->register_domain_receiving_protocol(AF_INET6, IPPROTO_ICMPV6, + "network/protocols/icmp6/v1"); + + return B_OK; +} + + +static status_t +icmp6_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return icmp6_init(); + + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +net_protocol_module_info sICMP6Module = { + { + "network/protocols/icmp6/v1", + 0, + icmp6_std_ops + }, + NET_PROTOCOL_ATOMIC_MESSAGES, + + icmp6_init_protocol, + icmp6_uninit_protocol, + icmp6_open, + icmp6_close, + icmp6_free, + icmp6_connect, + icmp6_accept, + icmp6_control, + icmp6_getsockopt, + icmp6_setsockopt, + icmp6_bind, + icmp6_unbind, + icmp6_listen, + icmp6_shutdown, + icmp6_send_data, + icmp6_send_routed_data, + icmp6_send_avail, + icmp6_read_data, + icmp6_read_avail, + icmp6_get_domain, + icmp6_get_mtu, + icmp6_receive_data, + icmp6_deliver_data, + icmp6_error, + icmp6_error_reply, + NULL, // add_ancillary_data() + NULL, // process_ancillary_data() + NULL, // process_ancillary_data_no_container() + NULL, // send_data_no_buffer() + NULL // read_data_no_buffer() +}; + +module_dependency module_dependencies[] = { + {NET_STACK_MODULE_NAME, (module_info **)&sStackModule}, + {NET_BUFFER_MODULE_NAME, (module_info **)&gBufferModule}, + {"network/datalink_protocols/ipv6_datagram/v1", + (module_info **)&sIPv6DatalinkModule}, + {} +}; + +module_info *modules[] = { + (module_info *)&sICMP6Module, + NULL +}; diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp index c99b03fad4..9c9a908533 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp @@ -1595,6 +1595,7 @@ ipv4_receive_data(net_buffer* buffer) memcpy(buffer->destination, &destination, sizeof(sockaddr_in)); uint8 protocol = buffer->protocol = header.protocol; + buffer->hoplimit = header.time_to_live; // remove any trailing/padding data status_t status = gBufferModule->trim(buffer, packetLength); diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp b/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp index 041d93ca94..d35d4c7a8c 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp @@ -489,14 +489,15 @@ ipv4_checksum_address(struct Checksum *checksum, const sockaddr *address) return B_OK; } + static void -ipv4_get_loopback_address(sockaddr *result) +ipv4_get_loopback_address(sockaddr *_address) { - sockaddr_in *resultIn = (sockaddr_in *)result; - memset(resultIn, 0, sizeof(resultIn)); - resultIn->sin_len = sizeof(sockaddr_in); - resultIn->sin_family = AF_INET; - resultIn->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sockaddr_in *address = (sockaddr_in *)_address; + memset(address, 0, sizeof(sockaddr_in)); + address->sin_len = sizeof(sockaddr_in); + address->sin_family = AF_INET; + address->sin_addr.s_addr = htonl(INADDR_LOOPBACK); } @@ -506,6 +507,7 @@ net_address_module_info gIPv4AddressModule = { 0, NULL }, + true, // has_broadcast_address ipv4_copy_address, ipv4_mask_address, ipv4_equal_addresses, diff --git a/src/add-ons/kernel/network/protocols/ipv6/Jamfile b/src/add-ons/kernel/network/protocols/ipv6/Jamfile new file mode 100644 index 0000000000..2f3371c2bd --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/Jamfile @@ -0,0 +1,29 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols ipv6 ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateKernelHeaders ; +UsePrivateHeaders net ; + +KernelAddon ipv6 : + ipv6.cpp + ipv6_address.cpp + ipv6_utils.cpp + multicast.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/protocols + : ipv6 ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/protocols/ipv6/ipv6.cpp b/src/add-ons/kernel/network/protocols/ipv6/ipv6.cpp new file mode 100644 index 0000000000..248752cc47 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/ipv6.cpp @@ -0,0 +1,1222 @@ +/* + * Copyright 2006-2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + * Atis Elsts, the.kfx@gmail.com + */ + + +#include "ipv6_address.h" +#include "ipv6_utils.h" +#include "multicast.h" +#include "../../stack/domains.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define TRACE_IPV6 +#ifdef TRACE_IPV6 +# define TRACE(format, args...) \ + dprintf("IPv6 [%llu] " format "\n", system_time() , ##args) +# define TRACE_SK(protocol, format, args...) \ + dprintf("IPv6 [%llu] %p " format "\n", system_time(), \ + protocol , ##args) +#else +# define TRACE(args...) do { } while (0) +# define TRACE_SK(args...) do { } while (0) +#endif + + +struct IPv6Header { + struct ip6_hdr h; + + uint8 ProtocolVersion() const { return h.ip6_vfc & IPV6_VERSION_MASK; } + uint8 ServiceType() const { return ntohl(h.ip6_flow) >> 20;} + uint16 PayloadLength() const { return ntohs(h.ip6_plen); } + const in6_addr& Dst() const { return h.ip6_dst; } + const in6_addr& Src() const { return h.ip6_src; } + uint16 GetTransportHeaderOffset(net_buffer* buffer) const; +}; + +class RawSocket + : public DoublyLinkedListLinkImpl, public DatagramSocket<> { +public: + RawSocket(net_socket* socket); +}; + +typedef DoublyLinkedList RawSocketList; + + +typedef MulticastGroupInterface IPv6GroupInterface; +typedef MulticastFilter IPv6MulticastFilter; + +struct MulticastStateHash { + typedef std::pair KeyType; + typedef IPv6GroupInterface ValueType; + + size_t HashKey(const KeyType &key) const; + size_t Hash(ValueType* value) const + { return HashKey(std::make_pair(&value->Address(), + value->Interface()->index)); } + bool Compare(const KeyType &key, ValueType* value) const + { return value->Interface()->index == key.second + && value->Address() == *key.first; } + bool CompareValues(ValueType* value1, ValueType* value2) const + { return value1->Interface()->index == value2->Interface()->index + && value1->Address() == value2->Address(); } + ValueType*& GetLink(ValueType* value) const { return value->HashLink(); } +}; + + +struct ipv6_protocol : net_protocol { + ipv6_protocol() + : + multicast_filter(this) + { + } + + RawSocket *raw; + uint8 service_type; + uint8 time_to_live; + uint8 multicast_time_to_live; + uint8 receive_hoplimit; + uint8 receive_pktinfo; + struct sockaddr* interface_address; // for IPV6_MULTICAST_IF + + IPv6MulticastFilter multicast_filter; +}; + + +static const int kDefaultTTL = 254; +static const int kDefaultMulticastTTL = 1; + + +extern net_protocol_module_info gIPv6Module; + // we need this in ipv6_std_ops() for registering the AF_INET domain + +net_stack_module_info* gStackModule; +net_buffer_module_info* gBufferModule; + +static struct net_domain* sDomain; +static net_datalink_module_info* sDatalinkModule; +static net_socket_module_info* sSocketModule; +static RawSocketList sRawSockets; +static mutex sRawSocketsLock; +static mutex sMulticastGroupsLock; + +typedef MultiHashTable MulticastState; +static MulticastState* sMulticastState; + +static net_protocol_module_info* sReceivingProtocol[256]; +static mutex sReceivingProtocolLock; + + +uint16 +IPv6Header::GetTransportHeaderOffset(net_buffer* buffer) const +{ + uint16 offset = sizeof(struct ip6_hdr); + uint8 next = h.ip6_nxt; + + // these are the extension headers that might be supported one day + while (next == IPPROTO_HOPOPTS + || next == IPPROTO_ROUTING + || next == IPPROTO_FRAGMENT + || next == IPPROTO_ESP + || next == IPPROTO_AH + || next == IPPROTO_DSTOPTS) { + struct ip6_ext extensionHeader; + status_t status = gBufferModule->read(buffer, offset, + &extensionHeader, sizeof(ip6_ext)); + if (status != B_OK) + break; + + next = extensionHeader.ip6e_nxt; + offset += extensionHeader.ip6e_len; + } + + buffer->protocol = next; + return offset; +} + + +RawSocket::RawSocket(net_socket* socket) + : + DatagramSocket<>("ipv6 raw socket", socket) +{ +} + + +size_t +MulticastStateHash::HashKey(const KeyType &key) const +{ + size_t result = 0; + result = jenkins_hashword((const uint32*)&key.first, + sizeof(in6_addr) / sizeof(uint32), result); + result = jenkins_hashword(&key.second, 1, result); + return result; +} + + +// #pragma mark - + + +static inline void +dump_ipv6_header(IPv6Header &header) +{ +#ifdef TRACE_IPV6 + char addrbuf[INET6_ADDRSTRLEN]; + dprintf(" version: %d\n", header.ProtocolVersion() >> 4); + dprintf(" service_type: %d\n", header.ServiceType()); + dprintf(" payload_length: %d\n", header.PayloadLength()); + dprintf(" next_header: %d\n", header.h.ip6_nxt); + dprintf(" hop_limit: %d\n", header.h.ip6_hops); + dprintf(" source: %s\n", ip6_sprintf(&header.h.ip6_src, addrbuf)); + dprintf(" destination: %s\n", ip6_sprintf(&header.h.ip6_dst, addrbuf)); +#endif +} + + +static status_t +deliver_multicast(net_protocol_module_info* module, net_buffer* buffer, + bool deliverToRaw, net_interface *interface) +{ + sockaddr_in6* multicastAddr = (sockaddr_in6*)buffer->destination; + + MulticastState::ValueIterator it = sMulticastState->Lookup(std::make_pair( + &multicastAddr->sin6_addr, interface->index)); + + while (it.HasNext()) { + IPv6GroupInterface* state = it.Next(); + ipv6_protocol* ipproto = state->Parent()->Socket(); + + if (deliverToRaw && ipproto->raw == NULL) + continue; + + if (state->FilterAccepts(buffer)) { + // TODO: do as in IPv4 code + module->deliver_data(ipproto, buffer); + } + } + + return B_OK; +} + + +static status_t +deliver_multicast(net_protocol_module_info* module, net_buffer* buffer, + bool deliverToRaw) +{ + if (module->deliver_data == NULL) + return B_OK; + + MutexLocker _(sMulticastGroupsLock); + + status_t status = B_OK; + if (buffer->interface) { + status = deliver_multicast(module, buffer, deliverToRaw, + buffer->interface); + } else { + // REVIEWME: does this look ok? + net_domain_private* domain = (net_domain_private*)sDomain; + RecursiveLocker locker(domain->lock); + + net_interface* interface = NULL; + while (true) { + interface = (net_interface*)list_get_next_item( + &domain->interfaces, interface); + if (interface == NULL) + break; + + status = deliver_multicast(module, buffer, deliverToRaw, interface); + if (status < B_OK) + break; + } + } + return status; +} + + +static void +raw_receive_data(net_buffer* buffer) +{ + MutexLocker locker(sRawSocketsLock); + + if (sRawSockets.IsEmpty()) + return; + + TRACE("RawReceiveData(%i)", buffer->protocol); + + if ((buffer->flags & MSG_MCAST) != 0) { + deliver_multicast(&gIPv6Module, buffer, true); + } else { + RawSocketList::Iterator iterator = sRawSockets.GetIterator(); + + while (iterator.HasNext()) { + RawSocket* raw = iterator.Next(); + + if (raw->Socket()->protocol == buffer->protocol) + raw->SocketEnqueue(buffer); + } + } +} + + +static inline sockaddr* +fill_sockaddr_in6(sockaddr_in6* target, const in6_addr &address) +{ + target->sin6_family = AF_INET6; + target->sin6_len = sizeof(sockaddr_in6); + target->sin6_port = 0; + target->sin6_flowinfo = 0; + memcpy(target->sin6_addr.s6_addr, address.s6_addr, sizeof(in6_addr)); + target->sin6_scope_id = 0; + return (sockaddr*)target; +} + + +status_t +IPv6Multicast::JoinGroup(IPv6GroupInterface* state) +{ + MutexLocker _(sMulticastGroupsLock); + + sockaddr_in6 groupAddr; + net_interface* interface = state->Interface(); + + status_t status = interface->first_info->join_multicast( + interface->first_protocol, + fill_sockaddr_in6(&groupAddr, state->Address())); + if (status != B_OK) + return status; + + sMulticastState->Insert(state); + return B_OK; +} + + +status_t +IPv6Multicast::LeaveGroup(IPv6GroupInterface* state) +{ + MutexLocker _(sMulticastGroupsLock); + + sMulticastState->Remove(state); + + sockaddr_in6 groupAddr; + net_interface* interface = state->Interface(); + + return interface->first_protocol->module->join_multicast( + interface->first_protocol, + fill_sockaddr_in6(&groupAddr, state->Address())); +} + + +static net_protocol_module_info* +receiving_protocol(uint8 protocol) +{ + net_protocol_module_info* module = sReceivingProtocol[protocol]; + if (module != NULL) + return module; + + MutexLocker locker(sReceivingProtocolLock); + + module = sReceivingProtocol[protocol]; + if (module != NULL) + return module; + + if (gStackModule->get_domain_receiving_protocol(sDomain, protocol, + &module) == B_OK) + sReceivingProtocol[protocol] = module; + + return module; +} + + +static status_t +ipv6_delta_group(IPv6GroupInterface* group, int option, + net_interface* interface, const in6_addr* sourceAddr) +{ + switch (option) { + case IPV6_JOIN_GROUP: + return group->Add(); + case IPV6_LEAVE_GROUP: + return group->Drop(); + } + + return B_ERROR; +} + + +static status_t +ipv6_delta_membership(ipv6_protocol* protocol, int option, + net_interface* interface, const in6_addr* groupAddr, + const in6_addr* sourceAddr) +{ + IPv6MulticastFilter &filter = protocol->multicast_filter; + IPv6GroupInterface* state = NULL; + status_t status = B_OK; + + switch (option) { + // TODO: support more options + case IPV6_JOIN_GROUP: + status = filter.GetState(*groupAddr, interface, state, true); + break; + + case IPV6_LEAVE_GROUP: + filter.GetState(*groupAddr, interface, state, false); + if (state == NULL) + return EADDRNOTAVAIL; + break; + } + + if (status != B_OK) + return status; + + status = ipv6_delta_group(state, option, interface, sourceAddr); + filter.ReturnState(state); + return status; +} + + +static status_t +ipv6_delta_membership(ipv6_protocol* protocol, int option, + uint32 interfaceIndex, in6_addr* groupAddr, in6_addr* sourceAddr) +{ + net_interface* interface; + + // TODO: can the interface be unspecified? + interface = sDatalinkModule->get_interface(sDomain, interfaceIndex); + + if (interface == NULL) + return ENODEV; + + return ipv6_delta_membership(protocol, option, interface, + groupAddr, sourceAddr); +} + + +static status_t +get_int_option(void* target, size_t length, int value) +{ + if (length != sizeof(int)) + return B_BAD_VALUE; + + return user_memcpy(target, &value, sizeof(int)); +} + + +template static status_t +set_int_option(Type &target, const void* _value, size_t length) +{ + int value; + + if (length != sizeof(int)) + return B_BAD_VALUE; + + if (user_memcpy(&value, _value, sizeof(int)) != B_OK) + return B_BAD_ADDRESS; + + target = value; + return B_OK; +} + + +// #pragma mark - + + +net_protocol* +ipv6_init_protocol(net_socket* socket) +{ + ipv6_protocol* protocol = new (std::nothrow) ipv6_protocol(); + if (protocol == NULL) + return NULL; + + protocol->raw = NULL; + protocol->service_type = 0; + protocol->time_to_live = kDefaultTTL; + protocol->multicast_time_to_live = kDefaultMulticastTTL; + protocol->receive_hoplimit = 0; + protocol->receive_pktinfo = 0; + protocol->interface_address = NULL; + return protocol; +} + + +status_t +ipv6_uninit_protocol(net_protocol* _protocol) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + + delete protocol->raw; + delete protocol->interface_address; + delete protocol; + return B_OK; +} + + +/*! Since open() is only called on the top level protocol, when we get here + it means we are on a SOCK_RAW socket. +*/ +status_t +ipv6_open(net_protocol* _protocol) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + + RawSocket* raw = new (std::nothrow) RawSocket(protocol->socket); + if (raw == NULL) + return B_NO_MEMORY; + + status_t status = raw->InitCheck(); + if (status != B_OK) { + delete raw; + return status; + } + + TRACE_SK(protocol, "Open()"); + + protocol->raw = raw; + + MutexLocker locker(sRawSocketsLock); + sRawSockets.Add(raw); + return B_OK; +} + + +status_t +ipv6_close(net_protocol* _protocol) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + RawSocket* raw = protocol->raw; + if (raw == NULL) + return B_ERROR; + + TRACE_SK(protocol, "Close()"); + + MutexLocker locker(sRawSocketsLock); + sRawSockets.Remove(raw); + delete raw; + protocol->raw = NULL; + + return B_OK; +} + + +status_t +ipv6_free(net_protocol* protocol) +{ + return B_OK; +} + + +status_t +ipv6_connect(net_protocol* protocol, const struct sockaddr* address) +{ + return B_ERROR; +} + + +status_t +ipv6_accept(net_protocol* protocol, struct net_socket** _acceptedSocket) +{ + return EOPNOTSUPP; +} + + +status_t +ipv6_control(net_protocol* _protocol, int level, int option, void* value, + size_t* _length) +{ + if ((level & LEVEL_MASK) != IPPROTO_IPV6) + return sDatalinkModule->control(sDomain, option, value, _length); + + return B_BAD_VALUE; +} + + +status_t +ipv6_getsockopt(net_protocol* _protocol, int level, int option, void* value, + int* _length) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + + if (level == IPPROTO_IPV6) { + // TODO: support more of these options + + if (option == IPV6_MULTICAST_HOPS) { + return get_int_option(value, *_length, + protocol->multicast_time_to_live); + } + if (option == IPV6_MULTICAST_LOOP) + return EOPNOTSUPP; + if (option == IPV6_UNICAST_HOPS) + return get_int_option(value, *_length, protocol->time_to_live); + if (option == IPV6_V6ONLY) + return EOPNOTSUPP; + if (option == IPV6_RECVPKTINFO) + return get_int_option(value, *_length, protocol->receive_pktinfo); + if (option == IPV6_RECVHOPLIMIT) + return get_int_option(value, *_length, protocol->receive_hoplimit); + if (option == IPV6_JOIN_GROUP + || option == IPV6_LEAVE_GROUP) + return EOPNOTSUPP; + + dprintf("IPv6::getsockopt(): get unknown option: %d\n", option); + return ENOPROTOOPT; + } + + return sSocketModule->get_option(protocol->socket, level, option, value, + _length); +} + + +status_t +ipv6_setsockopt(net_protocol* _protocol, int level, int option, + const void* value, int length) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + + if (level == IPPROTO_IPV6) { + // TODO: support more of these options + + if (option == IPV6_MULTICAST_IF) { + if (length != sizeof(struct in6_addr)) + return B_BAD_VALUE; + + struct sockaddr_in6* address = new (std::nothrow) sockaddr_in6; + if (address == NULL) + return B_NO_MEMORY; + + if (user_memcpy(&address->sin6_addr, value, sizeof(in6_addr)) + != B_OK) { + delete address; + return B_BAD_ADDRESS; + } + + // Using the unspecifed address to remove the previous setting. + if (IN6_IS_ADDR_UNSPECIFIED(&address->sin6_addr)) { + delete address; + delete protocol->interface_address; + protocol->interface_address = NULL; + return B_OK; + } + + struct net_interface* interface + = sDatalinkModule->get_interface_with_address(sDomain, + (struct sockaddr*)address); + if (interface == NULL) { + delete address; + return EADDRNOTAVAIL; + } + + delete protocol->interface_address; + protocol->interface_address = (struct sockaddr*)address; + return B_OK; + } + if (option == IPV6_MULTICAST_HOPS) { + return set_int_option(protocol->multicast_time_to_live, + value, length); + } + if (option == IPV6_MULTICAST_LOOP) + return EOPNOTSUPP; + if (option == IPV6_UNICAST_HOPS) + return set_int_option(protocol->time_to_live, value, length); + if (option == IPV6_V6ONLY) + return EOPNOTSUPP; + if (option == IPV6_RECVPKTINFO) + return set_int_option(protocol->receive_pktinfo, value, length); + if (option == IPV6_RECVHOPLIMIT) + return set_int_option(protocol->receive_hoplimit, value, length); + if (option == IPV6_JOIN_GROUP || option == IPV6_LEAVE_GROUP) { + ipv6_mreq mreq; + if (length != sizeof(ipv6_mreq)) + return B_BAD_VALUE; + if (user_memcpy(&mreq, value, sizeof(ipv6_mreq)) != B_OK) + return B_BAD_ADDRESS; + + return ipv6_delta_membership(protocol, option, mreq.ipv6mr_interface, + &mreq.ipv6mr_multiaddr, NULL); + } + + dprintf("IPv6::setsockopt(): set unknown option: %d\n", option); + return ENOPROTOOPT; + } + + return sSocketModule->set_option(protocol->socket, level, option, + value, length); +} + + +status_t +ipv6_bind(net_protocol* protocol, const sockaddr* _address) +{ + if (_address->sa_family != AF_INET6) + return EAFNOSUPPORT; + + const sockaddr_in6* address = (const sockaddr_in6*)_address; + + // only INADDR_ANY and addresses of local interfaces are accepted: + if (IN6_IS_ADDR_UNSPECIFIED(&address->sin6_addr) + || IN6_IS_ADDR_MULTICAST(&address->sin6_addr) + || sDatalinkModule->is_local_address(sDomain, _address, NULL, NULL)) { + memcpy(&protocol->socket->address, address, sizeof(sockaddr_in6)); + protocol->socket->address.ss_len = sizeof(sockaddr_in6); + // explicitly set length, as our callers can't be trusted to + // always provide the correct length! + return B_OK; + } + + return B_ERROR; + // address is unknown on this host +} + + +status_t +ipv6_unbind(net_protocol* protocol, struct sockaddr* address) +{ + // nothing to do here + return B_OK; +} + + +status_t +ipv6_listen(net_protocol* protocol, int count) +{ + return EOPNOTSUPP; +} + + +status_t +ipv6_shutdown(net_protocol* protocol, int direction) +{ + return EOPNOTSUPP; +} + + +static uint8 +ip6_select_hoplimit(net_protocol* _protocol, net_buffer* buffer) +{ + // TODO: the precedence should be as follows: + // 1. Hoplimit value specified via ioctl. + // 2. (If the outgoing interface is detected) the current + // hop limit of the interface specified by router advertisement. + // 3. The system default hoplimit. + + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + const bool isMulticast = buffer->flags & MSG_MCAST; + + if (protocol) { + return isMulticast ? protocol->multicast_time_to_live + : protocol->time_to_live; + } + return isMulticast ? kDefaultMulticastTTL : kDefaultTTL; +} + + +status_t +ipv6_send_routed_data(net_protocol* _protocol, struct net_route* route, + net_buffer* buffer) +{ + if (route == NULL) + return B_BAD_VALUE; + + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + net_interface* interface = route->interface; + uint8 protocolNumber; + if (protocol != NULL && protocol->socket != NULL) + protocolNumber = protocol->socket->protocol; + else + protocolNumber = buffer->protocol; + + TRACE_SK(protocol, "SendRoutedData(%p, %p [%ld bytes])", route, buffer, + buffer->size); + + sockaddr_in6& source = *(sockaddr_in6*)buffer->source; + sockaddr_in6& destination = *(sockaddr_in6*)buffer->destination; + + buffer->flags &= ~(MSG_BCAST | MSG_MCAST); + + if (IN6_IS_ADDR_UNSPECIFIED(&destination.sin6_addr)) + return EDESTADDRREQ; + + if (IN6_IS_ADDR_MULTICAST(&destination.sin6_addr)) + buffer->flags |= MSG_MCAST; + + uint16 dataLength = buffer->size; + + // Add IPv6 header + + NetBufferPrepend header(buffer); + if (header.Status() != B_OK) + return header.Status(); + + if (buffer->size > 0xffff) + return EMSGSIZE; + + uint32 flowinfo = 0; + // TODO: fill in the flow id from somewhere + if (protocol) { + // fill in traffic class + flowinfo |= htonl(protocol->service_type << 20); + } + // set lower 28 bits + header->ip6_flow = htonl(flowinfo) & IPV6_FLOWINFO_MASK; + // set upper 4 bits + header->ip6_vfc |= IPV6_VERSION; + header->ip6_plen = htons(dataLength); + header->ip6_nxt = protocolNumber; + header->ip6_hlim = ip6_select_hoplimit(protocol, buffer); + memcpy(&header->ip6_src, &source.sin6_addr, sizeof(in6_addr)); + memcpy(&header->ip6_dst, &destination.sin6_addr, sizeof(in6_addr)); + + header.Sync(); + + // write the checksum for ICMPv6 sockets + if (protocolNumber == IPPROTO_ICMPV6 + && dataLength >= sizeof(struct icmp6_hdr)) { + NetBufferField + icmpChecksum(buffer); + // first make sure the existing checksum is zero + *icmpChecksum = 0; + icmpChecksum.Sync(); + + uint16 checksum = gBufferModule->checksum(buffer, sizeof(ip6_hdr), + buffer->size - sizeof(ip6_hdr), false); + checksum = ipv6_checksum(&header->ip6_src, + &header->ip6_dst, dataLength, protocolNumber, + checksum); + *icmpChecksum = checksum; + } + + char addrbuf[INET6_ADDRSTRLEN]; + TRACE_SK(protocol, " SendRoutedData(): destination: %s", + ip6_sprintf(&destination.sin6_addr, addrbuf)); + + uint32 mtu = route->mtu ? route->mtu : interface->mtu; + if (buffer->size > mtu) { + // we need to fragment the packet + return EMSGSIZE; // TODO + //return send_fragments(protocol, route, buffer, mtu); + } + + return sDatalinkModule->send_data(route, buffer); +} + + +status_t +ipv6_send_data(net_protocol* _protocol, net_buffer* buffer) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + + TRACE_SK(protocol, "SendData(%p [%ld bytes])", buffer, buffer->size); + + sockaddr_in6* destination = (sockaddr_in6*)buffer->destination; + + // handle IPV6_MULTICAST_IF + if (IN6_IS_ADDR_MULTICAST(&destination->sin6_addr) + && protocol->interface_address != NULL) { + net_interface* interface + = sDatalinkModule->get_interface_with_address(sDomain, + protocol->interface_address); + if (interface == NULL || (interface->flags & IFF_UP) == 0) + return EADDRNOTAVAIL; + + buffer->interface = interface; + + net_route* route = sDatalinkModule->get_route(sDomain, + interface->address); + if (route == NULL) + return ENETUNREACH; + + return sDatalinkModule->send_data(route, buffer); + } + + return sDatalinkModule->send_datagram(protocol, sDomain, buffer); +} + + +ssize_t +ipv6_send_avail(net_protocol* protocol) +{ + return B_ERROR; +} + + +status_t +ipv6_read_data(net_protocol* _protocol, size_t numBytes, uint32 flags, + net_buffer** _buffer) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + RawSocket* raw = protocol->raw; + if (raw == NULL) + return B_ERROR; + + TRACE_SK(protocol, "ReadData(%lu, 0x%lx)", numBytes, flags); + + return raw->SocketDequeue(flags, _buffer); +} + + +ssize_t +ipv6_read_avail(net_protocol* _protocol) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + RawSocket* raw = protocol->raw; + if (raw == NULL) + return B_ERROR; + + return raw->AvailableData(); +} + + +struct net_domain* +ipv6_get_domain(net_protocol* protocol) +{ + return sDomain; +} + + +size_t +ipv6_get_mtu(net_protocol* protocol, const struct sockaddr* address) +{ + net_route* route = sDatalinkModule->get_route(sDomain, address); + if (route == NULL) + return 0; + + size_t mtu; + if (route->mtu != 0) + mtu = route->mtu; + else + mtu = route->interface->mtu; + + sDatalinkModule->put_route(sDomain, route); + // TODO: what about extension headers? + // this function probably shoud be changed in calling places, not here + return mtu - sizeof(ip6_hdr); +} + + +status_t +ipv6_receive_data(net_buffer* buffer) +{ + TRACE("ReceiveData(%p [%ld bytes])", buffer, buffer->size); + + NetBufferHeaderReader bufferHeader(buffer); + if (bufferHeader.Status() != B_OK) + return bufferHeader.Status(); + + IPv6Header &header = bufferHeader.Data(); + // dump_ipv6_header(header); + + if (header.ProtocolVersion() != IPV6_VERSION) + return B_BAD_TYPE; + + uint16 packetLength = header.PayloadLength() + sizeof(ip6_hdr); + if (packetLength > buffer->size) + return B_BAD_DATA; + + // lower layers notion of Broadcast or Multicast have no relevance to us + buffer->flags &= ~(MSG_BCAST | MSG_MCAST); + + sockaddr_in6 destination; + fill_sockaddr_in6(&destination, header.Dst()); + + if (IN6_IS_ADDR_MULTICAST(&destination.sin6_addr)) { + buffer->flags |= MSG_MCAST; + } else { + // test if the packet is really for us + if (!sDatalinkModule->is_local_address(sDomain, (sockaddr*)&destination, + &buffer->interface, NULL) + && !sDatalinkModule->is_local_link_address(sDomain, true, + buffer->destination, &buffer->interface)) { + char srcbuf[INET6_ADDRSTRLEN]; + char dstbuf[INET6_ADDRSTRLEN]; + TRACE(" ReceiveData(): packet was not for us %s -> %s", + ip6_sprintf(&header.Src(), srcbuf), + ip6_sprintf(&header.Dst(), dstbuf)); + return B_ERROR; + } + } + + // set net_buffer's source/destination address + fill_sockaddr_in6((struct sockaddr_in6*)buffer->source, header.Src()); + memcpy(buffer->destination, &destination, sizeof(sockaddr_in6)); + + // get the transport protocol and transport header offset + uint16 transportHeaderOffset = header.GetTransportHeaderOffset(buffer); + uint8 protocol = buffer->protocol; + + buffer->hoplimit = header.h.ip6_hlim; + + // remove any trailing/padding data + status_t status = gBufferModule->trim(buffer, packetLength); + if (status != B_OK) + return status; + + // + // TODO: check for fragmentation + // + + gBufferModule->remove_header(buffer, transportHeaderOffset); + + raw_receive_data(buffer); + + net_protocol_module_info* module = receiving_protocol(protocol); + if (module == NULL) { + // no handler for this packet + return EAFNOSUPPORT; + } + + if ((buffer->flags & MSG_MCAST) != 0) { + // Unfortunely historical reasons dictate that the IP multicast + // model be a little different from the unicast one. We deliver + // this frame directly to all sockets registered with interest + // for this multicast group. + return deliver_multicast(module, buffer, false); + } + + return module->receive_data(buffer); +} + + +status_t +ipv6_deliver_data(net_protocol* _protocol, net_buffer* buffer) +{ + ipv6_protocol* protocol = (ipv6_protocol*)_protocol; + + if (protocol->raw == NULL) + return B_ERROR; + + return protocol->raw->SocketEnqueue(buffer); +} + + +status_t +ipv6_error(uint32 code, net_buffer* data) +{ + return B_ERROR; +} + + +status_t +ipv6_error_reply(net_protocol* protocol, net_buffer* causedError, uint32 code, + void* errorData) +{ + return B_ERROR; +} + + +ssize_t +ipv6_process_ancillary_data_no_container(net_protocol* protocol, + net_buffer* buffer, void* msgControl, size_t msgControlLen) +{ + ssize_t bytesWritten = 0; + + if (((ipv6_protocol*)protocol)->receive_hoplimit != 0) { + TRACE("receive_hoplimit"); + + if (msgControlLen < CMSG_SPACE(sizeof(int))) + return B_NO_MEMORY; + + cmsghdr* messageHeader = (cmsghdr*)((char*)msgControl + bytesWritten); + messageHeader->cmsg_len = CMSG_LEN(sizeof(int)); + messageHeader->cmsg_level = IPPROTO_IPV6; + messageHeader->cmsg_type = IPV6_HOPLIMIT; + + int hoplimit = buffer->hoplimit; + memcpy(CMSG_DATA(messageHeader), &hoplimit, sizeof(int)); + + bytesWritten += CMSG_SPACE(sizeof(int)); + msgControlLen -= CMSG_SPACE(sizeof(int)); + } + + if (((ipv6_protocol*)protocol)->receive_pktinfo != 0) { + TRACE("receive_pktinfo"); + + if (msgControlLen < CMSG_SPACE(sizeof(struct in6_pktinfo))) + return B_NO_MEMORY; + + cmsghdr* messageHeader = (cmsghdr*)((char*)msgControl + bytesWritten); + messageHeader->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + messageHeader->cmsg_level = IPPROTO_IPV6; + messageHeader->cmsg_type = IPV6_PKTINFO; + + struct in6_pktinfo pi; + memcpy(&pi.ipi6_addr, + &((struct sockaddr_in6*)buffer->destination)->sin6_addr, + sizeof(struct in6_addr)); + // REVIEWME: assume buffer->interface cannot be NULL + pi.ipi6_ifindex = buffer->interface->index; + memcpy(CMSG_DATA(messageHeader), &pi, sizeof(struct in6_pktinfo)); + + bytesWritten += CMSG_SPACE(sizeof(struct in6_pktinfo)); + msgControlLen -= CMSG_SPACE(sizeof(struct in6_pktinfo)); + } + + return bytesWritten; +} + + +// #pragma mark - + + +status_t +init_ipv6() +{ + mutex_init(&sRawSocketsLock, "raw sockets"); + mutex_init(&sMulticastGroupsLock, "IPv6 multicast groups"); + mutex_init(&sReceivingProtocolLock, "IPv6 receiving protocols"); + + status_t status; + + sMulticastState = new MulticastState(); + if (sMulticastState == NULL) { + status = B_NO_MEMORY; + goto err; + } + + status = sMulticastState->Init(); + if (status != B_OK) + goto err; + + new (&sRawSockets) RawSocketList; + // static initializers do not work in the kernel, + // so we have to do it here, manually + // TODO: for modules, this shouldn't be required + + status = gStackModule->register_domain_protocols(AF_INET6, SOCK_RAW, 0, + NET_IPV6_MODULE_NAME, NULL); + if (status != B_OK) + goto err; + + status = gStackModule->register_domain(AF_INET6, "internet6", &gIPv6Module, + &gIPv6AddressModule, &sDomain); + if (status != B_OK) + goto err; + + TRACE("init_ipv6: OK\n"); + return B_OK; + +err: + delete sMulticastState; + mutex_destroy(&sReceivingProtocolLock); + mutex_destroy(&sMulticastGroupsLock); + mutex_destroy(&sRawSocketsLock); + TRACE("init_ipv6: error, status=%u\n", status); + return status; +} + + +status_t +uninit_ipv6() +{ + mutex_lock(&sReceivingProtocolLock); + + // put all the domain receiving protocols we gathered so far + for (uint32 i = 0; i < 256; i++) { + if (sReceivingProtocol[i] != NULL) + gStackModule->put_domain_receiving_protocol(sDomain, i); + } + + delete sMulticastState; + + gStackModule->unregister_domain(sDomain); + mutex_unlock(&sReceivingProtocolLock); + + mutex_destroy(&sMulticastGroupsLock); + mutex_destroy(&sRawSocketsLock); + mutex_destroy(&sReceivingProtocolLock); + + return B_OK; +} + + +static status_t +ipv6_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return init_ipv6(); + case B_MODULE_UNINIT: + return uninit_ipv6(); + + default: + return B_ERROR; + } +} + + +net_protocol_module_info gIPv6Module = { + { + NET_IPV6_MODULE_NAME, + 0, + ipv6_std_ops + }, + NET_PROTOCOL_ATOMIC_MESSAGES, + + ipv6_init_protocol, + ipv6_uninit_protocol, + ipv6_open, + ipv6_close, + ipv6_free, + ipv6_connect, + ipv6_accept, + ipv6_control, + ipv6_getsockopt, + ipv6_setsockopt, + ipv6_bind, + ipv6_unbind, + ipv6_listen, + ipv6_shutdown, + ipv6_send_data, + ipv6_send_routed_data, + ipv6_send_avail, + ipv6_read_data, + ipv6_read_avail, + ipv6_get_domain, + ipv6_get_mtu, + ipv6_receive_data, + ipv6_deliver_data, + ipv6_error, + ipv6_error_reply, + NULL, // add_ancillary_data() + NULL, // process_ancillary_data() + ipv6_process_ancillary_data_no_container, + NULL, // send_data_no_buffer() + NULL // read_data_no_buffer() +}; + +module_dependency module_dependencies[] = { + {NET_STACK_MODULE_NAME, (module_info**)&gStackModule}, + {NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule}, + {NET_DATALINK_MODULE_NAME, (module_info**)&sDatalinkModule}, + {NET_SOCKET_MODULE_NAME, (module_info**)&sSocketModule}, + {} +}; + +module_info* modules[] = { + (module_info*)&gIPv6Module, + NULL +}; diff --git a/src/add-ons/kernel/network/protocols/ipv6/ipv6_address.cpp b/src/add-ons/kernel/network/protocols/ipv6/ipv6_address.cpp new file mode 100644 index 0000000000..00d2429dbf --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/ipv6_address.cpp @@ -0,0 +1,569 @@ +/* + * Copyright 2006-2009, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + * Oliver Tappe, zooey@hirschkaefer.de + * Atis Elsts, the.kfx@gmail.com + */ + + +#include + +#include + +#include +#include +#include +#include + +#include "ipv6_address.h" +#include "ipv6_utils.h" +#include "jenkins.h" + + +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; + + +static void +ipv6_mask_adress_inplace(sockaddr *address, const sockaddr *mask) +{ + in6_addr &i6addr = ((sockaddr_in6 *)address)->sin6_addr; + const in6_addr &i6mask = ((const sockaddr_in6 *)mask)->sin6_addr; + + for (uint32 i = 0; i < sizeof(in6_addr); i++) + i6addr.s6_addr[i] &= i6mask.s6_addr[i]; +} + + +/*! Routing utility function: copies address \a from into a new address + that is put into \a to. + If \a replaceWithZeros is set \a from will be replaced by an empty + address. + If a \a mask is given it is applied to \a from (such that \a to is the + result of \a from & \a mask). + \return B_OK if the address could be copied + \return B_NO_MEMORY if the new address could not be allocated + \return B_BAD_VALUE if any of \a from or \a mask refers to an uninitialized + address + \return B_MISMATCHED_VALUES if \a address does not match family AF_INET +*/ +static status_t +ipv6_copy_address(const sockaddr *from, sockaddr **to, + bool replaceWithZeros = false, const sockaddr *mask = NULL) +{ + if (replaceWithZeros) { + *to = (sockaddr *)malloc(sizeof(sockaddr_in6)); + if (*to == NULL) + return B_NO_MEMORY; + + memset(*to, 0, sizeof(sockaddr_in6)); + (*to)->sa_family = AF_INET6; + (*to)->sa_len = sizeof(sockaddr_in6); + } else { + if (from == NULL) + return B_OK; + if (from->sa_len == 0 || (mask != NULL && mask->sa_len == 0)) + return B_BAD_VALUE; + if (from->sa_family != AF_INET6) + return B_MISMATCHED_VALUES; + + *to = (sockaddr *)malloc(sizeof(sockaddr_in6)); + if (*to == NULL) + return B_NO_MEMORY; + + memcpy(*to, from, sizeof(sockaddr_in6)); + + if (mask != NULL) + ipv6_mask_adress_inplace(*to, mask); + } + return B_OK; +} + + +/*! Routing utility function: applies \a mask to given \a address and puts + the resulting address into \a result. + \return B_OK if the mask has been applied + \return B_BAD_VALUE if \a address is NULL or if any of \a address or \a mask + refers to an uninitialized address +*/ +static status_t +ipv6_mask_address(const sockaddr *address, const sockaddr *mask, + sockaddr *result) +{ + if (address == NULL || address->sa_len == 0 || result == NULL + || (mask != NULL && mask->sa_len == 0)) + return B_BAD_VALUE; + + memcpy(result, address, sizeof(sockaddr_in6)); + if (mask != NULL) + ipv6_mask_adress_inplace(result, mask); + + return B_OK; +} + + +/*! Checks if the given \a address is the empty address. By default, the port + is checked, too, but you can avoid that by passing \a checkPort = false. + \return true if \a address is NULL, uninitialized or the empty address, + false if not +*/ +static bool +ipv6_is_empty_address(const sockaddr *_address, bool checkPort) +{ + if (_address == NULL || _address->sa_len == 0) + return true; + + const sockaddr_in6 *address = (const sockaddr_in6 *)_address; + if (checkPort && address->sin6_port != 0) return false; + return IN6_IS_ADDR_UNSPECIFIED(&address->sin6_addr); +} + + +/*! Checks if the given \a address is an Ipv6 address. + \return false if \a address is NULL, or with family different from AF_INET + true if it has AF_INET address family +*/ +static bool +ipv6_is_same_family(const sockaddr *address) +{ + if (address == NULL) + return false; + + return address->sa_family == AF_INET6; +} + + +/*! Compares the IP-addresses of the two given address structures \a a and \a b. + \return true if IP-addresses of \a a and \a b are equal, false if not +*/ +static bool +ipv6_equal_addresses(const sockaddr *a, const sockaddr *b) +{ + if (a == NULL && b == NULL) + return true; + if (a != NULL && b == NULL) + return ipv6_is_empty_address(a, false); + if (a == NULL && b != NULL) + return ipv6_is_empty_address(b, false); + + const sockaddr_in6 *i6a = (const sockaddr_in6 *)a; + const sockaddr_in6 *i6b = (const sockaddr_in6 *)b; + return !memcmp(&i6a->sin6_addr, &i6b->sin6_addr, sizeof(in6_addr)); +} + + +/*! Compares the ports of the two given address structures \a a and \a b. + \return true if ports of \a a and \a b are equal, false if not +*/ +static bool +ipv6_equal_ports(const sockaddr *a, const sockaddr *b) +{ + uint16 portA = a ? ((sockaddr_in6 *)a)->sin6_port : 0; + uint16 portB = b ? ((sockaddr_in6 *)b)->sin6_port : 0; + return portA == portB; +} + + +/*! Compares the IP-addresses and ports of the two given address structures + \a a and \a b. + \return true if IP-addresses and ports of \a a and \a b are equal, false if + not +*/ +static bool +ipv6_equal_addresses_and_ports(const sockaddr *a, const sockaddr *b) +{ + if (a == NULL && b == NULL) + return true; + if (a != NULL && b == NULL) + return ipv6_is_empty_address(a, true); + if (a == NULL && b != NULL) + return ipv6_is_empty_address(b, true); + + const sockaddr_in6 *i6a = (const sockaddr_in6 *)a; + const sockaddr_in6 *i6b = (const sockaddr_in6 *)b; + return i6a->sin6_port == i6b->sin6_port + && !memcmp(&i6a->sin6_addr, &i6b->sin6_addr, sizeof(in6_addr)); +} + + +/*! Applies the given \a mask two \a a and \a b and then checks whether + the masked addresses match. + \return true if \a a matches \a b after masking both, false if not +*/ +static bool +ipv6_equal_masked_addresses(const sockaddr *a, const sockaddr *b, + const sockaddr *mask) +{ + if (a == NULL && b == NULL) + return true; + + const in6_addr *i6a; + if (a == NULL) + i6a = &in6addr_any; + else + i6a = &((const sockaddr_in6*)a)->sin6_addr; + + const in6_addr *i6b; + if (b == NULL) + i6b = &in6addr_any; + else + i6b = &((const sockaddr_in6*)b)->sin6_addr; + + if (!mask) + return !memcmp(i6a, i6b, sizeof(in6_addr)); + + const uint8 *pmask = ((const sockaddr_in6 *)mask)->sin6_addr.s6_addr; + for (uint8 i = 0; i < sizeof(in6_addr); ++i) { + if (pmask[i] != 0xff) { + return (i6a->s6_addr[i] & pmask[i]) + == (i6b->s6_addr[i] & pmask[i]); + } + + if (i6a->s6_addr[i] != i6b->s6_addr[i]) + return false; + } + + return true; +} + + +/*! Routing utility function: determines the least significant bit that is set + in the given \a mask. + \return the number of the first bit that is set (0-32, where 32 means + that there's no bit set in the mask). +*/ +static int32 +ipv6_first_mask_bit(const sockaddr *_mask) +{ + if (_mask == NULL) + return 0; + + const uint8 *pmask = ((const sockaddr_in6 *)_mask)->sin6_addr.s6_addr; + for (uint8 i = 0; i < sizeof(in6_addr); ++i) { + if (pmask[i] == 0xff) + continue; + + for (uint8 bit = 0; bit < 8; bit++) { + if (pmask[i] & (1 << bit)) + return bit; + } + } + + return 128; +} + + +/*! Routing utility function: checks the given \a mask for correctness (which + means that (starting with LSB) consists zero or more unset bits, followed + by bits that are all set). + \return true if \a mask is ok, false if not +*/ +static bool +ipv6_check_mask(const sockaddr *_mask) +{ + if (_mask == NULL) + return true; + + bool zero = false; + const uint8 *pmask = ((const sockaddr_in6 *)_mask)->sin6_addr.s6_addr; + for (uint8 i = 0; i < sizeof(in6_addr); ++i) { + if (pmask[i] == 0xff) { + if (zero) + return false; + } else if (pmask[i] == 0) { + zero = true; + } else { + for (int8 bit = 7; bit > 0; bit--) { + if (pmask[i] & (1 << bit)) { + if (zero) + return false; + } else { + zero = true; + } + } + } + } + + return true; +} + + +/*! Creates a buffer for the given \a address and prints the address into + it (hexadecimal representation in network byte order or ''). + If \a printPort is set, the port is printed, too. + \return B_OK if the address could be printed, \a buffer will point to + the resulting string + \return B_BAD_VALUE if no buffer has been given + \return B_NO_MEMORY if the buffer could not be allocated, + or does not have enogh space +*/ +static status_t +ipv6_print_address_buffer(const sockaddr *_address, char *buffer, + size_t bufferSize, bool printPort) +{ + const sockaddr_in6 *address = (const sockaddr_in6 *)_address; + + if (buffer == NULL) + return B_BAD_VALUE; + + if (address == NULL) { + if (bufferSize < sizeof("")) + return B_NO_MEMORY; + strcpy(buffer, ""); + } else { + if (printPort && bufferSize > 0) { + *buffer = '['; + buffer++; + bufferSize--; + } + + if (!ip6_sprintf(&address->sin6_addr, buffer, bufferSize)) + return B_NO_MEMORY; + + if (printPort) { + char port[7]; + sprintf(port, "]:%d", ntohs(address->sin6_port)); + if (bufferSize - strlen(buffer) < strlen(port) + 1) + return B_NO_MEMORY; + strcat(buffer, port); + } + } + + return B_OK; +} + + +static status_t +ipv6_print_address(const sockaddr *_address, char **_buffer, bool printPort) +{ + if (_buffer == NULL) + return B_BAD_VALUE; + + char tmp[64]; + ipv6_print_address_buffer(_address, tmp, sizeof(tmp), printPort); + + *_buffer = strdup(tmp); + if (*_buffer == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +/*! Determines the port of the given \a address. + \return uint16 representing the port-nr +*/ +static uint16 +ipv6_get_port(const sockaddr *address) +{ + if (address == NULL || address->sa_len == 0) + return 0; + + return ((sockaddr_in6 *)address)->sin6_port; +} + + +/*! Sets the port of the given \a address to \a port. + \return B_OK if the port has been set + \return B_BAD_VALUE if \a address is NULL or has not been initialized +*/ +static status_t +ipv6_set_port(sockaddr *address, uint16 port) +{ + if (address == NULL || address->sa_len == 0) + return B_BAD_VALUE; + + ((sockaddr_in6 *)address)->sin6_port = port; + return B_OK; +} + + +/*! Sets \a address to \a from. + \return B_OK if \a from has been copied into \a address + \return B_BAD_VALUE if either \a address or \a from is NULL or if the + address given in from has not been initialized + \return B_MISMATCHED_VALUES if from is not of family AF_INET6 +*/ +static status_t +ipv6_set_to(sockaddr *address, const sockaddr *from) +{ + if (address == NULL || from == NULL || from->sa_len == 0) + return B_BAD_VALUE; + + if (from->sa_family != AF_INET6) + return B_MISMATCHED_VALUES; + + memcpy(address, from, sizeof(sockaddr_in6)); + address->sa_len = sizeof(sockaddr_in6); + return B_OK; +} + + +/*! Updates missing parts in \a address with the values in \a from. + \return B_OK if \a address has been updated from \a from + \return B_BAD_VALUE if either \a address or \a from is NULL or if the + address given in from has not been initialized + \return B_MISMATCHED_VALUES if from is not of family AF_INET6 +*/ +static status_t +ipv6_update_to(sockaddr *_address, const sockaddr *_from) +{ + sockaddr_in6 *address = (sockaddr_in6 *)_address; + const sockaddr_in6 *from = (const sockaddr_in6 *)_from; + + if (address == NULL || from == NULL || from->sin6_len == 0) + return B_BAD_VALUE; + + if (from->sin6_family != AF_INET6) + return B_BAD_VALUE; + + address->sin6_family = AF_INET6; + address->sin6_len = sizeof(sockaddr_in6); + + if (address->sin6_port == 0) + address->sin6_port = from->sin6_port; + + if (IN6_IS_ADDR_UNSPECIFIED(&address->sin6_addr)) { + memcpy(address->sin6_addr.s6_addr, from->sin6_addr.s6_addr, + sizeof(in6_addr)); + } + + return B_OK; +} + + +/*! Sets \a address to the empty address (0.0.0.0). + \return B_OK if \a address has been set + \return B_BAD_VALUE if \a address is NULL +*/ +static status_t +ipv6_set_to_empty_address(sockaddr *address) +{ + if (address == NULL) + return B_BAD_VALUE; + + memset(address, 0, sizeof(sockaddr_in6)); + address->sa_len = sizeof(sockaddr_in6); + address->sa_family = AF_INET6; + return B_OK; +} + + +static status_t +ipv6_set_to_defaults(sockaddr *_defaultMask, sockaddr *_defaultBroadcast, + sockaddr *_address, sockaddr *_mask) +{ + sockaddr_in6 *defaultMask = (sockaddr_in6 *)_defaultMask; + sockaddr_in6 *address = (sockaddr_in6 *)_address; + sockaddr_in6 *mask = (sockaddr_in6 *)_mask; + + if (address == NULL || defaultMask == NULL) + return B_BAD_VALUE; + + defaultMask->sin6_len = sizeof(sockaddr_in); + defaultMask->sin6_family = AF_INET6; + defaultMask->sin6_port = 0; + if (mask != NULL) { + memcpy(defaultMask->sin6_addr.s6_addr, + mask->sin6_addr.s6_addr, sizeof(in6_addr)); + } else { + // use /128 as the default mask + memset(defaultMask->sin6_addr.s6_addr, 0xff, sizeof(in6_addr)); + } + + return B_OK; +} + + +/*! Computes a hash-value of the given addresses \a ourAddress + and \a peerAddress. + \return uint32 representing the hash-value +*/ +static uint32 +ipv6_hash_address_pair(const sockaddr *ourAddress, const sockaddr *peerAddress) +{ + uint32 result = 0; + if (ourAddress) { + const sockaddr_in6 *our = (const sockaddr_in6 *)ourAddress; + uint32 port = our->sin6_port; + + result = jenkins_hashword((const uint32*)&our->sin6_addr, + sizeof(in6_addr) / sizeof(uint32), result); + result = jenkins_hashword(&port, 1, result); + } + if (peerAddress) { + const sockaddr_in6 *peer = (const sockaddr_in6 *)peerAddress; + uint32 port = peer->sin6_port; + + result = jenkins_hashword((const uint32*)&peer->sin6_addr, + sizeof(in6_addr) / sizeof(uint32), result); + result = jenkins_hashword(&port, 1, result); + } + + // TODO: also use sin6_flowinfo and sin6_scope_id? + return result; +} + + +/*! Adds the given \a address to the IP-checksum \a checksum. + \return B_OK if \a address has been added to the checksum + \return B_BAD_VALUE if either \a address or \a checksum is NULL or if + the given address is not initialized +*/ +static status_t +ipv6_checksum_address(struct Checksum *checksum, const sockaddr *address) +{ + if (checksum == NULL || address == NULL || address->sa_len == 0) + return B_BAD_VALUE; + + in6_addr &a = ((sockaddr_in6 *)address)->sin6_addr; + for (uint32 i = 0; i < sizeof(in6_addr); i++) + (*checksum) << a.s6_addr[i]; + + return B_OK; +} + + +static void +ipv6_get_loopback_address(sockaddr *_address) +{ + sockaddr_in6 *address = (sockaddr_in6 *)_address; + memset(address, 0, sizeof(sockaddr_in6)); + address->sin6_len = sizeof(sockaddr_in6); + address->sin6_family = AF_INET6; + memcpy(&address->sin6_addr, &in6addr_loopback, sizeof(in6_addr)); +} + + +net_address_module_info gIPv6AddressModule = { + { + NULL, + 0, + NULL + }, + false, // has_broadcast_address + ipv6_copy_address, + ipv6_mask_address, + ipv6_equal_addresses, + ipv6_equal_ports, + ipv6_equal_addresses_and_ports, + ipv6_equal_masked_addresses, + ipv6_is_empty_address, + ipv6_is_same_family, + ipv6_first_mask_bit, + ipv6_check_mask, + ipv6_print_address, + ipv6_print_address_buffer, + ipv6_get_port, + ipv6_set_port, + ipv6_set_to, + ipv6_set_to_empty_address, + ipv6_set_to_defaults, + ipv6_update_to, + ipv6_hash_address_pair, + ipv6_checksum_address, + ipv6_get_loopback_address +}; diff --git a/src/add-ons/kernel/network/protocols/ipv6/ipv6_address.h b/src/add-ons/kernel/network/protocols/ipv6/ipv6_address.h new file mode 100644 index 0000000000..91641f2085 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/ipv6_address.h @@ -0,0 +1,27 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef IPV6_ADDRESS_H +#define IPV6_ADDRESS_H + + +#include +#include + + +extern struct net_address_module_info gIPv6AddressModule; + + +#define NET_IPV6_MODULE_NAME "network/protocols/ipv6/v1" + + +static inline bool +operator==(const in6_addr &a1, const in6_addr &a2) +{ + // TODO: optimize + return !memcmp(&a1, &a2, sizeof(in6_addr)); +} + + +#endif // IPV6_ADDRESS_H diff --git a/src/add-ons/kernel/network/protocols/ipv6/ipv6_utils.cpp b/src/add-ons/kernel/network/protocols/ipv6/ipv6_utils.cpp new file mode 100644 index 0000000000..c312b2a7e8 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/ipv6_utils.cpp @@ -0,0 +1,141 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + +/* + * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") + * Copyright (c) 1996-1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ipv6_utils.h" + + +#define NS_IN6ADDRSZ 16 +#define NS_INT16SZ 2 + +#define SPRINTF(x) ((size_t)sprintf x) + + +/*! Convert IPv6 binary address into presentation (printable) format. + Author: Paul Vixie, 1996. + \return pointer to dst string if address as been printed + \return NULL if the buffer is too short +*/ +const char * +ip6_sprintf(const in6_addr *srcaddr, char *dst, size_t size) +{ + /* + * Note that int32_t and int16_t need only be "at least" large enough + * to contain a value of the specified size. On some systems, like + * Crays, there is no such thing as an integer variable with 16 bits. + * Keep this in mind if you think this function should have been coded + * to use pointer overlays. All the world's not a VAX. + */ + char tmp[INET6_ADDRSTRLEN], *tp; + struct { int base, len; } best, cur; + uint16 words[NS_IN6ADDRSZ / NS_INT16SZ]; + int i; + const uint8 *src = srcaddr->s6_addr; + + /* + * Preprocess: + * Copy the input (bytewise) array into a wordwise array. + * Find the longest run of 0x00's in src[] for :: shorthanding. + */ + memset(words, '\0', sizeof words); + for (i = 0; i < NS_IN6ADDRSZ; i++) + words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3)); + best.base = -1; + best.len = 0; + cur.base = -1; + cur.len = 0; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + if (words[i] == 0) { + if (cur.base == -1) + cur.base = i, cur.len = 1; + else + cur.len++; + } else { + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + cur.base = -1; + } + } + } + if (cur.base != -1) { + if (best.base == -1 || cur.len > best.len) + best = cur; + } + if (best.base != -1 && best.len < 2) + best.base = -1; + + /* + * Format the result. + */ + tp = tmp; + for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) { + /* Are we inside the best run of 0x00's? */ + if (best.base != -1 && i >= best.base && + i < (best.base + best.len)) { + if (i == best.base) + *tp++ = ':'; + continue; + } + /* Are we following an initial run of 0x00s or any real hex? */ + if (i != 0) + *tp++ = ':'; + /* Is this address an encapsulated IPv4? */ +#if 0 + if (i == 6 && best.base == 0 && (best.len == 6 || + (best.len == 7 && words[7] != 0x0001) || + (best.len == 5 && words[5] == 0xffff))) { + if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp))) + return (NULL); + tp += strlen(tp); + break; + } +#endif + tp += SPRINTF((tp, "%x", words[i])); + } + /* Was it a trailing run of 0x00's? */ + if (best.base != -1 && (best.base + best.len) == + (NS_IN6ADDRSZ / NS_INT16SZ)) + *tp++ = ':'; + *tp++ = '\0'; + + /* + * Check for overflow, copy, and we're done. + */ + if ((size_t)(tp - tmp) > size) + return NULL; + + strcpy(dst, tmp); + return dst; +} diff --git a/src/add-ons/kernel/network/protocols/ipv6/ipv6_utils.h b/src/add-ons/kernel/network/protocols/ipv6/ipv6_utils.h new file mode 100644 index 0000000000..280aab84f9 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/ipv6_utils.h @@ -0,0 +1,59 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Atis Elsts, the.kfx@gmail.com + */ +#ifndef IPV6_UTILS_H +#define IPV6_UTILS_H + + +#include +#include +#include + + +const char *ip6_sprintf(const in6_addr *addr, char *dst, + size_t size = INET6_ADDRSTRLEN); + + +static inline uint16 +compute_checksum(uint8* _buffer, size_t length) +{ + uint16* buffer = (uint16*)_buffer; + uint32 sum = 0; + + while (length >= 2) { + sum += *buffer++; + length -= 2; + } + + return sum; +} + + +static inline uint16 +ipv6_checksum(const struct in6_addr* source, + const struct in6_addr* destination, + uint16 length, uint16 protocol, + uint16 checksum) +{ + uint32 sum = checksum; + + length = htons(length); + protocol = htons(protocol); + + sum += compute_checksum((uint8*)source, sizeof(in6_addr)); + sum += compute_checksum((uint8*)destination, sizeof(in6_addr)); + sum += compute_checksum((uint8*)&length, sizeof(uint16)); + sum += compute_checksum((uint8*)&protocol, sizeof(uint16)); + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~(uint16)sum; +} + + +#endif // IPV6_UTILS_H diff --git a/src/add-ons/kernel/network/protocols/ipv6/jenkins.h b/src/add-ons/kernel/network/protocols/ipv6/jenkins.h new file mode 100644 index 0000000000..f7cc463ebf --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/jenkins.h @@ -0,0 +1,187 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Taken from http://burtleburtle.net/bob/c/lookup3.c + */ +#ifndef LIBKERN_JENKINS_H +#define LIBKERN_JENKINS_H + + +/* + ------------------------------------------------------------------------------- + lookup3.c, by Bob Jenkins, May 2006, Public Domain. + + These are functions for producing 32-bit hashes for hash table lookup. + hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() + are externally useful functions. Routines to test the hash are included + if SELF_TEST is defined. You can use this free for any purpose. It's in + the public domain. It has no warranty. + + You probably want to use hashlittle(). hashlittle() and hashbig() + hash byte arrays. hashlittle() is is faster than hashbig() on + little-endian machines. Intel and AMD are little-endian machines. + On second thought, you probably want hashlittle2(), which is identical to + hashlittle() except it returns two 32-bit hashes for the price of one. + You could implement hashbig2() if you wanted but I haven't bothered here. + + If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); + then use c as the hash value. If you have a variable length array of + 4-byte integers to hash, use hashword(). If you have a byte array (like + a character string), use hashlittle(). If you have several byte arrays, or + a mix of things, see the comments above hashlittle(). + + Why is this so big? I read 12 bytes at a time into 3 4-byte integers, + then mix those integers. This is fast (you can do a lot more thorough + mixing with 12*3 instructions on 3 integers than you can with 3 instructions + on 1 byte), but shoehorning those bytes into integers efficiently is messy. + ------------------------------------------------------------------------------- +*/ + +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* + ------------------------------------------------------------------------------- + mix -- mix 3 32-bit values reversibly. + + This is reversible, so any information in (a,b,c) before mix() is + still in (a,b,c) after mix(). + + If four pairs of (a,b,c) inputs are run through mix(), or through + mix() in reverse, there are at least 32 bits of the output that + are sometimes the same for one pair and different for another pair. + This was tested for: + * pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). + * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. + * the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + + Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that + satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 + Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing + for "differ" defined as + with a one-bit base and a two-bit delta. I + used http://burtleburtle.net/bob/hash/avalanche.html to choose + the operations, constants, and arrangements of the variables. + + This does not achieve avalanche. There are input bits of (a,b,c) + that fail to affect some output bits of (a,b,c), especially of a. The + most thoroughly mixed value is c, but it doesn't really even achieve + avalanche in c. + + This allows some parallelism. Read-after-writes are good at doubling + the number of bits affected, so the goal of mixing pulls in the opposite + direction as the goal of parallelism. I did what I could. Rotates + seem to cost as much as shifts on every machine I could lay my hands + on, and rotates are much kinder to the top and bottom bits, so I used + rotates. + ------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ + { \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ + } + +/* + ------------------------------------------------------------------------------- + final -- final mixing of 3 32-bit values (a,b,c) into c + + Pairs of (a,b,c) values differing in only a few bits will usually + produce values of c that look totally different. This was tested for + * pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). + * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. + * the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + + These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 + and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 + ------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ + { \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ + } + +/* + -------------------------------------------------------------------- + This works on all machines. To be useful, it requires + -- that the key be an array of uint32's, and + -- that the length be the number of uint32's in the key + + The function hashword() is identical to hashlittle() on little-endian + machines, and identical to hashbig() on big-endian machines, + except that the length has to be measured in uint32s rather than in + bytes. hashlittle() is more complicated than hashword() only because + hashlittle() has to dance around fitting the key bytes into registers. + -------------------------------------------------------------------- +*/ +static uint32 +jenkins_hashword(const uint32 *k, /* the key, an array of uint32 values */ + size_t length, /* the length of the key, in uint32s */ + uint32 initval) /* the previous hash, or an arbitrary value */ +{ + uint32 a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + (((uint32)length)<<2) + initval; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + return c; +} + +#endif diff --git a/src/add-ons/kernel/network/protocols/ipv6/multicast.cpp b/src/add-ons/kernel/network/protocols/ipv6/multicast.cpp new file mode 100644 index 0000000000..f21e4beeb4 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/multicast.cpp @@ -0,0 +1,209 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Atis Elsts, the.kfx@gmail.com + */ + + +#include "ipv6_address.h" +#include "multicast.h" + +#include + +#include + +#include + + +using std::nothrow; + + +template +MulticastGroupInterface::MulticastGroupInterface(Filter *parent, + const AddressType &address, net_interface *interface) + : fParent(parent), fMulticastAddress(address), fInterface(interface) +{ +} + + +template +MulticastGroupInterface::~MulticastGroupInterface() +{ + Clear(); +} + + +template status_t +MulticastGroupInterface::Add() +{ + if (fFilterMode == kInclude && !fAddresses.IsEmpty()) + return EINVAL; + + fFilterMode = kExclude; + return B_OK; +} + + +template status_t +MulticastGroupInterface::Drop() +{ + fAddresses.Clear(); + fFilterMode = kInclude; + return B_OK; +} + + +template status_t +MulticastGroupInterface::BlockSource( + const AddressType &sourceAddress) +{ + if (fFilterMode != kExclude) + return EINVAL; + + fAddresses.Add(sourceAddress); + return B_OK; +} + + +template status_t +MulticastGroupInterface::UnblockSource( + const AddressType &sourceAddress) +{ + if (fFilterMode != kExclude) + return EINVAL; + + if (!fAddresses.Has(sourceAddress)) + return EADDRNOTAVAIL; + + fAddresses.Add(sourceAddress); + return B_OK; +} + + +template status_t +MulticastGroupInterface::AddSSM(const AddressType &sourceAddress) +{ + if (fFilterMode == kExclude) + return EINVAL; + + fAddresses.Add(sourceAddress); + return B_OK; +} + + +template status_t +MulticastGroupInterface::DropSSM(const AddressType &sourceAddress) +{ + if (fFilterMode == kExclude) + return EINVAL; + + if (!fAddresses.Has(sourceAddress)) + return EADDRNOTAVAIL; + + fAddresses.Add(sourceAddress); + return B_OK; +} + + +template bool +MulticastGroupInterface::IsEmpty() const +{ + return fFilterMode == kInclude && fAddresses.IsEmpty(); +} + + +template void +MulticastGroupInterface::Clear() +{ + if (IsEmpty()) + return; + + fFilterMode = kInclude; + fAddresses.Clear(); + Addressing::LeaveGroup(this); +} + + +template bool +MulticastGroupInterface::FilterAccepts(net_buffer *buffer) const +{ + bool has = fAddresses.Has(Addressing::AddressFromSockAddr( + buffer->source)); + + return (has && fFilterMode == kInclude) + || (!has && fFilterMode == kExclude); +} + + +template +MulticastFilter::MulticastFilter(ProtocolType *socket) + : fParent(socket), fStates() +{ +} + + +template +MulticastFilter::~MulticastFilter() +{ + while (true) { + typename States::Iterator iterator = fStates.GetIterator(); + if (!iterator.HasNext()) + return; + + GroupInterface *state = iterator.Next(); + state->Clear(); + _ReturnState(state); + } +} + + +template status_t +MulticastFilter::GetState(const AddressType &groupAddress, + net_interface *interface, GroupInterface* &state, bool create) +{ + state = fStates.Lookup(std::make_pair(&groupAddress, interface->index)); + + if (state == NULL && create) { + state = new (nothrow) GroupInterface(this, groupAddress, interface); + if (state == NULL) + return B_NO_MEMORY; + + status_t status = fStates.Insert(state); + if (status < B_OK) { + delete state; + return status; + } + + status = Addressing::JoinGroup(state); + if (status < B_OK) { + fStates.Remove(state); + delete state; + return status; + } + + } + + return B_OK; +} + + +template void +MulticastFilter::ReturnState(GroupInterface *state) +{ + if (state->IsEmpty()) + _ReturnState(state); +} + + +template void +MulticastFilter::_ReturnState(GroupInterface *state) +{ + fStates.Remove(state); + delete state; +} + +// IPv6 explicit template instantiation +template class MulticastFilter; +template class MulticastGroupInterface; diff --git a/src/add-ons/kernel/network/protocols/ipv6/multicast.h b/src/add-ons/kernel/network/protocols/ipv6/multicast.h new file mode 100644 index 0000000000..9b3915da5d --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv6/multicast.h @@ -0,0 +1,232 @@ +/* + * Copyright 2010, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Atis Elsts, the.kfx@gmail.com + */ +#ifndef _IPV6_MULTICAST_H_ +#define _IPV6_MULTICAST_H_ + + +#include +#include + +#include + +#include + +#include + +#include "jenkins.h" + + +struct net_buffer; +struct net_protocol; + + +template class MulticastFilter; +template class MulticastGroupInterface; + + +struct IPv6Multicast { + typedef struct in6_addr AddressType; + typedef struct ipv6_protocol ProtocolType; + typedef MulticastGroupInterface GroupInterface; + + static status_t JoinGroup(GroupInterface *); + static status_t LeaveGroup(GroupInterface *); + + static const in6_addr &AddressFromSockAddr(const sockaddr *sockaddr) + { return ((const sockaddr_in6 *)sockaddr)->sin6_addr; } + static size_t HashAddress(const in6_addr &address) + { return jenkins_hashword((const uint32*)&address, + sizeof(in6_addr) / sizeof(uint32), 0); } +}; + + +template +class AddressSet { + struct ContainedAddress : DoublyLinkedListLinkImpl { + AddressType address; + }; + + typedef DoublyLinkedList AddressList; + +public: + AddressSet() + : fCount(0) {} + + ~AddressSet() { Clear(); } + + status_t Add(const AddressType &address) + { + if (Has(address)) + return B_OK; + + ContainedAddress *container = new ContainedAddress(); + if (container == NULL) + return B_NO_MEMORY; + + container->address = address; + fAddresses.Add(container); + + return B_OK; + } + + void Remove(const AddressType &address) + { + ContainedAddress *container = _Get(address); + if (container == NULL) + return; + + fAddresses.Remove(container); + delete container; + } + + bool Has(const AddressType &address) const + { + return _Get(address) != NULL; + } + + bool IsEmpty() const { return fAddresses.IsEmpty(); } + + void Clear() + { + while (!fAddresses.IsEmpty()) + Remove(fAddresses.Head()->address); + } + + class Iterator { + public: + Iterator(const AddressList &addresses) + : fBaseIterator(addresses.GetIterator()) {} + + bool HasNext() const { return fBaseIterator.HasNext(); } + AddressType &Next() { return fBaseIterator.Next()->address; } + + private: + typename AddressList::ConstIterator fBaseIterator; + }; + + Iterator GetIterator() const { return Iterator(fAddresses); } + +private: + ContainedAddress *_Get(const AddressType &address) const + { + typename AddressList::ConstIterator it = fAddresses.GetIterator(); + while (it.HasNext()) { + ContainedAddress *container = it.Next(); + if (container->address == address) + return container; + } + return NULL; + } + + AddressList fAddresses; + int fCount; +}; + + +template +class MulticastGroupInterface { +public: + typedef MulticastGroupInterface ThisType; + typedef typename Addressing::AddressType AddressType; + typedef MulticastFilter Filter; + typedef ::AddressSet AddressSet; + + enum FilterMode { + kInclude, + kExclude + }; + + MulticastGroupInterface(Filter *parent, const AddressType &address, + net_interface *interface); + ~MulticastGroupInterface(); + + Filter *Parent() const { return fParent; } + + const AddressType &Address() const { return fMulticastAddress; } + net_interface *Interface() const { return fInterface; } + + status_t Add(); + status_t Drop(); + status_t BlockSource(const AddressType &sourceAddress); + status_t UnblockSource(const AddressType &sourceAddress); + status_t AddSSM(const AddressType &sourceAddress); + status_t DropSSM(const AddressType &sourceAddress); + + bool IsEmpty() const; + void Clear(); + + FilterMode Mode() const { return fFilterMode; } + const AddressSet &Sources() const { return fAddresses; } + + bool FilterAccepts(net_buffer *buffer) const; + + struct HashDefinition { + typedef std::pair KeyType; + typedef ThisType ValueType; + + size_t HashKey(const KeyType &key) const + { + size_t result = 0; + result = jenkins_hashword((const uint32*)&key.first, + sizeof(in6_addr) / sizeof(uint32), result); + result = jenkins_hashword(&key.second, 1, result); + return result; + } + size_t Hash(ValueType *value) const + { return HashKey(std::make_pair(&value->Address(), + value->Interface()->index)); } + bool Compare(const KeyType &key, ValueType *value) const + { return value->Interface()->index == key.second + && value->Address() == *key.first; } + MulticastGroupInterface*& GetLink(ValueType *value) const + { return value->HashLink(); } + }; + + MulticastGroupInterface*& HashLink() { return fLink; } + +private: + // for g++ 2.95 + friend class HashDefinition; + + Filter *fParent; + AddressType fMulticastAddress; + net_interface *fInterface; + FilterMode fFilterMode; + AddressSet fAddresses; + MulticastGroupInterface* fLink; +}; + + +template +class MulticastFilter { +public: + typedef typename Addressing::AddressType AddressType; + typedef typename Addressing::ProtocolType ProtocolType; + typedef MulticastGroupInterface GroupInterface; + + MulticastFilter(ProtocolType *parent); + ~MulticastFilter(); + + ProtocolType *Socket() const { return fParent; } + + status_t GetState(const AddressType &groupAddress, + net_interface *interface, GroupInterface* &state, bool create); + void ReturnState(GroupInterface *state); + +private: + typedef typename GroupInterface::HashDefinition HashDefinition; + typedef BOpenHashTable States; + + void _ReturnState(GroupInterface *state); + + ProtocolType *fParent; + States fStates; +}; + + +#endif // _IPV6_MULTICAST_H_ diff --git a/src/add-ons/kernel/network/protocols/l2cap/l2cap_address.cpp b/src/add-ons/kernel/network/protocols/l2cap/l2cap_address.cpp index 4f00ab1b65..c4a3472f2c 100644 --- a/src/add-ons/kernel/network/protocols/l2cap/l2cap_address.cpp +++ b/src/add-ons/kernel/network/protocols/l2cap/l2cap_address.cpp @@ -413,6 +413,7 @@ net_address_module_info gL2cap4AddressModule = { 0, NULL }, + true, // has_broadcast_address l2cap_copy_address, l2cap_mask_address, l2cap_equal_addresses, diff --git a/src/add-ons/kernel/network/protocols/udp/udp.cpp b/src/add-ons/kernel/network/protocols/udp/udp.cpp index ebc0a15eb0..a329c52d8e 100644 --- a/src/add-ons/kernel/network/protocols/udp/udp.cpp +++ b/src/add-ons/kernel/network/protocols/udp/udp.cpp @@ -1207,17 +1207,34 @@ init_udp() NULL); if (status < B_OK) goto err1; + status = gStackModule->register_domain_protocols(AF_INET6, SOCK_DGRAM, IPPROTO_IP, + "network/protocols/udp/v1", + "network/protocols/ipv6/v1", + NULL); + if (status < B_OK) + goto err1; + status = gStackModule->register_domain_protocols(AF_INET, SOCK_DGRAM, IPPROTO_UDP, "network/protocols/udp/v1", "network/protocols/ipv4/v1", NULL); if (status < B_OK) goto err1; + status = gStackModule->register_domain_protocols(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, + "network/protocols/udp/v1", + "network/protocols/ipv6/v1", + NULL); + if (status < B_OK) + goto err1; status = gStackModule->register_domain_receiving_protocol(AF_INET, IPPROTO_UDP, "network/protocols/udp/v1"); if (status < B_OK) goto err1; + status = gStackModule->register_domain_receiving_protocol(AF_INET6, IPPROTO_UDP, + "network/protocols/udp/v1"); + if (status < B_OK) + goto err1; add_debugger_command("udp_endpoints", UdpEndpointManager::DumpEndpoints, "lists all open UDP endpoints"); @@ -1225,6 +1242,7 @@ init_udp() return B_OK; err1: + // TODO: shouldn't unregister the protocols here? delete sUdpEndpointManager; TRACE_EPM("init_udp() fails with %lx (%s)", status, strerror(status)); diff --git a/src/add-ons/kernel/network/protocols/unix/UnixAddress.cpp b/src/add-ons/kernel/network/protocols/unix/UnixAddress.cpp index fc8813c8ff..bbde876c9c 100644 --- a/src/add-ons/kernel/network/protocols/unix/UnixAddress.cpp +++ b/src/add-ons/kernel/network/protocols/unix/UnixAddress.cpp @@ -290,7 +290,7 @@ net_address_module_info gAddressModule = { 0, NULL }, - + true, // has_broadcast_address unix_copy_address, unix_mask_address, unix_equal_addresses, diff --git a/src/add-ons/kernel/network/stack/datalink.cpp b/src/add-ons/kernel/network/stack/datalink.cpp index 964b665cab..9dd1acc305 100644 --- a/src/add-ons/kernel/network/stack/datalink.cpp +++ b/src/add-ons/kernel/network/stack/datalink.cpp @@ -30,6 +30,8 @@ #include #include +#include // TODO + struct datalink_protocol : net_protocol { struct net_domain_private* domain; @@ -134,6 +136,21 @@ remove_default_routes(net_interface_private* interface, int32 option) route.flags = RTF_LOCAL | RTF_HOST; remove_route(interface->domain, &route); } + + // for IPv6 remove multicast route (ff00::/8) + // TODO: move this code + if (interface->address->sa_family == AF_INET6) { + sockaddr_in6 address; + memset(&address, 0, sizeof(sockaddr_in6)); + address.sin6_family = AF_INET6; + address.sin6_len = sizeof(sockaddr_in6); + address.sin6_addr.s6_addr[0] = 0xff; + + route.destination = (sockaddr*)&address; + route.mask = (sockaddr*)&address; + route.flags = 0; + remove_route(interface->domain, &route); + } } @@ -160,6 +177,21 @@ add_default_routes(net_interface_private* interface, int32 option) route.flags = RTF_LOCAL | RTF_HOST; add_route(interface->domain, &route); } + + // for IPv6 add multicast route (ff00::/8) + // TODO: move this code + if (interface->address->sa_family == AF_INET6) { + sockaddr_in6 address; + memset(&address, 0, sizeof(sockaddr_in6)); + address.sin6_family = AF_INET6; + address.sin6_len = sizeof(sockaddr_in6); + address.sin6_addr.s6_addr[0] = 0xff; + + route.destination = (sockaddr*)&address; + route.mask = (sockaddr*)&address; + route.flags = 0; + add_route(interface->domain, &route); + } } @@ -183,6 +215,14 @@ reallocate_address(sockaddr** _address, uint32 size) } +static void +free_address(sockaddr** _address) +{ + free(*_address); + *_address = NULL; +} + + static status_t datalink_control_interface(net_domain_private* domain, int32 option, void* value, size_t* _length, size_t expected, bool getByName) @@ -386,7 +426,8 @@ datalink_send_datagram(net_protocol* protocol, net_domain* domain, net_route* route = NULL; status_t status; - if (protocol != NULL && protocol->socket->bound_to_device > 0) { + if (protocol != NULL && protocol->socket != NULL + && protocol->socket->bound_to_device > 0) { status = get_device_route(domain, protocol->socket->bound_to_device, &route); } else @@ -709,8 +750,15 @@ interface_protocol_control(net_datalink_protocol* _protocol, int32 option, } else oldNetmask = address; - sockaddr* broadcast = reallocate_address( - &interface->destination, request.ifr_addr.sa_len); + // reset the broadcast address if the address family has such + sockaddr* broadcast; + if (interface->domain->address_module->has_broadcast_address) { + broadcast = reallocate_address(&interface->destination, + request.ifr_addr.sa_len); + } else { + broadcast = NULL; + free_address(&interface->destination); + } interface->domain->address_module->set_to_defaults( netmask, broadcast, interface->address, oldNetmask); @@ -923,6 +971,7 @@ net_datalink_protocol_module_info gDatalinkInterfaceProtocolModule = { interface_protocol_init, interface_protocol_uninit, interface_protocol_send_data, + NULL, // receive_data interface_protocol_up, interface_protocol_down, interface_protocol_control, diff --git a/src/add-ons/kernel/network/stack/net_buffer.cpp b/src/add-ons/kernel/network/stack/net_buffer.cpp index 40c87453a7..ede4337e74 100644 --- a/src/add-ons/kernel/network/stack/net_buffer.cpp +++ b/src/add-ons/kernel/network/stack/net_buffer.cpp @@ -1038,6 +1038,7 @@ copy_metadata(net_buffer* destination, const net_buffer* source) destination->interface = source->interface; destination->offset = source->offset; destination->protocol = source->protocol; + destination->hoplimit = source->hoplimit; destination->type = source->type; } diff --git a/src/add-ons/kernel/network/stack/stack.cpp b/src/add-ons/kernel/network/stack/stack.cpp index 42f1cb06a5..fb4a4932f9 100644 --- a/src/add-ons/kernel/network/stack/stack.cpp +++ b/src/add-ons/kernel/network/stack/stack.cpp @@ -813,11 +813,17 @@ init_stack() // TODO: for now! register_domain_datalink_protocols(AF_INET, IFT_LOOP, "network/datalink_protocols/loopback_frame/v1", NULL); + register_domain_datalink_protocols(AF_INET6, IFT_LOOP, + "network/datalink_protocols/loopback_frame/v1", NULL); register_domain_datalink_protocols(AF_INET, IFT_ETHER, "network/datalink_protocols/ipv4_datagram/v1", "network/datalink_protocols/arp/v1", "network/datalink_protocols/ethernet_frame/v1", NULL); + register_domain_datalink_protocols(AF_INET6, IFT_ETHER, + "network/datalink_protocols/ipv6_datagram/v1", + "network/datalink_protocols/ethernet_frame/v1", + NULL); return B_OK;