Add 'qcc' ALPN protocol to both ACME and insecure TLS configs. Bump golang.org/x/crypto and transitive x/ deps. Add golang.org/x/mobile for mobile client bindings.
142 lines
3.2 KiB
Go
142 lines
3.2 KiB
Go
package transport
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
|
|
"github.com/apernet/quic-go"
|
|
"github.com/caddyserver/certmagic"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type Server struct {
|
|
listener *quic.Listener
|
|
tr *quic.Transport
|
|
logger *zap.Logger
|
|
handler ServerHandler
|
|
}
|
|
|
|
type ServerHandler interface {
|
|
OnConnect(ctx context.Context, conn *quic.Conn)
|
|
}
|
|
|
|
func NewServer(listenAddr string, tlsConfig *tls.Config, logger *zap.Logger, handler ServerHandler) (*Server, error) {
|
|
addr, err := net.ResolveUDPAddr("udp", listenAddr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("resolve addr: %w", err)
|
|
}
|
|
|
|
conn, err := net.ListenUDP("udp", addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listen udp: %w", err)
|
|
}
|
|
|
|
tr := &quic.Transport{Conn: conn}
|
|
|
|
listener, err := tr.Listen(tlsConfig, &quic.Config{
|
|
Allow0RTT: true,
|
|
KeepAlivePeriod: 15,
|
|
EnableDatagrams: true,
|
|
MaxIncomingStreams: 1000,
|
|
MaxIncomingUniStreams: -1,
|
|
})
|
|
if err != nil {
|
|
conn.Close()
|
|
return nil, fmt.Errorf("quic listen: %w", err)
|
|
}
|
|
|
|
return &Server{
|
|
listener: listener,
|
|
tr: tr,
|
|
logger: logger,
|
|
handler: handler,
|
|
}, nil
|
|
}
|
|
|
|
func NewTLSConfig(domains []string, email, caType string, dnsSolver *certmagic.DNS01Solver, logger *zap.Logger) (*tls.Config, error) {
|
|
dataDir := "data/acme"
|
|
cmCfg := &certmagic.Config{
|
|
RenewalWindowRatio: certmagic.DefaultRenewalWindowRatio,
|
|
KeySource: certmagic.DefaultKeyGenerator,
|
|
Storage: &certmagic.FileStorage{Path: dataDir},
|
|
Logger: logger,
|
|
}
|
|
|
|
cmIssuer := certmagic.NewACMEIssuer(cmCfg, certmagic.ACMEIssuer{
|
|
Email: email,
|
|
Agreed: true,
|
|
Logger: logger,
|
|
})
|
|
|
|
switch caType {
|
|
case "letsencrypt", "le", "":
|
|
cmIssuer.CA = certmagic.LetsEncryptProductionCA
|
|
case "zerossl", "zero":
|
|
cmIssuer.CA = certmagic.ZeroSSLProductionCA
|
|
default:
|
|
return nil, fmt.Errorf("unsupported CA: %s", caType)
|
|
}
|
|
|
|
if dnsSolver != nil {
|
|
cmIssuer.DisableHTTPChallenge = true
|
|
cmIssuer.DisableTLSALPNChallenge = true
|
|
cmIssuer.DNS01Solver = dnsSolver
|
|
} else {
|
|
cmIssuer.DisableHTTPChallenge = false
|
|
cmIssuer.DisableTLSALPNChallenge = true
|
|
}
|
|
|
|
cmCfg.Issuers = []certmagic.Issuer{cmIssuer}
|
|
cmCache := certmagic.NewCache(certmagic.CacheOptions{
|
|
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
|
|
return cmCfg, nil
|
|
},
|
|
Logger: logger,
|
|
})
|
|
cmCfg = certmagic.New(cmCache, *cmCfg)
|
|
|
|
if len(domains) == 0 {
|
|
return nil, fmt.Errorf("no domains configured for ACME")
|
|
}
|
|
|
|
if err := cmCfg.ManageSync(context.Background(), domains); err != nil {
|
|
return nil, fmt.Errorf("acme manage sync: %w", err)
|
|
}
|
|
|
|
return &tls.Config{
|
|
MinVersion: tls.VersionTLS13,
|
|
NextProtos: []string{"qcc"},
|
|
GetCertificate: cmCfg.GetCertificate,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) Start(ctx context.Context) error {
|
|
s.logger.Info("QUIC server listening",
|
|
zap.String("addr", s.listener.Addr().String()))
|
|
|
|
for {
|
|
conn, err := s.listener.Accept(ctx)
|
|
if err != nil {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
default:
|
|
s.logger.Error("Accept error", zap.Error(err))
|
|
continue
|
|
}
|
|
}
|
|
|
|
go s.handler.OnConnect(ctx, conn)
|
|
}
|
|
}
|
|
|
|
func (s *Server) Close() error {
|
|
err1 := s.listener.Close()
|
|
err2 := s.tr.Close()
|
|
if err1 != nil {
|
|
return err1
|
|
}
|
|
return err2
|
|
}
|