Files
scylladb/net/arp.hh
Gleb Natapov d698811bdd fix smp broadcast packet handling
Some packets, like arp replies, are broadcast to all cpus for handling,
but only packet structure is copied for each cpu, the actual packet data
is the same for all of them. Currently networking stack mangles a
packet data during its travel up the stack while doing ntoh()
translations which cannot obviously work for broadcaster packets. This
patches fixes the code to not modify packet data while doing ntoh(), but
do it in a stack allocated copy of a data instead.
2014-11-06 10:30:30 +02:00

247 lines
6.6 KiB
C++

/*
* Copyright (C) 2014 Cloudius Systems, Ltd.
*
*/
#ifndef ARP_HH_
#define ARP_HH_
#include "net.hh"
#include "core/reactor.hh"
#include "byteorder.hh"
#include "ethernet.hh"
#include "core/print.hh"
#include <unordered_map>
namespace net {
class arp;
class arp_for_protocol;
template <typename L3>
class arp_for;
class arp_for_protocol {
protected:
arp& _arp;
uint16_t _proto_num;
public:
arp_for_protocol(arp& a, uint16_t proto_num);
virtual ~arp_for_protocol();
virtual future<> received(packet p) = 0;
virtual unsigned forward(packet& p, size_t off) { return engine.cpu_id(); }
};
class arp {
interface* _netif;
l3_protocol _proto;
subscription<packet, ethernet_address> _rx_packets;
std::unordered_map<uint16_t, arp_for_protocol*> _arp_for_protocol;
private:
struct arp_hdr {
packed<uint16_t> htype;
packed<uint16_t> ptype;
template <typename Adjuster>
void adjust_endianness(Adjuster a) { return a(htype, ptype); }
};
public:
explicit arp(interface* netif);
void add(uint16_t proto_num, arp_for_protocol* afp);
void del(uint16_t proto_num);
private:
ethernet_address l2self() { return _netif->hw_address(); }
future<> process_packet(packet p, ethernet_address from);
unsigned handle_on_cpu(packet& p, size_t off);
template <class l3_proto>
friend class arp_for;
};
template <typename L3>
class arp_for : public arp_for_protocol {
public:
using l2addr = ethernet_address;
using l3addr = typename L3::address_type;
private:
static constexpr auto max_waiters = 512;
enum oper {
op_request = 1,
op_reply = 2,
};
struct arp_hdr {
packed<uint16_t> htype;
packed<uint16_t> ptype;
uint8_t hlen;
uint8_t plen;
packed<uint16_t> oper;
l2addr sender_hwaddr;
l3addr sender_paddr;
l2addr target_hwaddr;
l3addr target_paddr;
template <typename Adjuster>
void adjust_endianness(Adjuster a) {
a(htype, ptype, oper, sender_hwaddr, sender_paddr, target_hwaddr, target_paddr);
}
};
struct resolution {
std::vector<promise<l2addr>> _waiters;
timer _timeout_timer;
};
private:
l3addr _l3self = L3::broadcast_address();
std::unordered_map<l3addr, l2addr> _table;
std::unordered_map<l3addr, resolution> _in_progress;
private:
packet make_query_packet(l3addr paddr);
virtual future<> received(packet p) override;
virtual unsigned forward(packet& p, size_t off) override;
future<> handle_request(arp_hdr* ah);
l2addr l2self() { return _arp.l2self(); }
public:
future<> send_query(const l3addr& paddr);
explicit arp_for(arp& a) : arp_for_protocol(a, L3::arp_protocol_type()) {}
future<ethernet_address> lookup(const l3addr& addr);
void learn(l2addr l2, l3addr l3);
void run();
void set_self_addr(l3addr addr) { _l3self = addr; }
friend class arp;
};
template <typename L3>
packet
arp_for<L3>::make_query_packet(l3addr paddr) {
arp_hdr hdr;
hdr.htype = ethernet::arp_hardware_type();
hdr.ptype = L3::arp_protocol_type();
hdr.hlen = sizeof(l2addr);
hdr.plen = sizeof(l3addr);
hdr.oper = op_request;
hdr.sender_hwaddr = l2self();
hdr.sender_paddr = _l3self;
hdr.target_hwaddr = ethernet::broadcast_address();
hdr.target_paddr = paddr;
hdr = hton(hdr);
return packet(reinterpret_cast<char*>(&hdr), sizeof(hdr));
}
template <typename L3>
future<>
arp_for<L3>::send_query(const l3addr& paddr) {
return _arp._proto.send(ethernet::broadcast_address(), make_query_packet(paddr));
}
class arp_error : public std::runtime_error {
public:
arp_error(const std::string& msg) : std::runtime_error(msg) {}
};
class arp_timeout_error : public arp_error {
public:
arp_timeout_error() : arp_error("ARP timeout") {}
};
class arp_queue_full_error : public arp_error {
public:
arp_queue_full_error() : arp_error("ARP waiter's queue is full") {}
};
template <typename L3>
future<ethernet_address>
arp_for<L3>::lookup(const l3addr& paddr) {
auto i = _table.find(paddr);
if (i != _table.end()) {
return make_ready_future<ethernet_address>(i->second);
}
auto j = _in_progress.find(paddr);
auto first_request = j == _in_progress.end();
auto& res = first_request ? _in_progress[paddr] : j->second;
if (first_request) {
res._timeout_timer.set_callback([paddr, this, &res] {
send_query(paddr);
for (auto& w : res._waiters) {
w.set_exception(arp_timeout_error());
}
res._waiters.clear();
});
res._timeout_timer.arm_periodic(std::chrono::seconds(1));
send_query(paddr);
}
if (res._waiters.size() >= max_waiters) {
return make_exception_future<ethernet_address>(arp_queue_full_error());
}
res._waiters.emplace_back();
return res._waiters.back().get_future();
}
template <typename L3>
void
arp_for<L3>::learn(l2addr hwaddr, l3addr paddr) {
_table[paddr] = hwaddr;
auto i = _in_progress.find(paddr);
if (i != _in_progress.end()) {
auto& res = i->second;
res._timeout_timer.cancel();
for (auto &&pr : res._waiters) {
pr.set_value(hwaddr);
}
_in_progress.erase(i);
}
}
template <typename L3>
unsigned arp_for<L3>::forward(packet& p, size_t off)
{
auto ah = p.get_header<arp_hdr>(off);
if (ntoh(ah->oper) == op_reply) {
return std::numeric_limits<unsigned>::max(); // broadcast reply
}
return engine.cpu_id();
}
template <typename L3>
future<>
arp_for<L3>::received(packet p) {
auto ah = p.get_header<arp_hdr>();
if (!ah) {
return make_ready_future<>();
}
auto h = ntoh(*ah);
if (h.hlen != sizeof(l2addr) || h.plen != sizeof(l3addr)) {
return make_ready_future<>();
}
switch (h.oper) {
case op_request:
return handle_request(&h);
case op_reply:
learn(h.sender_hwaddr, h.sender_paddr);
return make_ready_future<>();
default:
return make_ready_future<>();
}
}
template <typename L3>
future<>
arp_for<L3>::handle_request(arp_hdr* ah) {
if (ah->target_paddr == _l3self
&& _l3self != L3::broadcast_address()) {
ah->oper = op_reply;
ah->target_hwaddr = ah->sender_hwaddr;
ah->target_paddr = ah->sender_paddr;
ah->sender_hwaddr = l2self();
ah->sender_paddr = _l3self;
*ah = hton(*ah);
packet p(reinterpret_cast<char*>(ah), sizeof(*ah));
return _arp._proto.send(ah->target_hwaddr, std::move(p));
} else {
return make_ready_future<>();
}
}
}
#endif /* ARP_HH_ */