|
|
nono 1.7.0
//
// nono
// Copyright (C) 2021 nono project
// Licensed under nono-license.txt
//
//
// ホストネットワークの AF_PACKET ドライバ
// (Linux 専用)
//
#include "netdriver_afpacket.h"
#include "autofd.h"
#include "hostnet.h"
#include <ifaddrs.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
// コンストラクタ
NetDriverAFPacket::NetDriverAFPacket(HostDevice *hostdev_,
const std::string& ifname_)
: inherited(hostdev_, "afpacket")
{
ifname = ifname_;
if (ifname.empty()) {
ifname = "auto";
}
}
// デストラクタ
NetDriverAFPacket::~NetDriverAFPacket()
{
Close();
}
// ドライバ初期化
bool
NetDriverAFPacket::InitDriver(bool startup)
{
packet_mreq mreq {};
int len;
socklen_t so_len;
putmsg(1, "trying afpacket...");
putmsg(1, "argument: ifname=\"%s\"", ifname.c_str());
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETH_P_ALL);
sll.sll_ifindex = SelectInterface();
if (sll.sll_ifindex == 0) {
// errmsg はセットしてある
goto abort;
}
// bind するまでの期間、このソケットにはシステムのすべての
// インタフェースからのパケットが着信することに注意。
fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (fd < 0) {
errmsg = string_format("%s: %s", "socket(AF_PACKET)", strerror(errno));
putmsg(1, "%s", errmsg.c_str());
if (errno == EPERM) {
// Linux では setcap(8) が必要。分かりづらいのでメッセージを出すが
// その例示より先に errmsg を表示したい。
warnx("%s", errmsg.c_str());
errmsg.clear();
fprintf(stderr,
" ** Set CAP_NET_RAW capability to the nono executable file.\n"
" ** ex) sudo setcap cap_net_raw=ep path/to/%s\n",
getprogname());
}
return false;
}
if (bind(fd, (const sockaddr *)&sll, sizeof(sll)) != 0) {
errmsg = string_format("bind(%s): %s", ifname.c_str(), strerror(errno));
goto abort;
}
#if defined(SIOCETHTOOL) && defined(ETHTOOL_GGRO)
// GRO が設定されていると再構成されたジャンボパケットが届いてしまうが、
// VM (というか当時の NIC) にそんなものが届いても困るので、
// GRO が設定されていたらエラー終了する。
//
// 確認 /sbin/ethtool -k <ifname> | generic-receive-offload
// 設定 sudo ethtool -K <ifname> generic-receive-offload <on|off>
struct ifreq ifr;
struct ethtool_value ethval;
memset(&ifr, 0, sizeof(ifr));
memset(ðval, 0, sizeof(ethval));
strlcpy(ifr.ifr_name, ifname.c_str(), sizeof(ifr.ifr_name));
ethval.cmd = ETHTOOL_GGRO;
ifr.ifr_data = (char *)ðval;
if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
if (errno == EOPNOTSUPP) {
// この ioctl 自体をサポートしていなければ
// たぶんオフローディングは設定されていないはず。
ethval.data = 1;
} else {
errmsg = string_format("SIOCETHTOOL(%s): %s",
ifname.c_str(), strerror(errno));
goto abort;
}
}
if (ethval.data != 0) {
errmsg = "GRO is enabled";
putmsg(1, "%s", errmsg.c_str());
warnx("%s", errmsg.c_str());
errmsg.clear();
fprintf(stderr,
" ** Disable GRO(generic-receive-offload)"
" to avoid bother network problem.\n"
" ** ex) sudo ethtool -K %s gro off\n", ifname.c_str());
return false;
}
#endif
// PROMISC セット
mreq.mr_ifindex = sll.sll_ifindex;
mreq.mr_type = PACKET_MR_PROMISC;
if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
&mreq, sizeof(mreq)) != 0) {
errmsg = string_format("%s: %s", "set PROMISC", strerror(errno));
goto abort;
}
so_len = sizeof(len);
if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, &so_len) != 0) {
errmsg = string_format("%s: %s",
"getsockopt(SO_RCVBUF)", strerror(errno));
goto abort;
}
if (hostdev->AddOuter(fd) < 0) {
errmsg = string_format("AddOuter(fd=%d): %s", (int)fd, strerror(errno));
goto abort;
}
putmsg(1, "opened for %s", ifname.c_str());
// AFPacket では ifup スクリプトは実行しない
return true;
abort:
if (errmsg.empty() == false) {
putmsg(1, "%s", errmsg.c_str());
}
Close();
return false;
}
// クローズ
// (private)
void
NetDriverAFPacket::Close()
{
if (fd.Valid()) {
hostdev->DelOuter(fd);
fd.Close();
}
}
// モニタ (ドライバ依存情報のみ)
void
NetDriverAFPacket::MonitorScreenMD(TextScreen& screen, int y)
{
screen.Print(0, y, "Interface: %s", ifname.c_str());
}
// パケットを送信する
void
NetDriverAFPacket::Write(const void *buf, int buflen)
{
ssize_t n;
n = sendto(fd, buf, buflen, 0, (const sockaddr *)&sll, sizeof(sll));
if (n < 0) {
putmsg(0, "sendto(%d): %s", buflen, strerror(errno));
return;
}
if (n < buflen) {
putmsg(0, "sendto(%d): short(%zd)", buflen, n);
return;
}
}
// パケットを受信する
int
NetDriverAFPacket::Read(NetPacket *p)
{
uint8 buf[1600];
int n;
n = recv(fd, buf, sizeof(buf), 0);
if (n < 0) {
putmsg(0, "recv: %s", strerror(errno));
return NODATA;
}
p->Assign(buf, n);
return 0;
}
// インタフェース番号を返す。
// auto のときは選択したインタフェース名を ifname にセットする。
// インタフェースが決定できないときは errmsg をセットして 0 を返す。
int
NetDriverAFPacket::SelectInterface()
{
int ifindex = 0;
struct ifaddrs *ifa_list, *ifa;
if (ifname == "auto") {
// インタフェースを探す
int level = 1;
if (getifaddrs(&ifa_list) == -1) {
errmsg = string_format("getifaddrs: %s", strerror(errno));
return 0;
}
for (ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
if ((ifa->ifa_flags & IFF_UP) == 0) {
continue;
}
if (ifa->ifa_addr == NULL) {
continue;
}
// AF_PACKET をサポートするインタフェースを探す
if (ifa->ifa_addr->sa_family != AF_PACKET) {
continue;
}
// ETHERNET を探す
sockaddr_ll *sa = (sockaddr_ll *)ifa->ifa_addr;
if (sa->sll_hatype != ARPHRD_ETHER) {
continue;
}
if (ifindex == 0) {
// 最初に見つかったものを採用
ifname = ifa->ifa_name;
ifindex = sa->sll_ifindex;
} else {
// インタフェース候補が 2個以上ある場合はログレベル 0 にしたい
level = 0;
break;
}
}
freeifaddrs(ifa_list);
if (ifindex == 0) {
errmsg = "auto could not find a suitable interface";
return ifindex;
}
putmsg(level, "auto-selected ifname \"%s\"", ifname.c_str());
} else {
ifindex = if_nametoindex(ifname.c_str());
if (ifindex == 0) {
errmsg = string_format("interface %s not found", ifname.c_str());
}
}
return ifindex;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.