diff --git a/README.md b/README.md index b068d70..37944ca 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,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 c8df995..2fe6586 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..f59288c 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.29.0 + +require ( + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..80a0db2 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,12 @@ +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/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +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/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +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= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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..d1bb80c --- /dev/null +++ b/protocol/etch/cert.go @@ -0,0 +1,37 @@ +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 NewCert() 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: doa.Try(time.Parse(time.DateOnly, "1970-01-01")), + NotAfter: doa.Try(time.Parse(time.DateOnly, "9999-12-31")), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + 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/cert_test.go b/protocol/etch/cert_test.go new file mode 100644 index 0000000..4440954 --- /dev/null +++ b/protocol/etch/cert_test.go @@ -0,0 +1,9 @@ +package etch + +import ( + "testing" +) + +func TestProtocolEtchCert(t *testing.T) { + NewCert() +} diff --git a/protocol/etch/engine.go b/protocol/etch/engine.go new file mode 100644 index 0000000..da31361 --- /dev/null +++ b/protocol/etch/engine.go @@ -0,0 +1,143 @@ +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 io.Writer. +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{NewCert()}, + 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 { + log.Println("main:", err) + break + } + go func() { + defer mux.Close() + for { + cli, err := mux.AcceptStream(context.Background()) + if err != nil { + log.Println("main:", err) + break + } + idx++ + ctx := &daze.Context{Cid: idx} + log.Printf("conn: %08x accept", ctx.Cid) + go func() { + defer cli.Close() + if err := s.Serve(ctx, NewStream(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])) +}