diff --git a/bufpool.go b/bufpool.go index 165b190..29b4572 100644 --- a/bufpool.go +++ b/bufpool.go @@ -4,25 +4,12 @@ package wpn import ( "bytes" "sync" -) -const ipPacketMaxSize = 65536 -var ipBufPool = sync.Pool{ - New: func()(any){ - buf := make([]byte, ipPacketMaxSize) - return &buf - }, -} - -func getIPPktBuf()(buf []byte, free func()){ - p := ipBufPool.Get().(*[]byte) - return *p, func(){ - ipBufPool.Put(p) - } -} + "github.com/kmcsr/wpn/internal/pool" +) var packetFormatBufPool = sync.Pool{ New: func()(any){ - return bytes.NewBuffer(make([]byte, 0, ipPacketMaxSize + 1024)) + return bytes.NewBuffer(make([]byte, 0, pool.IPPacketMaxSize + 1024)) }, } diff --git a/client/desktop/main.go b/client/desktop/main.go index ce327e2..f1f691c 100644 --- a/client/desktop/main.go +++ b/client/desktop/main.go @@ -1,4 +1,5 @@ +// Copyright (C) 2023 Kevin Z package main import ( @@ -11,8 +12,7 @@ import ( "time" "github.com/kmcsr/wpn" - "github.com/kmcsr/wpn/socks5" - "github.com/kmcsr/wpn/wssocks" + "github.com/kmcsr/wpn/l2tp" ) func main(){ @@ -23,7 +23,7 @@ func main(){ { ping, err := client.Ping() if err != nil { - loger.Fatalf("Cannot ping the server: %v", err) + // loger.Fatalf("Cannot ping the server: %v", err) } loger.Infof("Connected to the server: ping=%v", ping) } @@ -42,20 +42,16 @@ func main(){ } } }() - shandler := &wssocks.Handler{ - Client: client, - } - server := &socks5.Server{ - Addr: config.SocksAddr, - Handler: shandler, - DialTimeout: time.Second * 30, + server := &l2tp.Server{ + // Addr: config.SocksAddr, + Logger: loger, } go func(){ defer close(done) - loger.Infof("Starting socks5 server on %q", server.Addr) + loger.Infof("Starting L2TP server at %q", server.Addr) if err := server.ListenAndServe(); err != nil && !errors.Is(err, net.ErrClosed) { - loger.Fatalf("Error when running socks5 server: %v", err) + loger.Fatalf("Error when running L2TP server: %v", err) } }() diff --git a/conn.go b/conn.go index 8427dc6..c06afce 100644 --- a/conn.go +++ b/conn.go @@ -13,6 +13,7 @@ import ( "time" "nhooyr.io/websocket" + "github.com/kmcsr/wpn/internal/pool" ) var ( @@ -326,7 +327,7 @@ func (c *Conn)handleBinary(r io.Reader)(err error){ data := &packetDataT{ addr: &addr, } - buf, data.freeBuf = getIPPktBuf() + buf, data.freeBuf = pool.GetIPPacketBuf() var n int if n, err = br.Read(buf); err != nil { if errors.Is(err, io.EOF) { @@ -533,7 +534,7 @@ func (c *Conn)serveStream()(err error){ return } defer conn.Close() - buf, freeBuf := getIPPktBuf() + buf, freeBuf := pool.GetIPPacketBuf() defer freeBuf() for { var n int @@ -582,7 +583,7 @@ func (c *Conn)servePacket()(err error){ return } defer conn.Close() - buf, freeBuf := getIPPktBuf() + buf, freeBuf := pool.GetIPPacketBuf() defer freeBuf() for { var ( diff --git a/internal/pool/ippkt.go b/internal/pool/ippkt.go new file mode 100644 index 0000000..142f3a2 --- /dev/null +++ b/internal/pool/ippkt.go @@ -0,0 +1,21 @@ + +package pool + +import ( + "sync" +) + +const IPPacketMaxSize = 65536 +var ipBufPool = sync.Pool{ + New: func()(any){ + buf := make([]byte, IPPacketMaxSize) + return &buf + }, +} + +func GetIPPacketBuf()(buf []byte, free func()){ + p := ipBufPool.Get().(*[]byte) + return *p, func(){ + ipBufPool.Put(p) + } +} diff --git a/l2tp/errors.go b/l2tp/errors.go new file mode 100644 index 0000000..0174b9e --- /dev/null +++ b/l2tp/errors.go @@ -0,0 +1,72 @@ + +package l2tp + +import ( + "errors" + "fmt" + "runtime" + "sync" +) + +var WrongLengthErr = errors.New("Wrong length of the packet") + +type VersionError struct { + Version byte +} + +func (e *VersionError)Error()(string){ + return fmt.Sprintf("Unsupport version %d, support %d", e.Version, L2TPVersion) +} + +type UnexpectFlagValue struct { + Flag string + Expect bool +} + +func (e *UnexpectFlagValue)Error()(string){ + if e.Expect { + return fmt.Sprintf("Flag %s is expected to set", e.Flag) + } + return fmt.Sprintf("Flag %s is unexpected to set", e.Flag) +} + +type PanicError struct { + err any + stacktrace string +} + +var stackBufPool = sync.Pool{ + New: func()(any){ + buf := make([]byte, 1024) + return &buf + }, +} + +func getStack()(string){ + buf := *(stackBufPool.Get().(*[]byte)) + for { + n := runtime.Stack(buf, false) + if n < len(buf) { + s := (string)(buf[:n]) + stackBufPool.Put(&buf) + return s + } + stackBufPool.Put(&buf) + buf = make([]byte, len(buf) + 1024) + } +} + +func recoverAsError()(*PanicError){ + err := recover() + if err == nil { + return nil + } + return &PanicError{ + err: err, + stacktrace: getStack(), + } +} + +func (e *PanicError)Error()(string){ + return fmt.Sprintf("panic: %v\n", e.err) + e.stacktrace +} diff --git a/l2tp/server.go b/l2tp/server.go new file mode 100644 index 0000000..7585a1e --- /dev/null +++ b/l2tp/server.go @@ -0,0 +1,224 @@ + +// See +package l2tp + +import ( + "context" + "fmt" + "net" + + "github.com/kmcsr/go-logger" + "github.com/kmcsr/wpn/internal/pool" +) + +const L2TPVersion byte = 0x02 +const DefaultL2TPPort = ":1701" + +type Server struct { + Addr string + Logger logger.Logger +} + +func (s *Server)recordErr(addr net.Addr, err error){ + if s.Logger != nil { + s.Logger.Debugf("Error when handling %v: %v", addr, err) + } +} + +// serve a UDP connection +func (s *Server)Serve(conn net.PacketConn)(err error){ + defer conn.Close() + var ( + buf []byte + free func() + n int + addr net.Addr + ) + for { + buf, free = pool.GetIPPacketBuf() + if n, addr, err = conn.ReadFrom(buf); err != nil { + free() + return + } + go func(buf []byte, free func(), addr net.Addr){ + defer free() + s.handle(buf, addr) + }(buf[:n], free, addr) + } +} + +func (s *Server)Shutdown(ctx context.Context)(err error){ + return +} + +func (s *Server)ListenAndServe()(err error){ + addr := s.Addr + if addr == "" { + addr = DefaultL2TPPort + } + conn, err := net.ListenPacket("udp", addr) + if err != nil { + return + } + return s.Serve(conn) +} + +func (s *Server)handle(buf []byte, addr net.Addr){ + var err error + defer func(){ + if rer := recoverAsError(); rer != nil { + s.recordErr(addr, rer) + }else if err != nil { + s.recordErr(addr, err) + } + }() + var head *header + if head, buf, err = parseHeader(buf); err != nil { + return + } + if head.isControl { + avps := make(*avpPayload, 0, 3) + for len(buf) > 0 { + var avp *avpPayload + if avp, buf, err = parseAVP(buf); err != nil { + return + } + avps = append(avps, avp) + } + _ = avps[0] + } +} + +// This header is formatted: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |T|L|x|x|S|x|O|P|x|x|x|x| Ver | Length (opt) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Tunnel ID | Session ID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Ns (opt) | Nr (opt) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Offset Size (opt) | Offset pad... (opt) +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type header struct { + isControl bool + priority bool + tunnelID uint16 + sessionID uint16 + /* Ns indicates the sequence number for this data or control message */ + ns uint16 + /* Nr indicates the sequence number expected in the next control message to be received. */ + nr uint16 + length int +} + +const ( + flagType = 1 << (15 - 0) // The Type (T) bit indicates the type of message. It is set to 0 for a data message and 1 for a control message. + flagLength = 1 << (15 - 1) // If the Length (L) bit is 1, the Length field is present. This bit MUST be set to 1 for control messages. + flagSequence = 1 << (15 - 4) // If the Sequence (S) bit is set to 1 the Ns and Nr fields are present. The S bit MUST be set to 1 for control messages. + flagOffset = 1 << (15 - 6) // If the Offset (O) bit is 1, the Offset Size field is present. The O bit MUST be set to 0 (zero) for control messages. + flagPriority = 1 << (15 - 7) // If the Priority (P) bit is 1, this data message should receive preferential treatment in its local queuing and transmission. +) + +func parseHeader(buf []byte)(head *header, payload []byte, err error){ + if len(buf) < 6 { + return nil, nil, WrongLengthErr + } + flags := ((uint16)(buf[0]) << 8) | (uint16)(buf[1]) + buf = buf[2:] + version := (byte)(flags & 0xf) + if version != L2TPVersion { + err = &VersionError{version} + return + } + var ( + isControl bool = flags & flagType != 0 + isPriority bool = flags & flagPriority != 0 + leng int + tunnelID uint16 + sessionID uint16 + ns, nr uint16 + offset uint16 + ) + if flags & flagLength != 0 { + leng = ((int)(buf[0]) << 8) | (int)(buf[1]) + if len(buf) + 2 < leng { + return nil, nil, WrongLengthErr + } + buf = buf[2:leng - 2] + } + tunnelID = ((uint16)(buf[0]) << 8) | (uint16)(buf[1]) + sessionID = ((uint16)(buf[2]) << 8) | (uint16)(buf[3]) + buf = buf[4:] + if flags & flagSequence != 0 { + ns = ((uint16)(buf[0]) << 8) | (uint16)(buf[1]) + nr = ((uint16)(buf[2]) << 8) | (uint16)(buf[3]) + buf = buf[4:] + }else if isControl { + err = &UnexpectFlagValue{"Sequence", true} + return + } + if flags & flagOffset != 0 { + if isControl { + err = &UnexpectFlagValue{"Offset", false} + return + } + offset = ((uint16)(buf[0]) << 8) | (uint16)(buf[1]) + buf = buf[2 + offset:] + } + head = &header{ + isControl: isControl, + priority: isPriority, + tunnelID: tunnelID, + sessionID: sessionID, + ns: ns, + nr: nr, + length: leng, + } + payload = buf + return +} + +// Each AVP (Attribute-Value Pair) is encoded as: +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |M|H| rsvd | Length | Vendor ID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Attribute Type | Attribute Value... +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// [until Length is reached]... | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type avpPayload struct { + mandatory bool + hidden bool + length int + vendorID uint16 + attrType uint16 + value []byte +} + +const ( + flagMandatory = 1 << (15 - 0) // Mandatory (M) bit: Controls the behavior required of an implementation which receives an AVP which it does not recognize. + flagHidden = 1 << (15 - 1) // Hidden (H) bit: Identifies the hiding of data in the Attribute Value field of an AVP. +) + +func parseAVP(buf []byte)(msg *avpPayload, remain []byte, err error){ + if len(buf) < 6 { + return nil, nil, WrongLengthErr + } + flags := ((uint16)(buf[0]) << 8) | (uint16)(buf[1]) + msg = new(avpPayload) + msg.length = (int)(flags & 0x3ff) + if msg.length < 6 || len(buf) < msg.length { + return nil, nil, WrongLengthErr + } + msg.vendorID = ((uint16)(buf[2]) << 8) | (uint16)(buf[3]) + msg.attrType = ((uint16)(buf[4]) << 8) | (uint16)(buf[5]) + msg.value = buf[6:msg.length] + remain = buf[msg.length:] + return +} diff --git a/package.go b/package.go new file mode 100644 index 0000000..8642b3e --- /dev/null +++ b/package.go @@ -0,0 +1,17 @@ + +// This package provide a vpn/proxy based on websocket. +// Copyright (C) 2023 Kevin Z +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +package wpn diff --git a/server/main.go b/server/main.go index 3e379a5..ca09add 100644 --- a/server/main.go +++ b/server/main.go @@ -1,4 +1,5 @@ +// Copyright (C) 2023 Kevin Z package main import ( diff --git a/socks5/package.go b/socks5/package.go index b833af7..b501f20 100644 --- a/socks5/package.go +++ b/socks5/package.go @@ -1,3 +1,4 @@ -// This package provide a basic socks5 server +// This package provide a basic socks5 server. +// Copyright (C) 2023 Kevin Z package socks5 diff --git a/wssocks/handler.go b/wssocks/handler.go index 6b5f553..506de85 100644 --- a/wssocks/handler.go +++ b/wssocks/handler.go @@ -1,4 +1,5 @@ +// Copyright (C) 2023 Kevin Z package wssocks import (