OrbitalBeat/OrbitalBeat.go
DataHoarder 1a9b858eaf
Some checks failed
continuous-integration/drone/push Build is failing
Fix Alt-Svc header
2022-01-17 18:54:46 +01:00

958 lines
23 KiB
Go

package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"database/sql"
"encoding/base32"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"flag"
"fmt"
"github.com/cloudflare/circl/sign/ed25519"
"github.com/dgrr/http2"
"github.com/ipfs/go-cid"
_ "github.com/lib/pq"
"github.com/lucas-clemente/quic-go/http3"
"github.com/multiformats/go-multihash"
"github.com/valyala/fasthttp"
"log"
"math"
"math/big"
"net"
"net/http"
"net/url"
"os"
"path"
"strings"
"sync"
"time"
)
type ContentCacheEntry struct {
Entry ContentEntry
AccessTime time.Time
}
var base32Encoding = base32.NewEncoding("qpzry9x8gf2tvdw0s3jn54khce6mua7l").WithPadding(base32.NoPadding)
var dbHandle *sql.DB
var sniAddress string
var sha256Statement *sql.Stmt
var md5Statement *sql.Stmt
var fdlimit int
var fsHandler fasthttp.RequestHandler
var objectCacheMutex sync.RWMutex
var objectCache = make(map[string]*ContentCacheEntry)
var messageCacheLimit int
var messageCacheMutex sync.RWMutex
var messageCache = make(map[string]*ContentMessage)
var extraHeadersMutex sync.RWMutex
var extraHeaders = make(map[string]string)
var trustedPublicKeys []ed25519.PublicKey
var debugOutput = false
func isSNIAllowed(sni string) bool {
return len(sniAddress) == 0 || sni == sniAddress
}
func getFirstValidContentEntry(entries *[]ContentEntry) *ContentEntry {
for _, entry := range *entries {
stat, err := os.Stat(entry.Path)
if err == nil /*&& uint64(stat.Size()) == entry.Size*/ { //TODO: update Size if not found
copiedEntry := entry
copiedEntry.Size = uint64(stat.Size())
return &copiedEntry
}
}
return nil
}
func guessMimeType(p string) string {
//Need a few extra types from base Go
ext := strings.ToLower(path.Ext(p))
switch ext {
case ".flac":
fallthrough
case ".ogg":
fallthrough
case ".opus":
fallthrough
case ".tta":
fallthrough
case ".aac":
fallthrough
case ".wav":
fallthrough
case ".alac":
return "audio/" + ext[1:]
case ".m4a":
return "audio/mp4"
case ".mp3":
return "audio/mpeg;codecs=mp3"
}
return ""
}
func handleQueryRequest(ctx *RequestContext, identifier cid.Cid, extraArguments []string) {
cTime := time.Now()
var cacheEntry = tryGetCacheEntryForIdentifier(identifier)
if cacheEntry == nil {
result := getEntriesForCID(identifier)
entry := getFirstValidContentEntry(&result)
if entry != nil {
cacheEntry = getCacheEntryForContentEntry(entry, identifier)
}
pTime := cTime
cTime = time.Now()
addServerTimingMetric(ctx, "e", "Content Entry (MISS)", cTime.Sub(pTime))
} else {
pTime := cTime
cTime = time.Now()
addServerTimingMetric(ctx, "e", "Content Entry (HIT)", cTime.Sub(pTime))
}
if cacheEntry == nil {
var origin string
if len(ctx.getRequestHeader("Referer")) > 0 {
origin = ctx.getRequestHeader("Referer")
} else if len(ctx.getRequestHeader("Origin")) > 0 {
origin = ctx.getRequestHeader("Origin")
}
//Try to redirect back to origin
if len(extraArguments) > 0 && origin != "" {
mh, _ := multihash.Decode(identifier.Hash())
var kind string
if mh.Code == multihash.SHA2_256 {
kind = "sha256"
} else if mh.Code == multihash.MD5 {
kind = "md5"
}
if kind != "" {
ctx.doRedirect(fmt.Sprintf("%s/%s/%s/%s", origin, kind, hex.EncodeToString(mh.Digest), strings.Join(extraArguments, "/")), http.StatusFound)
return
}
}
ctx.setResponseCode(http.StatusNotFound)
return
}
mh, _ := multihash.Decode(cacheEntry.Entry.Identifier.Hash())
ctx.setResponseHeader("Accept-Ranges", "bytes")
ctx.setResponseHeader("X-Request-CID", identifier.String())
ctx.setResponseHeader("ETag", fmt.Sprintf("\"%s\"", cacheEntry.Entry.Identifier.String()))
if mh.Code == multihash.SHA2_256 {
ctx.setResponseHeader("Digest", fmt.Sprintf("sha-256=%s", base64.StdEncoding.EncodeToString(mh.Digest)))
}
//ctx.setResponseHeader("X-IPFS-Path", fmt.Sprintf("/ipfs/%s", cacheEntry.Entry.Identifier.String()))
ctx.setResponseHeader("Cache-Control", "public, max-age=2592000, immutable")
filename := path.Base(cacheEntry.Entry.Path)
//TODO: setting to hide filename
ctx.setResponseHeader("Content-Disposition", fmt.Sprintf("inline; filename*=utf-8''%s", url.PathEscape(filename)))
pTime := cTime
cTime = time.Now()
addServerTimingMetric(ctx, "s", "Content Serve", cTime.Sub(pTime))
mime := guessMimeType(cacheEntry.Entry.Path)
if len(mime) > 0 {
ctx.setResponseHeader("Content-Type", mime)
}
ctx.serveFile(cacheEntry.Entry.Path)
}
func setOtherHeaders(ctx *RequestContext) {
ctx.setResponseHeader("Server", "OrbitalBeat")
ctx.setResponseHeader("Vary", "Content-Encoding")
ctx.setResponseHeader("X-Content-Type-Options", "nosniff")
extraHeadersMutex.RLock()
defer extraHeadersMutex.RUnlock()
for k, v := range extraHeaders {
ctx.setResponseHeader(k, v)
}
}
func setCORSHeaders(ctx *RequestContext) {
ctx.setResponseHeader("Access-Control-Allow-Credentials", "true")
ctx.setResponseHeader("Access-Control-Max-Age", "7200") //Firefox caps this to 86400, Chrome to 7200. Default is 5 seconds (!!!)
ctx.setResponseHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS")
ctx.setResponseHeader("Access-Control-Allow-Headers", "DNT,ETag,Origin,Accept,Accept-Language,X-Requested-With,Range")
ctx.setResponseHeader("Access-Control-Allow-Origin", "*")
ctx.setResponseHeader("Access-Control-Expose-Headers", "*")
//CORP, COEP, COOP
ctx.setResponseHeader("Cross-Origin-Embedder-Policy", "require-corp")
ctx.setResponseHeader("Cross-Origin-Resource-Policy", "cross-origin")
ctx.setResponseHeader("Cross-Origin-Opener-Policy", "unsafe-none")
}
func getCacheEntryForContentEntry(entry *ContentEntry, originalIdentifier cid.Cid) *ContentCacheEntry {
cacheEntry := tryGetCacheEntryForIdentifier(entry.Identifier)
if cacheEntry != nil {
return cacheEntry
}
objectCacheMutex.Lock()
defer objectCacheMutex.Unlock()
if len(objectCache) >= fdlimit {
//Find oldest value, remove it
var item *ContentCacheEntry
for _, e := range objectCache {
if item == nil || e.AccessTime.Before(item.AccessTime) {
item = e
}
}
if item != nil {
delete(objectCache, item.Entry.Identifier.String())
}
}
c := &ContentCacheEntry{
Entry: *entry,
AccessTime: time.Now(),
}
objectCache[entry.Identifier.String()] = c
if originalIdentifier.String() != entry.Identifier.String() {
objectCache[originalIdentifier.String()] = c
}
return c
}
func tryGetCacheEntryForIdentifier(identifier cid.Cid) *ContentCacheEntry {
objectCacheMutex.RLock()
defer objectCacheMutex.RUnlock()
cacheEntry, ok := objectCache[identifier.String()]
if ok {
cacheEntry.AccessTime = time.Now().UTC()
return cacheEntry
}
return nil
}
func IsTrustedPublicKey(key ed25519.PublicKey) bool {
for _, k := range trustedPublicKeys {
if bytes.Compare(k, key) == 0 {
return true
}
}
return false
}
func addServerTimingMetric(ctx *RequestContext, name string, desc string, d time.Duration) {
if d < 0 {
d = 0
}
v := ctx.getResponseHeader("Server-Timing")
if len(v) > 0 {
ctx.setResponseHeader("Server-Timing", fmt.Sprintf("%s, %s;desc=\"%s\";dur=%.6F", v, name, desc, float64(d.Nanoseconds())/1e6))
} else {
ctx.setResponseHeader("Server-Timing", fmt.Sprintf("%s;desc=\"%s\";dur=%.6F", name, desc, float64(d.Nanoseconds())/1e6))
}
}
type RequestContext struct {
fasthttp *fasthttp.RequestCtx
httpWriter *http.ResponseWriter
httpRequest *http.Request
connTime time.Time
requestTime time.Time
}
func NewRequestContextFromFastHttp(ctx *fasthttp.RequestCtx) RequestContext {
return RequestContext{
fasthttp: ctx,
connTime: ctx.ConnTime(),
requestTime: ctx.Time(),
}
}
func NewRequestContextFromHttp(w http.ResponseWriter, r *http.Request) RequestContext {
return RequestContext{
httpWriter: &w,
httpRequest: r,
connTime: time.Now(),
requestTime: time.Now(),
}
}
func (c *RequestContext) getPath() string {
if c.fasthttp != nil {
return string(c.fasthttp.Path())
} else if c.httpRequest != nil {
return c.httpRequest.URL.Path
}
return ""
}
func (c *RequestContext) getConnectionTime() time.Time {
return c.connTime
}
func (c *RequestContext) getRequestTime() time.Time {
return c.requestTime
}
func (c *RequestContext) isFastHttp() bool {
return c.fasthttp != nil
}
func (c *RequestContext) getTLSServerName() string {
if c.fasthttp != nil {
return c.fasthttp.TLSConnectionState().ServerName
} else if c.httpRequest != nil {
return c.httpRequest.TLS.ServerName
}
return ""
}
func (c *RequestContext) getRequestHeader(name string) string {
if c.fasthttp != nil {
return string(c.fasthttp.Request.Header.Peek(name))
} else if c.httpRequest != nil {
return c.httpRequest.Header.Get(name)
}
return ""
}
func (c *RequestContext) getResponseHeader(name string) string {
if c.fasthttp != nil {
return string(c.fasthttp.Response.Header.Peek(name))
} else if c.httpWriter != nil {
return (*c.httpWriter).Header().Get(name)
}
return ""
}
func (c *RequestContext) setResponseHeader(name string, value string) {
if c.fasthttp != nil {
c.fasthttp.Response.Header.Set(name, value)
} else if c.httpWriter != nil {
(*c.httpWriter).Header().Set(name, value)
}
}
func (c *RequestContext) serveFile(path string) {
if c.fasthttp != nil {
c.fasthttp.Request.URI().Reset()
c.fasthttp.Request.URI().SetPath(path)
fsHandler(c.fasthttp)
} else if c.httpWriter != nil {
http.ServeFile(*c.httpWriter, c.httpRequest, path)
}
}
func (c *RequestContext) setResponseCode(code int) {
if c.fasthttp != nil {
c.fasthttp.Response.SetStatusCode(code)
} else if c.httpWriter != nil {
(*c.httpWriter).WriteHeader(code)
}
}
func (c *RequestContext) doRedirect(location string, code int) {
if c.fasthttp != nil {
c.fasthttp.Redirect(location, code)
} else if c.httpWriter != nil {
http.Redirect(*c.httpWriter, c.httpRequest, location, code)
}
}
func (c *RequestContext) isGet() bool {
if c.fasthttp != nil {
return c.fasthttp.IsGet()
} else if c.httpRequest != nil {
return c.httpRequest.Method == "GET"
}
return false
}
func (c *RequestContext) isOptions() bool {
if c.fasthttp != nil {
return c.fasthttp.IsOptions()
} else if c.httpRequest != nil {
return c.httpRequest.Method == "OPTIONS"
}
return false
}
func (c *RequestContext) isHead() bool {
if c.fasthttp != nil {
return c.fasthttp.IsHead()
} else if c.httpRequest != nil {
return c.httpRequest.Method == "HEAD"
}
return false
}
func handle(ctx RequestContext) {
if len(ctx.getRequestHeader("Host")) > 0 && string(ctx.getRequestHeader("Host")) == ctx.getTLSServerName() { //Prevents rebinding / DNS stuff
ctx.setResponseCode(http.StatusNotFound)
ctx.setResponseCode(http.StatusNotFound)
return
}
cTime := time.Now()
if ctx.isFastHttp() {
addServerTimingMetric(&ctx, "c", "Connection", ctx.getRequestTime().Sub(ctx.getConnectionTime()))
}
addServerTimingMetric(&ctx, "r", "Request Handler", cTime.Sub(ctx.getRequestTime()))
if ctx.isGet() || ctx.isHead() {
if debugOutput {
log.Printf("Serve %s", ctx.getPath())
}
setOtherHeaders(&ctx)
setCORSHeaders(&ctx)
pathElements := strings.Split(ctx.getPath(), "/")
if len(pathElements) < 2 {
ctx.setResponseCode(http.StatusBadRequest)
return
}
messageBytes, err := base32Encoding.DecodeString(pathElements[1])
if err != nil {
ctx.setResponseCode(http.StatusBadRequest)
return
}
message := DecodeContentMessage(messageBytes)
if message == nil {
ctx.setResponseCode(http.StatusBadRequest)
return
}
if !IsTrustedPublicKey(message.PublicKey) {
ctx.setResponseCode(http.StatusForbidden)
return
}
pTime := cTime
cTime := time.Now()
addServerTimingMetric(&ctx, "d", "Decode", cTime.Sub(pTime))
result, cacheHit := message.verify()
pTime = cTime
cTime = time.Now()
if cacheHit {
addServerTimingMetric(&ctx, "v", "Ed25519 Verify (HIT)", cTime.Sub(pTime))
} else {
addServerTimingMetric(&ctx, "v", "Ed25519 Verify (MISS)", cTime.Sub(pTime))
}
if !result {
ctx.setResponseCode(http.StatusForbidden)
return
}
if debugOutput {
log.Printf("Serving CID %s", message.Identifier.String())
}
handleQueryRequest(&ctx, message.Identifier, pathElements[2:])
} else if ctx.isOptions() {
setOtherHeaders(&ctx)
setCORSHeaders(&ctx)
ctx.setResponseCode(http.StatusNoContent)
} else {
ctx.setResponseCode(http.StatusNotImplemented)
}
}
// ContentMessage TODO: move this to a library
type ContentMessage struct {
Version uint64
PublicKey ed25519.PublicKey
IssueTime int64
Identifier cid.Cid
VerificationResult *bool
Signature []byte
}
func (s *ContentMessage) sign(privateKey ed25519.PrivateKey) {
s.PublicKey = make([]byte, ed25519.PublicKeySize)
copy(s.PublicKey, privateKey[32:])
s.Signature = ed25519.Sign(privateKey, s.encodeMessage())
s.VerificationResult = nil
}
func (s *ContentMessage) verify() (bool, bool) {
currentTime := time.Now()
issueTime := time.Unix(s.IssueTime, 0)
validityStart := issueTime.Add(-time.Hour) //Only one hour before time
validityEnd := issueTime.Add(time.Hour * 24) //Only 24 hours after time
if validityStart.After(currentTime) {
return false, false
}
if validityEnd.Before(currentTime) {
return false, false
}
messageCacheMutex.RLock()
k := string(s.encode())
cachedMessage, ok := messageCache[k]
messageCacheMutex.RUnlock()
if ok {
return *cachedMessage.VerificationResult, true
}
messageCacheMutex.Lock()
defer messageCacheMutex.Unlock()
if s.VerificationResult == nil {
makeBool := func(v bool) *bool { return &v }
s.VerificationResult = makeBool(ed25519.Verify(s.PublicKey, s.encodeMessage(), s.Signature))
}
if len(messageCache) >= messageCacheLimit {
//Find oldest value, remove it
var item *ContentMessage
for _, e := range messageCache {
if item == nil || e.IssueTime < item.IssueTime {
item = e
}
}
if item != nil {
delete(messageCache, string(item.encode()))
}
}
messageCache[k] = s
return *s.VerificationResult, false
}
func (s *ContentMessage) encodeMessage() []byte {
message := &bytes.Buffer{}
buf := make([]byte, binary.MaxVarintLen64)
n := binary.PutUvarint(buf, s.Version) //signature version
_, _ = message.Write(buf[:n])
if s.Version == 0 || s.Version == 1 {
_, _ = message.Write(s.PublicKey)
n = binary.PutVarint(buf, s.IssueTime) //time
_, _ = message.Write(buf[:n])
if s.Version == 1 {
_, _ = message.Write(s.Identifier.Hash())
} else {
_, _ = s.Identifier.WriteBytes(message)
}
return message.Bytes()
}
return nil
}
func (s *ContentMessage) encode() []byte {
message := s.encodeMessage()
if message == nil || len(s.Signature) != ed25519.SignatureSize {
return nil
}
return append(message, s.Signature...)
}
func (s ContentMessage) String() string {
return fmt.Sprintf("%d %x %d %s %x", s.Version, s.PublicKey, s.IssueTime, s.Identifier.String(), s.Signature)
}
func DecodeContentMessage(signatureBytes []byte) *ContentMessage {
message := ContentMessage{}
buffer := bytes.NewBuffer(signatureBytes)
var err error
message.Version, err = binary.ReadUvarint(buffer)
if err != nil {
return nil
}
if message.Version == 0 || message.Version == 1 {
message.PublicKey = make([]byte, ed25519.PublicKeySize)
_, err := buffer.Read(message.PublicKey)
if err != nil {
return nil
}
message.IssueTime, err = binary.ReadVarint(buffer)
if err != nil {
return nil
}
if message.Version == 1 {
read, mh, err := multihash.MHFromBytes(buffer.Bytes())
if err != nil {
return nil
}
buffer.Next(read)
message.Identifier = cid.NewCidV1(cid.Raw, mh)
} else {
_, message.Identifier, err = cid.CidFromReader(buffer)
if err != nil {
return nil
}
}
message.Signature = make([]byte, ed25519.SignatureSize)
_, err = buffer.Read(message.Signature)
if err != nil {
return nil
}
if buffer.Len() != 0 { //Unknown extra data
return nil
}
return &message
}
return nil
}
type ContentEntry struct {
Identifier cid.Cid
Path string
Size uint64
}
func handleQuery(rows *sql.Rows, err error) []ContentEntry {
if err != nil {
log.Print(err)
return []ContentEntry{}
}
defer rows.Close()
var result []ContentEntry
for rows.Next() {
var entry ContentEntry
var sha256 multihash.Multihash
var size sql.NullInt64
err := rows.Scan(&entry.Path, &size, &sha256)
if err != nil {
log.Print(err)
break
}
if size.Valid {
entry.Size = uint64(size.Int64)
}
mh, _ := multihash.Encode(sha256, multihash.SHA2_256)
entry.Identifier = cid.NewCidV1(cid.Raw, mh)
result = append(result, entry)
}
return result
}
func getEntriesForCID(identifier cid.Cid) []ContentEntry {
mh, _ := multihash.Decode(identifier.Hash())
if mh.Code == multihash.SHA2_256 {
return handleQuery(sha256Statement.Query(mh.Digest))
} else if mh.Code == multihash.MD5 {
return handleQuery(md5Statement.Query(mh.Digest))
}
return []ContentEntry{}
}
func createSelfSignedCertificate() ([]byte, []byte) {
serial, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
x509Template := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{},
NotBefore: time.Unix(0, 0).UTC(),
NotAfter: time.Date(time.Now().UTC().Year()+10, 0, 0, 0, 0, 0, 0, time.UTC),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
privateBogusKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
certBytes, err := x509.CreateCertificate(rand.Reader, &x509Template, &x509Template, privateBogusKey.Public(), privateBogusKey)
if err != nil {
log.Fatal(err)
}
keyBytes, err := x509.MarshalPKCS8PrivateKey(privateBogusKey)
if err != nil {
log.Fatal(err)
}
return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}),
pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: keyBytes,
})
}
func processCertificate(c *tls.Certificate) {
if c.Leaf == nil {
leaf, err := x509.ParseCertificate(c.Certificate[0])
if err != nil {
return
}
c.Leaf = leaf
}
}
func main() {
//TODO: OCSP
certificatePath := flag.String("certificate", "", "Path to SSL certificate file.")
keypairPath := flag.String("keypair", "", "Path to SSL key file.")
pgConnStr := flag.String("connstr", "", "Postgres connection string for postgres database")
listenAddress := flag.String("listen", ":7777", "Address/port to lisent on.")
trustedKeys := flag.String("trusted_keys", "", "Trusted list of public keys, comma separated.")
sniAddressOption := flag.String("sni", "", "Define SNI address if desired. Empty will serve any requests regardless.")
fdLimitOption := flag.Int("fdlimit", 512, "Maximum number of lingering cached open files.")
debugOption := flag.Bool("debug", false, "Output debug information.")
signatureCacheLimitOption := flag.Int("siglimit", 4096, "Maximum number of lingering valid signature cache results.")
flag.Parse()
fdlimit = *fdLimitOption
messageCacheLimit = *signatureCacheLimitOption
debugOutput = *debugOption
var err error
for _, k := range strings.Split(*trustedKeys, ",") {
var publicKey ed25519.PublicKey
publicKey, err = base32Encoding.DecodeString(strings.Trim(k, " "))
if err != nil {
log.Fatal(err)
}
if len(publicKey) != ed25519.PublicKeySize {
continue
}
trustedPublicKeys = append(trustedPublicKeys, publicKey)
log.Printf("Added public key %s", strings.Trim(k, " "))
}
dbHandle, err = sql.Open("postgres", *pgConnStr)
if err != nil {
log.Fatal(err)
}
defer dbHandle.Close()
sha256Statement, err = dbHandle.Prepare("SELECT path, size, sha256 FROM entries WHERE sha256 = $1;")
if err != nil {
log.Fatal(err)
}
defer sha256Statement.Close()
md5Statement, err = dbHandle.Prepare("SELECT path, size, sha256 FROM entries WHERE md5 = $1;")
if err != nil {
log.Fatal(err)
}
defer md5Statement.Close()
//TODO: create postgres tables
sniAddress = strings.ToLower(*sniAddressOption)
bogusCertificatePEM, bogusKeyPairPEM := createSelfSignedCertificate()
bogusCertificate, err := tls.X509KeyPair(bogusCertificatePEM, bogusKeyPairPEM)
if err != nil {
log.Fatal(err)
}
var serverCertificate tls.Certificate
if *certificatePath != "" && *keypairPath != "" {
serverCertificate, err = tls.LoadX509KeyPair(*certificatePath, *keypairPath)
if err != nil {
log.Fatal(err)
}
} else {
serverCertificate = bogusCertificate
}
processCertificate(&bogusCertificate)
processCertificate(&serverCertificate)
fs := fasthttp.FS{
Root: "/",
AcceptByteRange: true,
Compress: false,
CompressBrotli: false,
CacheDuration: time.Minute * 15,
PathRewrite: func(ctx *fasthttp.RequestCtx) []byte {
return ctx.Request.URI().PathOriginal()
},
}
fsHandler = fs.NewRequestHandler()
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: 0, //max supported, currently TLS 1.3
CurvePreferences: []tls.CurveID{
tls.X25519,
tls.CurveP256,
tls.CurveP384,
},
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
},
PreferServerCipherSuites: false,
SessionTicketsDisabled: false,
Renegotiation: tls.RenegotiateFreelyAsClient,
NextProtos: []string{
"h2",
"http/1.1",
},
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
if isSNIAllowed(info.ServerName) {
return &serverCertificate, nil
}
return &bogusCertificate, nil
},
}
var wg sync.WaitGroup
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
server := &fasthttp.Server{
ReadTimeout: 5 * time.Second,
IdleTimeout: 15 * time.Second,
Handler: func(ctx *fasthttp.RequestCtx) {
handle(NewRequestContextFromFastHttp(ctx))
},
NoDefaultServerHeader: true,
NoDefaultDate: true,
DisableKeepalive: false,
TCPKeepalive: true,
TLSConfig: tlsConfig,
}
http2.ConfigureServer(server, http2.ServerConfig{
Debug: false,
})
log.Printf("Serving TCP on %s", *listenAddress)
ln, err := net.Listen("tcp", *listenAddress)
if err != nil {
log.Panic(err)
}
defer ln.Close()
err = server.Serve(tls.NewListener(ln, server.TLSConfig.Clone()))
if err != nil {
log.Panic(err)
}
}(&wg)
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
server := &http3.Server{
Server: &http.Server{
Addr: *listenAddress,
TLSConfig: &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
if isSNIAllowed(info.ServerName) {
return &serverCertificate, nil
}
return &bogusCertificate, nil
},
},
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if debugOutput {
log.Print("Received HTTP/3 request")
}
handle(NewRequestContextFromHttp(w, r))
}),
},
}
h := http.Header{}
server.SetQuicHeaders(h)
extraHeadersMutex.Lock()
extraHeaders["Alt-Svc"] = h.Get("Alt-Svc")
extraHeadersMutex.Unlock()
log.Printf("Serving UDP on %s", *listenAddress)
err = server.ListenAndServe()
if err != nil {
log.Panic(err)
}
}(&wg)
wg.Wait()
}