#include "protoDebug.h"
#include "protoDetour.h"
#include "protoCap.h"
#include "protoNet.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/netfilter.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <fcntl.h>
#include <linux/if_ether.h>
#include <net/if_arp.h>
#include <linux/version.h>
class LinuxDetour : public ProtoDetour
{
public:
LinuxDetour();
~LinuxDetour();
bool Open(int hookFlags = 0,
const ProtoAddress& srcFilterAddr = PROTO_ADDR_NONE,
unsigned int srcFilterMask = 0,
const ProtoAddress& dstFilterAddr = PROTO_ADDR_NONE,
unsigned int dstFilterMask = 0,
int dscpValue = -1);
void Close();
virtual bool Recv(char* buffer,
unsigned int& numBytes,
Direction* direction = NULL, ProtoAddress* srcMac = NULL, unsigned int* ifIndex = NULL);
bool Allow(const char* buffer, unsigned int numBytes);
bool Drop();
bool Inject(const char* buffer, unsigned int numBytes);
virtual bool SetMulticastInterface(const char* interfaceName);
private:
enum Action
{
INSTALL,
DELETE
};
UINT32 JenkinsHash(UINT32 value);
bool SetIPTables(UINT16 nfqNum,
Action action,
int hookFlags ,
const ProtoAddress& srcFilterAddr,
unsigned int srcFilterMask,
const ProtoAddress& dstFilterAddr,
unsigned int dstFilterMask,
int dscpValue);
int raw_fd; int hook_flags;
ProtoAddress src_filter_addr;
unsigned int src_filter_mask;
ProtoAddress dst_filter_addr;
unsigned int dst_filter_mask;
int dscp_value;
enum {NFQ_BUFFER_SIZE = 8192};
static int NfqCallback(nfq_q_handle* nfqQueue,
struct nfgenmsg* nfqMsg,
nfq_data* nfqData,
void* userData);
static bool nfq_num_init;
static UINT16 nfq_num_next;
struct nfq_handle* nfq_handle;
struct nfq_q_handle* nfq_queue;
UINT16 nfq_num;
UINT32 nfq_pkt_id;
char* nfq_pkt_data;
unsigned int nfq_pkt_len;
Direction nfq_direction;
ProtoAddress nfq_src_macaddr;
unsigned int nfq_ifindex;
};
ProtoDetour* ProtoDetour::Create()
{
return static_cast<ProtoDetour*>(new LinuxDetour());
}
bool LinuxDetour::nfq_num_init = true;
UINT16 LinuxDetour::nfq_num_next = 0;
LinuxDetour::LinuxDetour()
: raw_fd(-1), hook_flags(0), dscp_value(-1),
nfq_handle(NULL), nfq_queue(NULL),
nfq_pkt_id(0), nfq_pkt_data(NULL), nfq_pkt_len(0),
nfq_direction(UNSPECIFIED), nfq_ifindex(0)
{
if (nfq_num_init)
{
char* cp = getenv("PROTO_NFQ_NUM_BASE");
if (NULL != cp)
nfq_num_next = (UINT16)atoi(cp);
else
nfq_num_next = (UINT16)JenkinsHash(getpid()); nfq_num_init = false;
}
}
LinuxDetour::~LinuxDetour()
{
Close();
}
UINT32 LinuxDetour::JenkinsHash(UINT32 a)
{
a = (a+0x7ed55d16) + (a<<12);
a = (a^0xc761c23c) ^ (a>>19);
a = (a+0x165667b1) + (a<<5);
a = (a+0xd3a2646c) ^ (a<<9);
a = (a+0xfd7046c5) + (a<<3);
a = (a^0xb55a4f09) ^ (a>>16);
return a;
}
bool LinuxDetour::SetIPTables(UINT16 nfqNum,
Action action,
int hookFlags ,
const ProtoAddress& srcFilterAddr,
unsigned int srcFilterMask,
const ProtoAddress& dstFilterAddr,
unsigned int dstFilterMask,
int dscpValue)
{
const char* cmd;
if (srcFilterAddr.GetType() != dstFilterAddr.GetType())
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: inconsistent src/dst filter addr families\n");
return false;
}
else if (ProtoAddress::IPv4 == srcFilterAddr.GetType())
{
cmd = "/sbin/iptables -t mangle"; }
else if (ProtoAddress::IPv6 == srcFilterAddr.GetType())
{
cmd = "/sbin/ip6tables -t mangle";
}
else
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: unspecified filter addr family\n");
return false;
}
const char* mode = (INSTALL == action) ? "-A" : "-D";
while (0 != hookFlags)
{
const char* target;
if (0 != (hookFlags & OUTPUT))
{
target = "OUTPUT";
hookFlags &= ~OUTPUT;
}
else if (0 != (hookFlags & INPUT))
{
target = "PREROUTING"; hookFlags &= ~INPUT;
}
else if (0 != (hookFlags & FORWARD))
{
target = "FORWARD";
hookFlags &= ~FORWARD;
}
else
{
break; }
const size_t RULE_MAX = 511;
char rule[RULE_MAX+1];
sprintf(rule, "%s %s %s -j NFQUEUE --queue-num %hu ", cmd, mode, target, nfqNum);
if (0 != srcFilterMask)
{
strcat(rule, "-s ");
size_t len = strlen(rule);
if (!srcFilterAddr.GetHostString(rule+len, RULE_MAX - len))
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: bad source addr filter\n");
return false;
}
len = strlen(rule);
sprintf(rule+len, "/%hu ", srcFilterMask);
}
if (0 != dstFilterMask)
{
strcat(rule, "-d ");
size_t len = strlen(rule);
if (!dstFilterAddr.GetHostString(rule+len, RULE_MAX - len))
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: bad destination addr filter\n");
return false;
}
len = strlen(rule);
sprintf(rule+len, "/%hu ", dstFilterMask);
}
if (dscpValue >= 0)
{
size_t len = strlen(rule);
sprintf(rule+len, "-m dscp --dscp %d ", dscpValue);
}
strcat(rule, " 2>&1");
FILE* p = popen(rule, "r");
if (NULL != p)
{
char errorMsg[256];
int result = fread(errorMsg, 1, 256, p);
if ((0 == result) && (0 != ferror(p)))
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() fread() error: %s\n",
GetErrorString());
return false;
}
char* ptr = strchr(errorMsg, '\n');
if (NULL != ptr) *ptr = '\0';
errorMsg[255] = '\0';
if (0 != pclose(p))
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() \"%s\" error: %s\n",
rule, errorMsg);
return false;
}
}
else
{
PLOG(PL_ERROR, "LinuxDetour::SetIPTables() error: popen(%s): %s\n",
rule, GetErrorString());
return false;
}
} return true;
}
bool LinuxDetour::Open(int hookFlags,
const ProtoAddress& srcFilterAddr,
unsigned int srcFilterMask,
const ProtoAddress& dstFilterAddr,
unsigned int dstFilterMask,
int dscpValue)
{
if (IsOpen()) Close();
if (0 > (raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))) {
PLOG(PL_ERROR, "LinuxDetour::Open() socket(IPPROTO_RAW) error: %s\n",
GetErrorString());
return false;
}
{
int enable = 1;
if (setsockopt(raw_fd, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable)))
{
PLOG(PL_ERROR, "LinuxDetour::Open() setsockopt(IP_HDRINCL) error: %s\n",
GetErrorString());
Close();
return false;
}
}
if(-1 == fcntl(raw_fd, F_SETFL, fcntl(raw_fd, F_GETFL, 0) | O_NONBLOCK))
{
PLOG(PL_ERROR, "LinuxDetour::Open() fcntl(F_SETFL(O_NONBLOCK)) error: %s\n", GetErrorString());
Close();
return false;
}
if (0 != (hookFlags & INJECT))
{
descriptor = raw_fd;
raw_fd = -1;
ProtoDetour::Open();
StopInputNotification();
return true;
}
int addrFamily;
if (srcFilterAddr.GetType() != dstFilterAddr.GetType())
{
PLOG(PL_ERROR, "LinuxDetour::Open() error: inconsistent src/dst filter addr families\n");
Close();
return false;
}
else if (ProtoAddress::IPv4 == srcFilterAddr.GetType())
{
addrFamily = AF_INET;
}
else if (ProtoAddress::IPv6 == srcFilterAddr.GetType())
{
addrFamily = AF_INET6;
}
else
{
PLOG(PL_ERROR, "LinuxDetour::Open() error: unspecified filter addr family\n");
Close();
return false;
}
FILE* p = popen("find /lib/modules/$(uname -r) -iname nfnetlink_queue.ko\\* 2>&1", "r");
if (NULL != p)
{
char errorMsg[256];
int result = fread(errorMsg, 1, 256, p);
if ((0 == result) && (0 != ferror(p)))
{
PLOG(PL_ERROR, "LinuxDetour::Open() fread() error: %s\n",
GetErrorString());
return false;
}
errorMsg[255] = '\0';
if (0 != pclose(p))
PLOG(PL_ERROR, "LinuxDetour::Open() warning: \"/sbin/modprobe -l nfnetlink_queue\" error: %s",
errorMsg);
}
else
{
PLOG(PL_ERROR, "LinuxDetour::Open() warning: popen(\"/sbin/modprobe ...\") error: %s\n",
GetErrorString());
}
nfq_num = nfq_num_next++;
hook_flags = hookFlags;
src_filter_addr = srcFilterAddr;
src_filter_mask = srcFilterMask;
dst_filter_addr = dstFilterAddr;
dst_filter_mask = dstFilterMask;
dscp_value = dscpValue;
if (0 != hookFlags)
{
if (!SetIPTables(nfq_num, INSTALL, hookFlags,
srcFilterAddr, srcFilterMask,
dstFilterAddr, dstFilterMask,
dscpValue))
{
PLOG(PL_ERROR, "LinuxDetour::Open() error: couldn't install firewall rules\n");
Close();
return false;
}
}
if (NULL == (nfq_handle = nfq_open()))
{
PLOG(PL_ERROR, "LinuxDetour::Open() nfq_open() error: %s\n", GetErrorString());
Close();
return false;
}
descriptor = nfq_fd(nfq_handle);
if (nfq_unbind_pf(nfq_handle, addrFamily) < 0)
{
PLOG(PL_ERROR, "LinuxDetour::Open() warning: nfq_unbind_pf() error: %s\n", GetErrorString());
}
if (nfq_bind_pf(nfq_handle, addrFamily) < 0)
{
PLOG(PL_ERROR, "LinuxDetour::Open() nfq_bind_pf() error: %s\n", GetErrorString());
Close();
return false;
}
if (NULL == (nfq_queue = nfq_create_queue(nfq_handle, nfq_num, NfqCallback, this)))
{
PLOG(PL_ERROR, "LinuxDetour::Open() nfq_create_queue() error: %s\n", GetErrorString());
Close();
return false;
}
if (nfq_set_mode(nfq_queue, NFQNL_COPY_PACKET, NFQ_BUFFER_SIZE) < 0)
{
PLOG(PL_ERROR, "LinuxDetour::Open() nfq_set_mode() error: %s\n", GetErrorString());
Close();
return false;
}
if (!ProtoDetour::Open())
{
PLOG(PL_ERROR, "LinuxDetour::Open() ProtoDetour::Open() error\n");
Close();
return false;
}
return true;
}
void LinuxDetour::Close()
{
if (raw_fd >= 0)
{
close(raw_fd);
raw_fd = -1;
}
if (0 != hook_flags)
{
SetIPTables(nfq_num, DELETE, hook_flags,
src_filter_addr, src_filter_mask,
dst_filter_addr, dst_filter_mask, dscp_value);
hook_flags = 0;
}
if (descriptor >= 0)
{
ProtoDetour::Close();
descriptor = INVALID_HANDLE;
}
if (NULL != nfq_queue)
{
nfq_destroy_queue(nfq_queue);
nfq_queue = NULL;
}
if (NULL != nfq_handle)
{
nfq_close(nfq_handle);
nfq_handle = NULL;
}
}
bool LinuxDetour::Recv(char* buffer,
unsigned int& numBytes,
Direction* direction,
ProtoAddress* srcMac,
unsigned int* ifIndex)
{
if (NULL != nfq_pkt_data)
{
PLOG(PL_ERROR, "LinuxDetour::Recv() error: existing packet pending allow/drop!\n");
return false;
}
char nfqBuffer[NFQ_BUFFER_SIZE];
int result = recv(descriptor, nfqBuffer, NFQ_BUFFER_SIZE, 0);
if (result < 0)
{
numBytes = 0;
if (NULL != direction) *direction = UNSPECIFIED;
if (NULL != srcMac) srcMac->Invalidate();
if (NULL != ifIndex) *ifIndex = 0;
if ((EAGAIN != errno) && (EINTR != errno))
{
PLOG(PL_ERROR, "LinuxDetour::Recv() recv() error: %s\n", GetErrorString());
return false;
}
}
else
{
nfq_handle_packet(nfq_handle, nfqBuffer, result);
if (NULL != nfq_pkt_data)
{
if (NULL != direction) *direction = nfq_direction;
if (NULL != srcMac) *srcMac = nfq_src_macaddr;
if (NULL != ifIndex) *ifIndex = nfq_ifindex;
if (numBytes < nfq_pkt_len)
memcpy(buffer, nfq_pkt_data, numBytes);
else
memcpy(buffer, nfq_pkt_data, nfq_pkt_len);
numBytes = nfq_pkt_len;
}
else
{
numBytes = 0;
if (NULL != direction) *direction = UNSPECIFIED;
if (NULL != srcMac) srcMac->Invalidate();
if (NULL != ifIndex) *ifIndex = 0;
}
}
return true;
}
int LinuxDetour::NfqCallback(nfq_q_handle* nfqQueue,
struct nfgenmsg* nfqMsg,
nfq_data* nfqData,
void* userData)
{
ASSERT(NULL != userData);
LinuxDetour* linuxDetour = reinterpret_cast<LinuxDetour*>(userData);
linuxDetour->nfq_direction = UNSPECIFIED;
struct nfqnl_msg_packet_hdr* header = nfq_get_msg_packet_hdr(nfqData);
if(NULL != header)
{
linuxDetour->nfq_pkt_id = ntohl(header->packet_id);
if (ProtoAddress::IPv4 == linuxDetour->src_filter_addr.GetType())
{
if (NF_IP_LOCAL_OUT == header->hook)
linuxDetour->nfq_direction = OUTBOUND;
else
linuxDetour->nfq_direction = INBOUND;
}
else {
if (NF_IP_LOCAL_OUT == header->hook)
linuxDetour->nfq_direction = OUTBOUND;
else
linuxDetour->nfq_direction = INBOUND;
}
}
struct nfqnl_msg_packet_hw* hw = nfq_get_packet_hw(nfqData);
if (NULL != hw)
linuxDetour->nfq_src_macaddr.SetRawHostAddress(ProtoAddress::ETH, (char*)hw->hw_addr, (UINT8)ntohs(hw->hw_addrlen));
else
linuxDetour->nfq_src_macaddr.Invalidate();
if (OUTBOUND == linuxDetour->nfq_direction)
linuxDetour->nfq_ifindex = nfq_get_outdev(nfqData);
else if (INBOUND == linuxDetour->nfq_direction)
linuxDetour->nfq_ifindex = nfq_get_indev(nfqData);
else if (0 != (linuxDetour->nfq_ifindex = nfq_get_indev(nfqData)))
linuxDetour->nfq_direction = INBOUND;
else if (0 != (linuxDetour->nfq_ifindex = nfq_get_outdev(nfqData)))
linuxDetour->nfq_direction = INBOUND;
#ifndef LINUX_VERSION_MAJOR
#define LINUX_VERSION_MAJOR (LINUX_VERSION_CODE/65536)
#endif #define LINUX_VERSION_MINOR ((LINUX_VERSION_CODE - (LINUX_VERSION_MAJOR*65536)) / 256)
#if ((LINUX_VERSION_MAJOR > 3) || ((LINUX_VERSION_MAJOR == 3) && (LINUX_VERSION_MINOR > 5)))
linuxDetour->nfq_pkt_len = nfq_get_payload(nfqData, (unsigned char**)(&linuxDetour->nfq_pkt_data));
#else
linuxDetour->nfq_pkt_len = nfq_get_payload(nfqData, &linuxDetour->nfq_pkt_data);
#endif return 0;
}
bool LinuxDetour::Allow(const char* buffer, unsigned int numBytes)
{
if (NULL == nfq_queue)
{
PLOG(PL_ERROR, "LinuxDetour::Allow() error: not opened\n");
return false;
}
if (NULL == nfq_pkt_data)
{
PLOG(PL_ERROR, "LinuxDetour::Allow() error: no pending packet\n");
return false;
}
if (0 > nfq_set_verdict(nfq_queue, nfq_pkt_id, NF_ACCEPT, numBytes, (unsigned char*)buffer))
{
PLOG(PL_ERROR, "LinuxDetour::Allow() nfq_set_verdict() error: %s\n",
GetErrorString());
return false;
}
nfq_pkt_data = NULL; return true;
}
bool LinuxDetour::Drop()
{
if (NULL == nfq_queue)
{
PLOG(PL_ERROR, "LinuxDetour::Drop() error: not opened\n");
return false;
}
if (NULL == nfq_pkt_data)
{
PLOG(PL_ERROR, "LinuxDetour::Drop() error: no pending packet\n");
return false;
}
if (0 > nfq_set_verdict(nfq_queue, nfq_pkt_id, NF_DROP, 0, NULL))
{
PLOG(PL_ERROR, "LinuxDetour::Drop() nfq_set_verdict() error: %s\n",
GetErrorString());
return false;
}
nfq_pkt_data = NULL; return true;
}
bool LinuxDetour::Inject(const char* buffer, unsigned int numBytes)
{
unsigned char version = buffer[0];
version >>= 4;
ProtoAddress dstAddr;
socklen_t addrLen;
if (4 == version)
{
dstAddr.SetRawHostAddress(ProtoAddress::IPv4, buffer+16, 4);
addrLen = sizeof(struct sockaddr);
}
else if (6 == version)
{
PLOG(PL_ERROR, "LinuxDetour::Inject() IPv6 injection not yet supported!\n");
return false;
}
else
{
PLOG(PL_ERROR, "LinuxDetour::Inject() unknown IP version!\n");
return false;
}
int fd = (raw_fd < 0) ? descriptor : raw_fd;
size_t result = sendto(fd, buffer, numBytes, 0,
&dstAddr.GetSockAddr(), addrLen);
if (result != numBytes)
{
PLOG(PL_ERROR, "LinuxDetour::Inject() sendto() error: %s\n", GetErrorString());
return false;
}
return true;
}
bool LinuxDetour::SetMulticastInterface(const char* interfaceName)
{
int fd = (raw_fd < 0) ? descriptor : raw_fd;
if (fd < 0)
{
PLOG(PL_ERROR, "LinuxDetour::SetMulticastInterface() error: detour not open\n");
return false;
}
if (interfaceName)
{
int result;
#ifdef HAVE_IPV6
if (ProtoAddress::IPv6 == src_filter_addr.GetType())
{
unsigned int interfaceIndex = ProtoNet::GetInterfaceIndex(interfaceName);
result = setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
(char*)&interfaceIndex, sizeof(interfaceIndex));
}
else
#endif {
struct in_addr localAddr;
ProtoAddress interfaceAddress;
if (ProtoNet::GetInterfaceAddress(interfaceName, ProtoAddress::IPv4, interfaceAddress))
{
localAddr.s_addr = htonl(interfaceAddress.IPv4GetAddress());
}
else
{
PLOG(PL_ERROR, "LinuxDetour::SetMulticastInterface() invalid interface name\n");
return false;
}
result = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (char*)&localAddr,
sizeof(localAddr));
}
if (result < 0)
{
PLOG(PL_ERROR, "LinuxDetour: setsockopt(IP_MULTICAST_IF) error: %s\n",
GetErrorString());
return false;
}
}
return true;
}