/* icmp.c * * Copyright (c) 2019-2023 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This code implements ICMP I/O functions for the Thread border router. */ #ifndef LINUX #include #include #include #include #include #include #else #define _GNU_SOURCE #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #if !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING #ifndef LINUX #include #endif // LINUX #endif // !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING #include #include #include #include #include #ifdef IOLOOP_MACOS #include #include #include #include #include #include #include #include #endif // IOLOOP_MACOS #include "srp.h" #include "dns-msg.h" #include "ioloop.h" #include "cti-services.h" #include "srp-mdns-proxy.h" #include "route.h" #include "icmp.h" #include "state-machine.h" #include "thread-service.h" #include "omr-watcher.h" icmp_listener_t icmp_listener; void icmp_message_free(icmp_message_t *message) { if (message->options != NULL) { free(message->options); } if (message->wakeup != NULL) { ioloop_cancel_wake_event(message->wakeup); ioloop_wakeup_release(message->wakeup); } free(message); } void icmp_message_dump(icmp_message_t *message, struct in6_addr *source_address, struct in6_addr *destination_address) { link_layer_address_t *lladdr; prefix_information_t *prefix_info; route_information_t *route_info; uint8_t *flags; int i; char retransmission_timer_buf[11]; // Maximum size of a uint32_t printed as decimal. char *retransmission_timer = "infinite"; if (message->retransmission_timer != ND6_INFINITE_LIFETIME) { snprintf(retransmission_timer_buf, sizeof(retransmission_timer_buf), "%" PRIu32, message->retransmission_timer); retransmission_timer = retransmission_timer_buf; } SEGMENTED_IPv6_ADDR_GEN_SRP(source_address->s6_addr, src_addr_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(destination_address->s6_addr, dst_addr_buf); if (message->type == icmp_type_router_advertisement) { INFO("router advertisement from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on " PUB_S_SRP ": checksum = %x " "cur_hop_limit = %d flags = %x router_lifetime = %d reachable_time = %" PRIu32 " retransmission_timer = " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf), message->hop_limit, message->interface->name, message->checksum, message->cur_hop_limit, message->flags, message->router_lifetime, message->reachable_time, retransmission_timer); } else if (message->type == icmp_type_router_solicitation) { INFO("router solicitation from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on " PUB_S_SRP ": code = %d checksum = %x", SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf), message->hop_limit, message->interface->name, message->code, message->checksum); } else { INFO("icmp message from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on " PUB_S_SRP ": type = %d code = %d checksum = %x", SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf), message->hop_limit, message->interface->name, message->type, message->code, message->checksum); } for (i = 0; i < message->num_options; i++) { icmp_option_t *option = &message->options[i]; switch(option->type) { case icmp_option_source_link_layer_address: lladdr = &option->option.link_layer_address; INFO(" source link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address)); break; case icmp_option_target_link_layer_address: lladdr = &option->option.link_layer_address; INFO(" destination link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address)); break; case icmp_option_prefix_information: prefix_info = &option->option.prefix_information; SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_info->prefix.s6_addr, prefix_buf); INFO(" prefix info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %" PRIu32 " %" PRIu32, SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_info->prefix.s6_addr, prefix_buf), prefix_info->length, prefix_info->flags, prefix_info->valid_lifetime, prefix_info->preferred_lifetime); break; case icmp_option_route_information: route_info = &option->option.route_information; SEGMENTED_IPv6_ADDR_GEN_SRP(route_info->prefix.s6_addr, router_prefix_buf); INFO(" route info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %d", SEGMENTED_IPv6_ADDR_PARAM_SRP(route_info->prefix.s6_addr, router_prefix_buf), route_info->length, route_info->flags, route_info->route_lifetime); break; case icmp_option_ra_flags_extension: flags = option->option.ra_flags_extension; INFO(" ra flags extension: %x %x %x %x %x %x", flags[0], flags[1], flags[2], flags[3], flags[4], flags[5]); break; default: INFO(" option type %d", option->type); break; } } } static bool icmp_message_parse_options(icmp_message_t *message, uint8_t *icmp_buf, unsigned length, unsigned *offset) { uint8_t option_type, option_length_8; unsigned option_length; unsigned scan_offset = *offset; icmp_option_t *option; uint32_t reserved32; prefix_information_t *prefix_information; route_information_t *route_information; int prefix_bytes; // Count the options and validate the lengths message->num_options = 0; while (scan_offset < length) { if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) { return false; } if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) { return false; } if (option_length_8 == 0) { // RFC4191 section 4.6: The value 0 is invalid. ERROR("icmp_option_parse: option type %d length 0 is invalid.", option_type); return false; } if (scan_offset + option_length_8 * 8 - 2 > length) { ERROR("icmp_option_parse: option type %d length %d is longer than remaining available space %u", option_type, option_length_8 * 8, length - scan_offset + 2); return false; } scan_offset += option_length_8 * 8 - 2; message->num_options++; } // If there are no options, we're done. No options is valid, so return true. if (message->num_options == 0) { return true; } message->options = calloc(message->num_options, sizeof(*message->options)); if (message->options == NULL) { ERROR("No memory for icmp options."); return false; } option = message->options; while (*offset < length) { scan_offset = *offset; if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) { return false; } if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) { return false; } // We already validated the length in the previous pass. option->type = option_type; option_length = option_length_8 * 8; switch(option_type) { case icmp_option_source_link_layer_address: case icmp_option_target_link_layer_address: // At this juncture we are assuming that everything we care about looks like an // ethernet interface. So for this case, length should be 8. if (option_length != 8) { INFO("Ignoring unexpectedly long link layer address: %d", option_length); // Don't store the option. message->num_options--; *offset += option_length; continue; } option->option.link_layer_address.length = 6; memcpy(option->option.link_layer_address.address, &icmp_buf[scan_offset], 6); break; case icmp_option_prefix_information: prefix_information = &option->option.prefix_information; // Only a length of 32 is valid. This is an invalid ICMP packet, not just misunderunderstood if (option_length != 32) { return false; } // prefix length 8 if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->length)) { return false; } // flags 8a if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->flags)) { return false; } // valid lifetime 32 if (!dns_u32_parse(icmp_buf, length, &scan_offset, &prefix_information->valid_lifetime)) { return false; } // preferred lifetime 32 if (!dns_u32_parse(icmp_buf, length, &scan_offset, &prefix_information->preferred_lifetime)) { return false; } // reserved2 32 if (!dns_u32_parse(icmp_buf, length, &scan_offset, &reserved32)) { return false; } // prefix 128 in6prefix_copy_from_data(&prefix_information->prefix, &icmp_buf[scan_offset], 16); break; case icmp_option_route_information: route_information = &option->option.route_information; // route length 8 if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->length)) { return false; } switch(option_length) { case 8: prefix_bytes = 0; break; case 16: prefix_bytes = 8; break; case 24: prefix_bytes = 16; break; default: ERROR("invalid route information option length %d for route length %d", option_length, route_information->length); return false; } // flags 8 if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->flags)) { return false; } // route lifetime 32 if (!dns_u32_parse(icmp_buf, length, &scan_offset, &route_information->route_lifetime)) { return false; } // route (64, 96 or 128) in6prefix_copy_from_data(&route_information->prefix, &icmp_buf[scan_offset], prefix_bytes); break; case icmp_option_ra_flags_extension: // The RA Flags extension as defined in RFC 5175 must have a length of 1 (meaning 8 bytes). // It's possible that a later spec will define a length > 1, but since we are implementing // RFC5175, we are required to silently ignore anything after the first 8 bytes. Since // we've already checked for length=0 (invalid), we can just take our six bytes of flags // and not bounds-check further. memcpy(option->option.ra_flags_extension, &icmp_buf[scan_offset], sizeof(option->option.ra_flags_extension)); break; default: case icmp_option_mtu: case icmp_option_redirected_header: // don't care break; } *offset += option_length; option++; } return true; } static void icmp_message(route_state_t *route_state, uint8_t *icmp_buf, unsigned length, int ifindex, int hop_limit, addr_t *src, addr_t *dest) { unsigned offset = 0; uint32_t reserved32; interface_t *interface; icmp_message_t *message = calloc(1, sizeof(*message)); if (message == NULL) { ERROR("Unable to allocate icmp_message_t for parsing"); return; } message->source = src->sin6.sin6_addr; message->destination = dest->sin6.sin6_addr; message->hop_limit = hop_limit; for (interface = route_state->interfaces; interface; interface = interface->next) { if (interface->index == ifindex) { message->interface = interface; break; } } message->received_time = ioloop_timenow(); message->received_time_already_adjusted = false; message->new_router = true; message->route_state = route_state; if (message->interface == NULL) { SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, src_buf); SEGMENTED_IPv6_ADDR_GEN_SRP(message->destination.s6_addr, dst_buf); INFO("ICMP message type %d from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " on interface index %d, which isn't listed.", icmp_buf[0], SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, src_buf), SEGMENTED_IPv6_ADDR_PARAM_SRP(message->destination.s6_addr, dst_buf), ifindex); icmp_message_free(message); return; } if (length < sizeof (struct icmp6_hdr)) { ERROR("Short ICMP message: length %d is shorter than ICMP header length %zd", length, sizeof(struct icmp6_hdr)); icmp_message_free(message); return; } INFO("length %d", length); // The increasingly innaccurately named dns parse functions will work fine for this. if (!dns_u8_parse(icmp_buf, length, &offset, &message->type)) { goto out; } if (!dns_u8_parse(icmp_buf, length, &offset, &message->code)) { goto out; } // XXX check the checksum if (!dns_u16_parse(icmp_buf, length, &offset, &message->checksum)) { goto out; } switch(message->type) { case icmp_type_router_advertisement: if (!dns_u8_parse(icmp_buf, length, &offset, &message->cur_hop_limit)) { goto out; } if (!dns_u8_parse(icmp_buf, length, &offset, &message->flags)) { goto out; } if (!dns_u16_parse(icmp_buf, length, &offset, &message->router_lifetime)) { goto out; } if (!dns_u32_parse(icmp_buf, length, &offset, &message->reachable_time)) { goto out; } if (!dns_u32_parse(icmp_buf, length, &offset, &message->retransmission_timer)) { goto out; } if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) { goto out; } icmp_message_dump(message, &message->source, &message->destination); router_advertisement(message); // router_advertisement() is given ownership of the message return; case icmp_type_router_solicitation: if (!dns_u32_parse(icmp_buf, length, &offset, &reserved32)) { goto out; } if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) { goto out; } icmp_message_dump(message, &message->source, &message->destination); router_solicit(message); // router_solicit() is given ownership of the message. return; case icmp_type_neighbor_advertisement: icmp_message_dump(message, &message->source, &message->destination); neighbor_advertisement(message); break; case icmp_type_neighbor_solicitation: case icmp_type_echo_request: case icmp_type_echo_reply: case icmp_type_redirect: break; } out: icmp_message_free(message); return; } #ifndef FUZZING static #endif void icmp_callback(io_t *NONNULL io, void *UNUSED context) { ssize_t rv; uint8_t icmp_buf[1500]; int ifindex = 0; addr_t src, dest; int hop_limit = 0; #ifndef FUZZING rv = ioloop_recvmsg(io->fd, &icmp_buf[0], sizeof(icmp_buf), &ifindex, &hop_limit, &src, &dest); #else rv = read(io->fd, &icmp_buf, sizeof(icmp_buf)); #endif if (rv < 0) { ERROR("icmp_callback: can't read ICMP message: " PUB_S_SRP, strerror(errno)); return; } for (route_state_t *route_state = route_states; route_state != NULL; route_state = route_state->next) { icmp_message(route_state, icmp_buf, (unsigned)rv, ifindex, hop_limit, &src, &dest); // rv will never be > sizeof(icmp_buf) } } static void route_information_to_wire(dns_towire_state_t *towire, void *prefix_data, const char *source_interface, const char *dest_interface) { uint8_t *prefix = prefix_data; #ifndef ND_OPT_ROUTE_INFORMATION #define ND_OPT_ROUTE_INFORMATION 24 #endif dns_u8_to_wire(towire, ND_OPT_ROUTE_INFORMATION); dns_u8_to_wire(towire, 2); // length / 8 dns_u8_to_wire(towire, 64); // Interface prefixes are always 64 bits dns_u8_to_wire(towire, 0); // There's no reason at present to prefer one Thread BR over another dns_u32_to_wire(towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes) dns_rdata_raw_data_to_wire(towire, prefix, 8); // /64 requires 8 bytes. SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, thread_prefix_buf); INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix, thread_prefix_buf), source_interface, dest_interface); } static void icmp_send(uint8_t *message, size_t length, interface_t *interface, const struct in6_addr *destination) { #ifdef FUZZING char buffer[length]; memcpy(buffer, message, length); return; #endif struct iovec iov; struct in6_pktinfo *packet_info; socklen_t cmsg_length = CMSG_SPACE(sizeof(*packet_info)) + CMSG_SPACE(sizeof (int)); uint8_t *cmsg_buffer; struct msghdr msg_header; struct cmsghdr *cmsg_pointer; int hop_limit = 255; ssize_t rv; struct sockaddr_in6 dest; // Make space for the control message buffer. cmsg_buffer = calloc(1, cmsg_length); if (cmsg_buffer == NULL) { ERROR("Unable to construct ICMP Router Advertisement: no memory"); return; } // Send the message memset(&dest, 0, sizeof(dest)); dest.sin6_family = AF_INET6; dest.sin6_scope_id = interface->index; #ifndef NOT_HAVE_SA_LEN dest.sin6_len = sizeof(dest); #endif msg_header.msg_namelen = sizeof(dest); dest.sin6_addr = *destination; msg_header.msg_name = &dest; iov.iov_base = message; iov.iov_len = length; msg_header.msg_iov = &iov; msg_header.msg_iovlen = 1; msg_header.msg_control = cmsg_buffer; msg_header.msg_controllen = cmsg_length; // Specify the interface cmsg_pointer = CMSG_FIRSTHDR(&msg_header); cmsg_pointer->cmsg_level = IPPROTO_IPV6; cmsg_pointer->cmsg_type = IPV6_PKTINFO; cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(*packet_info)); packet_info = (struct in6_pktinfo *)CMSG_DATA(cmsg_pointer); memset(packet_info, 0, sizeof(*packet_info)); packet_info->ipi6_ifindex = interface->index; // Router advertisements and solicitations have a hop limit of 255 cmsg_pointer = CMSG_NXTHDR(&msg_header, cmsg_pointer); cmsg_pointer->cmsg_level = IPPROTO_IPV6; cmsg_pointer->cmsg_type = IPV6_HOPLIMIT; cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsg_pointer), &hop_limit, sizeof(hop_limit)); // Send it rv = sendmsg(icmp_listener.io_state->fd, &msg_header, 0); if (rv < 0) { uint8_t *in6_addr_bytes = ((struct sockaddr_in6 *)(msg_header.msg_name))->sin6_addr.s6_addr; SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_bytes, in6_addr_buf); ERROR("icmp_send: sending " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " on interface " PUB_S_SRP " index %d: " PUB_S_SRP, message[0] == ND_ROUTER_SOLICIT ? "solicit" : "advertise", SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_bytes, in6_addr_buf), interface->name, interface->index, strerror(errno)); } else if ((size_t)rv != iov.iov_len) { ERROR("icmp_send: short send to interface " PUB_S_SRP ": %zd < %zd", interface->name, rv, iov.iov_len); } free(cmsg_buffer); } void router_advertisement_send(interface_t *interface, const struct in6_addr *destination) { uint8_t *message; dns_towire_state_t towire; route_state_t *route_state = interface->route_state; // Thread blocks RAs so no point sending them. if (interface->inactive #ifndef RA_TESTER || interface->is_thread #endif ) { return; } #define MAX_ICMP_MESSAGE 1280 message = malloc(MAX_ICMP_MESSAGE); if (message == NULL) { ERROR("router_advertisement_send: unable to construct ICMP Router Advertisement: no memory"); return; } // Construct the ICMP header and options for each interface. memset(&towire, 0, sizeof towire); towire.p = message; towire.lim = message + MAX_ICMP_MESSAGE; // Construct the ICMP header. // We use the DNS message construction functions because it's easy; probably should just make // the towire functions more generic. dns_u8_to_wire(&towire, ND_ROUTER_ADVERT); // icmp6_type dns_u8_to_wire(&towire, 0); // icmp6_code dns_u16_to_wire(&towire, 0); // The kernel computes the checksum (we don't technically have it). dns_u8_to_wire(&towire, 0); // Hop limit, we don't set. dns_u8_to_wire(&towire, 0); // Flags. We don't offer DHCP, so We set neither the M nor the O bit. // We are not a home agent, so no H bit. Lifetime is 0, so Prf is 0. #ifdef ROUTER_LIFETIME_HACK dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime, hacked. This shouldn't ever be enabled. #else #ifdef RA_TESTER // Advertise a default route on the simulated thread network if (!strcmp(interface->name, route_state->thread_interface_name)) { dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime for default route } else { #endif dns_u16_to_wire(&towire, 0); // Router lifetime for non-default default route(s). #ifdef RA_TESTER } #endif // RA_TESTER #endif // ROUTER_LIFETIME_HACK dns_u32_to_wire(&towire, 0); // Reachable time for NUD, we have no opinion on this. dns_u32_to_wire(&towire, 0); // Retransmission timer, again we have no opinion. #ifndef RA_TESTER // Send MTU of 1280 for Thread? if (interface->is_thread) { dns_u8_to_wire(&towire, ND_OPT_MTU); dns_u8_to_wire(&towire, 1); // length / 8 dns_u32_to_wire(&towire, 1280); INFO("advertising MTU of 1280 on " PUB_S_SRP, interface->name); } #endif // Send Prefix Information option if there's no IPv6 on the link. if (interface->our_prefix_advertised && !interface->suppress_ipv6_prefix && route_state->have_xpanid_prefix) { dns_u8_to_wire(&towire, ND_OPT_PREFIX_INFORMATION); dns_u8_to_wire(&towire, 4); // length / 8 dns_u8_to_wire(&towire, 64); // On-link prefix is always 64 bits dns_u8_to_wire(&towire, ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO); // On link, autoconfig dns_u32_to_wire(&towire, interface->valid_lifetime); dns_u32_to_wire(&towire, interface->preferred_lifetime); dns_u32_to_wire(&towire, 0); // Reserved dns_rdata_raw_data_to_wire(&towire, &interface->ipv6_prefix, sizeof interface->ipv6_prefix); SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf); INFO("advertising on-link prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf), interface->name); } // In principle we can either send routes to links that are reachable by this router, // or just advertise a router to the entire ULA /48. In theory it doesn't matter // which we do; if we support HNCP at some point we probably need to be specific, but // for now being general is fine because we have no way to share a ULA. // Unfortunately, some RIO implementations do not work with specific routes, so for now // We are doing it the easy way and just advertising the /48. #define SEND_INTERFACE_SPECIFIC_RIOS 1 #ifdef SEND_INTERFACE_SPECIFIC_RIOS // If neither ROUTE_BETWEEN_NON_THREAD_LINKS nor RA_TESTER are defined, then we never want to // send an RIO other than for the thread network prefix. #if defined (ROUTE_BETWEEN_NON_THREAD_LINKS) || defined(RA_TESTER) interface_t *ifroute; // Send Route Information option for other interfaces. for (ifroute = route_state->interfaces; ifroute; ifroute = ifroute->next) { if (ifroute->inactive) { continue; } if (want_routing(route_state) && ifroute->our_prefix_advertised && #ifdef SEND_ON_LINK_ROUTE // In theory we don't want to send RIO for the on-link prefix, but there's this bug, see. true && #else ifroute != interface && #endif #ifdef RA_TESTER // For the RA tester, we don't need to send an RIO to the thread network because we're the // default router for that network. strcmp(interface->name, route_state->thread_interface_name) #else true #endif ) { route_information_to_wire(&towire, &ifroute->ipv6_prefix, ifroute->name, interface->name); } } #endif // ROUTE_BETWEEN_NON_THREAD_LINKS || RA_TESTER #ifndef RA_TESTER // Send route information option for thread prefix if (route_state->omr_watcher != NULL) { omr_prefix_t *thread_prefixes = omr_watcher_prefixes_get(route_state->omr_watcher); // Send RIOs for any other prefixes that appear on the Thread network for (struct omr_prefix *prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) { route_information_to_wire(&towire, &prefix->prefix, route_state->thread_interface_name, interface->name); } } #endif #else #ifndef SKIP_SLASH_48 dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION); dns_u8_to_wire(&towire, 3); // length / 8 dns_u8_to_wire(&towire, 48); // ULA prefixes are always 48 bits dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another dns_u32_to_wire(&towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes) dns_rdata_raw_data_to_wire(&towire, &route_state->srp_server->ula_prefix, 16); // /48 requires 16 bytes #endif // SKIP_SLASH_48 #endif // SEND_INTERFACE_SPECIFIC_RIOS // Send the stub router flag dns_u8_to_wire(&towire, ND_OPT_RA_FLAGS_EXTENSION); dns_u8_to_wire(&towire, 1); // length / 8 dns_u8_to_wire(&towire, RA_FLAGS1_STUB_ROUTER); dns_u8_to_wire(&towire, 0); // Five bytes of zero flag bits dns_u32_to_wire(&towire, 0); // Send Source link-layer address option if (interface->have_link_layer_address) { dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR); dns_u8_to_wire(&towire, 1); // length / 8 dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer)); } if (towire.error) { ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line); towire.error = 0; } else { SEGMENTED_IPv6_ADDR_GEN_SRP(destination->s6_addr, destination_buf); INFO("sending advertisement to " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination->s6_addr, destination_buf), interface->name); icmp_send(message, towire.p - message, interface, destination); } free(message); } void router_solicit_send(interface_t *interface) { uint8_t *message; dns_towire_state_t towire; // Thread blocks RSs so no point sending them. if (interface->inactive #ifndef RA_TESTER || interface->is_thread #endif ) { return; } #define MAX_ICMP_MESSAGE 1280 message = malloc(MAX_ICMP_MESSAGE); if (message == NULL) { ERROR("Unable to construct ICMP Router Advertisement: no memory"); return; } // Construct the ICMP header and options for each interface. memset(&towire, 0, sizeof towire); towire.p = message; towire.lim = message + MAX_ICMP_MESSAGE; // Construct the ICMP header. // We use the DNS message construction functions because it's easy; probably should just make // the towire functions more generic. dns_u8_to_wire(&towire, ND_ROUTER_SOLICIT); // icmp6_type dns_u8_to_wire(&towire, 0); // icmp6_code dns_u16_to_wire(&towire, 0); // The kernel computes the checksum (we don't technically have it). dns_u32_to_wire(&towire, 0); // Reserved32 // Send Source link-layer address option if (interface->have_link_layer_address) { dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR); dns_u8_to_wire(&towire, 1); // length / 8 dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer)); } if (towire.error) { ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line); } else { if (interface->have_link_layer_address) { INFO("sending router solicit on " PUB_S_SRP " to all routers with source " PRI_MAC_ADDR_SRP, interface->name, MAC_ADDR_PARAM_SRP(interface->link_layer)); } else { INFO("sending router solicit on " PUB_S_SRP " to all routers", interface->name); } icmp_send(message, towire.p - message, interface, &in6addr_linklocal_allrouters); } free(message); } void neighbor_solicit_send(interface_t *interface, struct in6_addr *destination) { uint8_t *message; dns_towire_state_t towire; #define MAX_ICMP_MESSAGE 1280 message = malloc(MAX_ICMP_MESSAGE); if (message == NULL) { ERROR("Unable to construct ICMP Router Advertisement: no memory"); return; } // Construct the ICMP header and options for each interface. memset(&towire, 0, sizeof towire); towire.p = message; towire.lim = message + MAX_ICMP_MESSAGE; // Construct the ICMP header. // We use the DNS message construction functions because it's easy; probably should just make // the towire functions more generic. dns_u8_to_wire(&towire, ND_NEIGHBOR_SOLICIT); // icmp6_type dns_u8_to_wire(&towire, 0); // icmp6_code dns_u16_to_wire(&towire, 0); // The kernel computes the checksum (we don't technically have it). dns_u32_to_wire(&towire, 0); // Reserved32 dns_rdata_raw_data_to_wire(&towire, destination, sizeof(*destination)); // Target address of solicit // Send Source link-layer address option if (interface->have_link_layer_address) { dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR); dns_u8_to_wire(&towire, 1); // length / 8 dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer)); } if (towire.error) { ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line); } else { SEGMENTED_IPv6_ADDR_GEN_SRP(destination, dest_buf); if (interface->have_link_layer_address) { INFO("sending neighbor solicit on " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " with source " PRI_MAC_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination, dest_buf), MAC_ADDR_PARAM_SRP(interface->link_layer)); } else { INFO("sending neighbor solicit on " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP, interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination, dest_buf)); } icmp_send(message, towire.p - message, interface, destination); } free(message); } bool start_icmp_listener(void) { #ifndef SRP_TEST_SERVER int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); int true_flag = 1; #ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES int false_flag = 0; #endif struct icmp6_filter filter; ssize_t rv; if (sock < 0) { ERROR("Unable to listen for icmp messages: " PUB_S_SRP, strerror(errno)); close(sock); return false; } // Only accept router advertisements and router solicits. ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter); rv = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); if (rv < 0) { ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno)); close(sock); return false; } // We want a source address and interface index rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &true_flag, sizeof(true_flag)); if (rv < 0) { ERROR("Can't set IPV6_RECVPKTINFO: " PUB_S_SRP ".", strerror(errno)); close(sock); return false; } // We need to be able to reject RAs arriving from off-link. rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &true_flag, sizeof(true_flag)); if (rv < 0) { ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno)); close(sock); return false; } #ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES // Prevent our router advertisements from updating our routing table. rv = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &false_flag, sizeof(false_flag)); if (rv < 0) { ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno)); close(sock); return false; } #endif icmp_listener.io_state = ioloop_file_descriptor_create(sock, NULL, NULL); if (icmp_listener.io_state == NULL) { ERROR("No memory for ICMP I/O structure."); close(sock); return false; } // Beacon out a router advertisement every three minutes. icmp_listener.unsolicited_interval = 3 * 60 * 1000; ioloop_add_reader(icmp_listener.io_state, icmp_callback); #else (void)icmp_callback; #endif // !SRP_TEST_SERVER return true; } void icmp_interface_subscribe(interface_t *interface, bool added) { struct ipv6_mreq req; int rv; if (icmp_listener.io_state == NULL) { ERROR("Interface subscribe without ICMP listener."); return; } memset(&req, 0, sizeof req); if (interface->index == -1) { ERROR("icmp_interface_subscribe called before interface index fetch for " PUB_S_SRP, interface->name); return; } req.ipv6mr_multiaddr = in6addr_linklocal_allrouters; req.ipv6mr_interface = interface->index; rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req, sizeof req); if (rv < 0) { ERROR("Unable to " PUB_S_SRP " all-routers multicast group on " PUB_S_SRP ": " PUB_S_SRP, added ? "join" : "leave", interface->name, strerror(errno)); return; } else { INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un", interface->name); } req.ipv6mr_multiaddr = in6addr_linklocal_allnodes; req.ipv6mr_interface = interface->index; rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req, sizeof req); if (rv < 0) { ERROR("Unable to " PUB_S_SRP " all-nodes multicast group on " PUB_S_SRP ": " PUB_S_SRP, added ? "join" : "leave", interface->name, strerror(errno)); return; } else { INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un", interface->name); } } // Local Variables: // mode: C // tab-width: 4 // c-file-style: "bsd" // c-basic-offset: 4 // fill-column: 120 // indent-tabs-mode: nil // End: