mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-20 23:59:55 +00:00
relay: add WebTransport listener + WASM client, share UDP/443 via ALPN mux
The relay now accepts WebTransport sessions on the same UDP socket that
serves raw QUIC. The ALPN-multiplexing QUIC listener owns the socket and
dispatches incoming connections: "nb-quic" continues to the existing
relay handler, "h3" is handed to webtransport-go via http3.Server.
Browsers reach the relay over 443/udp without a second port.
Client side:
- Native builds keep using raw QUIC (no WT dialer registered).
- WASM/browser builds gain a WebTransport dialer that bridges syscall/js
to the browser's WebTransport API and uses datagrams (matching the
native QUIC dialer's semantics — no head-of-line blocking).
- The race dialer learned a transport hint so clients skip dialers a
given relay has not advertised.
Management protocol carries the hint as a new RelayEndpoint{url,
transports[]} list on RelayConfig, mirroring how peers and proxies
announce capabilities. Older management servers that only send urls keep
working unchanged.
devcert build: relay generates an ECDSA P-256 cert with 13-day validity
(within the WebTransport serverCertificateHashes 14-day cap) and exposes
its SHA-256 so the WASM dialer can pin it.
Bumps quic-go v0.55.0 -> v0.59.0 (no API breaks for relay's importers)
and adds github.com/quic-go/webtransport-go v0.10.0.
This commit is contained in:
@@ -3,23 +3,56 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// devCertHash holds the SHA-256 hash of the latest generated dev certificate.
|
||||
// The WASM WebTransport client reads it via DevCertHash() to pin the self-
|
||||
// signed cert through serverCertificateHashes — browsers require an ECDSA cert
|
||||
// with validity <= 14 days when this pinning mode is used.
|
||||
var (
|
||||
devCertHashMu sync.RWMutex
|
||||
devCertHash []byte
|
||||
)
|
||||
|
||||
// DevCertHash returns the SHA-256 hash of the dev TLS certificate, or nil if
|
||||
// no dev cert has been generated yet. WASM clients can pass this through
|
||||
// serverCertificateHashes on WebTransport handshake.
|
||||
func DevCertHash() []byte {
|
||||
devCertHashMu.RLock()
|
||||
defer devCertHashMu.RUnlock()
|
||||
if devCertHash == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]byte, len(devCertHash))
|
||||
copy(out, devCertHash)
|
||||
return out
|
||||
}
|
||||
|
||||
func setDevCertHash(certDER []byte) {
|
||||
sum := sha256.Sum256(certDER)
|
||||
devCertHashMu.Lock()
|
||||
devCertHash = sum[:]
|
||||
devCertHashMu.Unlock()
|
||||
}
|
||||
|
||||
func ServerQUICTLSConfig(originTLSCfg *tls.Config) (*tls.Config, error) {
|
||||
if originTLSCfg == nil {
|
||||
log.Warnf("QUIC server will use self signed certificate for testing!")
|
||||
return generateTestTLSConfig()
|
||||
return generateTestTLSConfig([]string{NBalpn})
|
||||
}
|
||||
|
||||
cfg := originTLSCfg.Clone()
|
||||
@@ -27,10 +60,26 @@ func ServerQUICTLSConfig(originTLSCfg *tls.Config) (*tls.Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// GenerateTestTLSConfig creates a self-signed certificate for testing
|
||||
func generateTestTLSConfig() (*tls.Config, error) {
|
||||
log.Infof("generating test TLS config")
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// ServerMuxTLSConfig returns a TLS config offering both ALPNs so a single UDP
|
||||
// socket can serve raw QUIC and WebTransport clients.
|
||||
func ServerMuxTLSConfig(originTLSCfg *tls.Config) (*tls.Config, error) {
|
||||
if originTLSCfg == nil {
|
||||
log.Warnf("QUIC/WT server will use self signed certificate for testing!")
|
||||
return generateTestTLSConfig([]string{NBalpn, H3alpn})
|
||||
}
|
||||
|
||||
cfg := originTLSCfg.Clone()
|
||||
cfg.NextProtos = []string{NBalpn, H3alpn}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// generateTestTLSConfig creates a self-signed ECDSA P-256 certificate suitable
|
||||
// for both raw QUIC and browser WebTransport. Validity is capped at 13 days so
|
||||
// the cert remains usable with WebTransport serverCertificateHashes pinning
|
||||
// (browser limit is 14 days).
|
||||
func generateTestTLSConfig(alpns []string) (*tls.Config, error) {
|
||||
log.Infof("generating test TLS config (ECDSA P-256, 13 day validity) for ALPNs %v", alpns)
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,40 +89,36 @@ func generateTestTLSConfig() (*tls.Config, error) {
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Test Organization"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 180), // Valid for 180 days
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
},
|
||||
NotBefore: time.Now().Add(-time.Hour),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 13),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{"localhost"},
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
}
|
||||
|
||||
// Create certificate
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setDevCertHash(certDER)
|
||||
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: certDER,
|
||||
})
|
||||
keyDER, err := x509.MarshalECPrivateKey(privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, privateKeyPEM)
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
NextProtos: []string{NBalpn},
|
||||
NextProtos: alpns,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user