enter eBPF, libpcap and CGO = bloat
This commit is contained in:
parent
3ae568639e
commit
0ad3e2b0d4
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ hyp.secret
|
|||||||
*.exe
|
*.exe
|
||||||
hypd/hypd
|
hypd/hypd
|
||||||
hyp/hyp
|
hyp/hyp
|
||||||
|
hypd/server/hyp_bpf_bpfe*
|
6
go.mod
6
go.mod
@ -3,13 +3,13 @@ module deadbeef.codes/steven/hyp
|
|||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/cilium/ebpf v0.14.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
golang.org/x/net v0.19.0 // indirect
|
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
|
||||||
golang.org/x/sys v0.15.0 // indirect
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
)
|
)
|
||||||
|
34
go.sum
34
go.sum
@ -1,28 +1,30 @@
|
|||||||
|
github.com/cilium/ebpf v0.14.0 h1:0PsxAjO6EjI1rcT+rkp6WcCnE0ZvfkXBYiMedJtrSUs=
|
||||||
|
github.com/cilium/ebpf v0.14.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||||
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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
100
hypd/server/bpf_endian.h
Normal file
100
hypd/server/bpf_endian.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Code lifted from the folks at Cilium from ebpf-go repo
|
||||||
|
|
||||||
|
#ifndef __BPF_ENDIAN__
|
||||||
|
#define __BPF_ENDIAN__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Isolate byte #n and put it into byte #m, for __u##b type.
|
||||||
|
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
|
||||||
|
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
|
||||||
|
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
|
||||||
|
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
|
||||||
|
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
|
||||||
|
*/
|
||||||
|
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
|
||||||
|
|
||||||
|
#define ___bpf_swab16(x) ((__u16)( \
|
||||||
|
___bpf_mvb(x, 16, 0, 1) | \
|
||||||
|
___bpf_mvb(x, 16, 1, 0)))
|
||||||
|
|
||||||
|
#define ___bpf_swab32(x) ((__u32)( \
|
||||||
|
___bpf_mvb(x, 32, 0, 3) | \
|
||||||
|
___bpf_mvb(x, 32, 1, 2) | \
|
||||||
|
___bpf_mvb(x, 32, 2, 1) | \
|
||||||
|
___bpf_mvb(x, 32, 3, 0)))
|
||||||
|
|
||||||
|
#define ___bpf_swab64(x) ((__u64)( \
|
||||||
|
___bpf_mvb(x, 64, 0, 7) | \
|
||||||
|
___bpf_mvb(x, 64, 1, 6) | \
|
||||||
|
___bpf_mvb(x, 64, 2, 5) | \
|
||||||
|
___bpf_mvb(x, 64, 3, 4) | \
|
||||||
|
___bpf_mvb(x, 64, 4, 3) | \
|
||||||
|
___bpf_mvb(x, 64, 5, 2) | \
|
||||||
|
___bpf_mvb(x, 64, 6, 1) | \
|
||||||
|
___bpf_mvb(x, 64, 7, 0)))
|
||||||
|
|
||||||
|
/* LLVM's BPF target selects the endianness of the CPU
|
||||||
|
* it compiles on, or the user specifies (bpfel/bpfeb),
|
||||||
|
* respectively. The used __BYTE_ORDER__ is defined by
|
||||||
|
* the compiler, we cannot rely on __BYTE_ORDER from
|
||||||
|
* libc headers, since it doesn't reflect the actual
|
||||||
|
* requested byte order.
|
||||||
|
*
|
||||||
|
* Note, LLVM's BPF target has different __builtin_bswapX()
|
||||||
|
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
|
||||||
|
* in bpfel and bpfeb case, which means below, that we map
|
||||||
|
* to cpu_to_be16(). We could use it unconditionally in BPF
|
||||||
|
* case, but better not rely on it, so that this header here
|
||||||
|
* can be used from application and BPF program side, which
|
||||||
|
* use different targets.
|
||||||
|
*/
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
# define __bpf_ntohs(x) __builtin_bswap16(x)
|
||||||
|
# define __bpf_htons(x) __builtin_bswap16(x)
|
||||||
|
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
|
||||||
|
# define __bpf_constant_htons(x) ___bpf_swab16(x)
|
||||||
|
# define __bpf_ntohl(x) __builtin_bswap32(x)
|
||||||
|
# define __bpf_htonl(x) __builtin_bswap32(x)
|
||||||
|
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
|
||||||
|
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
|
||||||
|
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
|
||||||
|
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
|
||||||
|
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
|
||||||
|
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
|
||||||
|
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
|
# define __bpf_ntohs(x) (x)
|
||||||
|
# define __bpf_htons(x) (x)
|
||||||
|
# define __bpf_constant_ntohs(x) (x)
|
||||||
|
# define __bpf_constant_htons(x) (x)
|
||||||
|
# define __bpf_ntohl(x) (x)
|
||||||
|
# define __bpf_htonl(x) (x)
|
||||||
|
# define __bpf_constant_ntohl(x) (x)
|
||||||
|
# define __bpf_constant_htonl(x) (x)
|
||||||
|
# define __bpf_be64_to_cpu(x) (x)
|
||||||
|
# define __bpf_cpu_to_be64(x) (x)
|
||||||
|
# define __bpf_constant_be64_to_cpu(x) (x)
|
||||||
|
# define __bpf_constant_cpu_to_be64(x) (x)
|
||||||
|
#else
|
||||||
|
# error "Fix your compiler's __BYTE_ORDER__?!"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define bpf_htons(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_htons(x) : __bpf_htons(x))
|
||||||
|
#define bpf_ntohs(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
|
||||||
|
#define bpf_htonl(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_htonl(x) : __bpf_htonl(x))
|
||||||
|
#define bpf_ntohl(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
|
||||||
|
#define bpf_cpu_to_be64(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
|
||||||
|
#define bpf_be64_to_cpu(x) \
|
||||||
|
(__builtin_constant_p(x) ? \
|
||||||
|
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
|
||||||
|
|
||||||
|
#endif /* __BPF_ENDIAN__ */
|
64
hypd/server/hyp_bpf.c
Normal file
64
hypd/server/hyp_bpf.c
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2024 Steven Polley <himself@stevenpolley.net>
|
||||||
|
*/
|
||||||
|
|
||||||
|
//go:build ignore
|
||||||
|
#include "vmlinux.h"
|
||||||
|
#include "bpf_endian.h"
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
|
||||||
|
|
||||||
|
char __license[] SEC("license") = "BSD";
|
||||||
|
|
||||||
|
#define ETH_P_IP 0x0800
|
||||||
|
#define IP_FRAGMENTED 65343
|
||||||
|
|
||||||
|
// representation of knock data that gets sent to userspace
|
||||||
|
struct knock_data {
|
||||||
|
__u32 srcip; // 4 bytes
|
||||||
|
__u16 dstport; // 2 bytes
|
||||||
|
__u16 pad; // required padding - struct must be multiple of 4 bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// ring buffer used to send data to userspace
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||||
|
__uint(max_entries, 1 << 24);
|
||||||
|
} rb SEC(".maps");
|
||||||
|
|
||||||
|
// force emitting struct event into the ELF
|
||||||
|
const struct knock_data *unused __attribute__((unused));
|
||||||
|
|
||||||
|
// hook into xpress data path attach point
|
||||||
|
SEC("xdp")
|
||||||
|
int xdp_prog_func(struct xdp_md *ctx) {
|
||||||
|
|
||||||
|
// xdp gives us the raw frame with no structures, it must be parsed
|
||||||
|
void *data = (void *)(long)ctx->data;
|
||||||
|
void *data_end = (void *)(long)ctx->data_end;
|
||||||
|
|
||||||
|
// parse ethernet header
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
|
||||||
|
if ((void *)eth + sizeof(*eth) <= data_end) {
|
||||||
|
// parse IP header
|
||||||
|
struct iphdr *ip = data + sizeof(*eth);
|
||||||
|
if ((void *)ip + sizeof(*ip) <= data_end) {
|
||||||
|
if (ip->protocol == IPPROTO_UDP) {
|
||||||
|
// parse UDP header
|
||||||
|
struct udphdr *udp = (void *)ip + sizeof(*ip);
|
||||||
|
if ((void *)udp + sizeof(*udp) <= data_end)
|
||||||
|
{
|
||||||
|
// pack into knock structure and send to userspace
|
||||||
|
struct knock_data knock;
|
||||||
|
knock.srcip = bpf_ntohl(ip->saddr);
|
||||||
|
knock.dstport = bpf_htons(udp->dest);
|
||||||
|
knock.pad = 0;
|
||||||
|
bpf_ringbuf_output(&rb, &knock, sizeof(knock), BPF_RB_FORCE_WAKEUP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return XDP_PASS;
|
||||||
|
}
|
@ -5,18 +5,24 @@ Copyright © 2024 Steven Polley <himself@stevenpolley.net>
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"deadbeef.codes/steven/hyp/otphyp"
|
"deadbeef.codes/steven/hyp/otphyp"
|
||||||
"github.com/google/gopacket"
|
"github.com/cilium/ebpf/link"
|
||||||
"github.com/google/gopacket/pcap"
|
"github.com/cilium/ebpf/ringbuf"
|
||||||
|
"github.com/cilium/ebpf/rlimit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go --type knock_data hyp_bpf hyp_bpf.c
|
||||||
|
|
||||||
// Client is used to keep track of a client attempting to perform an authentic knock sequence
|
// Client is used to keep track of a client attempting to perform an authentic knock sequence
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Progress int // index of current progress in sequence. Value of 1 means first port has been matched
|
Progress int // index of current progress in sequence. Value of 1 means first port has been matched
|
||||||
@ -31,7 +37,7 @@ type KnockSequence struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
clients map[string]*Client // Contains a map of clients
|
clients map[uint32]*Client // Contains a map of clients, key is IPv4 address
|
||||||
knockSequences []KnockSequence // We have 3 valid knock sequences at any time to account for clock skew
|
knockSequences []KnockSequence // We have 3 valid knock sequences at any time to account for clock skew
|
||||||
sharedSecret string // base32 encoded shared secret used for totp
|
sharedSecret string // base32 encoded shared secret used for totp
|
||||||
)
|
)
|
||||||
@ -40,46 +46,96 @@ var (
|
|||||||
// it sets up the pcap on the capture device and starts a goroutine
|
// it sets up the pcap on the capture device and starts a goroutine
|
||||||
// to rotate the knock sequence
|
// to rotate the knock sequence
|
||||||
func PacketServer(captureDevice string) error {
|
func PacketServer(captureDevice string) error {
|
||||||
|
|
||||||
|
iface, err := net.InterfaceByName(captureDevice)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("lookup network iface %q: %v", captureDevice, err)
|
||||||
|
}
|
||||||
|
|
||||||
secretBytes, err := os.ReadFile("hyp.secret")
|
secretBytes, err := os.ReadFile("hyp.secret")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to read file 'hyp.secret': %v", err)
|
log.Fatalf("failed to read file 'hyp.secret': %v", err)
|
||||||
}
|
}
|
||||||
sharedSecret = string(secretBytes)
|
sharedSecret = string(secretBytes)
|
||||||
|
|
||||||
clients = make(map[string]*Client, 0)
|
clients = make(map[uint32]*Client, 0)
|
||||||
knockSequences = []KnockSequence{}
|
knockSequences = []KnockSequence{}
|
||||||
|
|
||||||
// Open pcap handle on device
|
|
||||||
handle, err := pcap.OpenLive(captureDevice, 126, true, pcap.BlockForever)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open pcap on capture device: %w", err)
|
|
||||||
}
|
|
||||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
|
||||||
|
|
||||||
// Setup a goroutine to periodically rotate the authentic knock sequence
|
// Setup a goroutine to periodically rotate the authentic knock sequence
|
||||||
go rotateSequence(handle)
|
go rotateSequence()
|
||||||
|
|
||||||
// Read from the pcap handle until we exit
|
////////////////////////////////////
|
||||||
for packet := range packetSource.Packets() {
|
|
||||||
handlePacket(packet) // Do something with a packet here.
|
// Allow the current process to lock memory for eBPF resources.
|
||||||
|
if err := rlimit.RemoveMemlock(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load pre-compiled programs into the kernel.
|
||||||
|
objs := hyp_bpfObjects{}
|
||||||
|
if err := loadHyp_bpfObjects(&objs, nil); err != nil {
|
||||||
|
log.Fatalf("loading objects: %v", err)
|
||||||
|
}
|
||||||
|
defer objs.Close()
|
||||||
|
|
||||||
|
// Attach the program.
|
||||||
|
l, err := link.AttachXDP(link.XDPOptions{
|
||||||
|
Program: objs.XdpProgFunc,
|
||||||
|
Interface: iface.Index,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not attach XDP program: %v", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
log.Printf("Attached XDP program to iface %q (index %d)", iface.Name, iface.Index)
|
||||||
|
log.Printf("Press Ctrl-C to exit and remove the program")
|
||||||
|
|
||||||
|
rd, err := ringbuf.NewReader(objs.Rb)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not open ring buffer reader: %v", err)
|
||||||
|
}
|
||||||
|
defer rd.Close()
|
||||||
|
|
||||||
|
var event hyp_bpfKnockData
|
||||||
|
for {
|
||||||
|
record, err := rd.Read()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ringbuf.ErrClosed) {
|
||||||
|
log.Println("eBPF ring buffer closed, exiting...")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
log.Printf("error reading from ring buffer reader: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
|
||||||
|
log.Printf("error parsing ringbuf event: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handleKnock(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// intToIP converts IPv4 number to net.IP
|
||||||
|
func intToIP(ipNum uint32) net.IP {
|
||||||
|
ip := make(net.IP, 4)
|
||||||
|
binary.BigEndian.PutUint32(ip, ipNum)
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
// packets that match the BPF filter get passed to handlePacket
|
// packets that match the BPF filter get passed to handlePacket
|
||||||
func handlePacket(packet gopacket.Packet) {
|
func handleKnock(knockEvent hyp_bpfKnockData) {
|
||||||
port := binary.BigEndian.Uint16(packet.TransportLayer().TransportFlow().Dst().Raw())
|
|
||||||
srcip := packet.NetworkLayer().NetworkFlow().Src().String()
|
|
||||||
|
|
||||||
client, ok := clients[srcip]
|
client, ok := clients[knockEvent.Srcip]
|
||||||
if !ok { // client doesn't exist yet
|
if !ok { // client doesn't exist yet
|
||||||
for i, knockSequence := range knockSequences { // identify which of the 3 authentic knock sequences is matched
|
for i, knockSequence := range knockSequences { // identify which of the 3 authentic knock sequences is matched
|
||||||
if knockSequence.Used { // skip over sequences that are already used to prevent replay attack
|
if knockSequence.Used { // skip over sequences that are already used to prevent replay attack
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if port == knockSequence.PortSequence[0] {
|
if knockEvent.Dstport == knockSequence.PortSequence[0] {
|
||||||
// Create the client and mark the knock sequence as used
|
// Create the client and mark the knock sequence as used
|
||||||
clients[srcip] = &Client{Progress: 1, Sequence: knockSequence.PortSequence}
|
clients[knockEvent.Srcip] = &Client{Progress: 1, Sequence: knockSequence.PortSequence}
|
||||||
knockSequences[i].Used = true
|
knockSequences[i].Used = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,23 +145,23 @@ func handlePacket(packet gopacket.Packet) {
|
|||||||
// if it's wrong, reset progress
|
// if it's wrong, reset progress
|
||||||
// TBD: vulnerable to sweep attack - this won't be triggered if a wrong packet doesn't match BPF filter
|
// TBD: vulnerable to sweep attack - this won't be triggered if a wrong packet doesn't match BPF filter
|
||||||
// TBD: make the sweep attack fix on by default, but configurable to be off to allow for limited BPF filter for extremely low overhead as compromise.
|
// TBD: make the sweep attack fix on by default, but configurable to be off to allow for limited BPF filter for extremely low overhead as compromise.
|
||||||
if port != client.Sequence[client.Progress] {
|
if knockEvent.Dstport != client.Sequence[client.Progress] {
|
||||||
delete(clients, srcip)
|
delete(clients, knockEvent.Srcip)
|
||||||
fmt.Printf("port '%d' is in sequence, but came at unexpected order - resetting progress", port)
|
fmt.Printf("port '%d' is in sequence, but came at unexpected order - resetting progress", knockEvent.Dstport)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client increases progress through sequence and checks if sequence is completed
|
// Client increases progress through sequence and checks if sequence is completed
|
||||||
client.Progress++
|
client.Progress++
|
||||||
if client.Progress >= len(client.Sequence) {
|
if client.Progress >= len(client.Sequence) {
|
||||||
delete(clients, srcip)
|
delete(clients, knockEvent.Srcip)
|
||||||
handleSuccess(srcip) // The magic function, the knock is completed
|
handleSuccess(intToIP(knockEvent.Srcip)) // The magic function, the knock is completed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to rotate the authentic port knock sequence
|
// Used to rotate the authentic port knock sequence
|
||||||
func rotateSequence(handle *pcap.Handle) {
|
func rotateSequence() {
|
||||||
for {
|
for {
|
||||||
|
|
||||||
// Generate new knock sequences with time skew support
|
// Generate new knock sequences with time skew support
|
||||||
@ -120,12 +176,6 @@ func rotateSequence(handle *pcap.Handle) {
|
|||||||
}
|
}
|
||||||
fmt.Println("New sequences:", knockSequences)
|
fmt.Println("New sequences:", knockSequences)
|
||||||
|
|
||||||
// Set BPF filter
|
|
||||||
err := setPacketFilter(handle)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to change packet filter: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep until next 30 second offset
|
// Sleep until next 30 second offset
|
||||||
time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30)))
|
time.Sleep(time.Until(time.Now().Truncate(time.Second * 30).Add(time.Second * 30)))
|
||||||
|
|
||||||
@ -134,31 +184,10 @@ func rotateSequence(handle *pcap.Handle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a pcap handle and list of authentic port knock sequences, configures a BPF filter
|
|
||||||
func setPacketFilter(handle *pcap.Handle) error {
|
|
||||||
filter := "udp && ("
|
|
||||||
for i, knockSequence := range knockSequences {
|
|
||||||
for j, port := range knockSequence.PortSequence {
|
|
||||||
if i == 0 && j == 0 {
|
|
||||||
filter += fmt.Sprint("port ", port)
|
|
||||||
} else {
|
|
||||||
filter += fmt.Sprint(" || port ", port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filter += ")"
|
|
||||||
err := handle.SetBPFFilter(filter)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set BPF filter '%s': %v", filter, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TBD: Implement - this is a temporary routine to demonstrate an application
|
// TBD: Implement - this is a temporary routine to demonstrate an application
|
||||||
func handleSuccess(srcip string) {
|
func handleSuccess(srcip net.IP) {
|
||||||
fmt.Println("Success for ", srcip)
|
fmt.Println("Success for ", srcip)
|
||||||
|
cmd := exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "-s", fmt.Sprint(srcip), "--dport", "22", "-j", "ACCEPT")
|
||||||
cmd := exec.Command("iptables", "-A", "INPUT", "-p", "tcp", "-s", srcip, "--dport", "22", "-j", "ACCEPT")
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to execute iptables command for '%s': %v", srcip, err)
|
log.Printf("failed to execute iptables command for '%s': %v", srcip, err)
|
||||||
|
112576
hypd/server/vmlinux.h
Normal file
112576
hypd/server/vmlinux.h
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user