fix: refactor processing of junk packets (#103)
- fix the bug that transport packet interprets as init/resp/cookie with the same size - cleanup error responses - reduce buffer allocations
This commit is contained in:
parent
f6542209f4
commit
0361c54dca
33 changed files with 852 additions and 2832 deletions
425
device/device.go
425
device/device.go
|
|
@ -6,57 +6,17 @@
|
|||
package device
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device/awg"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ratelimiter"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
)
|
||||
|
||||
type Version uint8
|
||||
|
||||
const (
|
||||
VersionDefault Version = iota
|
||||
VersionAwg
|
||||
VersionAwgSpecialHandshake
|
||||
)
|
||||
|
||||
// TODO:
|
||||
type AtomicVersion struct {
|
||||
value atomic.Uint32
|
||||
}
|
||||
|
||||
func NewAtomicVersion(v Version) *AtomicVersion {
|
||||
av := &AtomicVersion{}
|
||||
av.Store(v)
|
||||
return av
|
||||
}
|
||||
|
||||
func (av *AtomicVersion) Load() Version {
|
||||
return Version(av.value.Load())
|
||||
}
|
||||
|
||||
func (av *AtomicVersion) Store(v Version) {
|
||||
av.value.Store(uint32(v))
|
||||
}
|
||||
|
||||
func (av *AtomicVersion) CompareAndSwap(old, new Version) bool {
|
||||
return av.value.CompareAndSwap(uint32(old), uint32(new))
|
||||
}
|
||||
|
||||
func (av *AtomicVersion) Swap(new Version) Version {
|
||||
return Version(av.value.Swap(uint32(new)))
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
state struct {
|
||||
// state holds the device's state. It is accessed atomically.
|
||||
|
|
@ -130,8 +90,27 @@ type Device struct {
|
|||
closed chan struct{}
|
||||
log *Logger
|
||||
|
||||
version Version
|
||||
awg awg.Protocol
|
||||
junk struct {
|
||||
min int
|
||||
max int
|
||||
count int
|
||||
}
|
||||
|
||||
headers struct {
|
||||
init *magicHeader
|
||||
cookie *magicHeader
|
||||
response *magicHeader
|
||||
transport *magicHeader
|
||||
}
|
||||
|
||||
paddings struct {
|
||||
init int
|
||||
response int
|
||||
cookie int
|
||||
transport int
|
||||
}
|
||||
|
||||
ipackets [5]*obfChain
|
||||
}
|
||||
|
||||
// deviceState represents the state of a Device.
|
||||
|
|
@ -342,6 +321,11 @@ func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
|||
device.rate.limiter.Init()
|
||||
device.indexTable.Init()
|
||||
|
||||
device.headers.init = &magicHeader{start: MessageInitiationType, end: MessageInitiationType}
|
||||
device.headers.response = &magicHeader{start: MessageResponseType, end: MessageResponseType}
|
||||
device.headers.cookie = &magicHeader{start: MessageCookieReplyType, end: MessageCookieReplyType}
|
||||
device.headers.transport = &magicHeader{start: MessageTransportType, end: MessageTransportType}
|
||||
|
||||
device.PopulatePools()
|
||||
|
||||
// create queues
|
||||
|
|
@ -439,8 +423,6 @@ func (device *Device) Close() {
|
|||
|
||||
device.rate.limiter.Close()
|
||||
|
||||
device.resetProtocol()
|
||||
|
||||
device.log.Verbosef("Device closed")
|
||||
close(device.closed)
|
||||
}
|
||||
|
|
@ -580,358 +562,3 @@ func (device *Device) BindClose() error {
|
|||
device.net.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (device *Device) isAWG() bool {
|
||||
return device.version >= VersionAwg
|
||||
}
|
||||
|
||||
func (device *Device) resetProtocol() {
|
||||
// restore default message type values
|
||||
MessageInitiationType = DefaultMessageInitiationType
|
||||
MessageResponseType = DefaultMessageResponseType
|
||||
MessageCookieReplyType = DefaultMessageCookieReplyType
|
||||
MessageTransportType = DefaultMessageTransportType
|
||||
}
|
||||
|
||||
func (device *Device) handlePostConfig(tempAwg *awg.Protocol) error {
|
||||
if !tempAwg.Cfg.IsSet && !tempAwg.HandshakeHandler.IsSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
isAwgOn := false
|
||||
device.awg.Mux.Lock()
|
||||
if tempAwg.Cfg.JunkPacketCount < 0 {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
"JunkPacketCount should be non negative",
|
||||
),
|
||||
)
|
||||
}
|
||||
device.awg.Cfg.JunkPacketCount = tempAwg.Cfg.JunkPacketCount
|
||||
if tempAwg.Cfg.JunkPacketCount != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
device.awg.Cfg.JunkPacketMinSize = tempAwg.Cfg.JunkPacketMinSize
|
||||
if tempAwg.Cfg.JunkPacketMinSize != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
if device.awg.Cfg.JunkPacketCount > 0 &&
|
||||
tempAwg.Cfg.JunkPacketMaxSize == tempAwg.Cfg.JunkPacketMinSize {
|
||||
|
||||
tempAwg.Cfg.JunkPacketMaxSize++ // to make rand gen work
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.JunkPacketMaxSize >= MaxSegmentSize {
|
||||
device.awg.Cfg.JunkPacketMinSize = 0
|
||||
device.awg.Cfg.JunkPacketMaxSize = 1
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
"JunkPacketMaxSize: %d; should be smaller than maxSegmentSize: %d",
|
||||
tempAwg.Cfg.JunkPacketMaxSize,
|
||||
MaxSegmentSize,
|
||||
))
|
||||
} else if tempAwg.Cfg.JunkPacketMaxSize < tempAwg.Cfg.JunkPacketMinSize {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
"maxSize: %d; should be greater than minSize: %d",
|
||||
tempAwg.Cfg.JunkPacketMaxSize,
|
||||
tempAwg.Cfg.JunkPacketMinSize,
|
||||
))
|
||||
} else {
|
||||
device.awg.Cfg.JunkPacketMaxSize = tempAwg.Cfg.JunkPacketMaxSize
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.JunkPacketMaxSize != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
magicHeaders := make([]awg.MagicHeader, 4)
|
||||
|
||||
if len(tempAwg.Cfg.MagicHeaders.Values) != 4 {
|
||||
return ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
"magic headers should have 4 values; got: %d",
|
||||
len(tempAwg.Cfg.MagicHeaders.Values),
|
||||
)
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.MagicHeaders.Values[0].Min > 4 {
|
||||
isAwgOn = true
|
||||
device.log.Verbosef("UAPI: Updating init_packet_magic_header")
|
||||
magicHeaders[0] = tempAwg.Cfg.MagicHeaders.Values[0]
|
||||
|
||||
MessageInitiationType = magicHeaders[0].Min
|
||||
} else {
|
||||
device.log.Verbosef("UAPI: Using default init type")
|
||||
MessageInitiationType = DefaultMessageInitiationType
|
||||
magicHeaders[0] = awg.NewMagicHeaderSameValue(DefaultMessageInitiationType)
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.MagicHeaders.Values[1].Min > 4 {
|
||||
isAwgOn = true
|
||||
|
||||
device.log.Verbosef("UAPI: Updating response_packet_magic_header")
|
||||
magicHeaders[1] = tempAwg.Cfg.MagicHeaders.Values[1]
|
||||
MessageResponseType = magicHeaders[1].Min
|
||||
} else {
|
||||
device.log.Verbosef("UAPI: Using default response type")
|
||||
MessageResponseType = DefaultMessageResponseType
|
||||
magicHeaders[1] = awg.NewMagicHeaderSameValue(DefaultMessageResponseType)
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.MagicHeaders.Values[2].Min > 4 {
|
||||
isAwgOn = true
|
||||
|
||||
device.log.Verbosef("UAPI: Updating underload_packet_magic_header")
|
||||
magicHeaders[2] = tempAwg.Cfg.MagicHeaders.Values[2]
|
||||
MessageCookieReplyType = magicHeaders[2].Min
|
||||
} else {
|
||||
device.log.Verbosef("UAPI: Using default underload type")
|
||||
MessageCookieReplyType = DefaultMessageCookieReplyType
|
||||
magicHeaders[2] = awg.NewMagicHeaderSameValue(DefaultMessageCookieReplyType)
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.MagicHeaders.Values[3].Min > 4 {
|
||||
isAwgOn = true
|
||||
|
||||
device.log.Verbosef("UAPI: Updating transport_packet_magic_header")
|
||||
magicHeaders[3] = tempAwg.Cfg.MagicHeaders.Values[3]
|
||||
MessageTransportType = magicHeaders[3].Min
|
||||
} else {
|
||||
device.log.Verbosef("UAPI: Using default transport type")
|
||||
MessageTransportType = DefaultMessageTransportType
|
||||
magicHeaders[3] = awg.NewMagicHeaderSameValue(DefaultMessageTransportType)
|
||||
}
|
||||
|
||||
var err error
|
||||
device.awg.Cfg.MagicHeaders, err = awg.NewMagicHeaders(magicHeaders)
|
||||
if err != nil {
|
||||
errs = append(errs, ipcErrorf(ipc.IpcErrorInvalid, "new magic headers: %w", err))
|
||||
}
|
||||
|
||||
isSameHeaderMap := map[uint32]struct{}{
|
||||
MessageInitiationType: {},
|
||||
MessageResponseType: {},
|
||||
MessageCookieReplyType: {},
|
||||
MessageTransportType: {},
|
||||
}
|
||||
|
||||
// size will be different if same values
|
||||
if len(isSameHeaderMap) != 4 {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
`magic headers should differ; got: init:%d; recv:%d; unde:%d; tran:%d`,
|
||||
MessageInitiationType,
|
||||
MessageResponseType,
|
||||
MessageCookieReplyType,
|
||||
MessageTransportType,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
newInitSize := MessageInitiationSize + tempAwg.Cfg.InitHeaderJunkSize
|
||||
|
||||
if newInitSize >= MaxSegmentSize {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
`init header size(148) + junkSize:%d; should be smaller than maxSegmentSize: %d`,
|
||||
tempAwg.Cfg.InitHeaderJunkSize,
|
||||
MaxSegmentSize,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
device.awg.Cfg.InitHeaderJunkSize = tempAwg.Cfg.InitHeaderJunkSize
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.InitHeaderJunkSize != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
newResponseSize := MessageResponseSize + tempAwg.Cfg.ResponseHeaderJunkSize
|
||||
|
||||
if newResponseSize >= MaxSegmentSize {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
`response header size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d`,
|
||||
tempAwg.Cfg.ResponseHeaderJunkSize,
|
||||
MaxSegmentSize,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
device.awg.Cfg.ResponseHeaderJunkSize = tempAwg.Cfg.ResponseHeaderJunkSize
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.ResponseHeaderJunkSize != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
newCookieSize := MessageCookieReplySize + tempAwg.Cfg.CookieReplyHeaderJunkSize
|
||||
|
||||
if newCookieSize >= MaxSegmentSize {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
`cookie reply size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d`,
|
||||
tempAwg.Cfg.CookieReplyHeaderJunkSize,
|
||||
MaxSegmentSize,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
device.awg.Cfg.CookieReplyHeaderJunkSize = tempAwg.Cfg.CookieReplyHeaderJunkSize
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.CookieReplyHeaderJunkSize != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
newTransportSize := MessageTransportSize + tempAwg.Cfg.TransportHeaderJunkSize
|
||||
|
||||
if newTransportSize >= MaxSegmentSize {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
`transport size(92) + junkSize:%d; should be smaller than maxSegmentSize: %d`,
|
||||
tempAwg.Cfg.TransportHeaderJunkSize,
|
||||
MaxSegmentSize,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
device.awg.Cfg.TransportHeaderJunkSize = tempAwg.Cfg.TransportHeaderJunkSize
|
||||
}
|
||||
|
||||
if tempAwg.Cfg.TransportHeaderJunkSize != 0 {
|
||||
isAwgOn = true
|
||||
}
|
||||
|
||||
isSameSizeMap := map[int]struct{}{
|
||||
newInitSize: {},
|
||||
newResponseSize: {},
|
||||
newCookieSize: {},
|
||||
newTransportSize: {},
|
||||
}
|
||||
|
||||
if len(isSameSizeMap) != 4 {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid,
|
||||
`new sizes should differ; init: %d; response: %d; cookie: %d; trans: %d`,
|
||||
newInitSize,
|
||||
newResponseSize,
|
||||
newCookieSize,
|
||||
newTransportSize,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
msgTypeToJunkSize = map[uint32]int{
|
||||
MessageInitiationType: device.awg.Cfg.InitHeaderJunkSize,
|
||||
MessageResponseType: device.awg.Cfg.ResponseHeaderJunkSize,
|
||||
MessageCookieReplyType: device.awg.Cfg.CookieReplyHeaderJunkSize,
|
||||
MessageTransportType: device.awg.Cfg.TransportHeaderJunkSize,
|
||||
}
|
||||
|
||||
packetSizeToMsgType = map[int]uint32{
|
||||
newInitSize: MessageInitiationType,
|
||||
newResponseSize: MessageResponseType,
|
||||
newCookieSize: MessageCookieReplyType,
|
||||
newTransportSize: MessageTransportType,
|
||||
}
|
||||
}
|
||||
|
||||
device.awg.IsOn.SetTo(isAwgOn)
|
||||
device.awg.JunkCreator = awg.NewJunkCreator(device.awg.Cfg)
|
||||
|
||||
if tempAwg.HandshakeHandler.IsSet {
|
||||
if err := tempAwg.HandshakeHandler.Validate(); err != nil {
|
||||
errs = append(errs, ipcErrorf(
|
||||
ipc.IpcErrorInvalid, "handshake handler validate: %w", err))
|
||||
} else {
|
||||
device.awg.HandshakeHandler = tempAwg.HandshakeHandler
|
||||
device.awg.HandshakeHandler.SpecialJunk.DefaultJunkCount = tempAwg.Cfg.JunkPacketCount
|
||||
device.version = VersionAwgSpecialHandshake
|
||||
}
|
||||
} else {
|
||||
device.version = VersionAwg
|
||||
}
|
||||
|
||||
device.awg.Mux.Unlock()
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (device *Device) ProcessAWGPacket(size int, packet *[]byte, buffer *[MaxMessageSize]byte) (uint32, error) {
|
||||
// TODO:
|
||||
// if awg.WaitResponse.ShouldWait.IsSet() {
|
||||
// awg.WaitResponse.Channel <- struct{}{}
|
||||
// }
|
||||
|
||||
expectedMsgType, isKnownSize := packetSizeToMsgType[size]
|
||||
if !isKnownSize {
|
||||
msgType, err := device.handleTransport(size, packet, buffer)
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("handle transport: %w", err)
|
||||
}
|
||||
|
||||
return msgType, nil
|
||||
}
|
||||
|
||||
junkSize := msgTypeToJunkSize[expectedMsgType]
|
||||
|
||||
// transport size can align with other header types;
|
||||
// making sure we have the right actualMsgType
|
||||
actualMsgType, err := device.getMsgType(packet, junkSize)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get msg type: %w", err)
|
||||
}
|
||||
|
||||
if actualMsgType == expectedMsgType {
|
||||
*packet = (*packet)[junkSize:]
|
||||
return actualMsgType, nil
|
||||
}
|
||||
|
||||
device.log.Verbosef("awg: transport packet lined up with another msg type")
|
||||
|
||||
msgType, err := device.handleTransport(size, packet, buffer)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("handle transport: %w", err)
|
||||
}
|
||||
|
||||
return msgType, nil
|
||||
}
|
||||
|
||||
func (device *Device) getMsgType(packet *[]byte, junkSize int) (uint32, error) {
|
||||
msgTypeValue := binary.LittleEndian.Uint32((*packet)[junkSize : junkSize+4])
|
||||
msgType, err := device.awg.GetMagicHeaderMinFor(msgTypeValue)
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get magic header min: %w", err)
|
||||
}
|
||||
|
||||
return msgType, nil
|
||||
}
|
||||
|
||||
func (device *Device) handleTransport(size int, packet *[]byte, buffer *[MaxMessageSize]byte) (uint32, error) {
|
||||
junkSize := device.awg.Cfg.TransportHeaderJunkSize
|
||||
|
||||
msgType, err := device.getMsgType(packet, junkSize)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("get msg type: %w", err)
|
||||
}
|
||||
|
||||
if msgType != MessageTransportType {
|
||||
// probably a junk packet
|
||||
return 0, fmt.Errorf("Received message with unknown type: %d", msgType)
|
||||
}
|
||||
|
||||
if junkSize > 0 {
|
||||
// remove junk from buffer by shifting the packet
|
||||
// this buffer is also used for decryption, so it needs to be corrected
|
||||
copy((*buffer)[:size], (*packet)[junkSize:])
|
||||
size -= junkSize
|
||||
// need to reinitialize packet as well
|
||||
(*packet) = (*packet)[:size]
|
||||
}
|
||||
|
||||
return msgType, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue