Skip to content

Commit

Permalink
QUIC
Browse files Browse the repository at this point in the history
  • Loading branch information
mohanson committed Aug 30, 2024
1 parent d72fd34 commit b740380
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 14 additions & 0 deletions cmd/daze/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
38 changes: 38 additions & 0 deletions protocol/etch/cert.go
Original file line number Diff line number Diff line change
@@ -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))
}
141 changes: 141 additions & 0 deletions protocol/etch/engine.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
95 changes: 95 additions & 0 deletions protocol/etch/engine_test.go
Original file line number Diff line number Diff line change
@@ -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]))
}

0 comments on commit b740380

Please sign in to comment.