diff --git a/conceal/conn_framed.go b/conceal/conn_framed.go index 855fdcb..955f248 100644 --- a/conceal/conn_framed.go +++ b/conceal/conn_framed.go @@ -10,18 +10,19 @@ import ( ) type FramedOpts struct { - H1 *rangedHeader - H2 *rangedHeader - H3 *rangedHeader - H4 *rangedHeader - S1 int - S2 int - S3 int - S4 int + H1 *RangedHeader + H2 *RangedHeader + H3 *RangedHeader + H4 *RangedHeader + S1 int + S2 int + S3 int + S4 int + HeaderCompat bool } func (o *FramedOpts) HasIntersections() bool { - headers := []*rangedHeader{o.H1, o.H2, o.H3, o.H4} + headers := []*RangedHeader{o.H1, o.H2, o.H3, o.H4} for i := range len(headers) { left := headers[i] @@ -51,28 +52,28 @@ func newFrameEncoding(opts FramedOpts) (e frameEncoding, ok bool) { e.header.initial = *opts.H1 ok = true } else { - e.header.initial = rangedHeader{WireguardMsgInitiationType, WireguardMsgInitiationType} + e.header.initial = RangedHeader{WireguardMsgInitiationType, WireguardMsgInitiationType} } if opts.H2 != nil { e.header.response = *opts.H2 ok = true } else { - e.header.response = rangedHeader{WireguardMsgResponseType, WireguardMsgResponseType} + e.header.response = RangedHeader{WireguardMsgResponseType, WireguardMsgResponseType} } if opts.H3 != nil { e.header.cookie = *opts.H3 ok = true } else { - e.header.cookie = rangedHeader{WireguardMsgCookieReplyType, WireguardMsgCookieReplyType} + e.header.cookie = RangedHeader{WireguardMsgCookieReplyType, WireguardMsgCookieReplyType} } if opts.H4 != nil { e.header.transport = *opts.H4 ok = true } else { - e.header.transport = rangedHeader{WireguardMsgTransportType, WireguardMsgTransportType} + e.header.transport = RangedHeader{WireguardMsgTransportType, WireguardMsgTransportType} } if opts.S1 != 0 { @@ -95,15 +96,16 @@ func newFrameEncoding(opts FramedOpts) (e frameEncoding, ok bool) { ok = true } + e.compat = opts.HeaderCompat return e, ok } type frameEncoding struct { header struct { - initial rangedHeader - response rangedHeader - cookie rangedHeader - transport rangedHeader + initial RangedHeader + response RangedHeader + cookie RangedHeader + transport RangedHeader } padding struct { initial int @@ -114,87 +116,127 @@ type frameEncoding struct { compat bool } -func (e *frameEncoding) encodeOne(dst, src []byte, header rangedHeader, padding int) int { +func encodeOne(dst, src []byte, header RangedHeader, padding int) int { + rand.Read(dst[:padding]) + dst = dst[padding:] + + binary.LittleEndian.PutUint32(dst[:4], header.Generate()) + dst = dst[4:] + + n := copy(dst, src[4:]) + return padding + 4 + n +} + +func encodeOneCompat(dst, src []byte, padding int) int { rand.Read(dst[:padding]) n := copy(dst[padding:], src) - - if !e.compat { - binary.LittleEndian.PutUint32(dst[padding:padding+4], header.Generate()) - } - return padding + n } func (e *frameEncoding) Encode(dst, src []byte) int { + if len(src) < 4 { + return 0 + } + header := binary.LittleEndian.Uint32(src[:4]) + if e.compat { if e.header.initial.Validate(header) { - return e.encodeOne(dst, src, e.header.initial, e.padding.initial) + return encodeOneCompat(dst, src, e.padding.initial) } else if e.header.response.Validate(header) { - return e.encodeOne(dst, src, e.header.response, e.padding.response) + return encodeOneCompat(dst, src, e.padding.response) } else if e.header.cookie.Validate(header) { - return e.encodeOne(dst, src, e.header.cookie, e.padding.cookie) + return encodeOneCompat(dst, src, e.padding.cookie) } else if e.header.transport.Validate(header) { - return e.encodeOne(dst, src, e.header.transport, e.padding.transport) + return encodeOneCompat(dst, src, e.padding.transport) } } else { switch src[0] { case WireguardMsgInitiationType: - return e.encodeOne(dst, src, e.header.initial, e.padding.initial) + return encodeOne(dst, src, e.header.initial, e.padding.initial) case WireguardMsgResponseType: - return e.encodeOne(dst, src, e.header.response, e.padding.response) + return encodeOne(dst, src, e.header.response, e.padding.response) case WireguardMsgCookieReplyType: - return e.encodeOne(dst, src, e.header.cookie, e.padding.cookie) + return encodeOne(dst, src, e.header.cookie, e.padding.cookie) case WireguardMsgTransportType: - return e.encodeOne(dst, src, e.header.transport, e.padding.transport) + return encodeOne(dst, src, e.header.transport, e.padding.transport) } } return 0 } -func (e *frameEncoding) decodeOne(b []byte, header rangedHeader, padding int, originalHeader uint32) (res []byte, ok bool) { +func decodeOneCompat(b []byte, header RangedHeader, padding int) int { bb := b[padding:] if !header.Validate(binary.LittleEndian.Uint32(bb[:4])) { - return nil, false + return 0 } - n := copy(b, bb) - b = b[:n] - - if !e.compat { - binary.LittleEndian.PutUint32(b[:4], originalHeader) - } - - return b, true + return copy(b, bb) } -func (e *frameEncoding) Decode(b []byte) []byte { +func decodeOne(b []byte, header RangedHeader, padding int, originalHeader uint32) int { + bb := b[padding:] + if !header.Validate(binary.LittleEndian.Uint32(bb[:4])) { + return 0 + } + + binary.LittleEndian.PutUint32(b[:4], originalHeader) + n := copy(b[4:], bb[4:]) + + return 4 + n +} + +func (e *frameEncoding) Decode(b []byte) int { if len(b) == WireguardMsgInitiationSize+e.padding.initial { - if bb, ok := e.decodeOne(b, e.header.initial, e.padding.initial, WireguardMsgInitiationType); ok { - return bb + if e.compat { + if n := decodeOneCompat(b, e.header.initial, e.padding.initial); n != 0 { + return n + } + } else { + if n := decodeOne(b, e.header.initial, e.padding.initial, WireguardMsgInitiationType); n != 0 { + return n + } } } if len(b) == WireguardMsgResponseSize+e.padding.response { - if bb, ok := e.decodeOne(b, e.header.response, e.padding.response, WireguardMsgResponseType); ok { - return bb + if e.compat { + if n := decodeOneCompat(b, e.header.response, e.padding.response); n != 0 { + return n + } + } else { + if n := decodeOne(b, e.header.response, e.padding.response, WireguardMsgResponseType); n != 0 { + return n + } } } if len(b) == WireguardMsgCookieReplySize+e.padding.cookie { - if bb, ok := e.decodeOne(b, e.header.cookie, e.padding.cookie, WireguardMsgCookieReplyType); ok { - return bb + if e.compat { + if n := decodeOneCompat(b, e.header.cookie, e.padding.cookie); n != 0 { + return n + } + } else { + if n := decodeOne(b, e.header.cookie, e.padding.cookie, WireguardMsgCookieReplyType); n != 0 { + return n + } } } if len(b) >= WireguardMsgTransportMinSize+e.padding.transport { - if bb, ok := e.decodeOne(b, e.header.transport, e.padding.transport, WireguardMsgTransportType); ok { - return bb + if e.compat { + if n := decodeOneCompat(b, e.header.transport, e.padding.transport); n != 0 { + return n + } + } else { + if n := decodeOne(b, e.header.transport, e.padding.transport, WireguardMsgTransportType); n != 0 { + return n + } } } - return b + return len(b) } func NewFramedConn(conn net.Conn, pool *sync.Pool, opts FramedOpts) (c *FramedConn, ok bool) { @@ -218,8 +260,8 @@ type FramedConn struct { func (c *FramedConn) Read(b []byte) (n int, err error) { n, err = c.Conn.Read(b) - b = c.enc.Decode(b[:n]) - return len(b), err + n = c.enc.Decode(b[:n]) + return n, err } func (c *FramedConn) Write(b []byte) (n int, err error) { @@ -254,8 +296,8 @@ type FramedUDPConn struct { func (c *FramedUDPConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) { n, oobn, flags, addr, err = c.UDPConn.ReadMsgUDP(b, oob) - b = c.enc.Decode(b[:n]) - return len(b), oobn, flags, addr, err + n = c.enc.Decode(b[:n]) + return n, oobn, flags, addr, err } func (c *FramedUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) { @@ -290,14 +332,16 @@ type FramedBatchConn struct { func (c *FramedBatchConn) ReadBatch(ms []ipv4.Message, flags int) (n int, err error) { n, err = c.BatchConn.ReadBatch(ms, flags) + if err != nil { + return 0, err + } for i := range ms[:n] { b := ms[i].Buffers[0][:ms[i].N] - b = c.enc.Decode(b) - ms[i].N = len(b) + ms[i].N = c.enc.Decode(b) } - return n, err + return n, nil } func (c *FramedBatchConn) WriteBatch(ms []ipv4.Message, flags int) (n int, err error) { @@ -309,7 +353,7 @@ func (c *FramedBatchConn) WriteBatch(ms []ipv4.Message, flags int) (n int, err e ms[i].Buffers[0] = t[:n] } - // ms[x].N has incorrect N because the original data was modifier above + // ms[i].N has incorrect N because the original data was modifier above // however, WG does not check this field, so this is fine return c.BatchConn.WriteBatch(ms, flags) } diff --git a/conceal/header.go b/conceal/header.go index b31b14d..040e411 100644 --- a/conceal/header.go +++ b/conceal/header.go @@ -9,12 +9,12 @@ import ( "strings" ) -type rangedHeader struct { +type RangedHeader struct { start uint32 end uint32 } -func NewRangedHeader(spec string) (*rangedHeader, error) { +func NewRangedHeader(spec string) (*RangedHeader, error) { parts := strings.Split(spec, "-") if len(parts) < 1 || len(parts) > 2 { return nil, errors.New("bad format") @@ -39,24 +39,24 @@ func NewRangedHeader(spec string) (*rangedHeader, error) { return nil, errors.New("wrong range specified") } - return &rangedHeader{ + return &RangedHeader{ start: uint32(start), end: uint32(end), }, nil } -func (h *rangedHeader) GenSpec() string { +func (h *RangedHeader) GenSpec() string { if h.start == h.end { return fmt.Sprintf("%d", h.start) } return fmt.Sprintf("%d-%d", h.start, h.end) } -func (h *rangedHeader) Validate(val uint32) bool { +func (h *RangedHeader) Validate(val uint32) bool { return h.start <= val && val <= h.end } -func (h *rangedHeader) Generate() uint32 { +func (h *RangedHeader) Generate() uint32 { high := int64(h.end - h.start + 1) r, _ := rand.Int(rand.Reader, big.NewInt(high)) return h.start + uint32(r.Int64()) diff --git a/device/cookie.go b/device/cookie.go index a093c8b..1a78373 100644 --- a/device/cookie.go +++ b/device/cookie.go @@ -118,6 +118,7 @@ func (st *CookieChecker) CreateReply( msg []byte, recv uint32, src []byte, + typ uint32, ) (*MessageCookieReply, error) { st.RLock() @@ -153,7 +154,7 @@ func (st *CookieChecker) CreateReply( smac1 := smac2 - blake2s.Size128 reply := new(MessageCookieReply) - reply.Type = MessageCookieReplyType + reply.Type = typ reply.Receiver = recv _, err := rand.Read(reply.Nonce[:]) diff --git a/device/cookie_test.go b/device/cookie_test.go index c937290..9df3049 100644 --- a/device/cookie_test.go +++ b/device/cookie_test.go @@ -99,7 +99,7 @@ func TestCookieMAC1(t *testing.T) { 0x8c, 0xe1, 0xe8, 0xfa, 0x67, 0x20, 0x80, 0x6d, } generator.AddMacs(msg) - reply, err := checker.CreateReply(msg, 1377, src) + reply, err := checker.CreateReply(msg, 1377, src, MessageCookieReplyType) if err != nil { t.Fatal("Failed to create cookie reply:", err) } diff --git a/device/device.go b/device/device.go index f697444..741e55c 100644 --- a/device/device.go +++ b/device/device.go @@ -41,12 +41,11 @@ type Device struct { net struct { stopping sync.WaitGroup sync.RWMutex - bind conn.Bind // bind interface - netlinkCancel *rwcancel.RWCancel - port uint16 // listening port - fwmark uint32 // mark value (0 = disabled) - brokenRoaming bool - + bind conn.Bind // bind interface + netlinkCancel *rwcancel.RWCancel + port uint16 // listening port + fwmark uint32 // mark value (0 = disabled) + brokenRoaming bool network string framedOpts conceal.FramedOpts preludeOpts conceal.PreludeOpts @@ -306,6 +305,7 @@ func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device { device.indexTable.Init() device.net.network = "udp" + device.net.framedOpts.HeaderCompat = true device.PopulatePools() diff --git a/device/noise-protocol.go b/device/noise-protocol.go index f3464eb..e37d41e 100644 --- a/device/noise-protocol.go +++ b/device/noise-protocol.go @@ -193,8 +193,16 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e handshake.mixHash(handshake.remoteStatic[:]) + opts := &device.net.framedOpts + var typ uint32 + if opts.HeaderCompat && opts.H1 != nil { + typ = opts.H1.Generate() + } else { + typ = MessageInitiationType + } + msg := MessageInitiation{ - Type: MessageInitiationType, + Type: typ, Ephemeral: handshake.localEphemeral.publicKey(), } @@ -250,8 +258,15 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer { chainKey [blake2s.Size]byte ) - if msg.Type != MessageInitiationType { - return nil + opts := &device.net.framedOpts + if opts.HeaderCompat && opts.H1 != nil { + if !opts.H1.Validate(msg.Type) { + return nil + } + } else { + if msg.Type != MessageInitiationType { + return nil + } } device.staticIdentity.RLock() @@ -366,8 +381,16 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error return nil, err } + opts := &device.net.framedOpts + var typ uint32 + if opts.HeaderCompat && opts.H2 != nil { + typ = opts.H2.Generate() + } else { + typ = MessageResponseType + } + var msg MessageResponse - msg.Type = MessageResponseType + msg.Type = typ msg.Sender = handshake.localIndex msg.Receiver = handshake.remoteIndex @@ -417,8 +440,15 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error } func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer { - if msg.Type != MessageResponseType { - return nil + opts := &device.net.framedOpts + if opts.HeaderCompat && opts.H2 != nil { + if !opts.H2.Validate(msg.Type) { + return nil + } + } else { + if msg.Type != MessageResponseType { + return nil + } } // lookup handshake by receiver diff --git a/device/receive.go b/device/receive.go index 723d3e8..8936148 100644 --- a/device/receive.go +++ b/device/receive.go @@ -137,6 +137,19 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive packet := bufsArrs[i][:size] msgType := binary.LittleEndian.Uint32(packet[:4]) + opts := &device.net.framedOpts + if opts.HeaderCompat { + if opts.H1 != nil && opts.H1.Validate(msgType) { + msgType = MessageInitiationType + } else if opts.H2 != nil && opts.H2.Validate(msgType) { + msgType = MessageResponseType + } else if opts.H3 != nil && opts.H3.Validate(msgType) { + msgType = MessageCookieReplyType + } else if opts.H4 != nil && opts.H4.Validate(msgType) { + msgType = MessageTransportType + } + } + switch msgType { // check if transport diff --git a/device/send.go b/device/send.go index 9a083ef..b522b54 100644 --- a/device/send.go +++ b/device/send.go @@ -183,8 +183,16 @@ func (peer *Peer) SendHandshakeResponse() error { func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement) error { device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString()) + opts := &device.net.framedOpts + var typ uint32 + if opts.HeaderCompat && opts.H3 != nil { + typ = opts.H3.Generate() + } else { + typ = MessageCookieReplyType + } + sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8]) - reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes()) + reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes(), typ) if err != nil { device.log.Errorf("Failed to create cookie reply: %v", err) return err @@ -460,7 +468,15 @@ func (device *Device) RoutineEncryption(id int) { fieldReceiver := header[4:8] fieldNonce := header[8:16] - binary.LittleEndian.PutUint32(fieldType, MessageTransportType) + opts := &device.net.framedOpts + var typ uint32 + if opts.HeaderCompat && opts.H4 != nil { + typ = opts.H4.Generate() + } else { + typ = MessageTransportType + } + + binary.LittleEndian.PutUint32(fieldType, typ) binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex) binary.LittleEndian.PutUint64(fieldNonce, elem.nonce) diff --git a/device/uapi.go b/device/uapi.go index bd62dbb..1d4a7dc 100644 --- a/device/uapi.go +++ b/device/uapi.go @@ -160,6 +160,8 @@ func (device *Device) IpcGetOperation(w io.Writer) error { sendf("format_out=%s", device.net.masqueradeOpts.RulesOut.Spec()) } + sendf("header_compat=%s", strconv.FormatBool(device.net.framedOpts.HeaderCompat)) + for _, peer := range device.peers.keyMap { // Serialize peer state. peer.handshake.mutex.RLock() @@ -573,29 +575,43 @@ func (device *Device) handleDeviceLine(key, value string) error { case "format_in": rules, err := conceal.ParseRules(value) if err != nil { - return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse obfuscators: %w", err) + return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse rules: %w", err) } - device.log.Verbosef("UAPI: Updating fmt_in") + device.log.Verbosef("UAPI: Updating format_in") device.net.masqueradeOpts.RulesIn = rules if err := device.BindUpdate(); err != nil { // TODO: change IpcErrorPortInUse to something reasonable - return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set fmt_in: %w", err) + return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set format_in: %w", err) } case "format_out": rules, err := conceal.ParseRules(value) if err != nil { - return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse obfuscators: %w", err) + return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse rules: %w", err) } - device.log.Verbosef("UAPI: Updating fmt_out") + device.log.Verbosef("UAPI: Updating format_out") device.net.masqueradeOpts.RulesOut = rules if err := device.BindUpdate(); err != nil { // TODO: change IpcErrorPortInUse to something reasonable - return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set fmt_out: %w", err) + return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set format_out: %w", err) + } + + case "header_compat": + compat, err := strconv.ParseBool(value) + if err != nil { + return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse header_compat: %w", err) + } + + device.log.Verbosef("UAPI: Updating header_compat") + device.net.framedOpts.HeaderCompat = compat + + if err := device.BindUpdate(); err != nil { + // TODO: change IpcErrorPortInUse to something reasonable + return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set header_compat: %w", err) } default: