blob: cb19ff7d4391ed17716482e7417cd6eda058886d [file] [log] [blame]
/*
* Copyright (C) 2017 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 "proxy.h"
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_packet.h>
#include <poll.h>
#include <signal.h>
#include <cutils/properties.h>
#include "log.h"
#include "message.h"
#include "packet.h"
#include "result.h"
// The prefix length for an address of a single unique node
static const uint8_t kNodePrefixLength = 128;
static const size_t kLinkAddressSize = 6;
static const size_t kRecursiveDnsOptHeaderSize = 8;
// Rewrite the link address of a neighbor discovery option to the link address
// of |interface|. This can be either a source or target link address as
// specified by |optionType|. The valid values are ND_OPT_TARGET_LINKADDR and
// ND_OPT_SOURCE_LINKADDR. This will modify the message data inside |packet|.
static void rewriteLinkAddressOption(Packet& packet,
const Interface& interface,
int optionType) {
for (nd_opt_hdr* opt = packet.firstOpt(); opt; opt = packet.nextOpt(opt)) {
if (opt->nd_opt_type == optionType) {
auto src = interface.linkAddr().get<sockaddr_ll>();
auto dest = reinterpret_cast<char*>(opt) + sizeof(nd_opt_hdr);
memcpy(dest, src->sll_addr, kLinkAddressSize);
}
}
}
static void extractRecursiveDnsServers(Packet& packet) {
for (nd_opt_hdr* opt = packet.firstOpt(); opt; opt = packet.nextOpt(opt)) {
if (opt->nd_opt_type != 25 || opt->nd_opt_len < 1) {
// Not a RNDSS option, skip it
continue;
}
size_t numEntries = (opt->nd_opt_len - 1) / 2;
//Found number of entries, dump each address
const char* option = reinterpret_cast<const char*>(opt);
option += kRecursiveDnsOptHeaderSize;
auto dnsServers = reinterpret_cast<const struct in6_addr*>(option);
std::vector<std::string> validServers;
for (size_t i = 0; i < numEntries; ++i) {
char buffer[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &dnsServers[i], buffer, sizeof(buffer))) {
validServers.push_back(buffer);
} else {
loge("Failed to convert RDNSS to string\n");
}
}
auto server = validServers.begin();
char propName[PROP_NAME_MAX];
char propValue[PROP_VALUE_MAX];
for (int i = 1; i <= 4; ++i) {
snprintf(propName, sizeof(propName), "net.eth0.ipv6dns%d", i);
if (server != validServers.end()) {
property_set(propName, server->c_str());
++server;
} else {
// Clear the property if it's no longer a valid server, don't
// want to leave old servers around
property_set(propName, "");
}
}
}
}
int Proxy::run() {
sigset_t blockMask, originalMask;
int status = ::sigfillset(&blockMask);
if (status != 0) {
loge("Unable to fill signal set: %s\n", strerror(errno));
return 1;
}
status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask);
if (status != 0) {
loge("Unable to set signal mask: %s\n", strerror(errno));
return 1;
}
// Init outer interface and router
if (!mOuterIf.init() || !mRouter.init()) {
return 1;
}
// Init all inner interfaces
for (size_t i = 0; i < mInnerIfs.size(); ++i) {
if (!mInnerIfs[i].init()) {
return 1;
}
}
// Create list of FDs to poll, we're only looking for input (POLLIN)
std::vector<pollfd> fds(mInnerIfs.size() + 1);
fds[0].fd = mOuterIf.ipSocket().get();
fds[0].events = POLLIN;
for (size_t i = 0; i < mInnerIfs.size(); ++i) {
fds[i + 1].fd = mInnerIfs[i].ipSocket().get();
fds[i + 1].events = POLLIN;
}
Message message;
while (status >= 0) {
status = ::ppoll(fds.data(), fds.size(), nullptr, &originalMask);
if (status > 0) {
// Something available to read
for (const struct pollfd& fd : fds) {
if (receiveIfPossible(fd, mOuterIf.ipSocket(), &message)) {
// Received a message on the outer interface
handleOuterMessage(message);
} else {
for (auto& inner : mInnerIfs) {
if (receiveIfPossible(fd, inner.ipSocket(), &message)) {
// Received a message on the inner interface
handleInnerMessage(inner, message);
}
}
}
}
}
}
loge("Polling failed: %s\n", strerror(errno));
return 1;
}
bool Proxy::receiveIfPossible(const pollfd& fd,
Socket& socket,
Message* message) {
// Check if it's actually the socket we're interested in
if (fd.fd != socket.get()) {
return false;
}
// Check if there is something to read on this socket
if ((fd.revents & POLLIN) == 0) {
return false;
}
// Receive the message and place the data in the message parameter
Result res = socket.receive(message);
if (!res) {
loge("Error receiving on socket: %s\n", res.c_str());
return false;
}
return true;
}
void Proxy::handleOuterMessage(Message& message) {
Packet packet(message);
uint32_t options = kForwardOnly;
switch (packet.type()) {
case Packet::Type::RouterAdvertisement:
extractRecursiveDnsServers(packet);
options = kRewriteSourceLink | kSetDefaultGateway;
break;
case Packet::Type::NeighborSolicitation:
options = kSpoofSource;
break;
case Packet::Type::NeighborAdvertisement:
options = kRewriteTargetLink;
break;
default:
return;
}
for (auto& inner : mInnerIfs) {
forward(mOuterIf, inner, packet, options);
}
}
void Proxy::handleInnerMessage(const Interface& inner, Message& message) {
Packet packet(message);
uint32_t options = kForwardOnly;
switch (packet.type()) {
case Packet::Type::RouterSolicitation:
options = kSpoofSource;
break;
case Packet::Type::NeighborSolicitation:
options = kSpoofSource | kAddRoute;
break;
case Packet::Type::NeighborAdvertisement:
options = kRewriteTargetLink | kSpoofSource | kAddRoute;
break;
default:
return;
}
forward(inner, mOuterIf, packet, options);
}
void Proxy::forward(const Interface& from,
Interface& to,
Packet& packet,
uint32_t options) {
if (mLogDebug) {
logd("Forwarding %s from %s/%s to %s/%s\n",
packet.description().c_str(),
from.name().c_str(), addrToStr(packet.ip()->ip6_src).c_str(),
to.name().c_str(), addrToStr(packet.ip()->ip6_dst).c_str());
}
if (options & kRewriteTargetLink) {
rewriteLinkAddressOption(packet, to, ND_OPT_TARGET_LINKADDR);
}
if (options & kRewriteSourceLink) {
rewriteLinkAddressOption(packet, to, ND_OPT_SOURCE_LINKADDR);
}
Result res = Result::success();
if (options & kSpoofSource) {
// Spoof the source of the packet so that it appears to originate from
// the same source that we see.
res = to.icmpSocket().sendFrom(packet.ip()->ip6_src,
packet.ip()->ip6_dst,
packet.icmp(),
packet.icmpSize());
} else {
res = to.icmpSocket().sendTo(packet.ip()->ip6_dst,
packet.icmp(),
packet.icmpSize());
}
if (!res) {
loge("Failed to forward %s from %s to %s: %s\n",
packet.description().c_str(),
from.name().c_str(), to.name().c_str(),
res.c_str());
}
if (options & kAddRoute) {
mRouter.addRoute(packet.ip()->ip6_src, kNodePrefixLength, from.index());
}
if (packet.type() == Packet::Type::RouterAdvertisement &&
options & kSetDefaultGateway) {
// Set the default gateway from this router advertisement. This is
// needed so that packets that are forwarded as a result of proxying
// actually have somewhere to go.
if (!mRouter.setDefaultGateway(packet.ip()->ip6_src, from.index())) {
loge("Failed to set default gateway %s\n",
addrToStr(packet.ip()->ip6_src).c_str());
}
}
}