331 lines
11 KiB
C++
331 lines
11 KiB
C++
/*
|
|
* 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"
|
|
|
|
// Kernel headers don't mix well with userspace headers, but there is no
|
|
// userspace header that provides the if_tun.h #defines. Include the kernel
|
|
// header, but move conflicting definitions out of the way using macros.
|
|
#define ethhdr __kernel_ethhdr
|
|
#include <linux/if_tun.h>
|
|
#undef ethhdr
|
|
|
|
#include <endian.h>
|
|
#include <fcntl.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/types.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/udp.h>
|
|
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <ios>
|
|
#include <memory>
|
|
#include <ostream>
|
|
#include <set>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/strings.h>
|
|
|
|
#include "common/libs/fs/shared_buf.h"
|
|
#include "common/libs/utils/subprocess.h"
|
|
|
|
namespace cuttlefish {
|
|
namespace {
|
|
|
|
// 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;
|
|
}
|
|
|
|
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_str, stdout_str, stderr_str;
|
|
RunWithManagedStdio(std::move(cmd), &stdin_str, &stdout_str, &stderr_str);
|
|
auto lines = android::base::Split(stdout_str, "\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;
|
|
}
|
|
|
|
bool ReleaseDhcpLeases(const std::string& lease_path, SharedFD tap_fd,
|
|
const std::uint8_t dhcp_server_ip[4]) {
|
|
auto lease_file_fd = SharedFD::Open(lease_path, O_RDONLY);
|
|
if (!lease_file_fd->IsOpen()) {
|
|
LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
|
|
return false;
|
|
}
|
|
bool success = true;
|
|
auto dhcp_leases = ParseDnsmasqLeases(lease_file_fd);
|
|
for (auto& lease : dhcp_leases) {
|
|
if (!ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address,
|
|
dhcp_server_ip)) {
|
|
LOG(ERROR) << "Failed to release " << lease;
|
|
success = false;
|
|
} else {
|
|
LOG(INFO) << "Successfully dropped " << lease;
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
} // namespace cuttlefish
|