failed proof-of-concept to use eBPF TC or XDP program to do in-kernel forwardning of TURN UDP trafic

Signed-off-by: Steffen Vogel <post@steffenvogel.de>
This commit is contained in:
Steffen Vogel
2022-08-31 12:56:09 +02:00
parent 82ca0f4146
commit 6965a8c42b
27 changed files with 2652 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
NS_A = ip netns exec nsa
NS_A = ip netns exec nsa
all: run
run: setup main
ip netns exec ns_a ./main t1a
# ip netns exec ns_b ./main t1b
bpf/bpf_bpfeb.go: $(wildcard bpf/kern/*)
go generate ./...
main: bpf/bpf_bpfeb.go $(wildcard *.go bpf/*.go)
go build -o $@ ./
setup:
ip netns del ns_a || true
ip netns del ns_b || true
ip netns add ns_a
ip netns add ns_b
ip link add t1a netns ns_a type veth peer t1b netns ns_b
ip -n ns_a link set dev t1a up
ip -n ns_b link set dev t1b up
ip -n ns_a addr add 10.0.0.1/24 dev t1a
ip -n ns_b addr add 10.0.0.2/24 dev t1b
show:
tc -n ns_b filter show dev t1b ingress
tc -n ns_b filter show dev t1b egress
send:
echo "hello1234" | ip netns exec ns_a nc -p 3333 -u 10.0.0.2 1234
recv:
ip netns exec ns_b nc -lu 1234
dump-a:
ip netns exec ns_a tshark \
-o udp.check_checksum:TRUE \
-o ip.check_checksum:TRUE \
-i t1a -Vx
dump-b:
ip netns exec ns_b tshark \
-o udp.check_checksum:TRUE \
-o ip.check_checksum:TRUE \
-i t1b -Vx
+61
View File
@@ -0,0 +1,61 @@
package bpf
import (
"fmt"
"github.com/florianl/go-tc"
"github.com/florianl/go-tc/core"
"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)
func AttachTCFilters(tcnl *tc.Tc, ifIndex int, objs *Objects) error {
qdisc := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(ifIndex),
Handle: core.BuildHandle(tc.HandleRoot, 0x0000),
Parent: tc.HandleIngress,
Info: 0,
},
Attribute: tc.Attribute{
Kind: "clsact",
},
}
if err := tcnl.Qdisc().Add(&qdisc); err != nil {
return fmt.Errorf("could not assign clsact to %w", err)
}
m := map[int]uint32{
objs.Programs.EgressFilter.FD(): core.BuildHandle(tc.HandleRoot, tc.HandleMinEgress),
objs.Programs.IngressFilter.FD(): core.BuildHandle(tc.HandleRoot, tc.HandleMinIngress),
}
for fd, parent := range m {
flags := uint32(nl.TCA_BPF_FLAG_ACT_DIRECT)
fd2 := uint32(fd)
filter := tc.Object{
Msg: tc.Msg{
Family: unix.AF_UNSPEC,
Ifindex: uint32(ifIndex),
Handle: 0,
Parent: parent,
Info: 0x300,
},
Attribute: tc.Attribute{
Kind: "bpf",
BPF: &tc.Bpf{
FD: &fd2,
Flags: &flags,
},
},
}
if err := tcnl.Filter().Add(&filter); err != nil {
return fmt.Errorf("failed to attach filter for eBPF program: %w", err)
}
}
return nil
}
+133
View File
@@ -0,0 +1,133 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64
// +build arm64be armbe mips mips64 mips64p32 ppc64 s390 s390x sparc sparc64
package bpf
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type bpfState struct {
ChannelId uint16
Lport uint16
}
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj any, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
EgressFilter *ebpf.ProgramSpec `ebpf:"egress_filter"`
IngressFilter *ebpf.ProgramSpec `ebpf:"ingress_filter"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
EgressMap *ebpf.MapSpec `ebpf:"egress_map"`
IngressMap *ebpf.MapSpec `ebpf:"ingress_map"`
SettingsMap *ebpf.MapSpec `ebpf:"settings_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
EgressMap *ebpf.Map `ebpf:"egress_map"`
IngressMap *ebpf.Map `ebpf:"ingress_map"`
SettingsMap *ebpf.Map `ebpf:"settings_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.EgressMap,
m.IngressMap,
m.SettingsMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
EgressFilter *ebpf.Program `ebpf:"egress_filter"`
IngressFilter *ebpf.Program `ebpf:"ingress_filter"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.EgressFilter,
p.IngressFilter,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//go:embed bpf_bpfeb.o
var _BpfBytes []byte
Binary file not shown.
+133
View File
@@ -0,0 +1,133 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64
// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64
package bpf
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type bpfState struct {
ChannelId uint16
Lport uint16
}
// loadBpf returns the embedded CollectionSpec for bpf.
func loadBpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_BpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load bpf: %w", err)
}
return spec, err
}
// loadBpfObjects loads bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *bpfObjects
// *bpfPrograms
// *bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj any, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfSpecs struct {
bpfProgramSpecs
bpfMapSpecs
}
// bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfProgramSpecs struct {
EgressFilter *ebpf.ProgramSpec `ebpf:"egress_filter"`
IngressFilter *ebpf.ProgramSpec `ebpf:"ingress_filter"`
}
// bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type bpfMapSpecs struct {
EgressMap *ebpf.MapSpec `ebpf:"egress_map"`
IngressMap *ebpf.MapSpec `ebpf:"ingress_map"`
SettingsMap *ebpf.MapSpec `ebpf:"settings_map"`
}
// bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfObjects struct {
bpfPrograms
bpfMaps
}
func (o *bpfObjects) Close() error {
return _BpfClose(
&o.bpfPrograms,
&o.bpfMaps,
)
}
// bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfMaps struct {
EgressMap *ebpf.Map `ebpf:"egress_map"`
IngressMap *ebpf.Map `ebpf:"ingress_map"`
SettingsMap *ebpf.Map `ebpf:"settings_map"`
}
func (m *bpfMaps) Close() error {
return _BpfClose(
m.EgressMap,
m.IngressMap,
m.SettingsMap,
)
}
// bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type bpfPrograms struct {
EgressFilter *ebpf.Program `ebpf:"egress_filter"`
IngressFilter *ebpf.Program `ebpf:"ingress_filter"`
}
func (p *bpfPrograms) Close() error {
return _BpfClose(
p.EgressFilter,
p.IngressFilter,
)
}
func _BpfClose(closers ...io.Closer) error {
for _, closer := range closers {
if err := closer.Close(); err != nil {
return err
}
}
return nil
}
// Do not access this directly.
//go:embed bpf_bpfel.o
var _BpfBytes []byte
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
package bpf_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"riasc.eu/wice/internal/test"
)
func TestSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "BPF Suite")
}
var logger = test.SetupLogging()
+47
View File
@@ -0,0 +1,47 @@
/* Helpers for debugging */
#pragma once
#include <assert.h>
#define bpf_debug_printk(fmt, ...) \
({ \
if (unlikely(is_debug())) { \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
} \
})
static_assert(sizeof(struct ethhdr) == ETH_HLEN, "ethernet header size does not match.");
/*
* Since packet handling and printk can be interleaved, this will
* add a unique identifier for an individual invocation so you can grep the
* request identifier and see the log messags in isolation.
*
* This is a macro because in a real-example you might want to make this
* a no-op for non-debug builds to avoid the cost of the call.
*/
#define REQUEST_ID() bpf_get_prandom_u32()
#define DEBUG(x, ...) bpf_debug_printk(x, ##__VA_ARGS__)
#define DEBUG_INGRESS(id, x, ...) DEBUG("[ingress][%u] " x, id, ##__VA_ARGS__)
#define DEBUG_EGRESS(id, x, ...) DEBUG("[egress][%u] " x, id, ##__VA_ARGS__)
#include "maps.h"
#if 0
forced_inline
unsigned int is_debug() {
__u32 index = SETTING_DEBUG;
__u32 *value = (__u32 *) bpf_map_lookup_elem(&settings_map, &index);
if (!value)
return 0;
return 1; *value;
}
#else
forced_inline
unsigned int is_debug() {
return 1;
}
#endif
+204
View File
@@ -0,0 +1,204 @@
/* Egress filter */
#pragma once
#define MAX_MTU 40
#define MAX_PACKET_OFF 0xffff
SEC("egress") int egress_filter(struct __sk_buff *skb)
{
struct ethhdr *eth;
struct iphdr *iph;
struct udphdr *udp, udp_old;
struct turn_cdata *cdata;
struct state *state;
// Generate a unique request id so we can identify each flow in
// the trace logs
unsigned long long request_id = REQUEST_ID();
/*
* the redundant casts are needed according to the documentation.
* possibly for the BPF verifier.
* https://www.spinics.net/lists/xdp-newbies/msg00181.html
*/
void *data_end = (void *) (long) skb->data_end;
void *data = (void *) (long) skb->data;
// The packet starts with the ethernet header, so let's get that going:
eth = (struct ethhdr *) (data);
if ((void *) (eth + 1) > data_end)
return TC_ACT_SHOT;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
iph = (struct iphdr *) (void *) (eth + 1);
if ((void *) (iph + 1) > data_end)
return TC_ACT_SHOT;
if (iph->protocol != IPPROTO_UDP)
return TC_ACT_OK;
// multiply ip header by 4 (bytes) to get the number of bytes of the header.
int iph_len = iph->ihl << 2;
udp = (struct udphdr *) (void *) ((void *) (iph) + iph_len);
if ((void *) (udp + 1) > data_end)
return TC_ACT_SHOT;
__u16 dport = bpf_ntohs(udp->dest);
void *map = bpf_map_lookup_elem(&egress_map, &dport);
if (map == NULL)
return TC_ACT_OK;
DEBUG_EGRESS(request_id, "found entry in egress: %d\n", bpf_ntohs(udp->dest));
state = (struct state*) bpf_map_lookup_elem(map, &iph->daddr);
if (state == NULL)
return TC_ACT_OK;
// Rewrite destination port
if (state->lport != 0) {
__u16 udp_old_port = udp->dest;
__u16 udp_new_port = bpf_htons(state->lport);
DEBUG_EGRESS(request_id, "rewriting destination port: %d => %d", bpf_ntohs(udp_old_port), bpf_ntohs(udp_new_port));
udp->dest = udp_new_port;
bpf_l4_csum_replace(skb, UDP_CSUM_OFF, udp_old_port, udp_new_port, 2);
}
if (state->channel_id != 0) {
DEBUG_EGRESS(request_id, "inserting turn channel id: %d", state->channel_id);
int pad_len = sizeof(struct turn_cdata);
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
udp = (struct udphdr *) (void *) (data + sizeof(struct ethhdr) + iph_len);
if ((void *) (udp + 1) > data_end)
return TC_ACT_SHOT;
__u16 newlen = sizeof(struct ethhdr) + iph_len + bpf_ntohs(udp->len) + pad_len;
// Make space for TURN channel data indication header
int ret = bpf_skb_change_tail(skb, newlen, 0);
if (ret) {
DEBUG_EGRESS(request_id, "failed bpf_skb_change_tail");
return TC_ACT_SHOT;
}
// Fix length field in IP header
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
iph = (struct iphdr *) (void *) (data + sizeof(struct ethhdr));
if ((void *) (iph + 1) > data_end)
return TC_ACT_SHOT;
__u16 iph_old_len = iph->tot_len;
__u16 iph_new_len = bpf_htons(bpf_ntohs(iph->tot_len) + pad_len);
iph->tot_len = iph_new_len;
// Adjust L3 checksum
bpf_l3_csum_replace(skb, IP_CSUM_OFF, iph_old_len, iph_new_len, 2);
// Update pointer to new UDP header
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
udp = (struct udphdr *) (void *) (data + sizeof(struct ethhdr) + iph_len);
if ((void *) (udp + 1) > data_end) {
DEBUG_EGRESS(request_id, "drop");
return TC_ACT_SHOT;
}
// Fix length field in UDP header
__u16 udp_old_len = udp->len;
__u16 udp_old_len_h = bpf_ntohs(udp_old_len);
__u16 udp_new_len = bpf_htons(udp_old_len_h + pad_len);
udp->len = udp_new_len;
#if 0
__u16 pl_len = udp_old_len_h - sizeof(struct udphdr);
char *pl = (char *) (udp + 1);
char buf[256];
__u16 pl_off = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr);
if (pl_len > 256)
return TC_ACT_SHOT;
ret = bpf_skb_load_bytes(skb, pl_off, buf, 5);
if (ret) {
DEBUG_EGRESS(request_id, "failed bpf_skb_load_bytes");
return TC_ACT_SHOT;
}
ret = bpf_skb_store_bytes(skb, pl_off + pad_len, buf, 5, 0);
if (ret) {
DEBUG_EGRESS(request_id, "failed bpf_skb_store_bytes");
return TC_ACT_SHOT;
}
#else
__u16 pl_len = bpf_ntohs(udp->len) - sizeof(struct udphdr) - pad_len;
DEBUG_EGRESS(request_id, "pad_len %d", pad_len);
DEBUG_EGRESS(request_id, "pl_len %d", pl_len);
DEBUG_EGRESS(request_id, "data_end %u", (__u64) data_end);
DEBUG_EGRESS(request_id, "pl %u", (__u64) pl);
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
char *pl = (char *) (udp + 1);
__u32 *src = (__u32 *) pl;
__u32 *dst = (__u32 *) (pl + pad_len);
__u32 temp = *dst;
for(__u32 i = 0; i < MAX_MTU; i += sizeof(temp)) {
if (i >= pl_len) {
break;
}
if ((void *) (dst + 1) > data_end)
return TC_ACT_SHOT;
if ((void *) (src + 1) > data_end)
return TC_ACT_SHOT;
*dst++ = temp;
temp = *dst;
DEBUG_EGRESS(request_id, "assign %u = %u", (__u64) (dst), (__u64)(src));
}
#endif
// Construct TURN channel data indication header
cdata = (struct turn_cdata *) pl;
if ((void *) (cdata + 1) > data_end)
return TC_ACT_SHOT;
struct turn_cdata cd = {
.ch_num = bpf_htons(0xAABB),
.len = bpf_htons(0xCCDD)
};
*cdata = cd;
// Adjust L4 checksum
bpf_l4_csum_replace(skb, UDP_CSUM_OFF, udp_old_len, udp_new_len, 2);
bpf_l4_csum_replace(skb, UDP_CSUM_OFF, iph_old_len, iph_new_len, BPF_F_PSEUDO_HDR | 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, 0, cd.ch_num, 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, 0, cd.len, 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, 0, 16, 2);
}
// return bpf_redirect(1, BPF_F_INGRESS);
return TC_ACT_OK;
}
static char _license[] SEC("license") = "GPL";
+145
View File
@@ -0,0 +1,145 @@
/* Egress filter */
#pragma once
SEC("egress") int egress_filter(struct __sk_buff *skb)
{
struct ethhdr *eth;
struct iphdr *iph;
struct udphdr *udp, udp_old;
struct turn_cdata *cdata;
struct state *state;
// Generate a unique request id so we can identify each flow in
// the trace logs
unsigned long long request_id = REQUEST_ID();
/*
* the redundant casts are needed according to the documentation.
* possibly for the BPF verifier.
* https://www.spinics.net/lists/xdp-newbies/msg00181.html
*/
void *data_end = (void *) (long) skb->data_end;
void *data = (void *) (long) skb->data;
// The packet starts with the ethernet header, so let's get that going:
eth = (struct ethhdr *) (data);
if ((void *) (eth + 1) > data_end)
return TC_ACT_SHOT;
if (eth->h_proto != bpf_htons(ETH_P_IP))
return TC_ACT_OK;
iph = (struct iphdr *) (void *) (eth + 1);
if ((void *) (iph + 1) > data_end)
return TC_ACT_SHOT;
if (iph->protocol != IPPROTO_UDP)
return TC_ACT_OK;
// multiply ip header by 4 (bytes) to get the number of bytes of the header.
int iph_len = iph->ihl << 2;
udp = (struct udphdr *) (void *) ((void *) (iph) + iph_len);
if ((void *) (udp + 1) > data_end)
return TC_ACT_SHOT;
__u16 dport = bpf_ntohs(udp->dest);
void *map = bpf_map_lookup_elem(&egress_map, &dport);
if (map == NULL)
return TC_ACT_OK;
DEBUG_EGRESS(request_id, "found entry in egress: %d\n", bpf_ntohs(udp->dest));
state = (struct state*) bpf_map_lookup_elem(map, &iph->daddr);
if (state == NULL)
return TC_ACT_OK;
// Rewrite destination port
if (state->lport != 0) {
__u16 udp_old_port = udp->dest;
__u16 udp_new_port = bpf_htons(state->lport);
DEBUG_EGRESS(request_id, "rewriting destination port: %d => %d", bpf_ntohs(udp_old_port), bpf_ntohs(udp_new_port));
udp->dest = udp_new_port;
bpf_l4_csum_replace(skb, UDP_CSUM_OFF, udp_old_port, udp_new_port, 2);
}
if (state->channel_id != 0) {
DEBUG_EGRESS(request_id, "inserting turn channel id: %d", state->channel_id);
int padlen = sizeof(struct turn_cdata);
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
udp = (struct udphdr *) (void *) (data + sizeof(struct ethhdr) + iph_len);
if ((void *) (udp + 1) > data_end)
return TC_ACT_SHOT;
udp_old = *udp;
// Make space for TURN channel data indication header
int ret = bpf_skb_adjust_room(skb, padlen, BPF_ADJ_ROOM_NET, BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 | BPF_F_ADJ_ROOM_ENCAP_L4_UDP);
if (ret) {
DEBUG_EGRESS(request_id, "failed bpf_skb_adjust_room");
return TC_ACT_SHOT;
}
// Fix length field in IP header
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
iph = (struct iphdr *) (void *) (data + sizeof(struct ethhdr));
if ((void *) (iph + 1) > data_end)
return TC_ACT_SHOT;
__u16 iph_old_len = iph->tot_len;
__u16 iph_new_len = bpf_htons(bpf_ntohs(iph->tot_len) + padlen);
iph->tot_len = iph_new_len;
// Adjust L3 checksum
bpf_l3_csum_replace(skb, IP_CSUM_OFF, iph_old_len, iph_new_len, 2);
// Update pointer to new UDP header
data_end = (void *) (long) skb->data_end;
data = (void *) (long) skb->data;
udp = (struct udphdr *) (void *) (data + sizeof(struct ethhdr) + iph_len);
if ((void *) (udp + 1) > data_end)
return TC_ACT_SHOT;
// Restore previous UDP header
*udp = udp_old;
// Fix length field in UDP header
__u16 udp_old_len = udp_old.len;
__u16 udp_new_len = bpf_htons(bpf_ntohs(udp->len) + padlen);
udp->len = udp_new_len;
// Construct TURN channel data indication header
cdata = (struct turn_cdata*) (void *) (udp + 1);
if ((void *) (cdata + 1) > data_end)
return TC_ACT_SHOT;
struct turn_cdata cd = {
.ch_num = bpf_htons(0xAABB),
.len = bpf_htons(0xCCDD)
};
*cdata = cd;
// Adjust L4 checksum
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, udp_old_len, udp_new_len, 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, iph_old_len, iph_new_len, BPF_F_PSEUDO_HDR | 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, 0, cd.ch_num, 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, 0, cd.len, 2);
// bpf_l4_csum_replace(skb, UDP_CSUM_OFF, 0, 16, 2);
}
// return bpf_redirect(1, BPF_F_INGRESS);
return TC_ACT_OK;
}
static char _license[] SEC("license") = "GPL";
+45
View File
@@ -0,0 +1,45 @@
/**
* A collection of useful definitions when writing eBPF.
* Some of these were taken from https://github.com/iovisor/bcc/blob/master/src/cc/export/helpers.h
*/
#pragma once
/**
* This means that tc will pin the map into the BPF pseudo file system as a node.
* Due to the PIN_GLOBAL_NS, the map will be placed under /sys/fs/bpf/tc/globals/$MAP
*/
#define PIN_GLOBAL_NS 2
#define IP_CSUM_OFF (sizeof(struct ethhdr) + offsetof(struct iphdr, check))
#define UDP_CSUM_OFF (sizeof(struct ethhdr) + sizeof(struct iphdr) + offsetof(struct udphdr, check))
/**
* Aside from BPF helper calls and BPF tail calls, the BPF instruction did not arbitrary
* support functions -- as a result all functions need the inline macro.
* Starting with Linux kernel 4.16 and LLVM 6.0 this restriction got lifted.
* The typical inline keyword is only a hint whereas this is definitive.
*/
#define forced_inline __attribute__((always_inline))
/*
* helper macro to make it simpler to print trace messages to
* bpf_trace_printk.
* ex. bpf_printk("BPF command: %d\n", op);
* you can find the output in /sys/kernel/debug/tracing/trace_pipe
* however it will collide with any othe rrunning process.
*/
#define bpf_printk(fmt, ...) \
({ \
char ____fmt[] = fmt; \
bpf_trace_printk(____fmt, sizeof(____fmt), \
##__VA_ARGS__); \
})
/*
* The __builtin_expect macros are GCC specific macros that use the branch prediction;
* they tell the processor whether a condition is likely to be true,
* so that the processor can prefetch instructions on the correct "side" of the branch.
*/
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
+99
View File
@@ -0,0 +1,99 @@
/* Egress filter */
#pragma once
/*
* The Internet Protocol (IP) is defined in RFC 791.
* The RFC specifies the format of the IP header.
* In the header there is the IHL (Internet Header Length) field which is 4bit
* long
* and specifies the header length in 32bit words.
* The IHL field can hold values from 0 (Binary 0000) to 15 (Binary 1111).
* 15 * 32bits = 480bits = 60 bytes
*/
#define MAX_IP_HDR_LEN 60
SEC(".ingress") int ingress_filter(struct __sk_buff *skb) {
// Generate a unique request id so we can identify each flow in
// the trace logs
unsigned long long request_id = REQUEST_ID();
/*
* the redundant casts are needed according to the documentation.
* possibly for the BPF verifier.
* https://www.spinics.net/lists/xdp-newbies/msg00181.html
*/
void *data_end = (void *) (long) skb->data_end;
void *data = (void *) (long) skb->data;
// The packet starts with the ethernet header, so let's get that going:
struct ethhdr *eth = (struct ethhdr *)(data);
/*
* Now, we can't just go "eth->h_proto", that's illegal. We have to
* explicitly test that such an access is in range and doesn't go
* beyond "data_end" -- again for the verifier.
* The eBPF verifier will see that "eth" holds a packet pointer,
* and also that you have made sure that from "eth" to "eth + 1"
* is inside the valid access range for the packet.
*/
if ((void *)(eth + 1) > data_end) {
return TC_ACT_SHOT;
}
/*
* We only care about IP packet frames. Don't do anything to other ethernet
* packets like ARP.
* hton -> host to network order. Network order is always big-endian.
* pedantic: the protocol is also directly accessible from __sk_buf
*/
if (eth->h_proto != bpf_htons(ETH_P_IP)) {
return TC_ACT_OK;
}
struct iphdr *iph = (struct iphdr *)(void *)(eth + 1);
if ((void *)(iph + 1) > data_end) {
return TC_ACT_SHOT;
}
// multiply ip header by 4 (bytes) to get the number of bytes of the header.
int iph_len = iph->ihl << 2;
if (iph_len > MAX_IP_HDR_LEN) {
return TC_ACT_SHOT;
}
if (iph->protocol != IPPROTO_UDP) {
return TC_ACT_OK;
}
struct udphdr *udp = (struct udphdr *)((void *)(iph) + iph_len);
if ((void *)(udp + 1) > data_end) {
return TC_ACT_SHOT;
}
if (udp->dest != bpf_htons(2222))
return TC_ACT_OK;
DEBUG_INGRESS(request_id, "found ingress data!!!!!!!.\n");
/*
* This is the amount of padding we need to remove to be just left
* with eth * iphdr.
*/
// int padlen = sizeof(struct turn_cdata);
/*
* Grow or shrink the room for data in the packet associated to
* skb by length and according to the selected mode.
* BPF_ADJ_ROOM_NET: Adjust room at the network layer
* (room space is added or removed below the layer 3 header).
*/
// int ret = bpf_skb_adjust_room(skb, -padlen, BPF_ADJ_ROOM_NET, 0);
// if (ret) {
// DEBUG_INGRESS(request_id, "error calling skb adjust room.\n");
// return TC_ACT_SHOT;
// }
return TC_ACT_OK;
}
+40
View File
@@ -0,0 +1,40 @@
/*******************************************************************************************
* MPLSinIP eBPF
* This file contains a BPF (Berkeley Packet Filter) for use within tc
* (traffic-control).
*
* BPF is a virtual-machine within the Linux kernel that supports a limited
* instruction set (not Turing complete). It allows user supplied code to be
* executed during key points within the kernel. The kernel verifies all BPF
* programs so that they don't address invalid memory & that the time spent in
* the BPF program is limited by dis-allowing loops & setting a maximum number
* of instructions.
*
*
* ----------------------------------------------------------------------------------------
* eBPF Guide & Checklist
*
* 1. eBPF does not support method calls so any function called from the
* entry-point needs to be inlined.
* 2. The kernel can JIT the eBPF however prior to 4.15, it was off by default
* (value 0). echo 1 > /proc/sys/net/core/bpf_jit_enable
*
* @author Farid Zakaria <farid.m.zakaria\@gmail.com>
*******************************************************************************************/
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/pkt_cls.h>
#include <linux/bpf.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_helpers.h>
#include "helpers.h"
#include "types.h"
#include "debug.h"
#include "maps.h"
#include "egress.h"
#include "ingress.h"
+34
View File
@@ -0,0 +1,34 @@
/* Definition of maps */
#pragma once
#include "types.h"
// For including the type into the BTF output
// So bpf2go can use it for generating the type in our Go wrapper
// typedef struct state state;
const struct state *_unused_state __attribute__((unused));
/**
* A really simple BPF map that controls a switch
* whether the debug printk messages are emitted.
*/
struct bpf_elf_map SEC("maps") settings_map = {
.type = BPF_MAP_TYPE_ARRAY,
.size_key = sizeof(__u32),
.size_value = sizeof(__u32),
.max_elem = SETTING_LAST,
};
struct bpf_elf_map SEC("maps") ingress_map = {
.type = BPF_MAP_TYPE_HASH_OF_MAPS,
.size_key = sizeof(__u16),
.size_value = sizeof(struct state),
.max_elem = 1 << 12,
};
struct bpf_elf_map SEC("maps") egress_map = {
.type = BPF_MAP_TYPE_HASH_OF_MAPS,
.size_key = sizeof(__u16),
.size_value = sizeof(struct state),
.max_elem = 1 << 12,
};
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <linux/types.h>
#define bool _Bool
// https://www.rfc-editor.org/rfc/rfc8489.html#section-5
const __u32 stun_cookie = 0x2112A442;
enum setting {
SETTING_DEBUG = 0,
SETTING_LAST
};
struct state {
__u16 channel_id;
__u16 lport;
};
struct stun_hdr {
__be16 msg_type;
__be16 len;
__u32 cookie;
__u32 tid[3];
};
// https://www.rfc-editor.org/rfc/rfc8656.html#name-the-channeldata-message
struct turn_cdata {
__be16 ch_num;
__be16 len;
};
/*
* ELF map definition used by iproute2.
* Cannot figure out how to get bpf_elf.h installed on system, so we've copied it here.
* iproute2 claims this struct will remain backwards compatible
* https://github.com/kinvolk/iproute2/blob/be55416addf76e76836af6a4dd94b19c4186e1b2/include/bpf_elf.h
*/
struct bpf_elf_map {
/*
* The various BPF MAP types supported (see enum bpf_map_type)
* https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/bpf.h
*/
__u32 type;
__u32 size_key;
__u32 size_value;
__u32 max_elem;
/*
* Various flags you can place such as `BPF_F_NO_COMMON_LRU`
*/
__u32 flags;
__u32 id;
/*
* Pinning is how the map are shared across process boundary.
* Cillium has a good explanation of them: http://docs.cilium.io/en/v1.3/bpf/#llvm
* PIN_GLOBAL_NS - will get pinned to `/sys/fs/bpf/tc/globals/${variable-name}`
* PIN_OBJECT_NS - will get pinned to a directory that is unique to this object
* PIN_NONE - the map is not placed into the BPF file system as a node,
and as a result will not be accessible from user space
*/
__u32 pinning;
};
+46
View File
@@ -0,0 +1,46 @@
package bpf
import (
"fmt"
"unsafe"
"github.com/cilium/ebpf"
)
var stateMapInnerSpec = ebpf.MapSpec{
Type: ebpf.Hash,
KeySize: 4,
ValueSize: uint32(unsafe.Sizeof(MapStateEntry{})),
MaxEntries: 1 << 12,
}
func Load() (*Objects, error) {
cs, err := loadBpf()
if err != nil {
return nil, fmt.Errorf("failed to load collection spec: %s", err)
}
for _, p := range cs.Programs {
p.Type = ebpf.SchedCLS
}
for n, m := range cs.Maps {
if n == "ingress_map" || n == "egress_map" {
m.InnerMap = &stateMapInnerSpec
}
}
objs := &bpfObjects{}
if err := cs.LoadAndAssign(objs, nil); err != nil {
return nil, fmt.Errorf("failed to load programs: %w", err)
}
return &Objects{
Maps: Maps{
SettingsMap: MapSettings{Map: objs.bpfMaps.SettingsMap},
IngressMap: MapState{Map: objs.bpfMaps.IngressMap},
EgressMap: MapState{Map: objs.bpfMaps.EgressMap},
},
Programs: objs.bpfPrograms,
}, nil
}
+124
View File
@@ -0,0 +1,124 @@
package bpf
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go@master -type state bpf kern/main.c
import (
"github.com/cilium/ebpf"
// #include "kern/types.h"
"C"
)
import (
"errors"
"fmt"
"net"
)
type Objects struct {
Programs bpfPrograms
Maps Maps
}
func (o *Objects) Close() error {
if err := o.Maps.Close(); err != nil {
return err
}
if err := o.Programs.Close(); err != nil {
return err
}
return nil
}
type Maps struct {
EgressMap MapState
IngressMap MapState
SettingsMap MapSettings
}
func (m *Maps) Close() error {
if err := m.EgressMap.Close(); err != nil {
return err
}
if err := m.IngressMap.Close(); err != nil {
return err
}
if err := m.SettingsMap.Close(); err != nil {
return err
}
return nil
}
type MapSettings struct {
*ebpf.Map
}
func (m *MapSettings) EnableDebug() error {
return m.Put(uint32(C.SETTING_DEBUG), uint32(1))
}
type MapState struct {
*ebpf.Map
}
type MapStateEntry = bpfState
func (m *MapState) AddEntry(addr *net.UDPAddr, me *MapStateEntry) error {
inner, err := m.getOrCreateInnerMap(addr)
if err != nil {
return err
}
return inner.Update(addr.IP.To4(), me, ebpf.UpdateNoExist)
}
func (m *MapState) GetEntry(addr *net.UDPAddr) (*MapStateEntry, error) {
inner, err := m.getInnerMap(addr)
if err != nil {
return nil, err
}
var me MapStateEntry
return &me, inner.Lookup(addr.IP.To4(), &me)
}
func (m *MapState) DeleteEntry(addr *net.UDPAddr) error {
inner, err := m.getInnerMap(addr)
if err != nil {
return err
}
return inner.Delete(addr.IP.To4())
}
func (m *MapState) getOrCreateInnerMap(addr *net.UDPAddr) (*ebpf.Map, error) {
inner, err := m.getInnerMap(addr)
if errors.Is(err, ebpf.ErrKeyNotExist) {
if inner, err = ebpf.NewMap(&stateMapInnerSpec); err != nil {
return nil, fmt.Errorf("failed to create new inner map: %w", err)
}
up := uint16(addr.Port)
if err := m.Put(&up, inner); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
return inner, nil
}
func (m *MapState) getInnerMap(addr *net.UDPAddr) (*ebpf.Map, error) {
up := uint16(addr.Port)
var inner *ebpf.Map
if err := m.Lookup(&up, &inner); err != nil {
return nil, err
}
return inner, nil
}
+359
View File
@@ -0,0 +1,359 @@
package bpf_test
import (
"encoding/binary"
"fmt"
"io"
"net"
"os"
"time"
"riasc.eu/wice/tc_test/bpf"
"github.com/cilium/ebpf"
"github.com/florianl/go-tc"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
g "github.com/stv0g/gont/pkg"
o "github.com/stv0g/gont/pkg/options"
"github.com/vishvananda/netlink"
)
func logTShark(hs ...*g.Host) {
for _, h := range hs {
stdout, _, _, err := h.Start("tshark", "-o", "udp.check_checksum:TRUE", "-o", "ip.check_checksum:TRUE", "-i", "eth0", "-V", "-x")
Expect(err).To(Succeed())
fn := fmt.Sprintf("tshark_%s.log", h.Name())
f, _ := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
go io.Copy(f, stdout)
}
time.Sleep(2 * time.Second)
}
var _ = Describe("bpf", Ordered, func() {
var err error
var n *g.Network
var h1, h2 *g.Host
var objs *bpf.Objects
BeforeAll(func() {
n, err = g.NewNetwork("", o.Persistent(true))
Expect(err).To(Succeed())
h1, err = n.AddHost("h1")
Expect(err).To(Succeed())
h2, err = n.AddHost("h2")
Expect(err).To(Succeed())
err = n.AddLink(
o.Interface("eth0", h1,
o.AddressIP("10.0.0.1/24"),
),
o.Interface("eth0", h2,
o.AddressIP("10.0.0.2/24"),
),
)
Expect(err).To(Succeed())
})
It("can ping between the hosts", func() {
stats, err := h1.Ping(h2)
Expect(err).To(Succeed())
Expect(stats.MaxRtt).To(BeNumerically("<", 10*time.Millisecond))
})
Describe("ebpf", Ordered, func() {
BeforeAll(func() {
objs, err = bpf.Load()
Expect(err).To(Succeed())
objs.Maps.SettingsMap.EnableDebug()
})
AfterAll(func() {
Expect(objs.Close()).To(Succeed())
})
It("has loaded properly", func() {
Expect(objs.Programs.EgressFilter.Type()).To(Equal(ebpf.SchedCLS))
})
Context("maps", Ordered, func() {
var addr = &net.UDPAddr{
IP: net.ParseIP("1.2.3.4"),
Port: 1234,
}
var me = &bpf.MapStateEntry{
ChannelId: 1,
Lport: 2222,
}
It("can put an entry in the map", func() {
Expect(objs).NotTo(BeNil())
err := objs.Maps.EgressMap.AddEntry(addr, me)
Expect(err).To(Succeed())
})
It("should fail to add the same entry again", func() {
err := objs.Maps.EgressMap.AddEntry(addr, &bpf.MapStateEntry{
ChannelId: 2,
Lport: 3333,
})
Expect(err).To(HaveOccurred())
})
It("can retrieve the entry again", func() {
me2, err := objs.Maps.EgressMap.GetEntry(addr)
Expect(err).To(Succeed())
Expect(me).To(Equal(me2))
})
It("can delete an entry from the map", func() {
err := objs.Maps.EgressMap.DeleteEntry(addr)
Expect(err).To(Succeed())
})
It("is not in the map after the delete", func() {
_, err := objs.Maps.EgressMap.GetEntry(addr)
Expect(err).To(MatchError(ebpf.ErrKeyNotExist))
})
})
Context("egress filtering", Ordered, func() {
var tcnl *tc.Tc
var link netlink.Link
var addr = &net.UDPAddr{
Port: 1234,
}
var me = &bpf.MapStateEntry{
ChannelId: 0,
Lport: 2222,
}
var bufSend []byte
BeforeAll(func() {
addr.IP = h2.Interfaces[1].Addresses[0].IP
})
BeforeAll(func() {
// Prepare some test data
bufSend = make([]byte, 128)
for i := 0; i < cap(bufSend); i++ {
bufSend[i] = byte(i)
}
})
// Attach filters
BeforeAll(func() {
link, err = h1.NetlinkHandle().LinkByName("eth0")
Expect(err).To(Succeed(), "could not get interface ID: %v\n", err)
tcnl, err = tc.Open(&tc.Config{
NetNS: int(h1.NsHandle),
})
Expect(err).To(Succeed(), "Failed to open rtnetlink socket: %v\n", err)
err := bpf.AttachTCFilters(tcnl, link.Attrs().Index, objs)
Expect(err).To(Succeed())
})
AfterAll(func() {
Expect(tcnl.Close()).To(Succeed())
})
// Configure maps
BeforeAll(func() {
Expect(objs.Maps.EgressMap.AddEntry(addr, me)).To(Succeed())
})
// AfterAll(func() {
// Expect(objs.Maps.EgressMap.DeleteEntry(addr)).To(Succeed())
// })
It("still ping", func() {
stats, err := h1.Ping(h2)
Expect(err).To(Succeed())
Expect(stats.MaxRtt).To(BeNumerically("<", 10*time.Millisecond))
})
It("performs a port redirect", func() {
done := make(chan any)
listening := make(chan any)
// logTShark(h1, h2)
fmt.Scanln()
time.Sleep(6 * time.Second)
go h2.RunFunc(func() error {
defer GinkgoRecover()
conn, err := net.ListenUDP("udp4", &net.UDPAddr{
Port: 2222,
})
Expect(err).To(Succeed())
close(listening)
bufRecv := make([]byte, 128)
_, _, err = conn.ReadFrom(bufRecv)
Expect(err).To(Succeed())
Expect(bufRecv).To(Equal(bufSend))
close(done)
return nil
})
time.Sleep(time.Second)
h1.RunFunc(func() error {
conn, err := net.DialUDP("udp4", &net.UDPAddr{Port: 52722}, &net.UDPAddr{
Port: 1234,
IP: net.ParseIP("10.0.0.2"),
})
Expect(err).To(Succeed())
_, err = conn.Write(bufSend)
Expect(err).To(Succeed())
return nil
})
Eventually(done).WithTimeout(10 * time.Second).Should(BeClosed())
})
})
})
Context("egress filtering with TURN channel data indication prefix", Ordered, func() {
var tcnl *tc.Tc
var link netlink.Link
var addr = &net.UDPAddr{
Port: 1234,
}
var me = &bpf.MapStateEntry{
ChannelId: 0xABAB,
Lport: 2222,
}
var bufSend []byte
var bufExpect []byte
BeforeAll(func() {
addr.IP = h2.Interfaces[1].Addresses[0].IP
})
BeforeAll(func() {
// Prepare some test data
bufSend = make([]byte, 128)
for i := 0; i < cap(bufSend); i++ {
bufSend[i] = byte(i)
}
bufExpect = make([]byte, 128+4)
binary.BigEndian.PutUint16(bufExpect[0:], me.ChannelId)
binary.BigEndian.PutUint16(bufExpect[2:], 0xCCDD)
for i := 4; i < cap(bufSend); i++ {
bufExpect[i] = byte(i - 4)
}
})
// Attach filters
BeforeAll(func() {
link, err = h1.NetlinkHandle().LinkByName("eth0")
Expect(err).To(Succeed(), "could not get interface ID: %v\n", err)
tcnl, err = tc.Open(&tc.Config{
NetNS: int(h1.NsHandle),
})
Expect(err).To(Succeed(), "Failed to open rtnetlink socket: %v\n", err)
err := bpf.AttachTCFilters(tcnl, link.Attrs().Index, objs)
Expect(err).To(Succeed())
})
AfterAll(func() {
Expect(tcnl.Close()).To(Succeed())
})
// Configure maps
BeforeAll(func() {
Expect(objs.Maps.EgressMap.AddEntry(addr, me)).To(Succeed())
})
// AfterAll(func() {
// Expect(objs.Maps.EgressMap.DeleteEntry(addr)).To(Succeed())
// })
It("still ping", func() {
stats, err := h1.Ping(h2)
Expect(err).To(Succeed())
Expect(stats.MaxRtt).To(BeNumerically("<", 10*time.Millisecond))
})
It("performs a port redirect", func() {
done := make(chan any)
listening := make(chan any)
// logTShark(h1, h2)
fmt.Scanln()
time.Sleep(6 * time.Second)
go h2.RunFunc(func() error {
defer GinkgoRecover()
conn, err := net.ListenUDP("udp4", &net.UDPAddr{
Port: 2222,
})
Expect(err).To(Succeed())
close(listening)
bufRecv := make([]byte, 128)
_, _, err = conn.ReadFrom(bufRecv)
Expect(err).To(Succeed())
Expect(bufRecv).To(Equal(bufExpect))
close(done)
return nil
})
time.Sleep(time.Second)
h1.RunFunc(func() error {
conn, err := net.DialUDP("udp4", &net.UDPAddr{Port: 52722}, &net.UDPAddr{
Port: 1234,
IP: net.ParseIP("10.0.0.2"),
})
Expect(err).To(Succeed())
_, err = conn.Write(bufSend)
Expect(err).To(Succeed())
return nil
})
Eventually(done).WithTimeout(10 * time.Second).Should(BeClosed())
})
})
AfterAll(func() {
Expect(n.Close()).To(Succeed())
})
})
+49
View File
@@ -0,0 +1,49 @@
package main
import (
"encoding/binary"
"encoding/hex"
"fmt"
"strings"
)
// 08 ae
// 04 d2 Port 1234
// 14 20 Old Checksum
//const hexStr = "45 00 00 20 | 48 e9 40 00 | 40 11 dd e1 | 0a 00 00 01 | 0a 00 00 02"
const hexStr = "cd f2 04 d2 00 0c 00 00 00 00 00 00"
const pseudoHdr = "0a 00 00 01 0a 00 00 02 00 11 00 0c"
func main() {
hexBytesPseudoHdr, err := hex.DecodeString(strings.ReplaceAll(pseudoHdr, " ", ""))
if err != nil {
panic(err)
}
hexBytesUDP, err := hex.DecodeString(strings.ReplaceAll(hexStr, " ", ""))
if err != nil {
panic(err)
}
allBytes := append(hexBytesPseudoHdr, hexBytesUDP...)
fmt.Printf("Bytes: %v\n", allBytes)
if len(allBytes)%2 != 0 {
hexBytesUDP = append(allBytes, 0)
}
csum := uint32(0)
for i := 0; i < len(allBytes); i = i + 2 {
csum += uint32(binary.BigEndian.Uint16(allBytes[i:]))
}
csum += csum >> 16
csum &= 0xffff
csum = ^csum
csum16 := uint16(csum)
fmt.Printf("Csum: %#x\n", csum16)
}
@@ -0,0 +1,287 @@
package net
import (
"fmt"
"net"
"time"
"github.com/cilium/ebpf"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/mdlayher/socket"
"go.uber.org/zap"
"golang.org/x/net/bpf"
"golang.org/x/sys/unix"
)
const (
// Socket option to attach a classic BPF program to the socket for
// use as a filter of incoming packets.
SO_ATTACH_FILTER int = 26
// Socket option to attach an extended BPF program to the socket for
// use as a filter of incoming packets.
SO_ATTACH_BPF int = 50
)
// Filter represents a classic BPF filter program that can be applied to a socket
type Filter []bpf.Instruction
type FilteredUDPConn struct {
conn4 *socket.Conn
conn6 *socket.Conn
running4 bool
running6 bool
packets chan packet
localPort int
logger *zap.Logger
}
func (f *FilteredUDPConn) LocalAddr() net.Addr {
return &net.UDPAddr{
IP: net.IPv4zero,
Port: f.localPort,
}
}
type packet struct {
N int
Address unix.Sockaddr
Buffer []byte
Error error
}
func (f *FilteredUDPConn) read(conn *socket.Conn, running *bool) {
*running = true
for {
buf := make([]byte, 1500)
if n, ra, err := conn.Recvfrom(buf, 0); err == nil {
buf = buf[:n]
f.packets <- packet{n, ra, buf, err}
} else {
f.packets <- packet{n, ra, buf, err}
break
}
}
*running = false
}
func (f *FilteredUDPConn) ReadFrom(buf []byte) (n int, addr net.Addr, err error) {
// Wait for the next packet either from the IPv4/IPv6 connection
pkt := <-f.packets
if err, ok := pkt.Error.(net.Error); ok && err.Timeout() {
return -1, nil, err
}
var ip net.IP
var decoder gopacket.Decoder
if sa, isIPv6 := pkt.Address.(*unix.SockaddrInet6); isIPv6 {
ip = sa.Addr[:]
decoder = layers.LayerTypeUDP
} else if sa, isIPv4 := pkt.Address.(*unix.SockaddrInet4); isIPv4 {
decoder = layers.LayerTypeIPv4
ip = sa.Addr[:]
} else {
return -1, nil, fmt.Errorf("received invalid address family")
}
packet := gopacket.NewPacket(pkt.Buffer, decoder, gopacket.DecodeOptions{
Lazy: true,
NoCopy: true,
})
// f.logger.Debug("Received packet",
// zap.Any("remote_address", ip),
// zap.Any("buf", hex.EncodeToString(pkt.Buffer)),
// zap.Any("decoder", decoder),
// )
// logWr := zapio.Writer{
// Log: f.logger,
// Level: zap.DebugLevel,
// }
// logWr.Write([]byte(packet.Dump()))
transport := packet.TransportLayer()
if transport == nil {
return -1, nil, fmt.Errorf("failed to decode packet")
}
udp, ok := transport.(*layers.UDP)
if !ok {
return -1, nil, fmt.Errorf("invalid layer type")
}
pl := packet.ApplicationLayer()
n = len(pl.Payload())
copy(buf[:n], pl.Payload()[:])
rUDPAddr := &net.UDPAddr{
IP: ip,
Port: int(udp.SrcPort),
}
return n, rUDPAddr, nil
}
func (f *FilteredUDPConn) WriteTo(buf []byte, rAddr net.Addr) (n int, err error) {
rUDPAddr, ok := rAddr.(*net.UDPAddr)
if !ok {
return -1, fmt.Errorf("invalid address type")
}
buffer := gopacket.NewSerializeBuffer()
payload := gopacket.Payload(buf)
udp := &layers.UDP{
SrcPort: layers.UDPPort(f.localPort),
DstPort: layers.UDPPort(rUDPAddr.Port),
}
var rSockAddr unix.Sockaddr
var nwLayer gopacket.NetworkLayer
var conn *socket.Conn
isIPv6 := rUDPAddr.IP.To4() == nil
if isIPv6 {
sa := &unix.SockaddrInet6{}
copy(sa.Addr[:], rUDPAddr.IP.To16())
conn = f.conn6
rSockAddr = sa
nwLayer = &layers.IPv6{
SrcIP: net.IPv6zero,
DstIP: rUDPAddr.IP,
}
} else {
sa := &unix.SockaddrInet4{}
copy(sa.Addr[:], rUDPAddr.IP.To4())
conn = f.conn4
rSockAddr = sa
nwLayer = &layers.IPv4{
SrcIP: net.IPv4zero,
DstIP: rUDPAddr.IP,
}
}
if err := udp.SetNetworkLayerForChecksum(nwLayer); err != nil {
return -1, fmt.Errorf("failed to set network layer for checksum: %w", err)
}
seropts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err := gopacket.SerializeLayers(buffer, seropts, udp, payload); err != nil {
return -1, fmt.Errorf("failed serialize packet: %s", err)
}
bufser := buffer.Bytes()
// f.logger.Debug("Sending packet",
// zap.Any("remote_address", rSockAddr),
// zap.Any("buf", hex.EncodeToString(buf)))
return 0, conn.Sendto(bufser, rSockAddr, 0)
}
func (f *FilteredUDPConn) ApplyFilter(prog *ebpf.Program) error {
// Attach filter program
if err := f.conn4.SetsockoptInt(unix.SOL_SOCKET, SO_ATTACH_BPF, prog.FD()); err != nil {
return fmt.Errorf("failed setsockopt(fd, SOL_SOCKET, SO_ATTACH_BPF): %w", err)
}
if err := f.conn6.SetsockoptInt(unix.SOL_SOCKET, SO_ATTACH_BPF, prog.FD()); err != nil {
return fmt.Errorf("failed setsockopt(fd, SOL_SOCKET, SO_ATTACH_BPF): %w", err)
}
return nil
}
func (f *FilteredUDPConn) setDeadlines(t time.Time, g func(*socket.Conn, time.Time) error) error {
if err := g(f.conn4, t); err != nil {
return fmt.Errorf("v4: %w", err)
}
if err := g(f.conn6, t); err != nil {
return fmt.Errorf("v6: %w", err)
}
if !f.running4 {
go f.read(f.conn4, &f.running4)
}
if !f.running6 {
go f.read(f.conn6, &f.running6)
}
return nil
}
func (f *FilteredUDPConn) SetDeadline(t time.Time) error {
return f.setDeadlines(t, (*socket.Conn).SetDeadline)
}
func (f *FilteredUDPConn) SetReadDeadline(t time.Time) error {
return f.setDeadlines(t, (*socket.Conn).SetReadDeadline)
}
func (f *FilteredUDPConn) SetWriteDeadline(t time.Time) error {
return f.setDeadlines(t, (*socket.Conn).SetWriteDeadline)
}
func (f *FilteredUDPConn) Close() error {
if err := f.conn4.Close(); err != nil {
return fmt.Errorf("v4: %w", err)
}
if err := f.conn6.Close(); err != nil {
return fmt.Errorf("v4: %w", err)
}
return nil
}
func NewFilteredUDPConn(lPort int) (*FilteredUDPConn, error) {
var err error
f := &FilteredUDPConn{
localPort: lPort,
logger: zap.L().Named("fuc"),
}
// SOCK_RAW sockets on Linux can only listen on a single address family (IPv4/IPv6)
// This is different from normal SOCK_STREAM/SOCK_DGRAM sockets which for the case
// of AF_INET6 also automatically listen on AF_INET.
// Hence we need to open two independent sockets here.
if f.conn4, err = socket.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_UDP, "raw_udp4", nil); err != nil {
return nil, fmt.Errorf("failed to open v4 raw socket: %w", err)
}
if f.conn6, err = socket.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_UDP, "raw_udp6", nil); err != nil {
return nil, fmt.Errorf("failed to open v6 raw socket: %w", err)
}
// fuc.conn4.Bind(&unix.SockaddrInet4{
// Port: fuc.localPort,
// Addr: ,
// })
f.packets = make(chan packet)
go f.read(f.conn4, &f.running4)
go f.read(f.conn6, &f.running6)
return f, nil
}
@@ -0,0 +1,153 @@
package net_test
import (
"fmt"
"net"
"time"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/pion/stun"
"golang.org/x/sys/unix"
netx "riasc.eu/wice/internal/net"
"riasc.eu/wice/internal/util"
"riasc.eu/wice/pkg/proxy"
)
func bpfSTUNTrafficOnPort(lPort int) asm.Instructions {
return asm.Instructions{
// LoadAbs() requires ctx in R6
asm.Mov.Reg(asm.R6, asm.R1),
// Offset of transport header from start of packet
// IPv6 raw sockets do not include the network layer
// so this is 0 by default
asm.LoadImm(asm.R7, 0, asm.DWord),
// r1 has ctx
// r0 = ctx[16] (aka protocol)
asm.LoadMem(asm.R0, asm.R1, 16, asm.Word),
// Perhaps IPv6? Then skip the IPv4 part..
asm.LoadImm(asm.R2, int64(unix.ETH_P_IPV6), asm.DWord),
asm.HostTo(asm.BE, asm.R2, asm.Half),
asm.JEq.Reg(asm.R0, asm.R2, "load"),
// Transport layer starts after 20 Byte IPv4 header
// TODO: use IHL field to account for IPv4 options
asm.LoadImm(asm.R7, 20, asm.DWord),
// Load UDP destination port
asm.LoadInd(asm.R0, asm.R7, 2, asm.Half).Sym("load"),
// Skip if is not matching our listen port
asm.JNE.Imm(asm.R0, int32(lPort), "skip"),
// Load STUN Magic Cookie from UDP payload
asm.LoadInd(asm.R0, asm.R7, 12, asm.Word),
// Skip if it is not the well know value
asm.JNE.Imm(asm.R0, int32(proxy.StunMagicCookie), "skip"),
asm.Mov.Imm(asm.R0, -1).Sym("exit"),
asm.Return(),
asm.Mov.Imm(asm.R0, 0).Sym("skip"),
asm.Return(),
}
}
var _ = Describe("FilteredUDPConn", Ordered, func() {
var c net.Conn
var f *netx.FilteredUDPConn
la := net.UDPAddr{
IP: nil,
Port: 12345,
}
BeforeAll(func() {
if !util.HasAdminPrivileges() {
Skip("Insufficient privileges")
}
// We are opening a standard UDP socket here which does not get used
// Its only there to avoid the system to send ICMP port unreachable messages
// as well as to test of the filtered UDP socket can run alongside already listening sockets
// without raising EADDRINUSE.
var err error
c, err = net.ListenUDP("udp", &la)
Expect(err).To(Succeed(), "Failed to open socket: %s", err)
spec := ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
License: "GPL",
Instructions: bpfSTUNTrafficOnPort(la.Port),
}
prog, err := ebpf.NewProgramWithOptions(&spec, ebpf.ProgramOptions{LogLevel: 6})
Expect(err).To(Succeed(), "Failed to create eBPF program: %s", err)
f, err = netx.NewFilteredUDPConn(la.Port)
Expect(err).To(Succeed(), "Failed to create filtered UDP connection: %s", err)
Expect(f.ApplyFilter(prog)).To(Succeed(), "failed to apply eBPF filter: %s", err)
})
DescribeTable("Send valid packets", FlakeAttempts(2), func(shouldSucceed, makeInvalid bool, netw string, port int) {
msg := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
if makeInvalid {
msg.Raw[4] = 0 // we destroy STUNs magic cookie here
}
var addr string
if netw == "udp6" {
addr = "[::1]"
} else {
addr = "127.0.0.1"
}
s, err := net.Dial(netw, fmt.Sprintf("%s:%d", addr, port))
Expect(err).To(Succeed(), "failed to dial IPv4: %s", err)
defer s.Close()
_, err = s.Write(msg.Raw)
Expect(err).To(Succeed(), "failed to send packet: %s", err)
// Invalid messages should never pass the filter
// So we set a timeout here and assert that the timeout will expire
if shouldSucceed {
err = f.SetDeadline(time.Time{}) // Reset
} else {
err = f.SetReadDeadline(time.Now().Add(5 * time.Millisecond))
}
Expect(err).To(Succeed())
recvMsg := make([]byte, 1024)
n, _, err := f.ReadFrom(recvMsg)
if shouldSucceed {
Expect(err).To(Succeed(), "failed to read from connection: %s", err)
Expect(n).To(Equal(len(msg.Raw)), "mismatching length")
Expect(msg.Raw).To(Equal(recvMsg[:n]), "mismatching contents")
} else {
err, isNetError := err.(net.Error)
Expect(isNetError).To(BeTrue(), "invalid error type: %s", err)
Expect(err.Timeout()).To(BeTrue(), "error is not a timeout")
}
},
Entry("Valid IPv4", true, false, "udp4", 12345),
Entry("Valid IPv6", true, false, "udp6", 12345),
Entry("Non-STUN IPv4", false, true, "udp4", 12345),
Entry("Non-STUN IPv6", false, true, "udp6", 12345),
Entry("STUN to different port", false, false, "udp4", 11111),
Entry("STUN to different port", false, false, "udp6", 11111),
Entry("Valid IPv4 (again)", true, false, "udp4", 12345),
Entry("Valid IPv6 (again)", true, false, "udp6", 12345),
)
AfterAll(func() {
Expect(c.Close()).To(Succeed())
Expect(f.Close()).To(Succeed())
})
})
+74
View File
@@ -0,0 +1,74 @@
package proxy
import (
"fmt"
"net"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/asm"
"golang.org/x/sys/unix"
)
func createFilteredSTUNConnection(listenPort int) (net.PacketConn, error) {
conn, err := netx.NewFilteredUDPConn(listenPort)
if err != nil {
return nil, fmt.Errorf("failed to create filtered UDP connection: %w", err)
}
spec := ebpf.ProgramSpec{
Type: ebpf.SocketFilter,
License: "Apache-2.0",
Instructions: asm.Instructions{
// LoadAbs() requires ctx in R6
asm.Mov.Reg(asm.R6, asm.R1),
// Offset of transport header from start of packet
// IPv6 raw sockets do not include the network layer
// so this is 0 by default
asm.LoadImm(asm.R7, 0, asm.DWord),
// r1 has ctx
// r0 = ctx[16] (aka protocol)
asm.LoadMem(asm.R0, asm.R1, 16, asm.Word),
// Perhaps IPv6? Then skip the IPv4 part..
asm.LoadImm(asm.R2, int64(unix.ETH_P_IPV6), asm.DWord),
asm.HostTo(asm.BE, asm.R2, asm.Half),
asm.JEq.Reg(asm.R0, asm.R2, "load"),
// Transport layer starts after 20 Byte IPv4 header
// TODO: use IHL field to account for IPv4 options
asm.LoadImm(asm.R7, 20, asm.DWord),
// Load UDP destination port
asm.LoadInd(asm.R0, asm.R7, 2, asm.Half).Sym("load"),
// Skip if is not matching our listen port
asm.JNE.Imm(asm.R0, int32(listenPort), "skip"),
// Load STUN Magic Cookie from UDP payload
asm.LoadInd(asm.R0, asm.R7, 12, asm.Word),
// Skip if it is not the well know value
asm.JNE.Imm(asm.R0, int32(StunMagicCookie), "skip"),
asm.Mov.Imm(asm.R0, -1).Sym("exit"),
asm.Return(),
asm.Mov.Imm(asm.R0, 0).Sym("skip"),
asm.Return(),
},
}
prog, err := ebpf.NewProgramWithOptions(&spec, ebpf.ProgramOptions{
LogLevel: 1, // TODO take configured log-level from args
})
if err != nil {
return nil, fmt.Errorf("failed to create BPF program: %w", err)
}
if err = conn.ApplyFilter(prog); err != nil {
return nil, fmt.Errorf("failed to attach eBPF program to socket: %w", err)
}
return conn, nil
}
+16
View File
@@ -0,0 +1,16 @@
package net_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"riasc.eu/wice/internal/test"
)
func TestSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Network Suite")
}
var _ = test.SetupLogging()
+61
View File
@@ -0,0 +1,61 @@
module riasc.eu/wice/tc_test
go 1.19
require (
github.com/cilium/ebpf v0.8.1
github.com/florianl/go-tc v0.4.1
github.com/onsi/ginkgo/v2 v2.1.4
github.com/onsi/gomega v1.19.0
github.com/stv0g/gont v0.3.0
github.com/vishvananda/netlink v1.2.0-beta
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
riasc.eu/wice v0.0.0
)
require (
github.com/aead/siphash v1.0.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/nftables v0.0.0-20220502152923-38a96768dbc6 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/jsimonetti/rtnetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect
github.com/pion/dtls/v2 v2.1.3 // indirect
github.com/pion/ice/v2 v2.2.6 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/stun v0.3.5 // indirect
github.com/pion/transport v0.13.0 // indirect
github.com/pion/turn/v2 v2.0.8 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b // indirect
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 // indirect
google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 // indirect
)
replace github.com/stv0g/gont => ../../../../gont
replace github.com/vishvananda/netlink => github.com/stv0g/netlink v1.1.1-gont
replace riasc.eu/wice => ../../..
+338
View File
@@ -0,0 +1,338 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/florianl/go-tc v0.4.1 h1:hVx6soOY/wbznLZ5yCNL5Fzc8+fiDr5dQPSIYDQnLV8=
github.com/florianl/go-tc v0.4.1/go.mod h1:kCCrW0ppJu2XcrUYS2utigmcMtEf9qZpAlIg5zdRqXk=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4=
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/nftables v0.0.0-20220502152923-38a96768dbc6 h1:EaB/VNqlxrV7rO5aWVW69eckhqBnDFsuslb/EXeE6MA=
github.com/google/nftables v0.0.0-20220502152923-38a96768dbc6/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
github.com/jsimonetti/rtnetlink v1.2.0 h1:KlwYLoRXgirTFbh1aVI6MJ7i+R/zJr+JkyhlIW1X3z4=
github.com/jsimonetti/rtnetlink v1.2.0/go.mod h1:RA0RtDj3hv4g6l/Y4B7RubIQkdTDAwXfMW/8bMaZ0FY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/pion/dtls/v2 v2.1.3 h1:3UF7udADqous+M2R5Uo2q/YaP4EzUoWKdfX2oscCUio=
github.com/pion/dtls/v2 v2.1.3/go.mod h1:o6+WvyLDAlXF7YiPB/RlskRoeK+/JtuaZa5emwQcWus=
github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig=
github.com/pion/ice/v2 v2.2.6/go.mod h1:SWuHiOGP17lGromHTFadUe1EuPgFh/oCU6FCMZHooVE=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.13.0 h1:KWTA5ZrQogizzYwPEciGtHPLwpAjE91FgXnyu+Hv2uY=
github.com/pion/transport v0.13.0/go.mod h1:yxm9uXpK9bpBBWkITk13cLo1y5/ur5VQpG22ny6EP7g=
github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw=
github.com/pion/turn/v2 v2.0.8/go.mod h1:+y7xl719J8bAEVpSXBXvTxStjJv3hbz9YFflvkpcGPw=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stv0g/netlink v1.1.1-gont h1:8NX2DjltMD4Ufi/z5ytdnPg9xtfTa+uo5t2sh+168bE=
github.com/stv0g/netlink v1.1.1-gont/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3 h1:q1kiSVscqoDeqTF27eQ2NnLLDmqF0I373qQNXYMy0fo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc=
k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 h1:E1U4GNGSXEdzQUT+mop0iYawCNXDUU46Y8nfodb+ZY0=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.64/go.mod h1:gtBlgvjXflnxHng9/3bXyXG3XmBYKDt35zu+lNmB+IA=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 h1:zlw/KoDjEObyddpFcvLiuu8frEvyEwVNc62WZQBp68w=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.64/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
Executable
BIN
View File
Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
package main
import (
"log"
"net"
"os"
"riasc.eu/wice/tc_test/bpf"
"github.com/cilium/ebpf/rlimit"
tc "github.com/florianl/go-tc"
)
func main() {
// Allow the current process to lock memory for eBPF resources.
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatal(err)
}
if len(os.Args) < 2 {
log.Fatalf("usage: %s INTF [INTF...]", os.Args[0])
}
intfNames := os.Args[1:]
tcnl, err := tc.Open(&tc.Config{})
if err != nil {
log.Fatalf("Failed to open rtnetlink socket: %v\n", err)
}
defer tcnl.Close()
objs, err := bpf.Load()
if err != nil {
log.Fatalf("Failed to load BPF code: %v\n", err)
}
addr := &net.UDPAddr{
IP: net.ParseIP("10.211.55.2"),
Port: 1234,
}
addr2 := &net.UDPAddr{
IP: net.ParseIP("10.211.55.2"),
Port: 1235,
}
if err := objs.Maps.EgressMap.AddEntry(addr, &bpf.MapStateEntry{
ChannelId: 0xAABB,
Lport: 2222,
}); err != nil {
log.Fatalf("Failed to add entry: %s", err)
}
if err := objs.Maps.EgressMap.AddEntry(addr2, &bpf.MapStateEntry{
ChannelId: 0,
Lport: 3333,
}); err != nil {
log.Fatalf("Failed to add entry: %s", err)
}
if err := objs.Maps.SettingsMap.EnableDebug(); err != nil {
log.Fatalf("Failed to enable debugging: %s", err)
}
for _, intfName := range intfNames {
intf, err := net.InterfaceByName(intfName)
if err != nil {
log.Fatalf("could not get interface ID: %v\n", err)
}
if err := bpf.AttachTCFilters(tcnl, intf.Index, objs); err != nil {
log.Fatalf("failed to attach BPF filter: %s", err)
}
}
}