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.go47
1 files changed, 47 insertions, 0 deletions
diff --git a/pkg/a2s/a2s.go b/pkg/a2s/a2s.go
index 04648c8..11cfc7c 100644
--- a/pkg/a2s/a2s.go
+++ b/pkg/a2s/a2s.go
@@ -6,8 +6,11 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
+ "crypto/hmac"
"crypto/rand"
+ "crypto/sha256"
"encoding/binary"
+ "encoding/json"
"errors"
"fmt"
"net"
@@ -67,6 +70,37 @@ func Probe(addr netip.AddrPort, timeout time.Duration) error {
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
@@ -134,6 +168,19 @@ func r2encodeGetChallenge(uid uint64) []byte {
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