Compare commits

8 Commits

Author SHA1 Message Date
a0d118b987 Ensure generated code is checked in
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-16 20:09:54 -06:00
e9aefaf8d6 README add/edit 2024-04-16 20:09:37 -06:00
beed9726e3 remove unreferenced macros...
These were previously used while trying to parse out specific headers.  They are no longer required though because the current length bounds checks covers edge cases.
2024-04-16 20:09:01 -06:00
e85b644e82 Add maxjitter flag to hyp client
All checks were successful
continuous-integration/drone/push Build is passing
This to allow configurable time between knock sequence transmissions.  It's important the sequence arrive in the correct order, and some networks have multiple paths.
2024-04-16 19:44:25 -06:00
2c43affac9 fix typo in help message 2024-04-16 19:43:39 -06:00
fbf1758ccb added generated go code from ebpg-go
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-14 21:03:22 -06:00
ffb4b7681f Merge branch 'main' of https://deadbeef.codes/steven/hyp
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-14 21:01:03 -06:00
7f2e3c0ed9 Added pre-compiled ebpf programs 2024-04-14 21:00:31 -06:00
10 changed files with 294 additions and 12 deletions

1
.gitignore vendored
View File

@ -2,4 +2,3 @@ hyp.secret
*.exe
hypd/hypd
hyp/hyp
hypd/server/hyp_bpf_bpfe*

View File

@ -25,14 +25,13 @@ Most port-knocking implementations are susceptible to replay attacks, a network
hyp supports a clock skew of up to 30 seconds between client and server.
### TBD: Protection Against Sweeping Attacks
### Protection Against Sweeping Attacks
~~hyp protects against sweeping attacks where an adversary modulates over the entire port range multiple times by ensuring the authentic knock sequence is strict and ordered correctly. If the first port is guessed, but the next pack arrives and is the incorrect second port in the sequence, the progress gets reset.~~
hyp protects against sweeping attacks where an adversary modulates over the entire port range multiple times by ensuring the authentic knock sequence is strict and ordered correctly. If the first port is guessed, but the next pack arrives and is the incorrect second port in the sequence, the progress gets reset. In addition to this, the correct authentic knock sequence must be entered within 5 seconds of the start of the sequence.
### Known Weaknesses
* Lossy networks can result in the knock sequence failing
* Networks with latency > 500ms can result in the knock sequence failing if packets arrive out of order
### References

View File

@ -38,6 +38,14 @@ Example usage:
panic(fmt.Errorf("failed to parse command flag 'secret': %w", err))
}
maxJitter, err := cmd.Flags().GetInt("maxjitter")
if err != nil {
panic(fmt.Errorf("failed to parse command flag 'maxjitter': %w", err))
}
if maxJitter < 1 || maxJitter > 1500 {
panic(fmt.Errorf("maxjitter must be value between 1 and 1500"))
}
secretBytes, err := os.ReadFile(secretFilePath)
if err != nil {
log.Fatalf("failed to read file 'hyp.secret': %v", err)
@ -50,12 +58,12 @@ Example usage:
}
// Transmit
fmt.Println("Transmitting knock sequence:", ports)
for _, port := range ports {
fmt.Printf("knock | %s:%d\n", args[0], port)
conn, _ := net.Dial("udp", fmt.Sprintf("%s:%d", args[0], port))
conn.Write([]byte{0})
conn.Close()
time.Sleep(time.Millisecond * 200) // TBD: Make this configurable with flag (maxJitter)
time.Sleep(time.Millisecond * time.Duration(maxJitter)) // TBD: Make this configurable with flag (maxJitter)
}
},
}
@ -64,4 +72,5 @@ func init() {
rootCmd.AddCommand(knockCmd)
knockCmd.PersistentFlags().String("secret", "hyp.secret", "Path to the file containing the hyp secret.")
knockCmd.PersistentFlags().Int("maxjitter", 200, "Specifies the time in milliseconds between knock sequence transmissions.")
}

View File

@ -19,7 +19,7 @@ server and to clients.
Example:
hypd generatesecret > hyp.secret`,
hypd generate secret > hyp.secret`,
Run: func(cmd *cobra.Command, args []string) {
sharedSecret, err := otphyp.GenerateSecret()
if err != nil {

30
hypd/server/README.md Normal file
View File

@ -0,0 +1,30 @@
# hypd server
hypd is the port knocking daemon which runs on an edge device connecting to an untrusted network. Leveraging eBPF's XDP hook point, it extracts header information directly and sends to userspace the specific information required. This method is faster than alternative methods such as using libpcap.
### eBPF
The hyp_bpf.c program can be recompiled using go generate.
```bash
# Debian: sudo apt install git clang linux-headers-amd64 libbpf-dev
go generate .
```
### Generating vmlinux.h
vmlinux.h is included in hyp_bpf.c and can be regenerated with bpftool.
```bash
# Debian: sudo apt install bpftool
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > ../headers/vmlinux.h
```
### Building hypd
hypd has no CGO dependencies and so can run on musl systems as well.
```bash
# To ensure it can run on systems don't use CGO
CGO_ENABLED=0 go build .
```

View File

@ -7,12 +7,8 @@ Copyright © 2024 Steven Polley <himself@stevenpolley.net>
#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
@ -44,7 +40,6 @@ int xdp_prog_func(struct xdp_md *ctx) {
// parse ethernet header
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) <= data_end) {
// parse IP header
struct iphdr *ip = data + sizeof(*eth);

View File

@ -0,0 +1,125 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build mips || mips64 || ppc64 || s390x
package server
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type hyp_bpfKnockData struct {
Srcip uint32
Dstport uint16
Pad uint16
}
// loadHyp_bpf returns the embedded CollectionSpec for hyp_bpf.
func loadHyp_bpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_Hyp_bpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load hyp_bpf: %w", err)
}
return spec, err
}
// loadHyp_bpfObjects loads hyp_bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *hyp_bpfObjects
// *hyp_bpfPrograms
// *hyp_bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadHyp_bpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadHyp_bpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// hyp_bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type hyp_bpfSpecs struct {
hyp_bpfProgramSpecs
hyp_bpfMapSpecs
}
// hyp_bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type hyp_bpfProgramSpecs struct {
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
}
// hyp_bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type hyp_bpfMapSpecs struct {
Rb *ebpf.MapSpec `ebpf:"rb"`
}
// hyp_bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadHyp_bpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type hyp_bpfObjects struct {
hyp_bpfPrograms
hyp_bpfMaps
}
func (o *hyp_bpfObjects) Close() error {
return _Hyp_bpfClose(
&o.hyp_bpfPrograms,
&o.hyp_bpfMaps,
)
}
// hyp_bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadHyp_bpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type hyp_bpfMaps struct {
Rb *ebpf.Map `ebpf:"rb"`
}
func (m *hyp_bpfMaps) Close() error {
return _Hyp_bpfClose(
m.Rb,
)
}
// hyp_bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadHyp_bpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type hyp_bpfPrograms struct {
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
}
func (p *hyp_bpfPrograms) Close() error {
return _Hyp_bpfClose(
p.XdpProgFunc,
)
}
func _Hyp_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 hyp_bpf_bpfeb.o
var _Hyp_bpfBytes []byte

BIN
hypd/server/hyp_bpf_bpfeb.o Normal file

Binary file not shown.

View File

@ -0,0 +1,125 @@
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64
package server
import (
"bytes"
_ "embed"
"fmt"
"io"
"github.com/cilium/ebpf"
)
type hyp_bpfKnockData struct {
Srcip uint32
Dstport uint16
Pad uint16
}
// loadHyp_bpf returns the embedded CollectionSpec for hyp_bpf.
func loadHyp_bpf() (*ebpf.CollectionSpec, error) {
reader := bytes.NewReader(_Hyp_bpfBytes)
spec, err := ebpf.LoadCollectionSpecFromReader(reader)
if err != nil {
return nil, fmt.Errorf("can't load hyp_bpf: %w", err)
}
return spec, err
}
// loadHyp_bpfObjects loads hyp_bpf and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *hyp_bpfObjects
// *hyp_bpfPrograms
// *hyp_bpfMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadHyp_bpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadHyp_bpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
// hyp_bpfSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type hyp_bpfSpecs struct {
hyp_bpfProgramSpecs
hyp_bpfMapSpecs
}
// hyp_bpfSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type hyp_bpfProgramSpecs struct {
XdpProgFunc *ebpf.ProgramSpec `ebpf:"xdp_prog_func"`
}
// hyp_bpfMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type hyp_bpfMapSpecs struct {
Rb *ebpf.MapSpec `ebpf:"rb"`
}
// hyp_bpfObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadHyp_bpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type hyp_bpfObjects struct {
hyp_bpfPrograms
hyp_bpfMaps
}
func (o *hyp_bpfObjects) Close() error {
return _Hyp_bpfClose(
&o.hyp_bpfPrograms,
&o.hyp_bpfMaps,
)
}
// hyp_bpfMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadHyp_bpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type hyp_bpfMaps struct {
Rb *ebpf.Map `ebpf:"rb"`
}
func (m *hyp_bpfMaps) Close() error {
return _Hyp_bpfClose(
m.Rb,
)
}
// hyp_bpfPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadHyp_bpfObjects or ebpf.CollectionSpec.LoadAndAssign.
type hyp_bpfPrograms struct {
XdpProgFunc *ebpf.Program `ebpf:"xdp_prog_func"`
}
func (p *hyp_bpfPrograms) Close() error {
return _Hyp_bpfClose(
p.XdpProgFunc,
)
}
func _Hyp_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 hyp_bpf_bpfel.o
var _Hyp_bpfBytes []byte

BIN
hypd/server/hyp_bpf_bpfel.o Normal file

Binary file not shown.