mirror of
https://github.com/sigcn/pg.git
synced 2025-09-26 22:05:50 +08:00
chore: upgrade go and mods version
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/net/stun"
|
||||
"github.com/sigcn/pg/stun"
|
||||
)
|
||||
|
||||
type stunRoundTripper struct {
|
||||
|
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
"github.com/sigcn/pg/cache"
|
||||
"github.com/sigcn/pg/disco"
|
||||
"github.com/sigcn/pg/stun"
|
||||
"golang.org/x/time/rate"
|
||||
"tailscale.com/net/stun"
|
||||
)
|
||||
|
||||
var (
|
||||
|
22
go.mod
22
go.mod
@@ -1,24 +1,23 @@
|
||||
module github.com/sigcn/pg
|
||||
|
||||
go 1.23.4
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/coreos/go-oidc/v3 v3.11.0
|
||||
github.com/coreos/go-oidc/v3 v3.12.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.3
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.7
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sys v0.27.0
|
||||
golang.org/x/time v0.7.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/net v0.37.0
|
||||
golang.org/x/oauth2 v0.28.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/time v0.11.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gvisor.dev/gvisor v0.0.0-20241218235220-7bf5820dea8f
|
||||
gvisor.dev/gvisor v0.0.0-20250307022919-35e47cb01460
|
||||
storj.io/common v0.0.0-20240425113201-9815a85cbc32
|
||||
tailscale.com v1.56.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -27,7 +26,8 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
golang.org/x/term v0.26.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
||||
|
44
go.sum
44
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
|
||||
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
|
||||
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
|
||||
@@ -10,8 +10,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.3 h1:nGqgS0tgIO1Hto47HSaaK4ac/I/Bu7usmdD3qvs0WvM=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.3/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
|
||||
github.com/jedib0t/go-pretty/v6 v6.6.7/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
|
||||
@@ -21,26 +21,28 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
@@ -51,11 +53,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20241218235220-7bf5820dea8f h1:u7Zpe3o5/MYKtMVvj8+bsEya99PYufLjqfdmXzJg7N8=
|
||||
gvisor.dev/gvisor v0.0.0-20241218235220-7bf5820dea8f/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
|
||||
gvisor.dev/gvisor v0.0.0-20250307022919-35e47cb01460 h1:nTgsrWccO1oBUcZMjQQDLfbJyluzbrA+orPvDLu2Y+g=
|
||||
gvisor.dev/gvisor v0.0.0-20250307022919-35e47cb01460/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||
storj.io/common v0.0.0-20240425113201-9815a85cbc32 h1:Py/vvWBasKRGG8pO1M89idDEW3LWdU5o/w83xwUNNgU=
|
||||
storj.io/common v0.0.0-20240425113201-9815a85cbc32/go.mod h1:MFl009RHY4tIqySVNy/6EmgRw2q60d26h9N/nb7JxGU=
|
||||
tailscale.com v1.56.1 h1:V3HBDJai3u7xo22Xlv7ioqKNZQdxOJebLYCNqCXVwZg=
|
||||
tailscale.com v1.56.1/go.mod h1:XQk6fCN8oMJ+qbCmW+2WS/VM3jTA9nIHT6O19t0hZeQ=
|
||||
|
313
stun/stun.go
Normal file
313
stun/stun.go
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright (c) sigcn/pg
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package STUN generates STUN request packets and parses response packets.
|
||||
package stun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const (
|
||||
attrNumSoftware = 0x8022
|
||||
attrNumFingerprint = 0x8028
|
||||
attrMappedAddress = 0x0001
|
||||
attrXorMappedAddress = 0x0020
|
||||
// This alternative attribute type is not
|
||||
// mentioned in the RFC, but the shift into
|
||||
// the "comprehension-optional" range seems
|
||||
// like an easy mistake for a server to make.
|
||||
// And servers appear to send it.
|
||||
attrXorMappedAddressAlt = 0x8020
|
||||
|
||||
software = "tailnode" // notably: 8 bytes long, so no padding
|
||||
bindingRequest = "\x00\x01"
|
||||
magicCookie = "\x21\x12\xa4\x42"
|
||||
lenFingerprint = 8 // 2+byte header + 2-byte length + 4-byte crc32
|
||||
headerLen = 20
|
||||
)
|
||||
|
||||
// TxID is a transaction ID.
|
||||
type TxID [12]byte
|
||||
|
||||
// NewTxID returns a new random TxID.
|
||||
func NewTxID() TxID {
|
||||
var tx TxID
|
||||
if _, err := crand.Read(tx[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
// Request generates a binding request STUN packet.
|
||||
// The transaction ID, tID, should be a random sequence of bytes.
|
||||
func Request(tID TxID) []byte {
|
||||
// STUN header, RFC5389 Section 6.
|
||||
const lenAttrSoftware = 4 + len(software)
|
||||
b := make([]byte, 0, headerLen+lenAttrSoftware+lenFingerprint)
|
||||
b = append(b, bindingRequest...)
|
||||
b = appendU16(b, uint16(lenAttrSoftware+lenFingerprint)) // number of bytes following header
|
||||
b = append(b, magicCookie...)
|
||||
b = append(b, tID[:]...)
|
||||
|
||||
// Attribute SOFTWARE, RFC5389 Section 15.5.
|
||||
b = appendU16(b, attrNumSoftware)
|
||||
b = appendU16(b, uint16(len(software)))
|
||||
b = append(b, software...)
|
||||
|
||||
// Attribute FINGERPRINT, RFC5389 Section 15.5.
|
||||
fp := fingerPrint(b)
|
||||
b = appendU16(b, attrNumFingerprint)
|
||||
b = appendU16(b, 4)
|
||||
b = appendU32(b, fp)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func fingerPrint(b []byte) uint32 { return crc32.ChecksumIEEE(b) ^ 0x5354554e }
|
||||
|
||||
func appendU16(b []byte, v uint16) []byte {
|
||||
return append(b, byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
func appendU32(b []byte, v uint32) []byte {
|
||||
return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
// ParseBindingRequest parses a STUN binding request.
|
||||
//
|
||||
// It returns an error unless it advertises that it came from
|
||||
// Tailscale.
|
||||
func ParseBindingRequest(b []byte) (TxID, error) {
|
||||
if !Is(b) {
|
||||
return TxID{}, ErrNotSTUN
|
||||
}
|
||||
if string(b[:len(bindingRequest)]) != bindingRequest {
|
||||
return TxID{}, ErrNotBindingRequest
|
||||
}
|
||||
var txID TxID
|
||||
copy(txID[:], b[8:8+len(txID)])
|
||||
var softwareOK bool
|
||||
var lastAttr uint16
|
||||
var gotFP uint32
|
||||
if err := foreachAttr(b[headerLen:], func(attrType uint16, a []byte) error {
|
||||
lastAttr = attrType
|
||||
if attrType == attrNumSoftware && string(a) == software {
|
||||
softwareOK = true
|
||||
}
|
||||
if attrType == attrNumFingerprint && len(a) == 4 {
|
||||
gotFP = binary.BigEndian.Uint32(a)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return TxID{}, err
|
||||
}
|
||||
if !softwareOK {
|
||||
return TxID{}, ErrWrongSoftware
|
||||
}
|
||||
if lastAttr != attrNumFingerprint {
|
||||
return TxID{}, ErrNoFingerprint
|
||||
}
|
||||
wantFP := fingerPrint(b[:len(b)-lenFingerprint])
|
||||
if gotFP != wantFP {
|
||||
return TxID{}, ErrWrongFingerprint
|
||||
}
|
||||
return txID, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotSTUN = errors.New("response is not a STUN packet")
|
||||
ErrNotSuccessResponse = errors.New("STUN packet is not a response")
|
||||
ErrMalformedAttrs = errors.New("STUN response has malformed attributes")
|
||||
ErrNotBindingRequest = errors.New("STUN request not a binding request")
|
||||
ErrWrongSoftware = errors.New("STUN request came from non-Tailscale software")
|
||||
ErrNoFingerprint = errors.New("STUN request didn't end in fingerprint")
|
||||
ErrWrongFingerprint = errors.New("STUN request had bogus fingerprint")
|
||||
)
|
||||
|
||||
func foreachAttr(b []byte, fn func(attrType uint16, a []byte) error) error {
|
||||
for len(b) > 0 {
|
||||
if len(b) < 4 {
|
||||
return ErrMalformedAttrs
|
||||
}
|
||||
attrType := binary.BigEndian.Uint16(b[:2])
|
||||
attrLen := int(binary.BigEndian.Uint16(b[2:4]))
|
||||
attrLenWithPad := (attrLen + 3) &^ 3
|
||||
b = b[4:]
|
||||
if attrLenWithPad > len(b) {
|
||||
return ErrMalformedAttrs
|
||||
}
|
||||
if err := fn(attrType, b[:attrLen]); err != nil {
|
||||
return err
|
||||
}
|
||||
b = b[attrLenWithPad:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response generates a binding response.
|
||||
func Response(txID TxID, addrPort netip.AddrPort) []byte {
|
||||
addr := addrPort.Addr()
|
||||
|
||||
var fam byte
|
||||
if addr.Is4() {
|
||||
fam = 1
|
||||
} else if addr.Is6() {
|
||||
fam = 2
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
attrsLen := 8 + addr.BitLen()/8
|
||||
b := make([]byte, 0, headerLen+attrsLen)
|
||||
|
||||
// Header
|
||||
b = append(b, 0x01, 0x01) // success
|
||||
b = appendU16(b, uint16(attrsLen))
|
||||
b = append(b, magicCookie...)
|
||||
b = append(b, txID[:]...)
|
||||
|
||||
// Attributes (well, one)
|
||||
b = appendU16(b, attrXorMappedAddress)
|
||||
b = appendU16(b, uint16(4+addr.BitLen()/8))
|
||||
b = append(b,
|
||||
0, // unused byte
|
||||
fam)
|
||||
b = appendU16(b, addrPort.Port()^0x2112) // first half of magicCookie
|
||||
ipa := addr.As16()
|
||||
for i, o := range ipa[16-addr.BitLen()/8:] {
|
||||
if i < 4 {
|
||||
b = append(b, o^magicCookie[i])
|
||||
} else {
|
||||
b = append(b, o^txID[i-len(magicCookie)])
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ParseResponse parses a successful binding response STUN packet.
|
||||
// The IP address is extracted from the XOR-MAPPED-ADDRESS attribute.
|
||||
func ParseResponse(b []byte) (tID TxID, addr netip.AddrPort, err error) {
|
||||
if !Is(b) {
|
||||
return tID, netip.AddrPort{}, ErrNotSTUN
|
||||
}
|
||||
copy(tID[:], b[8:8+len(tID)])
|
||||
if b[0] != 0x01 || b[1] != 0x01 {
|
||||
return tID, netip.AddrPort{}, ErrNotSuccessResponse
|
||||
}
|
||||
attrsLen := int(binary.BigEndian.Uint16(b[2:4]))
|
||||
b = b[headerLen:] // remove STUN header
|
||||
if attrsLen > len(b) {
|
||||
return tID, netip.AddrPort{}, ErrMalformedAttrs
|
||||
} else if len(b) > attrsLen {
|
||||
b = b[:attrsLen] // trim trailing packet bytes
|
||||
}
|
||||
|
||||
var fallbackAddr netip.AddrPort
|
||||
|
||||
// Read through the attributes.
|
||||
// The the addr+port reported by XOR-MAPPED-ADDRESS
|
||||
// as the canonical value. If the attribute is not
|
||||
// present but the STUN server responds with
|
||||
// MAPPED-ADDRESS we fall back to it.
|
||||
if err := foreachAttr(b, func(attrType uint16, attr []byte) error {
|
||||
switch attrType {
|
||||
case attrXorMappedAddress, attrXorMappedAddressAlt:
|
||||
ipSlice, port, err := xorMappedAddress(tID, attr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ip, ok := netip.AddrFromSlice(ipSlice); ok {
|
||||
addr = netip.AddrPortFrom(ip.Unmap(), port)
|
||||
}
|
||||
case attrMappedAddress:
|
||||
ipSlice, port, err := mappedAddress(attr)
|
||||
if err != nil {
|
||||
return ErrMalformedAttrs
|
||||
}
|
||||
if ip, ok := netip.AddrFromSlice(ipSlice); ok {
|
||||
fallbackAddr = netip.AddrPortFrom(ip.Unmap(), port)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}); err != nil {
|
||||
return TxID{}, netip.AddrPort{}, err
|
||||
}
|
||||
|
||||
if addr.IsValid() {
|
||||
return tID, addr, nil
|
||||
}
|
||||
if fallbackAddr.IsValid() {
|
||||
return tID, fallbackAddr, nil
|
||||
}
|
||||
return tID, netip.AddrPort{}, ErrMalformedAttrs
|
||||
}
|
||||
|
||||
func xorMappedAddress(tID TxID, b []byte) (addr []byte, port uint16, err error) {
|
||||
// XOR-MAPPED-ADDRESS attribute, RFC5389 Section 15.2
|
||||
if len(b) < 4 {
|
||||
return nil, 0, ErrMalformedAttrs
|
||||
}
|
||||
xorPort := binary.BigEndian.Uint16(b[2:4])
|
||||
addrField := b[4:]
|
||||
port = xorPort ^ 0x2112 // first half of magicCookie
|
||||
|
||||
addrLen := familyAddrLen(b[1])
|
||||
if addrLen == 0 {
|
||||
return nil, 0, ErrMalformedAttrs
|
||||
}
|
||||
if len(addrField) < addrLen {
|
||||
return nil, 0, ErrMalformedAttrs
|
||||
}
|
||||
xorAddr := addrField[:addrLen]
|
||||
addr = make([]byte, addrLen)
|
||||
for i := range xorAddr {
|
||||
if i < len(magicCookie) {
|
||||
addr[i] = xorAddr[i] ^ magicCookie[i]
|
||||
} else {
|
||||
addr[i] = xorAddr[i] ^ tID[i-len(magicCookie)]
|
||||
}
|
||||
}
|
||||
return addr, port, nil
|
||||
}
|
||||
|
||||
func familyAddrLen(fam byte) int {
|
||||
switch fam {
|
||||
case 0x01: // IPv4
|
||||
return net.IPv4len
|
||||
case 0x02: // IPv6
|
||||
return net.IPv6len
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func mappedAddress(b []byte) (addr []byte, port uint16, err error) {
|
||||
if len(b) < 4 {
|
||||
return nil, 0, ErrMalformedAttrs
|
||||
}
|
||||
port = uint16(b[2])<<8 | uint16(b[3])
|
||||
addrField := b[4:]
|
||||
addrLen := familyAddrLen(b[1])
|
||||
if addrLen == 0 {
|
||||
return nil, 0, ErrMalformedAttrs
|
||||
}
|
||||
if len(addrField) < addrLen {
|
||||
return nil, 0, ErrMalformedAttrs
|
||||
}
|
||||
return bytes.Clone(addrField[:addrLen]), port, nil
|
||||
}
|
||||
|
||||
// Is reports whether b is a STUN message.
|
||||
func Is(b []byte) bool {
|
||||
return len(b) >= headerLen &&
|
||||
b[0]&0b11000000 == 0 && // top two bits must be zero
|
||||
string(b[4:8]) == magicCookie
|
||||
}
|
Reference in New Issue
Block a user