/* * 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 #include #include #include "ndp.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; in6_addr local_address; }; static void ndp_timer(struct net_timer* timer, void* data); net_buffer_module_info* gBufferModule; static net_stack_module_info* sStackModule; static net_datalink_module_info* sDatalinkModule; static net_protocol_module_info* sIPv6Module; static net_protocol* sIPv6Protocol; static mutex sCacheLock; 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, theoretically there could be more than one option. uint8 option_type; uint8 option_length; uint8 link_address[ETHER_ADDRESS_LENGTH]; } _PACKED; struct router_advertisement_header { uint8 icmp6_type; uint8 icmp6_code; uint16 icmp6_checksum; uint8 hop_limit; uint8 flags; uint16 router_lifetime; uint32 reachable_time; uint32 retransmit_timer; uint8 options[0]; } _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 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(); }; struct ndpHash { typedef in6_addr KeyType; typedef ndp_entry ValueType; size_t HashKey(in6_addr key) const { return jenkins_hashword((const uint32*)&(key), sizeof(in6_addr) / sizeof(uint32), 0); } size_t Hash(ndp_entry* value) const { return HashKey(value->protocol_address); } bool Compare(in6_addr key, ndp_entry* value) const { return value->protocol_address == key; } ndp_entry*& GetLink(ndp_entry* value) const { return value->next; } }; typedef BOpenHashTable AddressCache; static AddressCache* sCache; #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 = htons(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; } // #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_get_and_set(&entry->request_buffer, kDeletedBuffer); if (buffer != NULL && buffer != kDeletedBuffer) gBufferModule->free(buffer); } ndp_entry* ndp_entry::Lookup(const in6_addr& address) { return sCache->Lookup(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 = htons(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 (sCache->Insert(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)); sIPv6Module->setsockopt(sIPv6Protocol, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &value, sizeof(value)); mutex_init(&sCacheLock, "ndp cache"); sCache = new(std::nothrow) AddressCache(); if (sCache == NULL || sCache->Init(64) != B_OK) { 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; } static void ndp_remove_local_entry(ipv6_datalink_protocol* protocol, const sockaddr* local, bool updateLocalAddress) { in6_addr inetAddress; if (local == NULL) { // interface has not yet been set memset(&inetAddress, 0, sizeof(in6_addr)); } else { memcpy(&inetAddress, &((sockaddr_in6*)local)->sin6_addr, sizeof(in6_addr)); // leave the NS multicast address sockaddr_in6 multicast; ipv6_to_solicited_multicast(&multicast, inetAddress); 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)); } } // TRACE(("%s(): address %s\n", __FUNCTION__, inet6_to_string(inetAddress))); MutexLocker locker(sCacheLock); ndp_entry* entry = ndp_entry::Lookup(inetAddress); if (entry != NULL) { sCache->Remove(entry); entry->flags |= NDP_FLAG_REMOVED; } if (updateLocalAddress && protocol->local_address == inetAddress) { // find new local sender address memset(&protocol->local_address, 0, sizeof(in6_addr)); net_interface_address* address = NULL; while (sDatalinkModule->get_next_interface_address(protocol->interface, &address)) { if (address->local == NULL || address->local->sa_family != AF_INET6) continue; memcpy(&protocol->local_address, &((sockaddr_in6*)address->local)->sin6_addr, sizeof(in6_addr)); } } locker.Unlock(); delete entry; } /*! Removes all entries belonging to the local interface of the \a procotol given. */ static void ndp_remove_local(ipv6_datalink_protocol* protocol) { net_interface_address* address = NULL; while (sDatalinkModule->get_next_interface_address(protocol->interface, &address)) { if (address->local == NULL || address->local->sa_family != AF_INET6) continue; ndp_remove_local_entry(protocol, address->local, false); } } static status_t ndp_set_local_entry(ipv6_datalink_protocol* protocol, const sockaddr* local) { MutexLocker locker(sCacheLock); net_interface* interface = protocol->interface; in6_addr inetAddress; if (local == NULL) { // interface has not yet been set memset(&inetAddress, 0, sizeof(in6_addr)); } else { memcpy(&inetAddress, &((sockaddr_in6*)local)->sin6_addr, sizeof(in6_addr)); // join multicast address for listening to NS packets sockaddr_in6 multicast; ipv6_to_solicited_multicast(&multicast, inetAddress); 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)); } } // TRACE(("%s(): address %s\n", __FUNCTION__, inet6_to_string(inetAddress))); if (IN6_IS_ADDR_UNSPECIFIED(&protocol->local_address)) memcpy(&protocol->local_address, &inetAddress, 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 = htons(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(inetAddress, &address, NDP_FLAG_LOCAL | NDP_FLAG_PERMANENT, &entry); if (status == B_OK) entry->protocol = protocol; return status; } /*! Creates permanent local entries for all addresses of the interface belonging to this protocol. Returns an error if no entry could be added. */ static status_t ndp_update_local(ipv6_datalink_protocol* protocol) { memset(&protocol->local_address, 0, sizeof(in6_addr)); ssize_t count = 0; net_interface_address* address = NULL; while (sDatalinkModule->get_next_interface_address(protocol->interface, &address)) { if (address->local == NULL || address->local->sa_family != AF_INET6) continue; if (ndp_set_local_entry(protocol, address->local) == B_OK) { count++; } } if (count == 0) return ndp_set_local_entry(protocol, NULL); return B_OK; } static status_t ndp_receive_solicitation(net_buffer* buffer, bool* reuseBuffer) { *reuseBuffer = false; 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 = htons(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_ADVERT; 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 if (sIPv6Protocol == NULL) return B_ERROR; *reuseBuffer = true; // send the ICMPv6 packet out TRACE(("Sending Neighbor Advertisement\n")); return sIPv6Module->send_data(sIPv6Protocol, buffer); } static void ndp_receive_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 = htons(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_receive_router_advertisement(net_buffer* buffer) { NetBufferHeaderReader bufferHeader(buffer); if (bufferHeader.Status() < B_OK) return; // TODO: check for validity // TODO: parse the options } static status_t ndp_receive_data(net_buffer* buffer) { TRACE("ndp_receive_data\n"); NetBufferHeaderReader icmp6Header(buffer); if (icmp6Header.Status() < B_OK) return icmp6Header.Status(); bool reuseBuffer = false; switch (icmp6Header->icmp6_type) { case ND_NEIGHBOR_SOLICIT: TRACE((" received Neighbor Solicitation\n")); ndp_receive_solicitation(buffer, &reuseBuffer); break; case ND_NEIGHBOR_ADVERT: TRACE((" received Neighbor Advertisement\n")); ndp_receive_advertisement(buffer); break; case ND_ROUTER_ADVERT: TRACE((" received Router Advertisement\n")); ndp_receive_router_advertisement(buffer); break; } if (reuseBuffer == false) gBufferModule->free(buffer); return B_OK; } static void ndp_timer(struct net_timer* timer, void* data) { ndp_entry* entry = (ndp_entry*)data; TRACE(("NDP timer %" B_PRId32 ", 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; } sCache->Remove(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(ipv6_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, protocol->local_address); // protocol->local_address 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_domain* domain, net_datalink_protocol** _protocol) { if (domain->family != AF_INET6) return B_BAD_TYPE; status_t status = sStackModule->register_domain_device_handler( interface->device, B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_IPV6), domain); if (status != B_OK) return status; 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)); memset(&protocol->local_address, 0, sizeof(in6_addr)); *_protocol = protocol; return B_OK; } static status_t ipv6_datalink_uninit(net_datalink_protocol* protocol) { sStackModule->unregister_device_handler(protocol->interface->device, B_NET_FRAME_TYPE(IFT_ETHER, ETHER_TYPE_IPV6)); 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_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; // cache this device's address for later use status = ndp_update_local(protocol); if (status != B_OK) { protocol->next->module->interface_down(protocol->next); return status; } return B_OK; } static void ipv6_datalink_down(net_datalink_protocol *protocol) { // remove local NDP entries from the cache ndp_remove_local((ipv6_datalink_protocol*)protocol); protocol->next->module->interface_down(protocol->next); } status_t ipv6_datalink_change_address(net_datalink_protocol* _protocol, net_interface_address* address, int32 option, const struct sockaddr* oldAddress, const struct sockaddr* newAddress) { ipv6_datalink_protocol* protocol = (ipv6_datalink_protocol*)_protocol; switch (option) { case SIOCSIFADDR: case SIOCAIFADDR: case SIOCDIFADDR: // Those are the options we handle if ((protocol->interface->flags & IFF_UP) != 0) { // Update NDP entry for the local address if (newAddress != NULL && newAddress->sa_family == AF_INET6) { status_t status = ndp_set_local_entry(protocol, newAddress); if (status != B_OK) return status; // add IPv6 multicast route (ff00::/8) sockaddr_in6 socketAddress; memset(&socketAddress, 0, sizeof(sockaddr_in6)); socketAddress.sin6_family = AF_INET6; socketAddress.sin6_len = sizeof(sockaddr_in6); socketAddress.sin6_addr.s6_addr[0] = 0xff; net_route route; memset(&route, 0, sizeof(net_route)); route.destination = (sockaddr*)&socketAddress; route.mask = (sockaddr*)&socketAddress; route.flags = 0; sDatalinkModule->add_route(address->domain, &route); } if (oldAddress != NULL && oldAddress->sa_family == AF_INET6) { ndp_remove_local_entry(protocol, oldAddress, true); // remove IPv6 multicast route (ff00::/8) sockaddr_in6 socketAddress; memset(&socketAddress, 0, sizeof(sockaddr_in6)); socketAddress.sin6_family = AF_INET6; socketAddress.sin6_len = sizeof(sockaddr_in6); socketAddress.sin6_addr.s6_addr[0] = 0xff; net_route route; memset(&route, 0, sizeof(net_route)); route.destination = (sockaddr*)&socketAddress; route.mask = (sockaddr*)&socketAddress; route.flags = 0; sDatalinkModule->remove_route(address->domain, &route); } } break; default: break; } return protocol->next->module->change_address(protocol->next, address, option, oldAddress, newAddress); } 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_up, ipv6_datalink_down, ipv6_datalink_change_address, ipv6_datalink_control, ipv6_datalink_join_multicast, ipv6_datalink_leave_multicast, }; net_ndp_module_info gIPv6NDPModule = { { "network/datalink_protocols/ipv6_datagram/ndp/v1", 0, NULL }, ndp_receive_data }; module_dependency module_dependencies[] = { {NET_STACK_MODULE_NAME, (module_info**)&sStackModule}, {NET_DATALINK_MODULE_NAME, (module_info**)&sDatalinkModule}, {NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule}, {"network/protocols/ipv6/v1", (module_info**)&sIPv6Module}, {} }; module_info* modules[] = { (module_info*)&gIPv6DataLinkModule, (module_info*)&gIPv6NDPModule, NULL };