ostinato/server/bsdhostdevice.cpp
Srivats P 75c8f44079 Fix MacOS specific Hostdev bugs
* Fix SA_SIZE definition for MacOS
* Use sdl_index instead of rtm_index as the ifIndex
* Remove the embedded ifIndex in link local addresses
* Extract default IPv6 gateway correctly
2019-04-03 20:41:46 +05:30

489 lines
16 KiB
C++

/*
Copyright (C) 2018 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "bsdhostdevice.h"
#include "netdefs.h"
#include "packetbuffer.h"
#include <QHostAddress>
#ifdef Q_OS_BSD4
#include "../common/qtport.h"
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <pcap.h>
#include <unistd.h>
#ifndef SA_SIZE // For some reason MacOS doesn't define this while BSD does
// And the story of how to roundup is ugly - see
// https://github.com/FRRouting/frr/blob/master/zebra/kernel_socket.c
#ifdef __APPLE__
#define ROUNDUP_TYPE int
#else
#define ROUNDUP_TYPE long
#endif
#define SA_SIZE(sa) \
( (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \
sizeof(ROUNDUP_TYPE) : \
1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(ROUNDUP_TYPE) - 1) ) )
#endif
quint32 sumUInt128(UInt128 value);
BsdHostDevice::BsdHostDevice(QString portName,
DeviceManager *deviceManager)
: Device(deviceManager)
{
ifName_ = portName;
ifIndex_ = if_nametoindex(qPrintable(ifName_));
qDebug("Port %s: ifIndex %d", qPrintable(ifName_), ifIndex_);
rtSock_ = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
shutdown(rtSock_, SHUT_RD); // we don't read from rtSock
char errbuf[PCAP_ERRBUF_SIZE] = "";
txHandle_ = pcap_open_live(qPrintable(ifName_), 64, 0, 0, errbuf);
if (txHandle_ == NULL) {
qWarning("pcap open %s failed (%s)", qPrintable(ifName_), errbuf);
}
}
void BsdHostDevice::receivePacket(PacketBuffer* /*pktBuf*/)
{
// Do Nothing
}
void BsdHostDevice::clearNeighbors(Device::NeighborSet set)
{
// No need to do anything - see AbstractPort::resolveDeviceNeighbors()
// on when this is used
if (set == kUnresolvedNeighbors)
return;
size_t len;
int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, 0};
const int mibLen = sizeof(mib)/sizeof(mib[0]);
QByteArray buf;
#if defined(RTF_LLDATA)
mib[5] = RTF_LLDATA;
#else
mib[5] = RTF_LLINFO;
#endif
if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) { // find buffer len
qWarning("sysctl NET_RT_FLAGS(1) failed (%s)\n", strerror(errno));
return;
}
buf.resize(len);
if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) { // now retreive ARP/NDP
qWarning("sysctl NET_RT_FLAGS(2) failed(%s)\n", strerror(errno));
return;
}
int count=0, fail=0;
char *p = buf.data();
const char *end = p + len;
while (p < end)
{
struct rt_msghdr *rtm = (struct rt_msghdr*) p;
if ((rtm->rtm_index == ifIndex_) && !(rtm->rtm_flags & RTF_PINNED)) {
const struct sockaddr *sa = (const struct sockaddr*)(rtm + 1);
rtm->rtm_type = RTM_DELETE;
if (write(rtSock_, p, rtm->rtm_msglen) < 0) {
qWarning("RTM_DELETE failed for ip %s (%s)",
qPrintable(QHostAddress(sa).toString()) , strerror(errno));
fail++;
}
count++;
}
p += rtm->rtm_msglen;
}
qDebug("Flush ARP table for ifIndex %u: %d/%d deleted",
ifIndex_, count - fail, count);
// We need to query AF_INET and AF_INET6 separately as sysctl with AF_UNSPEC
// doesn't work
mib[3] = AF_INET6;
if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) { // find buffer len
qWarning("sysctl NET_RT_FLAGS(3) failed (%s)\n", strerror(errno));
return;
}
buf.resize(len);
if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) { // now retreive ARP/NDP
qWarning("sysctl NET_RT_FLAGS(4) failed(%s)\n", strerror(errno));
return;
}
count = fail = 0;
p = buf.data();
end = p + len;
while (p < end)
{
struct rt_msghdr *rtm = (struct rt_msghdr*) p;
if ((rtm->rtm_index == ifIndex_) && !(rtm->rtm_flags & RTF_PINNED)) {
const struct sockaddr *sa = (const struct sockaddr*)(rtm + 1);
rtm->rtm_type = RTM_DELETE;
if (write(rtSock_, p, rtm->rtm_msglen) < 0) {
qWarning("RTM_DELETE failed for ip %s (%s)",
qPrintable(QHostAddress(sa).toString()) , strerror(errno));
fail++;
}
count++;
}
p += rtm->rtm_msglen;
}
qDebug("Flush ND table for ifIndex %u: %d/%d deleted",
ifIndex_, count - fail, count);
}
void BsdHostDevice::getNeighbors(OstEmul::DeviceNeighborList *neighbors)
{
size_t len;
int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, 0};
const int mibLen = sizeof(mib)/sizeof(mib[0]);
QByteArray buf;
#if defined(RTF_LLDATA)
mib[5] = RTF_LLDATA;
#else
mib[5] = RTF_LLINFO;
#endif
if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) { // find buffer len
qWarning("sysctl NET_RT_FLAGS(1) failed (%s)\n", strerror(errno));
return;
}
buf.resize(len);
if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) { // now retreive ARP/NDP
qWarning("sysctl NET_RT_FLAGS(2) failed(%s)\n", strerror(errno));
return;
}
const char *p = buf.constData();
const char *end = p + len;
while (p < end)
{
const struct rt_msghdr *rtm = (const struct rt_msghdr*) p;
const struct sockaddr_in *sin = (const struct sockaddr_in*)(rtm + 1);
const struct sockaddr_dl *sdl = (const struct sockaddr_dl*)
((char*)sin + SA_SIZE(sin));
if (sdl->sdl_index == ifIndex_) {
OstEmul::ArpEntry *arp = neighbors->add_arp();
arp->set_ip4(qFromBigEndian<quint32>(sin->sin_addr.s_addr));
arp->set_mac(qFromBigEndian<quint64>(LLADDR(sdl)) >> 16);
}
p += rtm->rtm_msglen;
}
// We need to query AF_INET and AF_INET6 separately as sysctl with AF_UNSPEC
// doesn't work
mib[3] = AF_INET6;
if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) { // find buffer len
qWarning("sysctl NET_RT_FLAGS(1) failed (%s)\n", strerror(errno));
return;
}
buf.resize(len);
if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) { // now retreive ARP/NDP
qWarning("sysctl NET_RT_FLAGS(2) failed(%s)\n", strerror(errno));
return;
}
p = buf.constData();
end = p + len;
while (p < end)
{
const struct rt_msghdr *rtm = (const struct rt_msghdr*) p;
const struct sockaddr_in6 *sin = (const struct sockaddr_in6*)(rtm + 1);
const struct sockaddr_dl *sdl = (const struct sockaddr_dl*)
((char*)sin + SA_SIZE(sin));
if (sdl->sdl_index == ifIndex_) {
#ifdef __KAME__
if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)
|| IN6_IS_ADDR_MC_LINKLOCAL(&sin->sin6_addr)) {
// remove the embedded ifIndex in the 2nd hextet (u16)
const_cast<struct sockaddr_in6*>(sin)->sin6_addr.s6_addr[2] = 0;
const_cast<struct sockaddr_in6*>(sin)->sin6_addr.s6_addr[3] = 0;
#endif
}
OstEmul::NdpEntry *ndp = neighbors->add_ndp();
ndp->mutable_ip6()->set_hi(qFromBigEndian<quint64>(sin->sin6_addr.s6_addr));
ndp->mutable_ip6()->set_lo(qFromBigEndian<quint64>(sin->sin6_addr.s6_addr+8));
ndp->set_mac(qFromBigEndian<quint64>(LLADDR(sdl)) >> 16);
}
p += rtm->rtm_msglen;
}
}
quint64 BsdHostDevice::arpLookup(quint32 ip)
{
quint64 mac = 0;
size_t len;
int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, 0};
const int mibLen = sizeof(mib)/sizeof(mib[0]);
QByteArray buf;
#if defined(RTF_LLDATA)
mib[5] = RTF_LLDATA;
#else
mib[5] = RTF_LLINFO;
#endif
if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) { // find buffer len
qWarning("sysctl NET_RT_FLAGS(1) failed (%s)\n", strerror(errno));
return mac;
}
buf.resize(len);
if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) { // now retreive ARP/NDP
qWarning("sysctl NET_RT_FLAGS(2) failed(%s)\n", strerror(errno));
return mac;
}
const char *p = buf.constData();
const char *end = p + len;
while (p < end)
{
const struct rt_msghdr *rtm = (const struct rt_msghdr*) p;
if (rtm->rtm_index == ifIndex_) {
const struct sockaddr_in *sin = (const struct sockaddr_in*)(rtm + 1);
if (qFromBigEndian<quint32>(sin->sin_addr.s_addr) == ip) {
const struct sockaddr_dl *sdl = (const struct sockaddr_dl*)
((char*)sin + SA_SIZE(sin));
mac = qFromBigEndian<quint64>(LLADDR(sdl)) >> 16;
break;
}
}
p += rtm->rtm_msglen;
}
return mac;
}
quint64 BsdHostDevice::ndpLookup(UInt128 ip)
{
quint64 mac = 0;
size_t len;
int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, 0};
const int mibLen = sizeof(mib)/sizeof(mib[0]);
QByteArray buf;
#if defined(RTF_LLDATA)
mib[5] = RTF_LLDATA;
#else
mib[5] = RTF_LLINFO;
#endif
if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) { // find buffer len
qWarning("sysctl NET_RT_FLAGS(1) failed (%s)\n", strerror(errno));
return mac;
}
buf.resize(len);
if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) { // now retreive ARP/NDP
qWarning("sysctl NET_RT_FLAGS(2) failed(%s)\n", strerror(errno));
return mac;
}
const char *p = buf.constData();
const char *end = p + len;
while (p < end)
{
const struct rt_msghdr *rtm = (const struct rt_msghdr*) p;
if (rtm->rtm_index == ifIndex_) {
const struct sockaddr_in6 *sin = (const struct sockaddr_in6*)(rtm + 1);
if ((qFromBigEndian<quint64>(sin->sin6_addr.s6_addr) == ip.hi64())
&& (qFromBigEndian<quint64>(sin->sin6_addr.s6_addr+8) == ip.lo64())) {
const struct sockaddr_dl *sdl = (const struct sockaddr_dl*)
((char*)sin + SA_SIZE(sin));
mac = qFromBigEndian<quint64>(LLADDR(sdl)) >> 16;
break;
}
}
p += rtm->rtm_msglen;
}
return mac;
}
void BsdHostDevice::sendArpRequest(quint32 tgtIp)
{
//
// XXX: I can't seem to find a BSD syscall to trigger the kernel to send an ARP;
// so for now craft one from scratch and send
// NOTE: RTM_RESOLVE has been removed - see
// http://conferences.sigcomm.org/sigcomm/2009/workshops/presto/papers/p37.pdf
//
quint32 srcIp = ip4_;
PacketBuffer *reqPkt;
uchar *pktData;
// Validate target IP
if (!tgtIp)
return;
reqPkt = new PacketBuffer;
reqPkt->reserve(encapSize());
pktData = reqPkt->put(28);
if (pktData) {
// HTYP, PTYP
*(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800));
// HLEN, PLEN, OPER
*(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040001));
// Source H/W Addr, Proto Addr
*(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16));
*(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff));
*(quint32*)(pktData+14) = qToBigEndian(srcIp);
// Target H/W Addr, Proto Addr
*(quint32*)(pktData+18) = qToBigEndian(quint32(0));
*(quint16*)(pktData+22) = qToBigEndian(quint16(0));
*(quint32*)(pktData+24) = qToBigEndian(tgtIp);
}
ethEncap(reqPkt, kBcastMac, kEthTypeArp);
pcap_sendpacket((pcap_t*)txHandle_, reqPkt->data(), reqPkt->length());
qDebug("Sent ARP Request for srcIp/tgtIp=%s/%s",
qPrintable(QHostAddress(srcIp).toString()),
qPrintable(QHostAddress(tgtIp).toString()));
}
void BsdHostDevice::sendNeighborSolicit(UInt128 tgtIp)
{
// XXX: See note in sendArpRequest() - applies here too
UInt128 dstIp, srcIp = ip6_;
PacketBuffer *reqPkt;
uchar *pktData;
// Validate target IP
if (tgtIp == UInt128(0, 0))
return;
// Form the solicited node address to be used as dstIp
// ff02::1:ffXX:XXXX/104
dstIp = UInt128((quint64(0xff02) << 48),
(quint64(0x01ff) << 24) | (tgtIp.lo64() & 0xFFFFFF));
reqPkt = new PacketBuffer;
reqPkt->reserve(encapSize() + kIp6HdrLen);
pktData = reqPkt->put(32);
if (pktData) {
// Calculate checksum first -
// start with fixed fields in ICMP Header and IPv6 Pseudo Header ...
quint32 sum = 0x8700 + 0x0101 + 32 + kIpProtoIcmp6;
// then variable fields from ICMP header ...
sum += sumUInt128(tgtIp);
sum += (mac_ >> 32) + ((mac_ >> 16) & 0xffff) + (mac_ & 0xffff);
// and variable fields from IPv6 pseudo header
sum += sumUInt128(ip6_);
sum += sumUInt128(dstIp);
while(sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
// Type, Code
*(quint16*)(pktData ) = qToBigEndian(quint16(0x8700));
// Checksum
*(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum));
// Reserved
*(quint32*)(pktData+ 4) = qToBigEndian(quint32(0));
// Target IP
memcpy(pktData+ 8, tgtIp.toArray(), 16);
// Source Addr TLV + MacAddr
*(quint16*)(pktData+24) = qToBigEndian(quint16(0x0101));
*(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16));
*(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff));
}
int payloadLen = reqPkt->length();
uchar *p = reqPkt->push(kIp6HdrLen);
quint64 dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff);
// Ver(4), TrfClass(8), FlowLabel(8)
*(quint32*)(p ) = qToBigEndian(quint32(0x60000000));
*(quint16*)(p+ 4) = qToBigEndian(quint16(payloadLen));
p[6] = kIpProtoIcmp6; // protocol
p[7] = 255; // HopLimit
memcpy(p+ 8, ip6_.toArray(), 16); // Source IP
memcpy(p+24, dstIp.toArray(), 16); // Destination IP
ethEncap(reqPkt, dstMac, kEthTypeIp6);
pcap_sendpacket((pcap_t*)txHandle_, reqPkt->data(), reqPkt->length());
qDebug("Sent NDP Request for srcIp/tgtIp=%s/%s",
qPrintable(QHostAddress(srcIp.toArray()).toString()),
qPrintable(QHostAddress(tgtIp.toArray()).toString()));
}
int BsdHostDevice::encapSize()
{
Q_ASSERT(numVlanTags_ >= 0);
// ethernet header + vlans
int size = 14 + kMaxVlan*numVlanTags_;
return size;
}
void BsdHostDevice::ethEncap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type)
{
int ofs;
quint64 srcMac = mac_;
uchar *p = pktBuf->push(encapSize());
if (!p) {
qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__,
encapSize(), pktBuf->head(), pktBuf->data());
goto _exit;
}
*(quint32*)(p ) = qToBigEndian(quint32(dstMac >> 16));
*(quint16*)(p + 4) = qToBigEndian(quint16(dstMac & 0xffff));
*(quint32*)(p + 6) = qToBigEndian(quint32(srcMac >> 16));
*(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff));
ofs = 12;
for (int i = 0; i < numVlanTags_; i++) {
*(quint32*)(p + ofs) = qToBigEndian(vlan_[i]);
ofs += 4;
}
*(quint16*)(p + ofs) = qToBigEndian(type);
ofs += 2;
Q_ASSERT(ofs == encapSize());
_exit:
return;
}
#endif