11package etch
22
33import (
4+ "context"
5+ "crypto/ecdsa"
6+ "crypto/elliptic"
7+ "crypto/rand"
8+ "crypto/tls"
9+ "crypto/x509"
10+ "errors"
411 "io"
512 "log"
613 "math"
14+ "math/big"
15+ "net"
16+ "sync"
717 "time"
818
919 "github.com/libraries/daze"
20+ "github.com/libraries/daze/lib/doa"
21+ "github.com/libraries/daze/lib/once"
1022 "github.com/libraries/daze/lib/rate"
1123 "github.com/libraries/daze/protocol/ashe"
24+ "golang.org/x/net/quic"
1225)
1326
14- // The etch engine is the ashe protocol carried over the etch rudp transport. It exists for the same reason as the
15- // czar engine: to let the ashe protocol travel over a transport that is not vanilla tcp. The benefit of moving ashe
16- // onto rudp is that a single udp flow is much harder for middleboxes to throttle than a long-lived tcp connection, and
17- // the connection survives transient packet loss without giving up.
27+ var (
28+ ServerConfig = once .NewOnceNew (func () * quic.Config {
29+ key := doa .Try (ecdsa .GenerateKey (elliptic .P256 (), rand .Reader ))
30+ tpl := & x509.Certificate {
31+ SerialNumber : big .NewInt (1 ),
32+ NotBefore : time .Now ().Add (- time .Hour ),
33+ NotAfter : time .Now ().Add (time .Hour * 24 * 365 * 10 ),
34+ }
35+ der := doa .Try (x509 .CreateCertificate (rand .Reader , tpl , tpl , & key .PublicKey , key ))
36+ return & quic.Config {
37+ TLSConfig : & tls.Config {
38+ Certificates : []tls.Certificate {{Certificate : [][]byte {der }, PrivateKey : key }},
39+ MinVersion : tls .VersionTLS13 ,
40+ NextProtos : []string {"ashe" },
41+ },
42+ MaxBidiRemoteStreams : math .MaxInt32 ,
43+ MaxStreamReadBufferSize : 4 * 1024 * 1024 ,
44+ MaxStreamWriteBufferSize : 4 * 1024 * 1024 ,
45+ MaxConnReadBufferSize : 4 * 1024 * 1024 ,
46+ MaxIdleTimeout : - 1 ,
47+ KeepAlivePeriod : time .Second * 30 ,
48+ }
49+ })
50+ ClientConfig = once .NewOnceNew (func () * quic.Config {
51+ return & quic.Config {
52+ TLSConfig : & tls.Config {
53+ InsecureSkipVerify : true ,
54+ MinVersion : tls .VersionTLS13 ,
55+ NextProtos : []string {"ashe" },
56+ },
57+ MaxBidiRemoteStreams : math .MaxInt32 ,
58+ MaxStreamReadBufferSize : 4 * 1024 * 1024 ,
59+ MaxStreamWriteBufferSize : 4 * 1024 * 1024 ,
60+ MaxConnReadBufferSize : 4 * 1024 * 1024 ,
61+ MaxIdleTimeout : - 1 ,
62+ KeepAlivePeriod : time .Second * 30 ,
63+ }
64+ })
65+ )
66+
67+ // Stream wraps a quic stream as an io.ReadWriteCloser. Writes are flushed immediately so that the ashe handshake,
68+ // which exchanges very short messages, progresses without waiting for the quic stream buffer to fill.
69+ type Stream struct {
70+ con * quic.Conn
71+ rem net.Addr
72+ stm * quic.Stream
73+ }
74+
75+ // Close closes the stream. If the stream owns its connection, the connection is closed as well.
76+ func (s * Stream ) Close () error {
77+ s .stm .CloseRead ()
78+ s .stm .CloseWrite ()
79+ if s .con != nil {
80+ return s .con .Close ()
81+ }
82+ return nil
83+ }
84+
85+ // Read reads up to len(p) bytes.
86+ func (s * Stream ) Read (p []byte ) (int , error ) {
87+ return s .stm .Read (p )
88+ }
89+
90+ // RemoteAddr returns the remote network address.
91+ func (s * Stream ) RemoteAddr () net.Addr {
92+ return s .rem
93+ }
94+
95+ // Write writes len(p) bytes and flushes the stream so the data reaches the wire immediately.
96+ func (s * Stream ) Write (p []byte ) (int , error ) {
97+ n , err := s .stm .Write (p )
98+ if err != nil {
99+ return n , err
100+ }
101+ if err := s .stm .Flush (); err != nil {
102+ return n , err
103+ }
104+ return n , nil
105+ }
18106
19- // Server implemented the ashe-over-etch protocol.
107+ // Server implemented the ashe-over-quic protocol.
20108type Server struct {
21109 Cipher []byte
22- Closer io. Closer
110+ Ep * quic. Endpoint
23111 Limits * rate.Limits
24112 Listen string
25113}
@@ -32,39 +120,57 @@ func (s *Server) Serve(ctx *daze.Context, cli io.ReadWriteCloser) error {
32120
33121// Close listener. Established connections will not be closed.
34122func (s * Server ) Close () error {
35- if s .Closer != nil {
36- return s .Closer .Close ()
123+ if s .Ep != nil {
124+ return s .Ep .Close (context . Background () )
37125 }
38126 return nil
39127}
40128
41129// Run it.
42130func (s * Server ) Run () error {
43- l , err := Listen (s .Listen )
131+ ep , err := quic . Listen ("udp" , s .Listen , ServerConfig . Do () )
44132 if err != nil {
45133 return err
46134 }
47- s .Closer = l
135+ s .Ep = ep
48136 log .Println ("main: listen and serve on" , s .Listen )
49137
50138 go func () {
51139 idx := uint32 (math .MaxUint32 )
52- for cli := range l .Accept () {
53- idx ++
54- ctx := & daze.Context {Cid : idx }
55- log .Printf ("conn: %08x accept remote=%s" , ctx .Cid , cli .RemoteAddr ())
56- rtc := & daze.ReadWriteCloser {
57- Reader : io .TeeReader (cli , rate .NewLimitsWriter (s .Limits )),
58- Writer : io .MultiWriter (cli , rate .NewLimitsWriter (s .Limits )),
59- Closer : cli ,
140+ for {
141+ con , err := ep .Accept (context .Background ())
142+ if err != nil {
143+ if ! errors .Is (err , context .Canceled ) && ! errors .Is (err , net .ErrClosed ) {
144+ log .Println ("main:" , err )
145+ }
146+ break
60147 }
61- go func () {
62- defer rtc .Close ()
63- if err := s .Serve (ctx , rtc ); err != nil {
64- log .Printf ("conn: %08x error %s" , ctx .Cid , err )
148+ go func (con * quic.Conn ) {
149+ defer con .Close ()
150+ peer := net .UDPAddrFromAddrPort (con .RemoteAddr ())
151+ for {
152+ stm , err := con .AcceptStream (context .Background ())
153+ if err != nil {
154+ return
155+ }
156+ idx ++
157+ ctx := & daze.Context {Cid : idx }
158+ cli := & Stream {rem : peer , stm : stm }
159+ log .Printf ("conn: %08x accept remote=%s" , ctx .Cid , peer )
160+ rtc := & daze.ReadWriteCloser {
161+ Reader : io .TeeReader (cli , rate .NewLimitsWriter (s .Limits )),
162+ Writer : io .MultiWriter (cli , rate .NewLimitsWriter (s .Limits )),
163+ Closer : cli ,
164+ }
165+ go func () {
166+ defer rtc .Close ()
167+ if err := s .Serve (ctx , rtc ); err != nil {
168+ log .Printf ("conn: %08x error %s" , ctx .Cid , err )
169+ }
170+ log .Printf ("conn: %08x closed" , ctx .Cid )
171+ }()
65172 }
66- log .Printf ("conn: %08x closed" , ctx .Cid )
67- }()
173+ }(con )
68174 }
69175 }()
70176
@@ -80,35 +186,71 @@ func NewServer(listen string, cipher string) *Server {
80186 }
81187}
82188
83- // Client implemented the ashe-over-etch protocol.
189+ // Client implemented the ashe-over-quic protocol.
84190type Client struct {
85191 Cipher []byte
86192 Server string
193+
194+ mu sync.Mutex
195+ ep * quic.Endpoint
196+ }
197+
198+ // endpoint lazily creates the shared quic endpoint that originates outbound connections. A single endpoint can
199+ // originate many connections, so reusing it avoids burning a new udp socket for every Dial.
200+ func (c * Client ) endpoint () (* quic.Endpoint , error ) {
201+ c .mu .Lock ()
202+ defer c .mu .Unlock ()
203+ if c .ep != nil {
204+ return c .ep , nil
205+ }
206+ // The client endpoint also needs a tls config that satisfies the "certificate or GetCertificate" requirement of
207+ // quic.Listen even though the endpoint never accepts inbound connections.
208+ ep , err := quic .Listen ("udp" , ":0" , ClientConfig .Do ())
209+ if err != nil {
210+ return nil , err
211+ }
212+ c .ep = ep
213+ return ep , nil
87214}
88215
89216// Estab dials the etch server and establishes an ashe channel over it. It is the caller's responsibility to close the
90217// returned conn.
91218func (c * Client ) Estab (ctx * daze.Context , network string , address string ) (io.ReadWriteCloser , error ) {
92- srv , err := Dial ( c . Server )
219+ ep , err := c . endpoint ( )
93220 if err != nil {
94221 return nil , err
95222 }
223+ dialCtx , cancel := context .WithTimeout (context .Background (), daze .Conf .DialerTimeout )
224+ defer cancel ()
225+ con , err := ep .Dial (dialCtx , "udp" , c .Server , ClientConfig .Do ())
226+ if err != nil {
227+ return nil , err
228+ }
229+ stm , err := con .NewStream (dialCtx )
230+ if err != nil {
231+ con .Close ()
232+ return nil , err
233+ }
234+ // NewStream is lazy: the peer does not see the stream until the first byte is written. Force a flush so the
235+ // server's AcceptStream returns promptly even if the ashe client decides to read before writing.
236+ if err := stm .Flush (); err != nil {
237+ con .Close ()
238+ return nil , err
239+ }
240+ addr := net .UDPAddrFromAddrPort (con .RemoteAddr ())
241+ srv := & Stream {con : con , rem : addr , stm : stm }
96242 spy := & ashe.Client {Cipher : c .Cipher }
97- con , err := spy .Estab (ctx , srv , network , address )
243+ out , err := spy .Estab (ctx , srv , network , address )
98244 if err != nil {
99245 srv .Close ()
100246 return nil , err
101247 }
102- return con , nil
248+ return out , nil
103249}
104250
105251// Dial connects to the address on the named network.
106252func (c * Client ) Dial (ctx * daze.Context , network string , address string ) (io.ReadWriteCloser , error ) {
107- con , err := c .Estab (ctx , network , address )
108- if err != nil {
109- return nil , err
110- }
111- return con , nil
253+ return c .Estab (ctx , network , address )
112254}
113255
114256// NewClient returns a new Client. Cipher is a password in string form, with no length limit.
0 commit comments