From 8c7de7842282632e9cfe7d38fb7a88d3db5123bf Mon Sep 17 00:00:00 2001 From: mohanson Date: Tue, 6 Aug 2024 14:30:54 +0800 Subject: [PATCH] QUIC --- README.md | 9 +++ cmd/daze/main.go | 14 ++++ go.mod | 7 ++ go.sum | 6 ++ protocol/etch/cert.go | 38 ++++++++++ protocol/etch/engine.go | 141 +++++++++++++++++++++++++++++++++++ protocol/etch/engine_test.go | 95 +++++++++++++++++++++++ 7 files changed, 310 insertions(+) create mode 100644 protocol/etch/cert.go create mode 100644 protocol/etch/engine.go create mode 100644 protocol/etch/engine_test.go diff --git a/README.md b/README.md index 9fac9b1..78988f5 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,15 @@ $ daze client -l :20002 -s 127.0.0.1:20001 -p dahlia Reminder again: Dahlia is not a proxy protocol but a port forwarding protocol. +### Etch + +Protocol etch is an implementation of the ashe protocol based on [QUIC](https://en.wikipedia.org/wiki/QUIC). Since QUIC is a UDP-based transport protocol, the experience may be much better than other protocols in some cases (but it may also be the opposite, depending on your network operator). + +```sh +$ daze server ... -p etch +$ daze client ... -p etch +``` + # Proxy Control Proxy control is a rule that determines whether network requests (TCP and UDP) go directly to the destination or are forwarded to the daze server. Use the `-f` option in the daze client to adjust the proxy configuration. diff --git a/cmd/daze/main.go b/cmd/daze/main.go index 69965b0..fce0d68 100644 --- a/cmd/daze/main.go +++ b/cmd/daze/main.go @@ -18,6 +18,7 @@ import ( "github.com/mohanson/daze/protocol/baboon" "github.com/mohanson/daze/protocol/czar" "github.com/mohanson/daze/protocol/dahlia" + "github.com/mohanson/daze/protocol/etch" ) // Conf is acting as package level configuration. @@ -106,6 +107,10 @@ func main() { server := dahlia.NewServer(*flListen, *flExtend, *flCipher) defer server.Close() doa.Nil(server.Run()) + case "etch": + server := etch.NewServer(*flListen, *flCipher) + defer server.Close() + doa.Nil(server.Run()) } if *flGpprof != "" { _ = pprof.Handler @@ -175,6 +180,15 @@ func main() { client := dahlia.NewClient(*flListen, *flServer, *flCipher) defer client.Close() doa.Nil(client.Run()) + case "etch": + client := etch.NewClient(*flServer, *flCipher) + locale := daze.NewLocale(*flListen, daze.NewAimbot(client, &daze.AimbotOption{ + Type: *flFilter, + Rule: *flRulels, + Cidr: *flCIDRls, + })) + defer locale.Close() + doa.Nil(locale.Run()) } if *flGpprof != "" { _ = pprof.Handler diff --git a/go.mod b/go.mod index c367949..00b4c8e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module github.com/mohanson/daze go 1.23 + +require golang.org/x/net v0.28.0 + +require ( + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.24.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..2cef2b1 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/protocol/etch/cert.go b/protocol/etch/cert.go new file mode 100644 index 0000000..1141b87 --- /dev/null +++ b/protocol/etch/cert.go @@ -0,0 +1,38 @@ +package etch + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "math/rand/v2" + "net" + "time" + + "github.com/mohanson/daze" + "github.com/mohanson/daze/lib/doa" +) + +func GenCert() tls.Certificate { + priv := doa.Try(ecdsa.GenerateKey(elliptic.P256(), &daze.RandomReader{})) + temp := x509.Certificate{ + SerialNumber: big.NewInt(rand.Int64()), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, + } + cert := doa.Try(x509.CreateCertificate(&daze.RandomReader{}, &temp, &temp, &priv.PublicKey, priv)) + certFile := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert}) + pkcs := doa.Try(x509.MarshalPKCS8PrivateKey(priv)) + pkcsFile := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs}) + return doa.Try(tls.X509KeyPair(certFile, pkcsFile)) +} diff --git a/protocol/etch/engine.go b/protocol/etch/engine.go new file mode 100644 index 0000000..8961649 --- /dev/null +++ b/protocol/etch/engine.go @@ -0,0 +1,141 @@ +package etch + +import ( + "context" + "crypto/tls" + "io" + "log" + "math" + + "github.com/mohanson/daze" + "github.com/mohanson/daze/lib/doa" + "github.com/mohanson/daze/protocol/ashe" + "golang.org/x/net/quic" +) + +// Stream is an ordered byte stream. Reads are buffered, writes are not buffered. +type Stream struct { + *quic.Stream +} + +// Write implements the Conn Write method. +func (c *Stream) Write(p []byte) (int, error) { + n, err := c.Stream.Write(p) + c.Stream.Flush() + return n, err +} + +// NewStream returns a new Stream. +func NewStream(s *quic.Stream) *Stream { + return &Stream{s} +} + +// Server implemented the etch protocol. +type Server struct { + Cipher []byte + Closer *quic.Endpoint + Listen string +} + +// Close listener. Calling this function will disconnect all connections. +func (s *Server) Close() error { + if s.Closer != nil { + return s.Closer.Close(context.Background()) + } + return nil +} + +// Serve incoming connections. Parameter cli will be closed automatically when the function exits. +func (s *Server) Serve(ctx *daze.Context, cli io.ReadWriteCloser) error { + spy := &ashe.Server{Cipher: s.Cipher} + return spy.Serve(ctx, cli) +} + +// Run it. +func (s *Server) Run() error { + l, err := quic.Listen("udp", s.Listen, &quic.Config{ + TLSConfig: &tls.Config{ + Certificates: []tls.Certificate{GenCert()}, + MinVersion: tls.VersionTLS13, + }, + MaxIdleTimeout: -1, + }) + if err != nil { + return err + } + s.Closer = l + log.Println("main: listen and serve on", s.Listen) + go func() { + idx := uint32(math.MaxUint32) + for { + mux, err := l.Accept(context.Background()) + if err != nil { + break + } + go func() { + defer mux.Close() + for { + cli, err := mux.AcceptStream(context.Background()) + if err != nil { + break + } + idx++ + ctx := &daze.Context{Cid: idx} + log.Printf("conn: %08x accept", ctx.Cid) + go func() { + cli := NewStream(cli) + defer cli.Close() + if err := s.Serve(ctx, cli); err != nil { + log.Printf("conn: %08x error %s", ctx.Cid, err) + } + log.Printf("conn: %08x closed", ctx.Cid) + }() + } + }() + } + }() + return nil +} + +// NewServer returns a new Server. +func NewServer(listen string, cipher string) *Server { + return &Server{ + Cipher: daze.Salt(cipher), + Listen: listen, + } +} + +// Client implemented the etch protocol. +type Client struct { + Cipher []byte + Quicon *quic.Conn + Server string +} + +// Dial connects to the address on the named network. +func (c *Client) Dial(ctx *daze.Context, network string, address string) (io.ReadWriteCloser, error) { + srv, err := c.Quicon.NewStream(context.Background()) + if err != nil { + return nil, err + } + spy := &ashe.Client{Cipher: c.Cipher} + con, err := spy.Estab(ctx, NewStream(srv), network, address) + return con, err +} + +// NewClient returns a new Client. A secret data needs to be passed in Cipher, as a sign to interface with the Server. +func NewClient(server, cipher string) *Client { + quiced := doa.Try(quic.Listen("udp", ":0", nil)) + quicon := doa.Try(quiced.Dial(context.Background(), "udp", server, &quic.Config{ + TLSConfig: &tls.Config{ + InsecureSkipVerify: true, + MinVersion: tls.VersionTLS13, + }, + MaxIdleTimeout: -1, + })) + return &Client{ + Cipher: daze.Salt(cipher), + Quicon: quicon, + Server: server, + } +} diff --git a/protocol/etch/engine_test.go b/protocol/etch/engine_test.go new file mode 100644 index 0000000..c163992 --- /dev/null +++ b/protocol/etch/engine_test.go @@ -0,0 +1,95 @@ +package etch + +import ( + "io" + "testing" + + "github.com/mohanson/daze" + "github.com/mohanson/daze/lib/doa" +) + +const ( + EchoServerListenOn = "127.0.0.1:28080" + DazeServerListenOn = "127.0.0.1:28081" + Password = "password" +) + +func TestProtocolEtchTCP(t *testing.T) { + remote := daze.NewTester(EchoServerListenOn) + defer remote.Close() + remote.TCP() + + dazeServer := NewServer(DazeServerListenOn, Password) + defer dazeServer.Close() + dazeServer.Run() + + dazeClient := NewClient(DazeServerListenOn, Password) + ctx := &daze.Context{} + cli := doa.Try(dazeClient.Dial(ctx, "tcp", EchoServerListenOn)) + defer cli.Close() + + buf := make([]byte, 2048) + doa.Try(cli.Write([]byte{0x00, 0x00, 0x00, 0x80})) + doa.Try(io.ReadFull(cli, buf[:128])) +} + +func TestProtocolEtchTCPClientClose(t *testing.T) { + remote := daze.NewTester(EchoServerListenOn) + defer remote.Close() + remote.TCP() + + dazeServer := NewServer(DazeServerListenOn, Password) + defer dazeServer.Close() + dazeServer.Run() + + dazeClient := NewClient(DazeServerListenOn, Password) + ctx := &daze.Context{} + cli := doa.Try(dazeClient.Dial(ctx, "tcp", EchoServerListenOn)) + defer cli.Close() + + cli.Close() + _, er1 := cli.Write([]byte{0x02, 0x00, 0x00, 0x00}) + doa.Doa(er1 != nil) + buf := make([]byte, 2048) + _, er2 := io.ReadFull(cli, buf[:1]) + doa.Doa(er2 != nil) +} + +func TestProtocolEtchTCPServerClose(t *testing.T) { + remote := daze.NewTester(EchoServerListenOn) + defer remote.Close() + remote.TCP() + + dazeServer := NewServer(DazeServerListenOn, Password) + defer dazeServer.Close() + dazeServer.Run() + + dazeClient := NewClient(DazeServerListenOn, Password) + ctx := &daze.Context{} + cli := doa.Try(dazeClient.Dial(ctx, "tcp", EchoServerListenOn)) + defer cli.Close() + + buf := make([]byte, 2048) + doa.Try(cli.Write([]byte{0x02, 0x00, 0x00, 0x00})) + _, err := io.ReadFull(cli, buf[:1]) + doa.Doa(err != nil) +} + +func TestProtocolEtchUDP(t *testing.T) { + remote := daze.NewTester(EchoServerListenOn) + defer remote.Close() + remote.UDP() + + dazeServer := NewServer(DazeServerListenOn, Password) + defer dazeServer.Close() + dazeServer.Run() + + dazeClient := NewClient(DazeServerListenOn, Password) + ctx := &daze.Context{} + cli := doa.Try(dazeClient.Dial(ctx, "udp", EchoServerListenOn)) + defer cli.Close() + + buf := make([]byte, 2048) + doa.Try(cli.Write([]byte{0x00, 0x00, 0x00, 0x80})) + doa.Try(io.ReadFull(cli, buf[:128])) +}