blob: 9dd113b8ddf0d99021bf15078dc76eb600331c38 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/shims/netfilter_queue_processor.h"
#include <arpa/inet.h>
#include <errno.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <linux/ip.h>
#include <linux/netfilter.h> /* for NF_ACCEPT */
#include <linux/types.h>
#include <linux/udp.h>
#include <net/if.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <deque>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
using std::deque;
namespace shill {
namespace shims {
// static
const int NetfilterQueueProcessor::kBufferSize = 4096;
const int NetfilterQueueProcessor::kExpirationIntervalSeconds = 5;
const int NetfilterQueueProcessor::kIPHeaderLengthUnitBytes = 4;
const int NetfilterQueueProcessor::kMaxIPHeaderLength =
16; // ihl is a 4-bit field.
const size_t NetfilterQueueProcessor::kMaxListenerEntries = 32;
const int NetfilterQueueProcessor::kPayloadCopySize = 0xffff;
NetfilterQueueProcessor::Packet::Packet()
: packet_id_(0),
in_device_(0),
out_device_(0),
is_udp_(false),
source_ip_(INADDR_ANY),
destination_ip_(INADDR_ANY),
source_port_(0),
destination_port_(0) {}
NetfilterQueueProcessor::Packet::~Packet() = default;
std::string NetfilterQueueProcessor::AddressAndPortToString(uint32_t ip,
uint16_t port) {
struct in_addr addr;
addr.s_addr = htonl(ip);
return base::StringPrintf("%s:%d", inet_ntoa(addr), port);
}
bool NetfilterQueueProcessor::Packet::ParseNetfilterData(
struct nfq_data* netfilter_data) {
struct nfqnl_msg_packet_hdr* packet_header =
nfq_get_msg_packet_hdr(netfilter_data);
if (!packet_header) {
return false;
}
packet_id_ = ntohl(packet_header->packet_id);
in_device_ = nfq_get_indev(netfilter_data);
out_device_ = nfq_get_outdev(netfilter_data);
unsigned char* payload;
int payload_len = nfq_get_payload(netfilter_data, &payload);
if (payload_len >= 0) {
is_udp_ = ParsePayloadUDPData(payload, payload_len);
}
return true;
}
bool NetfilterQueueProcessor::Packet::ParsePayloadUDPData(
const unsigned char* payload, size_t payload_len) {
struct iphdr ip;
if (payload_len <= sizeof(ip)) {
return false;
}
memcpy(&ip, payload, sizeof(ip));
size_t iphdr_len = ip.ihl * kIPHeaderLengthUnitBytes;
if (iphdr_len < sizeof(ip) || ip.version != IPVERSION ||
ip.protocol != IPPROTO_UDP) {
return false;
}
struct udphdr udp;
if (payload_len < iphdr_len + sizeof(udp)) {
return false;
}
memcpy(&udp, payload + iphdr_len, sizeof(udp));
source_ip_ = ntohl(ip.saddr);
destination_ip_ = ntohl(ip.daddr);
source_port_ = ntohs(udp.source);
destination_port_ = ntohs(udp.dest);
return true;
}
void NetfilterQueueProcessor::Packet::SetValues(int in_device,
int out_device,
bool is_udp,
uint32_t packet_id,
uint32_t source_ip,
uint32_t destination_ip,
uint16_t source_port,
uint16_t destination_port) {
in_device_ = in_device;
out_device_ = out_device;
is_udp_ = is_udp;
packet_id_ = packet_id;
source_ip_ = source_ip;
destination_ip_ = destination_ip;
source_port_ = source_port;
destination_port_ = destination_port;
}
NetfilterQueueProcessor::NetfilterQueueProcessor(int input_queue,
int output_queue)
: input_queue_(input_queue),
output_queue_(output_queue),
nfq_handle_(nullptr),
input_queue_handle_(nullptr),
output_queue_handle_(nullptr) {
VLOG(2) << "Created netfilter queue processor.";
}
NetfilterQueueProcessor::~NetfilterQueueProcessor() {
Stop();
}
void NetfilterQueueProcessor::Run() {
LOG(INFO) << "Netfilter queue processor running.";
CHECK(nfq_handle_);
int file_handle = nfq_fd(nfq_handle_);
char buffer[kBufferSize] __attribute__((aligned));
for (;;) {
int receive_count = recv(file_handle, buffer, sizeof(buffer), 0);
if (receive_count <= 0) {
if (receive_count < 0 && errno == ENOBUFS) {
LOG(WARNING) << "Packets dropped in the queue.";
continue;
}
LOG(ERROR) << "Receive failed; exiting";
break;
}
nfq_handle_packet(nfq_handle_, buffer, receive_count);
}
}
bool NetfilterQueueProcessor::Start() {
VLOG(2) << "Netfilter queue processor starting.";
if (!nfq_handle_) {
nfq_handle_ = nfq_open();
if (!nfq_handle_) {
LOG(ERROR) << "nfq_open() returned an error";
return false;
}
}
if (nfq_unbind_pf(nfq_handle_, AF_INET) < 0) {
LOG(ERROR) << "nfq_unbind_pf() returned an error";
return false;
}
if (nfq_bind_pf(nfq_handle_, AF_INET) < 0) {
LOG(ERROR) << "nfq_bind_pf() returned an error";
return false;
}
input_queue_handle_ =
nfq_create_queue(nfq_handle_, input_queue_,
&NetfilterQueueProcessor::InputQueueCallback, this);
if (!input_queue_handle_) {
LOG(ERROR) << "nfq_create_queue() failed for input queue " << input_queue_;
return false;
}
if (nfq_set_mode(input_queue_handle_, NFQNL_COPY_PACKET, kPayloadCopySize) <
0) {
LOG(ERROR) << "nfq_set_mode() failed: can't set input queue packet_copy.";
return false;
}
output_queue_handle_ =
nfq_create_queue(nfq_handle_, output_queue_,
&NetfilterQueueProcessor::OutputQueueCallback, this);
if (!output_queue_handle_) {
LOG(ERROR) << "nfq_create_queue() failed for output queue "
<< output_queue_;
return false;
}
if (nfq_set_mode(output_queue_handle_, NFQNL_COPY_PACKET, kPayloadCopySize) <
0) {
LOG(ERROR) << "nfq_set_mode() failed: can't set output queue packet_copy.";
return false;
}
return true;
}
void NetfilterQueueProcessor::Stop() {
if (input_queue_handle_) {
nfq_destroy_queue(input_queue_handle_);
input_queue_handle_ = nullptr;
}
if (output_queue_handle_) {
nfq_destroy_queue(output_queue_handle_);
output_queue_handle_ = nullptr;
}
if (nfq_handle_) {
nfq_close(nfq_handle_);
nfq_handle_ = nullptr;
}
}
// static
int NetfilterQueueProcessor::InputQueueCallback(
struct nfq_q_handle* queue_handle,
struct nfgenmsg* generic_message,
struct nfq_data* netfilter_data,
void* private_data) {
Packet packet;
if (!packet.ParseNetfilterData(netfilter_data)) {
LOG(FATAL) << "Unable to parse netfilter data.";
}
NetfilterQueueProcessor* processor =
reinterpret_cast<NetfilterQueueProcessor*>(private_data);
uint32_t verdict;
time_t now = time(nullptr);
if (processor->IsIncomingPacketAllowed(packet, now)) {
verdict = NF_ACCEPT;
} else {
verdict = NF_DROP;
}
return nfq_set_verdict(queue_handle, packet.packet_id(), verdict, 0, nullptr);
}
// static
int NetfilterQueueProcessor::OutputQueueCallback(
struct nfq_q_handle* queue_handle,
struct nfgenmsg* generic_message,
struct nfq_data* netfilter_data,
void* private_data) {
Packet packet;
if (!packet.ParseNetfilterData(netfilter_data)) {
LOG(FATAL) << "Unable to get parse netfilter data.";
}
NetfilterQueueProcessor* processor =
reinterpret_cast<NetfilterQueueProcessor*>(private_data);
time_t now = time(nullptr);
processor->LogOutgoingPacket(packet, now);
return nfq_set_verdict(queue_handle, packet.packet_id(), NF_ACCEPT, 0,
nullptr);
}
// static
uint32_t NetfilterQueueProcessor::GetNetmaskForDevice(int device_index) {
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
if (if_indextoname(device_index, ifr.ifr_name) != ifr.ifr_name) {
return INADDR_NONE;
}
int socket_fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (socket_fd < 0) {
return INADDR_NONE;
}
base::ScopedFD scoped_fd(socket_fd);
if (ioctl(socket_fd, SIOCGIFNETMASK, &ifr) != 0) {
return INADDR_NONE;
}
struct sockaddr_in* netmask_addr =
reinterpret_cast<struct sockaddr_in*>(&ifr.ifr_netmask);
return ntohl(netmask_addr->sin_addr.s_addr);
}
void NetfilterQueueProcessor::ExpireListeners(time_t now) {
time_t expiration_threshold = now - kExpirationIntervalSeconds;
VLOG(2) << __func__ << " entered.";
while (!listeners_.empty()) {
const ListenerEntryPtr& last_listener = listeners_.back();
if (last_listener->last_transmission >= expiration_threshold &&
listeners_.size() <= kMaxListenerEntries) {
break;
}
VLOG(2) << "Expired listener for "
<< AddressAndPortToString(last_listener->address,
last_listener->port);
listeners_.pop_back();
}
}
deque<NetfilterQueueProcessor::ListenerEntryPtr>::iterator
NetfilterQueueProcessor::FindListener(uint16_t port,
int device_index,
uint32_t address) {
deque<ListenerEntryPtr>::iterator it;
for (it = listeners_.begin(); it != listeners_.end(); ++it) {
if ((*it)->port == port && (*it)->device_index == device_index &&
(*it)->address == address) {
break;
}
}
return it;
}
deque<NetfilterQueueProcessor::ListenerEntryPtr>::iterator
NetfilterQueueProcessor::FindDestination(uint16_t port,
int device_index,
uint32_t destination) {
deque<ListenerEntryPtr>::iterator it;
for (it = listeners_.begin(); it != listeners_.end(); ++it) {
if ((*it)->port == port && (*it)->device_index == device_index &&
(*it)->destination == destination) {
break;
}
}
return it;
}
bool NetfilterQueueProcessor::IsIncomingPacketAllowed(const Packet& packet,
time_t now) {
VLOG(2) << __func__ << " entered.";
VLOG(3) << "Incoming packet is from "
<< AddressAndPortToString(packet.source_ip(), packet.source_port())
<< " and to "
<< AddressAndPortToString(packet.destination_ip(),
packet.destination_port());
if (!packet.is_udp()) {
VLOG(2) << "Incoming packet is not udp.";
return false;
}
ExpireListeners(now);
uint16_t port = packet.destination_port();
uint32_t address = packet.destination_ip();
int device_index = packet.in_device();
deque<ListenerEntryPtr>::iterator entry_ptr = listeners_.end();
if (IN_MULTICAST(address)) {
VLOG(2) << "Incoming packet is multicast.";
entry_ptr = FindDestination(port, device_index, address);
} else {
entry_ptr = FindListener(port, device_index, address);
}
if (entry_ptr == listeners_.end()) {
VLOG(2) << "Incoming does not match any listener.";
return false;
}
uint32_t netmask = (*entry_ptr)->netmask;
if ((packet.source_ip() & netmask) != ((*entry_ptr)->address & netmask)) {
VLOG(2) << "Incoming packet is from a non-local address.";
return false;
}
VLOG(3) << "Accepting packet.";
return true;
}
void NetfilterQueueProcessor::LogOutgoingPacket(const Packet& packet,
time_t now) {
VLOG(2) << __func__ << " entered.";
if (!packet.is_udp()) {
VLOG(2) << "Outgoing packet is not udp.";
return;
}
if (!IN_MULTICAST(packet.destination_ip())) {
VLOG(2) << "Outgoing packet is not multicast.";
return;
}
int device_index = packet.out_device();
if (device_index == 0) {
VLOG(2) << "Outgoing packet is not assigned a valid device.";
return;
}
uint16_t port = packet.source_port();
uint32_t address = packet.source_ip();
uint32_t destination = 0;
// Allow multicast replies if the destination port of the packet is the
// same as the port the sender transmitted from;
if (packet.source_port() == packet.destination_port()) {
destination = packet.destination_ip();
}
deque<ListenerEntryPtr>::iterator entry_it =
FindListener(port, device_index, address);
if (entry_it != listeners_.end()) {
if (entry_it != listeners_.begin()) {
// Make this the newest entry.
ListenerEntryPtr entry_ptr = *entry_it;
listeners_.erase(entry_it);
listeners_.push_front(entry_ptr);
entry_it = listeners_.begin();
}
(*entry_it)->last_transmission = now;
} else {
uint32_t netmask = GetNetmaskForDevice(device_index);
ListenerEntryPtr entry_ptr(new ListenerEntry(
now, port, device_index, address, netmask, destination));
listeners_.push_front(entry_ptr);
VLOG(2) << "Added listener for " << AddressAndPortToString(address, port)
<< " with destination "
<< AddressAndPortToString(destination, port);
}
// Perform expiration at the end, so that we don't end up expiring something
// just to resurrect it again.
ExpireListeners(now);
}
} // namespace shims
} // namespace shill