Compare commits
8 Commits
0.0.2
...
a0d118b987
Author | SHA1 | Date | |
---|---|---|---|
a0d118b987 | |||
e9aefaf8d6 | |||
beed9726e3 | |||
e85b644e82 | |||
2c43affac9 | |||
fbf1758ccb | |||
ffb4b7681f | |||
7f2e3c0ed9 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@ hyp.secret
|
||||
*.exe
|
||||
hypd/hypd
|
||||
hyp/hyp
|
||||
hypd/server/hyp_bpf_bpfe*
|
@ -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
|
||||
|
||||
|
@ -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.")
|
||||
}
|
||||
|
@ -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
30
hypd/server/README.md
Normal 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 .
|
||||
```
|
@ -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);
|
||||
|
125
hypd/server/hyp_bpf_bpfeb.go
Normal file
125
hypd/server/hyp_bpf_bpfeb.go
Normal 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
BIN
hypd/server/hyp_bpf_bpfeb.o
Normal file
Binary file not shown.
125
hypd/server/hyp_bpf_bpfel.go
Normal file
125
hypd/server/hyp_bpf_bpfel.go
Normal 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
BIN
hypd/server/hyp_bpf_bpfel.o
Normal file
Binary file not shown.
Reference in New Issue
Block a user