qcc/internal/transport/server.go
2026-06-30 12:52:09 +03:00

141 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,
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
}