mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-23 01:50:35 +00:00
286 lines
8.8 KiB
C++
286 lines
8.8 KiB
C++
/*
|
|
* Copyright (C) 2014 Cloudius Systems, Ltd.
|
|
*/
|
|
|
|
#ifndef NET_HH_
|
|
#define NET_HH_
|
|
|
|
#include "reactor.hh"
|
|
#include "ip.hh"
|
|
#include <unordered_map>
|
|
|
|
namespace net {
|
|
|
|
class packet;
|
|
class interface;
|
|
class device;
|
|
class l3_protocol;
|
|
|
|
struct fragment {
|
|
char* base;
|
|
size_t size;
|
|
};
|
|
|
|
// Zero-copy friendly packet class
|
|
//
|
|
// For implementing zero-copy, we need a flexible destructor that can
|
|
// destroy packet data in different ways: decrementing a reference count,
|
|
// or calling a free()-like function.
|
|
//
|
|
// Moreover, we need different destructors for each set of fragments within
|
|
// a single fragment. For example, a header and trailer might need delete[]
|
|
// to be called, while the internal data needs a reference count to be
|
|
// released. Matters are complicated in that fragments can be split
|
|
// (due to virtual/physical translation).
|
|
//
|
|
// To implement this, we associate each packet with a single destructor,
|
|
// but allow composing a packet from another packet plus a fragment to
|
|
// be added, with its own destructor, causing the destructors to be chained.
|
|
//
|
|
// The downside is that the data needed for the destructor is duplicated,
|
|
// if it is already available in the fragment itself.
|
|
//
|
|
// As an optimization, when we allocate small fragments, we allocate some
|
|
// extra space, so prepending to the packet does not require extra
|
|
// allocations. This is useful when adding headers.
|
|
//
|
|
class packet final {
|
|
// enough for lots of headers, not quite two cache lines:
|
|
static constexpr size_t internal_data_size = 128 - 16;
|
|
struct deleter {
|
|
std::unique_ptr<deleter> next;
|
|
deleter(std::unique_ptr<deleter> next) : next(std::move(next)) {}
|
|
virtual ~deleter() {};
|
|
};
|
|
struct internal_deleter final : deleter {
|
|
// FIXME: make buf an std::array<>?
|
|
char* buf;
|
|
unsigned free_head;
|
|
internal_deleter(std::unique_ptr<deleter> next, char* buf, unsigned free_head)
|
|
: deleter(std::move(next)), buf(buf), free_head(free_head) {}
|
|
virtual ~internal_deleter() override { delete[] buf; }
|
|
};
|
|
template <typename Deleter>
|
|
struct lambda_deleter final : deleter {
|
|
Deleter del;
|
|
lambda_deleter(std::unique_ptr<deleter> next, Deleter&& del)
|
|
: deleter(std::move(next)), del(std::move(del)) {}
|
|
virtual ~lambda_deleter() override { del(); }
|
|
};
|
|
template <typename Deleter>
|
|
std::unique_ptr<deleter>
|
|
make_deleter(std::unique_ptr<deleter> next, Deleter d) {
|
|
return std::unique_ptr<deleter>(new lambda_deleter<Deleter>(std::move(next), std::move(d)));
|
|
}
|
|
// when destroyed, virtual destructor will reclaim resources
|
|
std::unique_ptr<deleter> _deleter;
|
|
public:
|
|
std::vector<fragment> fragments;
|
|
unsigned len = 0;
|
|
public:
|
|
// build empty packet
|
|
packet() = default;
|
|
// move existing packet
|
|
packet(packet&& x);
|
|
// copy data into packet
|
|
packet(char* data, size_t len);
|
|
// copy data into packet
|
|
packet(fragment frag);
|
|
// zero-copy single fragment
|
|
template <typename Deleter>
|
|
packet(fragment frag, Deleter deleter);
|
|
// zero-copy multiple fragment
|
|
template <typename Deleter>
|
|
packet(std::vector<fragment> frag, Deleter deleter);
|
|
// append fragment (copying new fragment)
|
|
packet(packet&& x, fragment frag);
|
|
// prepend fragment (copying new fragment, with header optimization)
|
|
packet(fragment frag, packet&& x);
|
|
// prepend fragment (zero-copy)
|
|
template <typename Deleter>
|
|
packet(fragment frag, Deleter deleter, packet&& x);
|
|
// append fragment (zero-copy)
|
|
template <typename Deleter>
|
|
packet(packet&& x, fragment frag, Deleter deleter);
|
|
|
|
void trim_front(size_t how_much);
|
|
|
|
// get a header pointer, linearizing if necessary
|
|
template <typename Header>
|
|
Header* get_header(size_t offset);
|
|
|
|
// get a header pointer, linearizing if necessary
|
|
char* get_header(size_t offset, size_t size);
|
|
|
|
private:
|
|
void linearize(size_t at_frag, size_t desired_size);
|
|
};
|
|
|
|
class l3_protocol {
|
|
interface* _netif;
|
|
uint16_t _proto_num;
|
|
public:
|
|
explicit l3_protocol(interface* netif, uint16_t proto_num) : _netif(netif), _proto_num(proto_num) {}
|
|
future<> send(ethernet_address to, packet p);
|
|
future<packet, ethernet_address> receive();
|
|
private:
|
|
void received(packet p);
|
|
friend class interface;
|
|
};
|
|
|
|
class interface {
|
|
std::unique_ptr<device> _dev;
|
|
std::unordered_map<uint16_t, promise<packet, ethernet_address>> _proto_map;
|
|
private:
|
|
future<packet, ethernet_address> receive(uint16_t proto_num);
|
|
future<> send(uint16_t proto_num, ethernet_address to, packet p);
|
|
public:
|
|
explicit interface(std::unique_ptr<device> dev) : _dev(std::move(dev)) {}
|
|
void run();
|
|
friend class l3_protocol;
|
|
};
|
|
|
|
class device {
|
|
public:
|
|
virtual ~device() {}
|
|
virtual future<packet> receive() = 0;
|
|
virtual future<> send(packet p) = 0;
|
|
};
|
|
|
|
inline
|
|
packet::packet(packet&& x)
|
|
: _deleter(std::move(x._deleter)), fragments(std::move(x.fragments)), len(x.len) {
|
|
x.len = 0;
|
|
}
|
|
|
|
inline
|
|
packet::packet(fragment frag) : len(frag.size) {
|
|
auto flen = std::max(frag.size, internal_data_size);
|
|
std::unique_ptr<char[]> buf{new char[flen]};
|
|
std::copy(frag.base, frag.base + frag.size, buf.get() + flen - frag.size);
|
|
fragments.push_back(frag);
|
|
_deleter.reset(new internal_deleter(std::unique_ptr<deleter>(),
|
|
buf.release(), flen - frag.size));
|
|
}
|
|
|
|
inline
|
|
packet::packet(char* data, size_t size) : packet(fragment{data, size}) {
|
|
}
|
|
|
|
template <typename Deleter>
|
|
inline
|
|
packet::packet(fragment frag, Deleter d)
|
|
: _deleter(make_deleter(std::unique_ptr<deleter>(), std::move(d))) {
|
|
fragments.push_back(frag);
|
|
len = frag.size;
|
|
}
|
|
|
|
template <typename Deleter>
|
|
inline
|
|
packet::packet(std::vector<fragment> frag, Deleter d)
|
|
: _deleter(make_deleter(std::unique_ptr<deleter>(), std::move(d)))
|
|
, fragments(std::move(frag))
|
|
, len(0) {
|
|
for (auto&& f : fragments) {
|
|
len += f.size;
|
|
}
|
|
}
|
|
|
|
inline
|
|
packet::packet(packet&& x, fragment frag)
|
|
: fragments(std::move(x.fragments))
|
|
, len(x.len + frag.size) {
|
|
std::unique_ptr<char[]> buf(new char[frag.size]);
|
|
std::copy(frag.base, frag.base + frag.size, buf.get());
|
|
_deleter = make_deleter(std::move(x._deleter), [buf = buf.release()] {
|
|
delete[] buf;
|
|
});
|
|
x.len = 0;
|
|
}
|
|
|
|
inline
|
|
packet::packet(fragment frag, packet&& x)
|
|
: _deleter(std::move(x._deleter)), len(x.len + frag.size) {
|
|
// try to prepend into existing internal fragment
|
|
auto id = dynamic_cast<internal_deleter*>(x._deleter.get());
|
|
if (id && id->free_head >= frag.size) {
|
|
id->free_head -= frag.size;
|
|
fragments[0].base -= frag.size;
|
|
fragments[0].size += frag.size;
|
|
std::copy(frag.base, frag.base + frag.size, fragments[0].base);
|
|
} else {
|
|
// didn't work out, allocate and copy
|
|
auto size = std::max(frag.size, internal_data_size);
|
|
std::unique_ptr<char[]> buf(new char[size]);
|
|
std::copy(frag.base, frag.base + frag.size, buf.get() + size - frag.size);
|
|
fragments.reserve(x.fragments.size() + 1);
|
|
fragments.push_back({buf.get() + size - frag.size, frag.size});
|
|
std::copy(x.fragments.begin(), x.fragments.end(), std::back_inserter(fragments));
|
|
x.fragments.clear();
|
|
_deleter.reset(new internal_deleter(std::move(_deleter), buf.release(), size - frag.size));
|
|
}
|
|
x.len = 0;
|
|
}
|
|
|
|
template <typename Deleter>
|
|
inline
|
|
packet::packet(fragment frag, Deleter d, packet&& x) : len(x.len + frag.size) {
|
|
fragments.reserve(x.fragments.size() + 1);
|
|
fragments.push_back(frag);
|
|
std::copy(x.fragments.begin(), x.fragments.end(), std::back_inserter(fragments));
|
|
x.fragments.clear();
|
|
_deleter.reset(make_deleter(std::move(_deleter), d));
|
|
x.len = 0;
|
|
}
|
|
|
|
template <typename Deleter>
|
|
inline
|
|
packet::packet(packet&& x, fragment frag, Deleter d)
|
|
: fragments(std::move(x.fragments)), len(x.len + frag.size) {
|
|
fragments.push_back(frag);
|
|
_deleter.reset(make_deleter(std::move(_deleter), d));
|
|
x.len = 0;
|
|
}
|
|
|
|
inline
|
|
char* packet::get_header(size_t offset, size_t size) {
|
|
if (offset + size > len) {
|
|
return nullptr;
|
|
}
|
|
size_t i = 0;
|
|
while (i != fragments.size() && offset >= fragments[i].size) {
|
|
offset -= fragments[i++].size;
|
|
}
|
|
if (i == fragments.size()) {
|
|
return nullptr;
|
|
}
|
|
if (offset + size > fragments[i].size) {
|
|
linearize(i, offset + size);
|
|
}
|
|
return fragments[i].base + offset;
|
|
}
|
|
|
|
template <typename Header>
|
|
inline
|
|
Header* packet::get_header(size_t offset) {
|
|
return reinterpret_cast<Header*>(get_header(offset, sizeof(Header)));
|
|
}
|
|
|
|
inline
|
|
void packet::trim_front(size_t how_much) {
|
|
assert(how_much <= len);
|
|
len -= how_much;
|
|
size_t i = 0;
|
|
while (how_much >= fragments[i].size) {
|
|
how_much -= fragments[i++].size;
|
|
}
|
|
fragments.erase(fragments.begin(), fragments.begin() + i);
|
|
fragments[0].base += how_much;
|
|
fragments[0].size -= how_much;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
#endif /* NET_HH_ */
|