diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index a964a14..9166d71 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -1,7 +1,6 @@ package outbound import ( - "bytes" "net" "strconv" "time" @@ -9,6 +8,8 @@ import ( "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" + + "github.com/Dreamacro/protobytes" ) func tcpKeepAlive(c net.Conn) { @@ -19,24 +20,24 @@ func tcpKeepAlive(c net.Conn) { } func serializesSocksAddr(metadata *C.Metadata) []byte { - var buf [][]byte + buf := protobytes.BytesWriter{} + addrType := metadata.AddrType() - aType := uint8(addrType) + buf.PutUint8(uint8(addrType)) + p, _ := strconv.ParseUint(metadata.DstPort, 10, 16) - port := []byte{uint8(p >> 8), uint8(p & 0xff)} switch addrType { case socks5.AtypDomainName: - len := uint8(len(metadata.Host)) - host := []byte(metadata.Host) - buf = [][]byte{{aType, len}, host, port} + buf.PutUint8(uint8(len(metadata.Host))) + buf.PutString(metadata.Host) case socks5.AtypIPv4: - host := metadata.DstIP.To4() - buf = [][]byte{{aType}, host, port} + buf.PutSlice(metadata.DstIP.To4()) case socks5.AtypIPv6: - host := metadata.DstIP.To16() - buf = [][]byte{{aType}, host, port} + buf.PutSlice(metadata.DstIP.To16()) } - return bytes.Join(buf, nil) + + buf.PutUint16be(uint16(p)) + return buf.Bytes() } func resolveUDPAddr(network, address string) (*net.UDPAddr, error) { diff --git a/common/pool/buffer.go b/common/pool/buffer.go index 18d14b8..6a3b6e2 100644 --- a/common/pool/buffer.go +++ b/common/pool/buffer.go @@ -3,9 +3,14 @@ package pool import ( "bytes" "sync" + + "github.com/Dreamacro/protobytes" ) -var bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }} +var ( + bufferPool = sync.Pool{New: func() any { return &bytes.Buffer{} }} + bytesBufferPool = sync.Pool{New: func() any { return &protobytes.BytesWriter{} }} +) func GetBuffer() *bytes.Buffer { return bufferPool.Get().(*bytes.Buffer) @@ -15,3 +20,12 @@ func PutBuffer(buf *bytes.Buffer) { buf.Reset() bufferPool.Put(buf) } + +func GetBytesBuffer() *protobytes.BytesWriter { + return bytesBufferPool.Get().(*protobytes.BytesWriter) +} + +func PutBytesBuffer(buf *protobytes.BytesWriter) { + buf.Reset() + bufferPool.Put(buf) +} diff --git a/go.mod b/go.mod index 7420a80..f4d9401 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( ) require ( + github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd // indirect github.com/ajg/form v1.5.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.5.9 // indirect diff --git a/go.sum b/go.sum index 6fc37b3..d0e6fea 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd h1:ygk7IF14j4ep4H2ZyeDe3IEoMZF8JdbX851RVVa/4D8= +github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd/go.mod h1:QvmEZ/h6KXszPOr2wUFl7Zn3hfFNYdfbXwPVDTyZs6k= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= diff --git a/hub/route/connections.go b/hub/route/connections.go index bfe3b42..b033555 100644 --- a/hub/route/connections.go +++ b/hub/route/connections.go @@ -1,7 +1,6 @@ package route import ( - "bytes" "encoding/json" "net/http" "strconv" @@ -9,6 +8,7 @@ import ( "github.com/Dreamacro/clash/tunnel/statistic" + "github.com/Dreamacro/protobytes" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/gorilla/websocket" @@ -47,11 +47,11 @@ func getConnections(w http.ResponseWriter, r *http.Request) { interval = t } - buf := &bytes.Buffer{} + buf := protobytes.BytesWriter{} sendSnapshot := func() error { buf.Reset() snapshot := statistic.DefaultManager.Snapshot() - if err := json.NewEncoder(buf).Encode(snapshot); err != nil { + if err := json.NewEncoder(&buf).Encode(snapshot); err != nil { return err } diff --git a/hub/route/server.go b/hub/route/server.go index 1384360..8768a5c 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -12,6 +12,7 @@ import ( "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel/statistic" + "github.com/Dreamacro/protobytes" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" "github.com/go-chi/render" @@ -151,12 +152,12 @@ func traffic(w http.ResponseWriter, r *http.Request) { tick := time.NewTicker(time.Second) defer tick.Stop() t := statistic.DefaultManager - buf := &bytes.Buffer{} + buf := protobytes.BytesWriter{} var err error for range tick.C { buf.Reset() up, down := t.Now() - if err := json.NewEncoder(buf).Encode(Traffic{ + if err := json.NewEncoder(&buf).Encode(Traffic{ Up: up, Down: down, }); err != nil { diff --git a/transport/gun/gun.go b/transport/gun/gun.go index faadac7..d1cc7e6 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -126,11 +126,11 @@ func (g *Conn) Write(b []byte) (n int, err error) { grpcPayloadLen := uint32(varuintSize + 1 + len(b)) binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen) - buf := pool.GetBuffer() - defer pool.PutBuffer(buf) - buf.Write(grpcHeader) - buf.Write(protobufHeader[:varuintSize+1]) - buf.Write(b) + buf := pool.GetBytesBuffer() + defer pool.PutBytesBuffer(buf) + buf.PutSlice(grpcHeader) + buf.PutSlice(protobufHeader[:varuintSize+1]) + buf.PutSlice(b) _, err = g.writer.Write(buf.Bytes()) if err == io.ErrClosedPipe && g.err != nil { diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 1d84fbf..763be8e 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -1,7 +1,6 @@ package obfs import ( - "bytes" "crypto/rand" "encoding/binary" "io" @@ -9,6 +8,8 @@ import ( "time" "github.com/Dreamacro/clash/common/pool" + + "github.com/Dreamacro/protobytes" ) const ( @@ -99,11 +100,11 @@ func (to *TLSObfs) write(b []byte) (int, error) { return len(b), err } - buf := pool.GetBuffer() - defer pool.PutBuffer(buf) - buf.Write([]byte{0x17, 0x03, 0x03}) - binary.Write(buf, binary.BigEndian, uint16(len(b))) - buf.Write(b) + buf := pool.GetBytesBuffer() + defer pool.PutBytesBuffer(buf) + buf.PutSlice([]byte{0x17, 0x03, 0x03}) + buf.PutUint16be(uint16(len(b))) + buf.PutSlice(b) _, err := to.Conn.Write(buf.Bytes()) return len(b), err } @@ -119,35 +120,29 @@ func NewTLSObfs(conn net.Conn, server string) net.Conn { } func makeClientHelloMsg(data []byte, server string) []byte { - random := make([]byte, 28) - sessionID := make([]byte, 32) - rand.Read(random) - rand.Read(sessionID) - - buf := &bytes.Buffer{} + buf := protobytes.BytesWriter{} // handshake, TLS 1.0 version, length - buf.WriteByte(22) - buf.Write([]byte{0x03, 0x01}) + buf.PutUint8(22) + buf.PutSlice([]byte{0x03, 0x01}) length := uint16(212 + len(data) + len(server)) - buf.WriteByte(byte(length >> 8)) - buf.WriteByte(byte(length & 0xff)) + buf.PutUint16be(length) // clientHello, length, TLS 1.2 version - buf.WriteByte(1) - buf.WriteByte(0) - binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server))) - buf.Write([]byte{0x03, 0x03}) + buf.PutUint8(1) + buf.PutUint8(0) + buf.PutUint16be(uint16(208 + len(data) + len(server))) + buf.PutSlice([]byte{0x03, 0x03}) // random with timestamp, sid len, sid - binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) - buf.Write(random) - buf.WriteByte(32) - buf.Write(sessionID) + buf.PutUint32be(uint32(time.Now().Unix())) + buf.ReadFull(rand.Reader, 28) + buf.PutUint8(32) + buf.ReadFull(rand.Reader, 32) // cipher suites - buf.Write([]byte{0x00, 0x38}) - buf.Write([]byte{ + buf.PutSlice([]byte{0x00, 0x38}) + buf.PutSlice([]byte{ 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, @@ -155,42 +150,42 @@ func makeClientHelloMsg(data []byte, server string) []byte { }) // compression - buf.Write([]byte{0x01, 0x00}) + buf.PutSlice([]byte{0x01, 0x00}) // extension length - binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server))) + buf.PutUint16be(uint16(79 + len(data) + len(server))) // session ticket - buf.Write([]byte{0x00, 0x23}) - binary.Write(buf, binary.BigEndian, uint16(len(data))) - buf.Write(data) + buf.PutSlice([]byte{0x00, 0x23}) + buf.PutUint16be(uint16(len(data))) + buf.PutSlice(data) // server name - buf.Write([]byte{0x00, 0x00}) - binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) - binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) - buf.WriteByte(0) - binary.Write(buf, binary.BigEndian, uint16(len(server))) - buf.Write([]byte(server)) + buf.PutSlice([]byte{0x00, 0x00}) + buf.PutUint16be(uint16(len(server) + 5)) + buf.PutUint16be(uint16(len(server) + 3)) + buf.PutUint8(0) + buf.PutUint16be(uint16(len(server))) + buf.PutSlice([]byte(server)) // ec_point - buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02}) + buf.PutSlice([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02}) // groups - buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) + buf.PutSlice([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) // signature - buf.Write([]byte{ + buf.PutSlice([]byte{ 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, }) // encrypt then mac - buf.Write([]byte{0x00, 0x16, 0x00, 0x00}) + buf.PutSlice([]byte{0x00, 0x16, 0x00, 0x00}) // extended master secret - buf.Write([]byte{0x00, 0x17, 0x00, 0x00}) + buf.PutSlice([]byte{0x00, 0x17, 0x00, 0x00}) return buf.Bytes() } diff --git a/transport/snell/snell.go b/transport/snell/snell.go index e2bd282..13de1af 100644 --- a/transport/snell/snell.go +++ b/transport/snell/snell.go @@ -1,7 +1,6 @@ package snell import ( - "encoding/binary" "errors" "fmt" "io" @@ -83,22 +82,22 @@ func (s *Snell) Read(b []byte) (int, error) { } func WriteHeader(conn net.Conn, host string, port uint, version int) error { - buf := pool.GetBuffer() - defer pool.PutBuffer(buf) - buf.WriteByte(Version) + buf := pool.GetBytesBuffer() + defer pool.PutBytesBuffer(buf) + buf.PutUint8(Version) if version == Version2 { - buf.WriteByte(CommandConnectV2) + buf.PutUint8(CommandConnectV2) } else { - buf.WriteByte(CommandConnect) + buf.PutUint8(CommandConnect) } // clientID length & id - buf.WriteByte(0) + buf.PutUint8(0) // host & port - buf.WriteByte(uint8(len(host))) - buf.WriteString(host) - binary.Write(buf, binary.BigEndian, uint16(port)) + buf.PutUint8(uint8(len(host))) + buf.PutString(host) + buf.PutUint16be(uint16(port)) if _, err := conn.Write(buf.Bytes()); err != nil { return err @@ -146,25 +145,25 @@ func PacketConn(conn net.Conn) net.PacketConn { } func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { - buf := pool.GetBuffer() - defer pool.PutBuffer(buf) + buf := pool.GetBytesBuffer() + defer pool.PutBytesBuffer(buf) // compose snell UDP address format (refer: icpz/snell-server-reversed) // a brand new wheel to replace socks5 address format, well done Yachen - buf.WriteByte(CommondUDPForward) + buf.PutUint8(CommondUDPForward) switch socks5Addr[0] { case socks5.AtypDomainName: hostLen := socks5Addr[1] - buf.Write(socks5Addr[1 : 1+1+hostLen+2]) + buf.PutSlice(socks5Addr[1 : 1+1+hostLen+2]) case socks5.AtypIPv4: - buf.Write([]byte{0x00, 0x04}) - buf.Write(socks5Addr[1 : 1+net.IPv4len+2]) + buf.PutSlice([]byte{0x00, 0x04}) + buf.PutSlice(socks5Addr[1 : 1+net.IPv4len+2]) case socks5.AtypIPv6: - buf.Write([]byte{0x00, 0x06}) - buf.Write(socks5Addr[1 : 1+net.IPv6len+2]) + buf.PutSlice([]byte{0x00, 0x06}) + buf.PutSlice(socks5Addr[1 : 1+net.IPv6len+2]) } - buf.Write(payload) + buf.PutSlice(payload) _, err := w.Write(buf.Bytes()) if err != nil { return 0, err diff --git a/transport/socks4/socks4.go b/transport/socks4/socks4.go index a294162..964b232 100644 --- a/transport/socks4/socks4.go +++ b/transport/socks4/socks4.go @@ -1,14 +1,15 @@ package socks4 import ( - "bytes" - "encoding/binary" "errors" "io" "net" + "net/netip" "strconv" "github.com/Dreamacro/clash/component/auth" + + "github.com/Dreamacro/protobytes" ) const Version = 0x04 @@ -46,21 +47,17 @@ func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr s return } - if req[0] != Version { + r := protobytes.BytesReader(req[:]) + if r.ReadUint8() != Version { err = errVersionMismatched return } - if command = req[1]; command != CmdConnect { + if command = r.ReadUint8(); command != CmdConnect { err = errCommandNotSupported return } - var ( - dstIP = req[4:8] // [4]byte - dstPort = req[2:4] // [2]byte - ) - var ( host string port string @@ -71,7 +68,9 @@ func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr s return } - if isReservedIP(dstIP) { + dstAddr := r.ReadIPv4() + dstPort := r.ReadUint16be() + if isReservedIP(dstAddr) { var target []byte if target, err = readUntilNull(rw); err != nil { return @@ -79,11 +78,11 @@ func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr s host = string(target) } - port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort))) + port = strconv.Itoa(int(dstPort)) if host != "" { addr = net.JoinHostPort(host, port) } else { - addr = net.JoinHostPort(net.IP(dstIP).String(), port) + addr = net.JoinHostPort(dstAddr.String(), port) } // SOCKS4 only support USERID auth. @@ -94,13 +93,13 @@ func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr s err = ErrRequestIdentdMismatched } - var reply [8]byte - reply[0] = 0x00 // reply code - reply[1] = code // result code - copy(reply[4:8], dstIP) - copy(reply[2:4], dstPort) + reply := protobytes.BytesWriter(make([]byte, 0, 8)) + reply.PutUint8(0) // reply code + reply.PutUint8(code) // result code + reply.PutSlice(dstAddr.AsSlice()) + reply.PutUint16be(dstPort) - _, wErr := rw.Write(reply[:]) + _, wErr := rw.Write(reply.Bytes()) if err == nil { err = wErr } @@ -118,26 +117,24 @@ func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID stri return err } - ip := net.ParseIP(host) - if ip == nil /* HOST */ { - ip = net.IPv4(0, 0, 0, 1).To4() - } else if ip.To4() == nil /* IPv6 */ { + ip, err := netip.ParseAddr(host) + if err != nil { // Host + ip = netip.AddrFrom4([4]byte{0, 0, 0, 1}) + } else if ip.Is6() { // IPv6 return errIPv6NotSupported } - dstIP := ip.To4() + req := protobytes.BytesWriter{} + req.PutUint8(Version) + req.PutUint8(command) + req.PutUint16be(uint16(port)) + req.Write(ip.AsSlice()) + req.PutString(userID) + req.PutUint8(0) /* NULL */ - req := &bytes.Buffer{} - req.WriteByte(Version) - req.WriteByte(command) - binary.Write(req, binary.BigEndian, uint16(port)) - req.Write(dstIP) - req.WriteString(userID) - req.WriteByte(0) /* NULL */ - - if isReservedIP(dstIP) /* SOCKS4A */ { - req.WriteString(host) - req.WriteByte(0) /* NULL */ + if isReservedIP(ip) /* SOCKS4A */ { + req.PutString(host) + req.PutUint8(0) /* NULL */ } if _, err = rw.Write(req.Bytes()); err != nil { @@ -174,17 +171,17 @@ func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID stri // Internet Assigned Numbers Authority -- such an address is inadmissible // as a destination IP address and thus should never occur if the client // can resolve the domain name.) -func isReservedIP(ip net.IP) bool { - subnet := net.IPNet{ - IP: net.IPv4zero, - Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00), - } +func isReservedIP(ip netip.Addr) bool { + subnet := netip.PrefixFrom( + netip.AddrFrom4([4]byte{0, 0, 0, 0}), + 24, + ) return !ip.IsUnspecified() && subnet.Contains(ip) } func readUntilNull(r io.Reader) ([]byte, error) { - buf := &bytes.Buffer{} + buf := protobytes.BytesWriter{} var data [1]byte for { @@ -194,6 +191,6 @@ func readUntilNull(r io.Reader) ([]byte, error) { if data[0] == 0 { return buf.Bytes(), nil } - buf.WriteByte(data[0]) + buf.PutUint8(data[0]) } } diff --git a/transport/socks5/socks5.go b/transport/socks5/socks5.go index 26ce379..ba30ea9 100644 --- a/transport/socks5/socks5.go +++ b/transport/socks5/socks5.go @@ -10,6 +10,8 @@ import ( "strconv" "github.com/Dreamacro/clash/component/auth" + + "github.com/Dreamacro/protobytes" ) // Error represents a SOCKS error @@ -235,12 +237,12 @@ func ClientHandshake(rw io.ReadWriter, addr Addr, command Command, user *User) ( } // password protocol version - authMsg := &bytes.Buffer{} - authMsg.WriteByte(1) - authMsg.WriteByte(uint8(len(user.Username))) - authMsg.WriteString(user.Username) - authMsg.WriteByte(uint8(len(user.Password))) - authMsg.WriteString(user.Password) + authMsg := protobytes.BytesWriter{} + authMsg.PutUint8(1) + authMsg.PutUint8(uint8(len(user.Username))) + authMsg.PutString(user.Username) + authMsg.PutUint8(uint8(len(user.Password))) + authMsg.PutString(user.Password) if _, err := rw.Write(authMsg.Bytes()); err != nil { return nil, err @@ -330,29 +332,26 @@ func SplitAddr(b []byte) Addr { // ParseAddr parses the address in string s. Returns nil if failed. func ParseAddr(s string) Addr { - var addr Addr + buf := protobytes.BytesWriter{} host, port, err := net.SplitHostPort(s) if err != nil { return nil } if ip := net.ParseIP(host); ip != nil { if ip4 := ip.To4(); ip4 != nil { - addr = make([]byte, 1+net.IPv4len+2) - addr[0] = AtypIPv4 - copy(addr[1:], ip4) + buf.PutUint8(AtypIPv4) + buf.PutSlice(ip4) } else { - addr = make([]byte, 1+net.IPv6len+2) - addr[0] = AtypIPv6 - copy(addr[1:], ip) + buf.PutUint8(AtypIPv6) + buf.PutSlice(ip) } } else { if len(host) > 255 { return nil } - addr = make([]byte, 1+1+len(host)+2) - addr[0] = AtypDomainName - addr[1] = byte(len(host)) - copy(addr[2:], host) + buf.PutUint8(AtypDomainName) + buf.PutUint8(byte(len(host))) + buf.PutString(host) } portnum, err := strconv.ParseUint(port, 10, 16) @@ -360,9 +359,8 @@ func ParseAddr(s string) Addr { return nil } - addr[len(addr)-2], addr[len(addr)-1] = byte(portnum>>8), byte(portnum) - - return addr + buf.PutUint16be(uint16(portnum)) + return Addr(buf.Bytes()) } // ParseAddrToSocksAddr parse a socks addr from net.addr @@ -383,20 +381,19 @@ func ParseAddrToSocksAddr(addr net.Addr) Addr { return ParseAddr(addr.String()) } - var parsed Addr + var parsed protobytes.BytesWriter if ip4 := hostip.To4(); ip4.DefaultMask() != nil { - parsed = make([]byte, 1+net.IPv4len+2) - parsed[0] = AtypIPv4 - copy(parsed[1:], ip4) - binary.BigEndian.PutUint16(parsed[1+net.IPv4len:], uint16(port)) - + parsed = make([]byte, 0, 1+net.IPv4len+2) + parsed.PutUint8(AtypIPv4) + parsed.PutSlice(ip4) + parsed.PutUint16be(uint16(port)) } else { - parsed = make([]byte, 1+net.IPv6len+2) - parsed[0] = AtypIPv6 - copy(parsed[1:], hostip) - binary.BigEndian.PutUint16(parsed[1+net.IPv6len:], uint16(port)) + parsed = make([]byte, 0, 1+net.IPv6len+2) + parsed.PutUint8(AtypIPv6) + parsed.PutSlice(hostip) + parsed.PutUint16be(uint16(port)) } - return parsed + return Addr(parsed) } func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr { @@ -416,28 +413,31 @@ func AddrFromStdAddrPort(addrPort netip.AddrPort) Addr { // DecodeUDPPacket split `packet` to addr payload, and this function is mutable with `packet` func DecodeUDPPacket(packet []byte) (addr Addr, payload []byte, err error) { - if len(packet) < 5 { + r := protobytes.BytesReader(packet) + + if r.Len() < 5 { err = errors.New("insufficient length of packet") return } // packet[0] and packet[1] are reserved - if !bytes.Equal(packet[:2], []byte{0, 0}) { + reserved, r := r.SplitAt(2) + if !bytes.Equal(reserved, []byte{0, 0}) { err = errors.New("reserved fields should be zero") return } - if packet[2] != 0 /* fragments */ { + if r.ReadUint8() != 0 /* fragments */ { err = errors.New("discarding fragmented payload") return } - addr = SplitAddr(packet[3:]) + addr = SplitAddr(r) if addr == nil { err = errors.New("failed to read UDP header") } - payload = packet[3+len(addr):] + _, payload = r.SplitAt(len(addr)) return } @@ -446,6 +446,10 @@ func EncodeUDPPacket(addr Addr, payload []byte) (packet []byte, err error) { err = errors.New("address is invalid") return } - packet = bytes.Join([][]byte{{0, 0, 0}, addr, payload}, []byte{}) + w := protobytes.BytesWriter{} + w.PutSlice([]byte{0, 0, 0}) + w.PutSlice(addr) + w.PutSlice(payload) + packet = w.Bytes() return } diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 686983b..fec3f02 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -12,10 +12,11 @@ import ( "net/http" "sync" - "github.com/Dreamacro/clash/common/pool" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/vmess" + + "github.com/Dreamacro/protobytes" ) const ( @@ -105,15 +106,13 @@ func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) } func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) error { - buf := pool.GetBuffer() - defer pool.PutBuffer(buf) + buf := protobytes.BytesWriter{} + buf.PutSlice(t.hexPassword) + buf.PutSlice(crlf) - buf.Write(t.hexPassword) - buf.Write(crlf) - - buf.WriteByte(command) - buf.Write(socks5Addr) - buf.Write(crlf) + buf.PutUint8(command) + buf.PutSlice(socks5Addr) + buf.PutSlice(crlf) _, err := w.Write(buf.Bytes()) return err @@ -126,14 +125,11 @@ func (t *Trojan) PacketConn(conn net.Conn) net.PacketConn { } func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) { - buf := pool.GetBuffer() - defer pool.PutBuffer(buf) - - buf.Write(socks5Addr) - binary.Write(buf, binary.BigEndian, uint16(len(payload))) - buf.Write(crlf) - buf.Write(payload) - + buf := protobytes.BytesWriter{} + buf.PutSlice(socks5Addr) + buf.PutUint16be(uint16(len(payload))) + buf.PutSlice(crlf) + buf.PutSlice(payload) return w.Write(buf.Bytes()) } diff --git a/transport/v2ray-plugin/mux.go b/transport/v2ray-plugin/mux.go index 6bff27b..aea1619 100644 --- a/transport/v2ray-plugin/mux.go +++ b/transport/v2ray-plugin/mux.go @@ -1,11 +1,13 @@ package obfs import ( - "bytes" "encoding/binary" "errors" "io" "net" + "net/netip" + + "github.com/Dreamacro/protobytes" ) type SessionStatus = byte @@ -33,7 +35,7 @@ type MuxOption struct { // Mux is an mux-compatible client for v2ray-plugin, not a complete implementation type Mux struct { net.Conn - buf bytes.Buffer + buf protobytes.BytesWriter id [2]byte length [2]byte status [2]byte @@ -112,11 +114,11 @@ func (m *Mux) Write(b []byte) (int, error) { m.otb = nil } m.buf.Reset() - binary.Write(&m.buf, binary.BigEndian, uint16(4)) - m.buf.Write(m.id[:]) - m.buf.WriteByte(SessionStatusKeep) - m.buf.WriteByte(OptionData) - binary.Write(&m.buf, binary.BigEndian, uint16(len(b))) + m.buf.PutUint16be(4) + m.buf.PutSlice(m.id[:]) + m.buf.PutUint8(SessionStatusKeep) + m.buf.PutUint8(OptionData) + m.buf.PutUint16be(uint16(len(b))) m.buf.Write(b) return m.Conn.Write(m.buf.Bytes()) @@ -131,35 +133,35 @@ func (m *Mux) Close() error { } func NewMux(conn net.Conn, option MuxOption) *Mux { - buf := &bytes.Buffer{} + buf := protobytes.BytesWriter{} // fill empty length - buf.Write([]byte{0x0, 0x0}) - buf.Write(option.ID[:]) - buf.WriteByte(SessionStatusNew) - buf.WriteByte(OptionNone) + buf.PutSlice([]byte{0x0, 0x0}) + buf.PutSlice(option.ID[:]) + buf.PutUint8(SessionStatusNew) + buf.PutUint8(OptionNone) // tcp netType := byte(0x1) if option.Type == "udp" { netType = byte(0x2) } - buf.WriteByte(netType) + buf.PutUint8(netType) // port - binary.Write(buf, binary.BigEndian, option.Port) + buf.PutUint16be(option.Port) // address - ip := net.ParseIP(option.Host) - if ip == nil { - buf.WriteByte(0x2) - buf.WriteString(option.Host) - } else if ipv4 := ip.To4(); ipv4 != nil { - buf.WriteByte(0x1) - buf.Write(ipv4) + ip, err := netip.ParseAddr(option.Host) + if err != nil { + buf.PutUint8(0x2) + buf.PutString(option.Host) + } else if ip.Is4() { + buf.PutUint8(0x1) + buf.PutSlice(ip.AsSlice()) } else { - buf.WriteByte(0x3) - buf.Write(ip.To16()) + buf.PutUint8(0x3) + buf.PutSlice(ip.AsSlice()) } metadata := buf.Bytes() diff --git a/transport/vmess/conn.go b/transport/vmess/conn.go index 5d4bbde..bb77cef 100644 --- a/transport/vmess/conn.go +++ b/transport/vmess/conn.go @@ -1,7 +1,6 @@ package vmess import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" @@ -16,6 +15,7 @@ import ( "net" "time" + "github.com/Dreamacro/protobytes" "golang.org/x/crypto/chacha20poly1305" ) @@ -57,48 +57,46 @@ func (vc *Conn) Read(b []byte) (int, error) { func (vc *Conn) sendRequest() error { timestamp := time.Now() - mbuf := &bytes.Buffer{} + mbuf := protobytes.BytesWriter{} if !vc.isAead { h := hmac.New(md5.New, vc.id.UUID.Bytes()) binary.Write(h, binary.BigEndian, uint64(timestamp.Unix())) - mbuf.Write(h.Sum(nil)) + mbuf.PutSlice(h.Sum(nil)) } - buf := &bytes.Buffer{} + buf := protobytes.BytesWriter{} // Ver IV Key V Opt - buf.WriteByte(Version) - buf.Write(vc.reqBodyIV[:]) - buf.Write(vc.reqBodyKey[:]) - buf.WriteByte(vc.respV) - buf.WriteByte(vc.option) + buf.PutUint8(Version) + buf.PutSlice(vc.reqBodyIV[:]) + buf.PutSlice(vc.reqBodyKey[:]) + buf.PutUint8(vc.respV) + buf.PutUint8(vc.option) p := mathRand.Intn(16) // P Sec Reserve Cmd - buf.WriteByte(byte(p<<4) | vc.security) - buf.WriteByte(0) + buf.PutUint8(byte(p<<4) | vc.security) + buf.PutUint8(0) if vc.dst.UDP { - buf.WriteByte(CommandUDP) + buf.PutUint8(CommandUDP) } else { - buf.WriteByte(CommandTCP) + buf.PutUint8(CommandTCP) } // Port AddrType Addr - binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) - buf.WriteByte(vc.dst.AddrType) - buf.Write(vc.dst.Addr) + buf.PutUint16be(uint16(vc.dst.Port)) + buf.PutUint8(vc.dst.AddrType) + buf.PutSlice(vc.dst.Addr) // padding if p > 0 { - padding := make([]byte, p) - rand.Read(padding) - buf.Write(padding) + buf.ReadFull(rand.Reader, p) } fnv1a := fnv.New32a() fnv1a.Write(buf.Bytes()) - buf.Write(fnv1a.Sum(nil)) + buf.PutSlice(fnv1a.Sum(nil)) if !vc.isAead { block, err := aes.NewCipher(vc.id.CmdKey) @@ -108,7 +106,7 @@ func (vc *Conn) sendRequest() error { stream := cipher.NewCFBEncrypter(block, hashTimestamp(timestamp)) stream.XORKeyStream(buf.Bytes(), buf.Bytes()) - mbuf.Write(buf.Bytes()) + mbuf.PutSlice(buf.Bytes()) _, err = vc.Conn.Write(mbuf.Bytes()) return err } diff --git a/transport/vmess/header.go b/transport/vmess/header.go index 67d407e..506c243 100644 --- a/transport/vmess/header.go +++ b/transport/vmess/header.go @@ -1,7 +1,6 @@ package vmess import ( - "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" @@ -11,6 +10,8 @@ import ( "hash" "hash/crc32" "time" + + "github.com/Dreamacro/protobytes" ) const ( @@ -49,14 +50,11 @@ func (h *hMacCreator) Create() hash.Hash { } func createAuthID(cmdKey []byte, time int64) [16]byte { - buf := &bytes.Buffer{} - binary.Write(buf, binary.BigEndian, time) - - random := make([]byte, 4) - rand.Read(random) - buf.Write(random) + buf := protobytes.BytesWriter{} + buf.PutUint64be(uint64(time)) + buf.ReadFull(rand.Reader, 4) zero := crc32.ChecksumIEEE(buf.Bytes()) - binary.Write(buf, binary.BigEndian, zero) + buf.PutUint32be(zero) aesBlock, _ := aes.NewCipher(kdf(cmdKey[:], kdfSaltConstAuthIDEncryptionKey)[:16]) var result [16]byte @@ -92,12 +90,12 @@ func sealVMessAEADHeader(key [16]byte, data []byte, t time.Time) []byte { payloadHeaderAEADEncrypted = payloadHeaderAEAD.Seal(nil, payloadHeaderAEADNonce, data, generatedAuthID[:]) } - outputBuffer := &bytes.Buffer{} + outputBuffer := protobytes.BytesWriter{} - outputBuffer.Write(generatedAuthID[:]) - outputBuffer.Write(payloadHeaderLengthAEADEncrypted) - outputBuffer.Write(connectionNonce) - outputBuffer.Write(payloadHeaderAEADEncrypted) + outputBuffer.PutSlice(generatedAuthID[:]) + outputBuffer.PutSlice(payloadHeaderLengthAEADEncrypted) + outputBuffer.PutSlice(connectionNonce) + outputBuffer.PutSlice(payloadHeaderAEADEncrypted) return outputBuffer.Bytes() }