diff --git a/transport/quic/quic.go b/transport/quic/quic.go new file mode 100644 index 00000000..fd72db54 --- /dev/null +++ b/transport/quic/quic.go @@ -0,0 +1,187 @@ +// Package quic provides a QUIC based transport +package quic + +import ( + "crypto/tls" + "encoding/gob" + + "github.com/micro/go-micro/cmd" + "github.com/lucas-clemente/quic-go" + "github.com/micro/go-micro/transport" + utls "github.com/micro/util/go/lib/tls" +) + +type quicSocket struct { + s quic.Session + st quic.Stream + enc *gob.Encoder + dec *gob.Decoder +} + +type quicTransport struct { + opts transport.Options +} + +type quicClient struct { + *quicSocket + t *quicTransport + opts transport.DialOptions +} + +type quicListener struct { + l quic.Listener + t *quicTransport + opts transport.ListenOptions +} + +func init() { + cmd.DefaultTransports["quic"] = NewTransport +} + +func (q *quicClient) Close() error { + return q.quicSocket.st.Close() +} + +func (q *quicSocket) Recv(m *transport.Message) error { + return q.dec.Decode(&m) +} + +func (q *quicSocket) Send(m *transport.Message) error { + return q.enc.Encode(m) +} + +func (q *quicSocket) Close() error { + return q.s.Close() +} + +func (q *quicSocket) Local() string { + return q.s.LocalAddr().String() +} + +func (q *quicSocket) Remote() string { + return q.s.RemoteAddr().String() +} + +func (q *quicListener) Addr() string { + return q.l.Addr().String() +} + +func (q *quicListener) Close() error { + return q.l.Close() +} + +func (q *quicListener) Accept(fn func(transport.Socket)) error { + for { + s, err := q.l.Accept() + if err != nil { + return err + } + + stream, err := s.AcceptStream() + if err != nil { + continue + } + + go func() { + fn(&quicSocket{ + s: s, + st: stream, + enc: gob.NewEncoder(stream), + dec: gob.NewDecoder(stream), + }) + }() + } +} + +func (q *quicTransport) Init(opts ...transport.Option) error { + for _, o := range opts { + o(&q.opts) + } + return nil +} + +func (q *quicTransport) Options() transport.Options { + return q.opts +} + +func (q *quicTransport) Dial(addr string, opts ...transport.DialOption) (transport.Client, error) { + var options transport.DialOptions + for _, o := range opts { + o(&options) + } + + config := q.opts.TLSConfig + if config == nil { + config = &tls.Config{ + InsecureSkipVerify: true, + } + } + s, err := quic.DialAddr(addr, config, nil) + if err != nil { + return nil, err + } + + st, err := s.OpenStreamSync() + if err != nil { + return nil, err + } + + enc := gob.NewEncoder(st) + dec := gob.NewDecoder(st) + + return &quicClient{ + &quicSocket{ + s: s, + st: st, + enc: enc, + dec: dec, + }, + q, + options, + }, nil +} + +func (q *quicTransport) Listen(addr string, opts ...transport.ListenOption) (transport.Listener, error) { + var options transport.ListenOptions + for _, o := range opts { + o(&options) + } + + config := q.opts.TLSConfig + if config == nil { + cfg, err := utls.Certificate(addr) + if err != nil { + return nil, err + } + config = &tls.Config{ + Certificates: []tls.Certificate{cfg}, + } + } + + l, err := quic.ListenAddr(addr, config, nil) + if err != nil { + return nil, err + } + + return &quicListener{ + l: l, + t: q, + opts: options, + }, nil +} + +func (q *quicTransport) String() string { + return "quic" +} + +func NewTransport(opts ...transport.Option) transport.Transport { + options := transport.Options{} + + for _, o := range opts { + o(&options) + } + + return &quicTransport{ + opts: options, + } +}