| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * 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 |
| * |
| * http://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. |
| */ |
| |
| #include "common/libs/utils/network.h" |
| |
| #include <arpa/inet.h> |
| #include <linux/if.h> |
| #include <linux/if_tun.h> |
| #include <linux/types.h> |
| #include <linux/if_packet.h> |
| #include <netinet/ip.h> |
| #include <netinet/udp.h> |
| #include <netinet/ether.h> |
| #include <string.h> |
| |
| #include <android-base/strings.h> |
| #include "android-base/logging.h" |
| |
| #include "common/libs/fs/shared_buf.h" |
| #include "common/libs/utils/environment.h" |
| #include "common/libs/utils/subprocess.h" |
| |
| namespace cuttlefish { |
| namespace { |
| |
| static std::string DefaultHostArtifactsPath(const std::string& file_name) { |
| return (StringFromEnv("ANDROID_HOST_OUT", StringFromEnv("HOME", ".")) + |
| "/") + |
| file_name; |
| } |
| |
| // This should be the size of virtio_net_hdr_v1, from linux/virtio_net.h, but |
| // the version of that header that ships with android in Pie does not include |
| // that struct (it was added in Q). |
| // This is what that struct looks like: |
| // struct virtio_net_hdr_v1 { |
| // u8 flags; |
| // u8 gso_type; |
| // u16 hdr_len; |
| // u16 gso_size; |
| // u16 csum_start; |
| // u16 csum_offset; |
| // u16 num_buffers; |
| // }; |
| static constexpr int SIZE_OF_VIRTIO_NET_HDR_V1 = 12; |
| |
| bool ParseAddress(const std::string& address, const std::string& separator, |
| const std::size_t expected_size, int base, std::uint8_t* out) { |
| auto components = android::base::Split(address, separator); |
| if (components.size() != expected_size) { |
| LOG(ERROR) << "Address \"" << address << "\" had wrong number of parts. " |
| << "Had " << components.size() << ", expected " << expected_size; |
| return false; |
| } |
| for (int i = 0; i < expected_size; i++) { |
| auto out_part = std::stoi(components[i], nullptr, base); |
| if (out_part < 0 || out_part > 255) { |
| LOG(ERROR) << "Address part " << i << " (" << out_part |
| << "): outside range [0,255]"; |
| return false; |
| } |
| out[i] = (std::uint8_t) out_part; |
| } |
| return true; |
| } |
| |
| bool ParseMacAddress(const std::string& address, std::uint8_t mac[6]) { |
| return ParseAddress(address, ":", 6, 16, mac); |
| } |
| |
| bool ParseIpAddress(const std::string& address, std::uint8_t ip[4]) { |
| return ParseAddress(address, ".", 4, 10, ip); |
| } |
| |
| } // namespace |
| |
| SharedFD OpenTapInterface(const std::string& interface_name) { |
| constexpr auto TUNTAP_DEV = "/dev/net/tun"; |
| |
| auto tap_fd = SharedFD::Open(TUNTAP_DEV, O_RDWR | O_NONBLOCK); |
| if (!tap_fd->IsOpen()) { |
| LOG(ERROR) << "Unable to open tun device: " << tap_fd->StrError(); |
| return tap_fd; |
| } |
| |
| if (HostArch() == Arch::Arm64) { |
| auto tapsetiff_path = DefaultHostArtifactsPath("bin/tapsetiff"); |
| Command cmd(tapsetiff_path); |
| cmd.AddParameter(tap_fd); |
| cmd.AddParameter(interface_name.c_str()); |
| int ret = cmd.Start().Wait(); |
| if (ret != 0) { |
| LOG(ERROR) << "Unable to run tapsetiff.py. Exited with status " << ret; |
| tap_fd->Close(); |
| return SharedFD(); |
| } |
| } else { |
| struct ifreq ifr; |
| memset(&ifr, 0, sizeof(ifr)); |
| ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; |
| strncpy(ifr.ifr_name, interface_name.c_str(), IFNAMSIZ); |
| |
| int err = tap_fd->Ioctl(TUNSETIFF, &ifr); |
| if (err < 0) { |
| LOG(ERROR) << "Unable to connect to " << interface_name |
| << " tap interface: " << tap_fd->StrError(); |
| tap_fd->Close(); |
| return SharedFD(); |
| } |
| |
| // The interface's configuration may have been modified or just not set |
| // correctly on creation. While qemu checks this and enforces the right |
| // configuration, crosvm does not, so it needs to be set before it's passed to |
| // it. |
| tap_fd->Ioctl(TUNSETOFFLOAD, |
| reinterpret_cast<void*>(TUN_F_CSUM | TUN_F_UFO | TUN_F_TSO4 | |
| TUN_F_TSO6)); |
| int len = SIZE_OF_VIRTIO_NET_HDR_V1; |
| tap_fd->Ioctl(TUNSETVNETHDRSZ, &len); |
| } |
| |
| return tap_fd; |
| } |
| |
| std::set<std::string> TapInterfacesInUse() { |
| Command cmd("/bin/bash"); |
| cmd.AddParameter("-c"); |
| cmd.AddParameter("egrep -h -e \"^iff:.*\" /proc/*/fdinfo/*"); |
| std::string stdin, stdout, stderr; |
| RunWithManagedStdio(std::move(cmd), &stdin, &stdout, &stderr); |
| auto lines = android::base::Split(stdout, "\n"); |
| std::set<std::string> tap_interfaces; |
| for (const auto& line : lines) { |
| if (line == "") { |
| continue; |
| } |
| if (!android::base::StartsWith(line, "iff:\t")) { |
| LOG(ERROR) << "Unexpected line \"" << line << "\""; |
| continue; |
| } |
| tap_interfaces.insert(line.substr(std::string("iff:\t").size())); |
| } |
| return tap_interfaces; |
| } |
| |
| std::vector<DnsmasqDhcp4Lease> ParseDnsmasqLeases(SharedFD lease_file) { |
| std::string lease_file_content; |
| if (ReadAll(lease_file, &lease_file_content) < 0) { |
| LOG(ERROR) << "Could not read lease_file: \"" << lease_file->StrError() |
| << "\". This may result in difficulty connecting to guest wifi."; |
| return {}; |
| } |
| std::vector<DnsmasqDhcp4Lease> leases; |
| auto lease_file_lines = android::base::Split(lease_file_content, "\n"); |
| for (const auto& line : lease_file_lines) { |
| if (line == "") { |
| continue; |
| } |
| auto line_elements = android::base::Split(line, " "); |
| if (line_elements.size() != 5) { |
| LOG(WARNING) << "Could not parse lease line: \"" << line << "\"\n"; |
| continue; |
| } |
| DnsmasqDhcp4Lease lease; |
| lease.expiry = std::stoll(line_elements[0]); |
| if (!ParseMacAddress(line_elements[1], &lease.mac_address[0])) { |
| LOG(WARNING) << "Could not parse MAC address: \'" << line_elements[1] |
| << "\""; |
| continue; |
| } |
| if (!ParseIpAddress(line_elements[2], &lease.ip_address[0])) { |
| LOG(WARNING) << "Could not parse IP address: " << line_elements[2] |
| << "\""; |
| } |
| lease.hostname = line_elements[3]; |
| lease.client_id = line_elements[4]; |
| leases.push_back(lease); |
| } |
| return leases; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const DnsmasqDhcp4Lease& lease) { |
| out << "DnsmasqDhcp4Lease(lease_time = \"" << std::dec << lease.expiry |
| << ", mac_address = \"" << std::hex; |
| for (int i = 0; i < 5; i++) { |
| out << (int) lease.mac_address[i] << ":"; |
| } |
| out << (int) lease.mac_address[5] << "\", ip_address = \"" << std::dec; |
| for (int i = 0; i < 3; i++) { |
| out << (int) lease.ip_address[i] << "."; |
| } |
| return out << (int) lease.ip_address[3] << "\", hostname = \"" |
| << lease.hostname << "\", client_id = \"" << lease.client_id |
| << "\")"; |
| } |
| |
| struct __attribute__((packed)) Dhcp4MessageTypeOption { |
| std::uint8_t code; |
| std::uint8_t len; |
| std::uint8_t message_type; |
| }; |
| |
| struct __attribute__((packed)) Dhcp4ServerIdentifier { |
| std::uint8_t code; |
| std::uint8_t len; |
| std::uint8_t server_ip[4]; |
| }; |
| |
| struct __attribute__((packed)) Dhcp4ReleaseMessage { |
| std::uint8_t op; |
| std::uint8_t htype; |
| std::uint8_t hlen; |
| std::uint8_t hops; |
| __be32 xid; |
| __be16 secs; |
| __be16 flags; |
| std::uint8_t client_ip[4]; |
| std::uint8_t assigned_ip[4]; |
| std::uint8_t server_ip[4]; |
| std::uint8_t gateway_ip[4]; |
| std::uint8_t client_harware_address[16]; |
| std::uint8_t server_name[64]; |
| std::uint8_t boot_filename[128]; |
| std::uint8_t magic_cookie[4]; |
| Dhcp4MessageTypeOption message_type; |
| Dhcp4ServerIdentifier server_identifier; |
| std::uint8_t end_code; |
| }; |
| |
| struct __attribute__((packed)) CompleteReleaseFrame { |
| std::uint8_t vnet[SIZE_OF_VIRTIO_NET_HDR_V1]; |
| ether_header eth; |
| iphdr ip; |
| udphdr udp; |
| Dhcp4ReleaseMessage dhcp; |
| }; |
| |
| static std::uint16_t ip_checksum(std::uint16_t *buf, std::size_t size) { |
| std::uint32_t sum = 0; |
| for (std::size_t i = 0; i < size; i++) { |
| sum += buf[i]; |
| } |
| sum = (sum >> 16) + (sum & 0xFFFF); |
| sum += sum >> 16; |
| return (std::uint16_t) ~sum; |
| } |
| |
| bool ReleaseDhcp4(SharedFD tap, const std::uint8_t mac_address[6], |
| const std::uint8_t ip_address[4], |
| const std::uint8_t dhcp_server_ip[4]) { |
| CompleteReleaseFrame frame = {}; |
| *reinterpret_cast<std::uint16_t*>(&frame.vnet[2]) = // hdr_len, little-endian |
| htole16(sizeof(ether_header) + sizeof(iphdr) + sizeof(udphdr)); |
| |
| memcpy(frame.eth.ether_shost, mac_address, 6); |
| memset(frame.eth.ether_dhost, 255, 6); // Broadcast |
| frame.eth.ether_type = htobe16(ETH_P_IP); |
| |
| frame.ip.ihl = 5; |
| frame.ip.version = 4; |
| frame.ip.id = 0; |
| frame.ip.ttl = 64; // hops |
| frame.ip.protocol = 17; // UDP |
| memcpy((std::uint8_t*) &frame.ip.saddr, ip_address, 4); |
| frame.ip.daddr = *(std::uint32_t*) dhcp_server_ip; |
| frame.ip.tot_len = htobe16(sizeof(frame.ip) + sizeof(frame.udp) |
| + sizeof(frame.dhcp)); |
| iphdr ip_copy = frame.ip; // original, it's in a packed struct |
| frame.ip.check = ip_checksum((unsigned short*) &ip_copy, |
| sizeof(ip_copy) / sizeof(short)); |
| |
| frame.udp.source = htobe16(68); |
| frame.udp.dest = htobe16(67); |
| frame.udp.len = htobe16(sizeof(frame.udp) + sizeof(frame.dhcp)); |
| |
| frame.dhcp.op = 1; /* bootrequest */ |
| frame.dhcp.htype = 1; // Ethernet |
| frame.dhcp.hlen = 6; /* mac address length */ |
| frame.dhcp.xid = rand(); |
| frame.dhcp.secs = htobe16(3); |
| frame.dhcp.flags = 0; |
| memcpy(frame.dhcp.client_ip, ip_address, 4); |
| memcpy(frame.dhcp.client_harware_address, mac_address, 6); |
| std::uint8_t magic_cookie[4] = {99, 130, 83, 99}; |
| memcpy(frame.dhcp.magic_cookie, magic_cookie, sizeof(magic_cookie)); |
| frame.dhcp.message_type = { .code = 53, .len = 1, .message_type = 7 }; |
| frame.dhcp.server_identifier.code = 54; |
| frame.dhcp.server_identifier.len = 4; |
| memcpy(frame.dhcp.server_identifier.server_ip, dhcp_server_ip, 4); |
| frame.dhcp.end_code = 255; |
| |
| if (tap->Write((void*) &frame, sizeof(frame)) != sizeof(frame)) { |
| LOG(ERROR) << "Could not write dhcprelease frame: \"" << tap->StrError() |
| << "\". Connecting to wifi will likely not work."; |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace cuttlefish |