-
Notifications
You must be signed in to change notification settings - Fork 510
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding ability to run simulation against loaded katran's bpf program this new call would allow to answer the question "where this specific flow is going to be sent (which real)". could be realy useful for debuging (when you have lots of reals, and specific 5 tuple does not work - you would know exactly which real are suppose to receive it (and therfor would be able to narrow down scope for debugging))
- Loading branch information
Showing
6 changed files
with
398 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
#include "katran/lib/KatranSimulator.h" | ||
|
||
#include <cstring> | ||
#include <folly/IPAddress.h> | ||
#include <glog/logging.h> | ||
|
||
#include "katran/lib/BpfAdapter.h" | ||
|
||
extern "C" { | ||
#include <arpa/inet.h> | ||
#include <linux/if_ether.h> | ||
#include <linux/in.h> | ||
#include <linux/ip.h> | ||
#include <linux/ipv6.h> | ||
#include <linux/udp.h> | ||
} | ||
|
||
namespace katran { | ||
|
||
namespace { | ||
constexpr uint16_t kMaxXdpPcktSize = 4096; | ||
constexpr uint16_t kTestPacketSize = 512; | ||
constexpr int kTestRepeatCount = 1; | ||
constexpr uint8_t kDefaultTtl = 64; | ||
constexpr uint8_t kIPv6AddrSize = 16; | ||
constexpr folly::StringPiece kEmptyString = ""; | ||
} // namespace | ||
|
||
namespace { | ||
|
||
void createV4Packet(const folly::IPAddress &src, const folly::IPAddress &dst, | ||
std::unique_ptr<folly::IOBuf> &buf, uint8_t proto, | ||
uint16_t size) { | ||
auto ehdr = reinterpret_cast<struct ethhdr *>(buf->writableData()); | ||
auto iph = reinterpret_cast<struct iphdr *>(buf->writableData() + | ||
sizeof(struct ethhdr)); | ||
ehdr->h_proto = htons(ETH_P_IP); | ||
iph->ihl = 5; | ||
iph->version = 4; | ||
iph->frag_off = 0; | ||
iph->protocol = proto; | ||
iph->check = 0; | ||
iph->tos = 0; | ||
iph->tot_len = htons(size); | ||
iph->daddr = dst.asV4().toLong(); | ||
iph->saddr = src.asV4().toLong(); | ||
iph->ttl = kDefaultTtl; | ||
} | ||
|
||
void createV6Packet(const folly::IPAddress &src, const folly::IPAddress &dst, | ||
std::unique_ptr<folly::IOBuf> &buf, uint8_t proto, | ||
uint16_t size) { | ||
auto ehdr = reinterpret_cast<struct ethhdr *>(buf->writableData()); | ||
auto ip6h = reinterpret_cast<struct ipv6hdr *>(buf->writableData() + | ||
sizeof(struct ethhdr)); | ||
ehdr->h_proto = htons(ETH_P_IPV6); | ||
ip6h->version = 6; | ||
ip6h->priority = 0; | ||
std::memset(ip6h->flow_lbl, 0, sizeof(ip6h->flow_lbl)); | ||
ip6h->nexthdr = proto; | ||
ip6h->payload_len = htons(size - sizeof(struct ipv6hdr)); | ||
ip6h->hop_limit = kDefaultTtl; | ||
std::memcpy(ip6h->daddr.s6_addr16, dst.asV6().toBinary().data(), | ||
kIPv6AddrSize); | ||
std::memcpy(ip6h->saddr.s6_addr16, src.asV6().toBinary().data(), | ||
kIPv6AddrSize); | ||
} | ||
|
||
void createTcpHeader(std::unique_ptr<folly::IOBuf> &buf, uint16_t srcPort, | ||
uint16_t dstPort, uint16_t offset) { | ||
auto tcph = reinterpret_cast<struct tcphdr *>(buf->writableData() + offset); | ||
std::memset(tcph, 0, sizeof(struct tcphdr)); | ||
tcph->source = htons(srcPort); | ||
tcph->dest = htons(dstPort); | ||
tcph->syn = 1; | ||
} | ||
|
||
void createUdpHeader(std::unique_ptr<folly::IOBuf> &buf, uint16_t srcPort, | ||
uint16_t dstPort, uint16_t offset, uint16_t size) { | ||
auto udph = reinterpret_cast<struct udphdr *>(buf->writableData() + offset); | ||
std::memset(udph, 0, sizeof(struct udphdr)); | ||
udph->source = htons(srcPort); | ||
udph->dest = htons(dstPort); | ||
udph->len = size; | ||
} | ||
|
||
const std::string toV4String(uint32_t addr) { | ||
return folly::IPAddressV4::fromLong(addr).str(); | ||
} | ||
|
||
const std::string toV6String(uint8_t const *v6) { | ||
folly::ByteRange bytes(v6, kIPv6AddrSize); | ||
return folly::IPAddressV6::fromBinary(bytes).str(); | ||
} | ||
|
||
std::string getPcktDst(std::unique_ptr<folly::IOBuf> &pckt) { | ||
if (pckt->length() < sizeof(struct ethhdr)) { | ||
LOG(ERROR) << "resulting packet is invalid"; | ||
return kEmptyString.data(); | ||
} | ||
const struct ethhdr *ehdr = | ||
reinterpret_cast<const struct ethhdr *>(pckt->data()); | ||
if (ehdr->h_proto == htons(ETH_P_IP)) { | ||
if (pckt->length() < (sizeof(struct ethhdr) + sizeof(struct iphdr))) { | ||
LOG(ERROR) << "resulting ipv4 packet is invalid"; | ||
return kEmptyString.data(); | ||
} | ||
const struct iphdr *iph = reinterpret_cast<const struct iphdr *>( | ||
pckt->data() + sizeof(struct ethhdr)); | ||
return toV4String(iph->daddr); | ||
} else { | ||
if (pckt->length() < (sizeof(struct ethhdr) + sizeof(struct ipv6hdr))) { | ||
LOG(ERROR) << "resulting ipv6 packet is invalid"; | ||
return kEmptyString.data(); | ||
} | ||
const struct ipv6hdr *ip6h = reinterpret_cast<const struct ipv6hdr *>( | ||
pckt->data() + sizeof(struct ethhdr)); | ||
return toV6String(ip6h->daddr.s6_addr); | ||
} | ||
} | ||
|
||
std::unique_ptr<folly::IOBuf> createPacketFromFlow(const KatranFlow &flow) { | ||
int offset = sizeof(struct ethhdr); | ||
size_t l3hdr_len, l4hdr_len; | ||
bool is_v4 = true; | ||
bool is_tcp = true; | ||
|
||
auto srcExp = folly::IPAddress::tryFromString(flow.src); | ||
auto dstExp = folly::IPAddress::tryFromString(flow.dst); | ||
if (srcExp.hasError() || dstExp.hasError()) { | ||
LOG(ERROR) << "malformed src or dst ip address. src: " << flow.src | ||
<< " dst: " << flow.dst; | ||
return nullptr; | ||
} | ||
auto src = srcExp.value(); | ||
auto dst = dstExp.value(); | ||
if (src.family() != dst.family()) { | ||
LOG(ERROR) << "src and dst must have same address family"; | ||
return nullptr; | ||
} | ||
auto pckt = folly::IOBuf::create(kTestPacketSize); | ||
if (pckt == nullptr) { | ||
LOG(ERROR) << "can not allocate IOBuf"; | ||
return pckt; | ||
} | ||
auto l3_len = kTestPacketSize - sizeof(struct ethhdr); | ||
if (src.family() == AF_INET) { | ||
l3hdr_len = sizeof(struct iphdr); | ||
offset += sizeof(struct iphdr); | ||
} else { | ||
is_v4 = false; | ||
l3hdr_len = sizeof(struct ipv6hdr); | ||
offset += sizeof(struct ipv6hdr); | ||
} | ||
if (flow.proto == IPPROTO_TCP) { | ||
l4hdr_len = sizeof(struct tcphdr); | ||
} else if (flow.proto == IPPROTO_UDP) { | ||
is_tcp = false; | ||
l4hdr_len = sizeof(struct udphdr); | ||
} else { | ||
LOG(ERROR) << "unsupported protocol: " << flow.proto | ||
<< " must be either TCP or UDP"; | ||
return nullptr; | ||
} | ||
pckt->append(sizeof(struct ethhdr) + l3hdr_len + l4hdr_len); | ||
if (is_v4) { | ||
createV4Packet(src, dst, pckt, flow.proto, l3_len); | ||
} else { | ||
createV6Packet(src, dst, pckt, flow.proto, l3_len); | ||
} | ||
if (is_tcp) { | ||
createTcpHeader(pckt, flow.srcPort, flow.dstPort, offset); | ||
} else { | ||
createUdpHeader(pckt, flow.srcPort, flow.dstPort, offset, | ||
l3_len - l3hdr_len); | ||
} | ||
return std::move(pckt); | ||
} | ||
|
||
} // namespace | ||
|
||
KatranSimulator::KatranSimulator(int progFd) : progFd_(progFd) {} | ||
|
||
KatranSimulator::~KatranSimulator() {} | ||
|
||
std::unique_ptr<folly::IOBuf> | ||
KatranSimulator::runSimulation(std::unique_ptr<folly::IOBuf> &pckt) { | ||
auto rpckt = folly::IOBuf::create(kMaxXdpPcktSize); | ||
if (rpckt == nullptr) { | ||
LOG(ERROR) << "was not able to allocate memory for resulting packet"; | ||
return rpckt; | ||
} | ||
uint32_t output_pckt_size{0}; | ||
uint32_t prog_ret_val{0}; | ||
auto res = BpfAdapter::testXdpProg( | ||
progFd_, kTestRepeatCount, pckt->writableData(), pckt->length(), | ||
rpckt->writableData(), &output_pckt_size, &prog_ret_val); | ||
if (res < 0) { | ||
LOG(ERROR) << "failed to run simulator"; | ||
return nullptr; | ||
} | ||
if (prog_ret_val != XDP_TX) { | ||
return nullptr; | ||
} | ||
rpckt->append(output_pckt_size); | ||
return std::move(rpckt); | ||
} | ||
|
||
const std::string KatranSimulator::getRealForFlow(const KatranFlow &flow) { | ||
auto pckt = createPacketFromFlow(flow); | ||
if (pckt == nullptr) { | ||
return kEmptyString.data(); | ||
} | ||
auto rpckt = runSimulation(pckt); | ||
if (rpckt == nullptr) { | ||
return kEmptyString.data(); | ||
} | ||
return getPcktDst(rpckt); | ||
} | ||
|
||
} // namespace katran |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#pragma once | ||
|
||
#include <folly/io/IOBuf.h> | ||
#include <memory> | ||
#include <string> | ||
|
||
namespace katran { | ||
|
||
/** | ||
* KatranFlow structs contains all the fields, which | ||
* are unique (from katran's point of view) for each flow | ||
*/ | ||
struct KatranFlow { | ||
// source ip address of the packet | ||
std::string src; | ||
// destination ip address of the packet | ||
std::string dst; | ||
uint16_t srcPort; | ||
uint16_t dstPort; | ||
// protocol number (e.g. 6 for TCP, 17 for UDP) | ||
uint8_t proto; | ||
}; | ||
|
||
/** | ||
* KatranSimulator allow end user to simulate what is going to happen | ||
* with specified packet after it is going to be processed by katran | ||
* load balancer. e.g. where (address of the real) this packet is going | ||
* to be sent | ||
*/ | ||
class KatranSimulator final { | ||
public: | ||
KatranSimulator() = delete; | ||
/** | ||
* @param int progFd descriptor of katran xdp program | ||
*/ | ||
explicit KatranSimulator(int progFd); | ||
~KatranSimulator(); | ||
|
||
/** | ||
* @param KatranFlow& flow which we are intersting in | ||
* @return string ip address of the real (or empty string if packet wont be | ||
* sent) | ||
* | ||
* getRealForFlow helps to answer the question (by returning ip address of the | ||
* real) "where specific flow is going to be sent" | ||
*/ | ||
const std::string getRealForFlow(const KatranFlow &flow); | ||
|
||
private: | ||
// runSimulation takes packet (in iobuf represenation) and | ||
// run it through katran bpf program. it returns modified pckt, if result | ||
// was XDP_TX or nullptr otherwise. | ||
std::unique_ptr<folly::IOBuf> | ||
runSimulation(std::unique_ptr<folly::IOBuf> &pckt); | ||
int progFd_; | ||
}; | ||
} // namespace katran |
Oops, something went wrong.