aboutsummaryrefslogtreecommitdiff
path: root/pkg/a2s/a2s.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/a2s/a2s.go')
-rw-r--r--pkg/a2s/a2s.go201
1 files changed, 0 insertions, 201 deletions
diff --git a/pkg/a2s/a2s.go b/pkg/a2s/a2s.go
deleted file mode 100644
index 11cfc7c..0000000
--- a/pkg/a2s/a2s.go
+++ /dev/null
@@ -1,201 +0,0 @@
-// Package a2s implements a small portion of the r2 netchannel used by Northstar
-// to probe servers.
-package a2s
-
-import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/rand"
- "crypto/sha256"
- "encoding/binary"
- "encoding/json"
- "errors"
- "fmt"
- "net"
- "net/netip"
- "os"
- "time"
-)
-
-const ProbeUID uint64 = 1000000001337
-
-var ErrTimeout = errors.New("connection timed out")
-
-func Probe(addr netip.AddrPort, timeout time.Duration) error {
- conn, err := net.DialUDP("udp", nil, net.UDPAddrFromAddrPort(addr))
- if err != nil {
- return fmt.Errorf("connect to server: %w", err)
- }
- defer conn.Close()
-
- t := time.Now().Add(timeout)
- conn.SetWriteDeadline(t)
- conn.SetReadDeadline(t)
-
- pkt, err := r2cryptoEncrypt(r2encodeGetChallenge(ProbeUID))
- if err != nil {
- return fmt.Errorf("encrypt connection packet: %w", err)
- }
- if _, err := conn.Write(pkt); err != nil {
- if errors.Is(err, os.ErrDeadlineExceeded) {
- err = fmt.Errorf("%w: %v", ErrTimeout, err)
- }
- return fmt.Errorf("send connection packet: %w", err)
- }
-
- resp := make([]byte, 1500)
- if n, err := conn.Read(resp); err != nil {
- if errors.Is(err, os.ErrDeadlineExceeded) {
- err = fmt.Errorf("%w: %v", ErrTimeout, err)
- }
- return fmt.Errorf("receive packet: %w", err)
- } else {
- resp = resp[:n]
- }
-
- dec, err := r2cryptoDecrypt(resp)
- if err != nil {
- return fmt.Errorf("failed to decrypt received packet: %w", err)
- }
-
- uid, _, err := r2decodeChallenge(dec)
- if err != nil {
- return fmt.Errorf("invalid challenge: %w", err)
- }
- if uid != ProbeUID {
- return fmt.Errorf("invalid challenge")
- }
- return nil
-}
-
-func AtlasSigreq1(addr netip.AddrPort, timeout time.Duration, key string, obj any) error {
- // TODO: unconnected udp for performance
-
- a := net.UDPAddrFromAddrPort(addr)
- conn, err := net.DialUDP("udp", nil, a)
- if err != nil {
- return err
- }
- defer conn.Close()
-
- t := time.Now().Add(timeout)
- conn.SetWriteDeadline(t)
-
- j, err := json.Marshal(obj)
- if err != nil {
- return err
- }
-
- pkt, err := r2cryptoEncrypt(r2encodeAtlasSigreq1([]byte(key), j))
- if err != nil {
- return fmt.Errorf("encrypt packet: %w", err)
- }
- if _, err := conn.Write(pkt); err != nil {
- if errors.Is(err, os.ErrDeadlineExceeded) {
- err = fmt.Errorf("%w: %v", ErrTimeout, err)
- }
- return fmt.Errorf("send packet: %w", err)
- }
- return nil
-}
-
-const (
- r2cryptoNonceSize = 12
- r2cryptoTagSize = 16
- r2cryptoKey = "X3V.bXCfe3EhN'wb"
- r2cryptoAAD = "\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10"
-)
-
-func r2cryptoEncrypt(b []byte) ([]byte, error) {
- pkt := make([]byte, r2cryptoNonceSize+r2cryptoTagSize+len(b))
-
- nonce := pkt[:r2cryptoNonceSize]
- if _, err := rand.Read(nonce); err != nil {
- return nil, err
- }
-
- c, err := aes.NewCipher([]byte(r2cryptoKey))
- if err != nil {
- panic(err)
- }
-
- a, err := cipher.NewGCMWithTagSize(c, r2cryptoTagSize)
- if err != nil {
- panic(err)
- }
-
- // Go appends the ciphertext, then the tag to the dest (nonce)
- tmp := a.Seal(nil, nonce, b, []byte(r2cryptoAAD))
- copy(pkt[r2cryptoNonceSize:], tmp[len(b):])
- copy(pkt[r2cryptoNonceSize+r2cryptoTagSize:], tmp)
- return pkt, nil
-}
-
-func r2cryptoDecrypt(b []byte) ([]byte, error) {
- if len(b) < r2cryptoNonceSize+r2cryptoTagSize+1 {
- return nil, fmt.Errorf("packet too small")
- }
-
- c, err := aes.NewCipher([]byte(r2cryptoKey))
- if err != nil {
- panic(err)
- }
-
- a, err := cipher.NewGCMWithTagSize(c, r2cryptoTagSize)
- if err != nil {
- panic(err)
- }
-
- tmp := make([]byte, len(b)-r2cryptoNonceSize)
- copy(tmp, b[r2cryptoNonceSize+r2cryptoTagSize:])
- copy(tmp[len(tmp)-r2cryptoTagSize:], b[r2cryptoNonceSize:])
-
- if tmp, err = a.Open(tmp[:0], b[:r2cryptoNonceSize], tmp, []byte(r2cryptoAAD)); err != nil {
- return nil, err
- }
- return tmp, nil
-}
-
-func r2encodeGetChallenge(uid uint64) []byte {
- var b bytes.Buffer
- binary.Write(&b, binary.LittleEndian, int32(-1))
- binary.Write(&b, binary.LittleEndian, uint8(72))
- binary.Write(&b, binary.LittleEndian, []byte("connect\x00"))
- binary.Write(&b, binary.LittleEndian, uint64(uid))
- binary.Write(&b, binary.LittleEndian, uint8(2))
- return b.Bytes()
-}
-
-func r2encodeAtlasSigreq1(key, data []byte) []byte {
- sig := hmac.New(sha256.New, key)
- sig.Write(data)
-
- var b []byte
- b = append(b, '\xFF', '\xFF', '\xFF', '\xFF') // connectionless: int32(-1)
- b = append(b, 'T') // packet kind
- b = append(b, "sigreq1\x00"...) // packet type
- b = sig.Sum(b) // sigreq1 - signature
- b = append(b, data...) // sigreq1 - data
- return b
-}
-
-func r2decodeChallenge(b []byte) (uint64, int32, error) {
- var pkt struct {
- Seq int32
- Type uint8
- Challenge int32
- UID uint64
- }
- if err := binary.Read(bytes.NewReader(b), binary.LittleEndian, &pkt); err != nil {
- return 0, 0, err
- }
- if pkt.Seq != -1 {
- return 0, 0, fmt.Errorf("not a connectionless packet")
- }
- if pkt.Type != 73 {
- return 0, 0, fmt.Errorf("not a challenge response")
- }
- return pkt.UID, pkt.Challenge, nil
-}