Ctrl K
ComunidadGuías…

browser_stream_helper/main.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"context"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"math/big"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/quic-go/quic-go/http3"
	webtransport "github.com/quic-go/webtransport-go"
)

const maxIPCFrameSize = 16 * 1024 * 1024
const maxControlFrameSize = 1024 * 1024

type readyInfo struct {
	Status     string `json:"status"`
	Addr       string `json:"addr"`
	CertSHA256 string `json:"cert_sha256"`
}

type ipcMessage struct {
	IPCToken     string          `json:"ipc_token"`
	Type         string          `json:"type"`
	SessionToken string          `json:"session_token,omitempty"`
	Payload      string          `json:"payload_b64,omitempty"`
	Payloads     []string        `json:"payloads_b64,omitempty"`
	Events       json.RawMessage `json:"events,omitempty"`
}

type controlStreamMessage struct {
	Type   string          `json:"type"`
	Events json.RawMessage `json:"events,omitempty"`
}

type sessionRegistry struct {
	mu       sync.Mutex
	tokens   map[string]struct{}
	sessions map[*webtransport.Session]string
}

func newSessionRegistry() *sessionRegistry {
	return &sessionRegistry{
		tokens:   make(map[string]struct{}),
		sessions: make(map[*webtransport.Session]string),
	}
}

func (r *sessionRegistry) register(token string) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.tokens[token] = struct{}{}
}

func (r *sessionRegistry) stop(token string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	_, existed := r.tokens[token]
	delete(r.tokens, token)
	for sess, sessionToken := range r.sessions {
		if sessionToken == token {
			_ = sess.CloseWithError(0, "session stopped")
			delete(r.sessions, sess)
			existed = true
		}
	}
	return existed
}

func (r *sessionRegistry) has(token string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	_, ok := r.tokens[token]
	return ok
}

func (r *sessionRegistry) consume(token string) bool {
	r.mu.Lock()
	defer r.mu.Unlock()
	if _, ok := r.tokens[token]; !ok {
		return false
	}
	delete(r.tokens, token)
	return true
}

func (r *sessionRegistry) attach(token string, sess *webtransport.Session) {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.sessions[sess] = token
}

func (r *sessionRegistry) detach(sess *webtransport.Session) {
	r.mu.Lock()
	defer r.mu.Unlock()
	delete(r.sessions, sess)
}

func (r *sessionRegistry) sendDatagram(token string, payload []byte) int {
	r.mu.Lock()
	targets := make([]*webtransport.Session, 0, len(r.sessions))
	for sess, sessionToken := range r.sessions {
		if token == "" || token == sessionToken {
			targets = append(targets, sess)
		}
	}
	r.mu.Unlock()

	sent := 0
	for _, sess := range targets {
		if err := sess.SendDatagram(payload); err == nil {
			sent++
		}
	}
	return sent
}

func tokenLabel(token string) string {
	if len(token) <= 8 {
		return token
	}
	return token[:8]
}

func addCertificateHost(dnsNames map[string]struct{}, ipAddresses map[string]net.IP, host string) {
	host = strings.TrimSpace(host)
	if host == "" {
		return
	}

	if splitHost, _, err := net.SplitHostPort(host); err == nil {
		host = splitHost
	}
	host = strings.Trim(host, "[]")
	if host == "" {
		return
	}

	if ip := net.ParseIP(host); ip != nil {
		ipAddresses[ip.String()] = ip
		return
	}

	dnsNames[strings.ToLower(host)] = struct{}{}
}

func localCertificateHosts() []string {
	hosts := []string{"localhost", "127.0.0.1", "::1"}
	if hostname, err := os.Hostname(); err == nil {
		hostname = strings.TrimSpace(hostname)
		if hostname != "" {
			hosts = append(hosts, hostname)
			shortName := strings.SplitN(hostname, ".", 2)[0]
			if shortName != "" {
				hosts = append(hosts, shortName, shortName+".lan", shortName+".local")
			}
		}
	}

	ifaces, err := net.Interfaces()
	if err != nil {
		return hosts
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue
		}
		addrs, err := iface.Addrs()
		if err != nil {
			continue
		}
		for _, addr := range addrs {
			var ip net.IP
			switch value := addr.(type) {
			case *net.IPNet:
				ip = value.IP
			case *net.IPAddr:
				ip = value.IP
			}
			if ip == nil {
				continue
			}
			hosts = append(hosts, ip.String())
		}
	}
	return hosts
}

func generateCertificate(now time.Time, extraHosts []string) (tls.Certificate, []byte, time.Time, error) {
	key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return tls.Certificate{}, nil, time.Time{}, err
	}

	notAfter := now.Add(13 * 24 * time.Hour)
	serialLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serial, err := rand.Int(rand.Reader, serialLimit)
	if err != nil {
		return tls.Certificate{}, nil, time.Time{}, err
	}

	dnsNames := make(map[string]struct{})
	ipAddresses := make(map[string]net.IP)
	for _, host := range localCertificateHosts() {
		addCertificateHost(dnsNames, ipAddresses, host)
	}
	for _, host := range extraHosts {
		addCertificateHost(dnsNames, ipAddresses, host)
	}

	certDNSNames := make([]string, 0, len(dnsNames))
	for host := range dnsNames {
		certDNSNames = append(certDNSNames, host)
	}
	certIPAddresses := make([]net.IP, 0, len(ipAddresses))
	for _, ip := range ipAddresses {
		certIPAddresses = append(certIPAddresses, ip)
	}

	template := &x509.Certificate{
		SerialNumber: serial,
		Subject: pkix.Name{
			CommonName: "Polaris Browser Stream",
		},
		NotBefore:             now.Add(-1 * time.Minute),
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		DNSNames:              certDNSNames,
		IPAddresses:           certIPAddresses,
	}

	der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
	if err != nil {
		return tls.Certificate{}, nil, time.Time{}, err
	}

	cert := tls.Certificate{
		Certificate: [][]byte{der},
		PrivateKey:  key,
		Leaf:        template,
	}
	return cert, der, notAfter, nil
}

func readIPCMessage(conn net.Conn) (ipcMessage, error) {
	var frameLen uint32
	if err := binary.Read(conn, binary.BigEndian, &frameLen); err != nil {
		return ipcMessage{}, err
	}
	if frameLen == 0 || frameLen > maxIPCFrameSize {
		return ipcMessage{}, fmt.Errorf("invalid IPC frame length %d", frameLen)
	}

	payload := make([]byte, frameLen)
	if _, err := io.ReadFull(conn, payload); err != nil {
		return ipcMessage{}, err
	}

	var msg ipcMessage
	if err := json.Unmarshal(payload, &msg); err != nil {
		return ipcMessage{}, err
	}
	return msg, nil
}

func writeIPCResponse(conn net.Conn, status string, errText string) {
	payload, _ := json.Marshal(map[string]string{
		"status": status,
		"error":  errText,
	})
	_ = binary.Write(conn, binary.BigEndian, uint32(len(payload)))
	_, _ = conn.Write(payload)
}

func writeIPCMessage(conn net.Conn, msg any) error {
	payload, err := json.Marshal(msg)
	if err != nil {
		return err
	}
	if len(payload) > maxIPCFrameSize {
		return fmt.Errorf("IPC payload is too large: %d", len(payload))
	}
	if err := binary.Write(conn, binary.BigEndian, uint32(len(payload))); err != nil {
		return err
	}
	_, err = conn.Write(payload)
	return err
}

func readIPCResponse(conn net.Conn) (map[string]string, error) {
	var frameLen uint32
	if err := binary.Read(conn, binary.BigEndian, &frameLen); err != nil {
		return nil, err
	}
	if frameLen == 0 || frameLen > maxIPCFrameSize {
		return nil, fmt.Errorf("invalid IPC response frame length %d", frameLen)
	}

	payload := make([]byte, frameLen)
	if _, err := io.ReadFull(conn, payload); err != nil {
		return nil, err
	}

	var response map[string]string
	if err := json.Unmarshal(payload, &response); err != nil {
		return nil, err
	}
	return response, nil
}

func readControlFrame(r io.Reader) ([]byte, error) {
	var frameLen uint32
	if err := binary.Read(r, binary.BigEndian, &frameLen); err != nil {
		return nil, err
	}
	if frameLen == 0 || frameLen > maxControlFrameSize {
		return nil, fmt.Errorf("invalid control frame length %d", frameLen)
	}

	payload := make([]byte, frameLen)
	if _, err := io.ReadFull(r, payload); err != nil {
		return nil, err
	}
	return payload, nil
}

func forwardInputEvents(inputIPCSocket string, inputIPCToken string, sessionToken string, events json.RawMessage) error {
	if inputIPCSocket == "" {
		return errors.New("input IPC socket is not configured")
	}
	if len(events) == 0 {
		return errors.New("input events payload is empty")
	}

	conn, err := net.Dial("unix", inputIPCSocket)
	if err != nil {
		return err
	}
	defer conn.Close()

	if err := writeIPCMessage(conn, ipcMessage{
		IPCToken:     inputIPCToken,
		Type:         "input_events",
		SessionToken: sessionToken,
		Events:       events,
	}); err != nil {
		return err
	}

	response, err := readIPCResponse(conn)
	if err != nil {
		return err
	}
	if response["status"] != "ok" {
		if response["error"] == "" {
			return errors.New("Polaris input IPC rejected the message")
		}
		return errors.New(response["error"])
	}
	return nil
}

func notifyTransportClosed(inputIPCSocket string, inputIPCToken string, sessionToken string) error {
	if inputIPCSocket == "" {
		return errors.New("input IPC socket is not configured")
	}

	conn, err := net.Dial("unix", inputIPCSocket)
	if err != nil {
		return err
	}
	defer conn.Close()

	if err := writeIPCMessage(conn, ipcMessage{
		IPCToken:     inputIPCToken,
		Type:         "transport_closed",
		SessionToken: sessionToken,
	}); err != nil {
		return err
	}

	response, err := readIPCResponse(conn)
	if err != nil {
		return err
	}
	if response["status"] != "ok" {
		if response["error"] == "" {
			return errors.New("Polaris rejected the transport close notification")
		}
		return errors.New(response["error"])
	}
	return nil
}

func handleIPCConn(conn net.Conn, ipcToken string, registry *sessionRegistry) {
	defer conn.Close()

	for {
		msg, err := readIPCMessage(conn)
		if err != nil {
			if !errors.Is(err, io.EOF) {
				writeIPCResponse(conn, "error", err.Error())
			}
			return
		}
		if msg.IPCToken != ipcToken {
			writeIPCResponse(conn, "error", "invalid IPC token")
			return
		}

		switch msg.Type {
		case "register_session":
			if msg.SessionToken == "" {
				writeIPCResponse(conn, "error", "missing session token")
				continue
			}
			registry.register(msg.SessionToken)
			log.Printf("registered session token=%s", tokenLabel(msg.SessionToken))
			writeIPCResponse(conn, "ok", "")
		case "stop_session":
			stopped := registry.stop(msg.SessionToken)
			log.Printf("stopped session token=%s existed=%t", tokenLabel(msg.SessionToken), stopped)
			writeIPCResponse(conn, "ok", "")
		case "media_datagram":
			payload, err := base64.StdEncoding.DecodeString(msg.Payload)
			if err != nil {
				writeIPCResponse(conn, "error", "invalid media payload")
				continue
			}
			registry.sendDatagram(msg.SessionToken, payload)
			writeIPCResponse(conn, "ok", "")
		case "media_datagrams":
			valid := true
			for _, encodedPayload := range msg.Payloads {
				payload, err := base64.StdEncoding.DecodeString(encodedPayload)
				if err != nil {
					valid = false
					break
				}
				registry.sendDatagram(msg.SessionToken, payload)
			}
			if !valid {
				writeIPCResponse(conn, "error", "invalid media payload")
				continue
			}
			writeIPCResponse(conn, "ok", "")
		default:
			writeIPCResponse(conn, "error", "unknown message type")
		}
	}
}

func serveIPC(ctx context.Context, path string, ipcToken string, registry *sessionRegistry) error {
	if path == "" {
		return nil
	}

	_ = os.Remove(path)
	ln, err := net.Listen("unix", path)
	if err != nil {
		return err
	}
	if err := os.Chmod(path, 0o600); err != nil {
		_ = ln.Close()
		return err
	}

	go func() {
		<-ctx.Done()
		_ = ln.Close()
		_ = os.Remove(path)
	}()

	go func() {
		for {
			conn, err := ln.Accept()
			if err != nil {
				return
			}
			go handleIPCConn(conn, ipcToken, registry)
		}
	}()
	return nil
}

func handleControlStream(token string, stream io.ReadWriteCloser, inputIPCSocket string, inputIPCToken string) {
	defer stream.Close()

	for {
		payload, err := readControlFrame(stream)
		if err != nil {
			if !errors.Is(err, io.EOF) {
				log.Printf("control stream read failed: %v", err)
			}
			return
		}

		var msg controlStreamMessage
		if err := json.Unmarshal(payload, &msg); err != nil {
			log.Printf("control stream payload rejected: %v", err)
			continue
		}

		switch msg.Type {
		case "input_events":
			if err := forwardInputEvents(inputIPCSocket, inputIPCToken, token, msg.Events); err != nil {
				log.Printf("input IPC forward failed: %v", err)
			}
		default:
			log.Printf("control stream ignored unknown message type %q", msg.Type)
		}
	}
}

func handleWebTransportSession(ctx context.Context, token string, sess *webtransport.Session, registry *sessionRegistry, inputIPCSocket string, inputIPCToken string) {
	registry.attach(token, sess)
	log.Printf("webtransport session attached token=%s", tokenLabel(token))
	defer func() {
		registry.detach(sess)
		log.Printf("webtransport session detached token=%s", tokenLabel(token))
		if err := notifyTransportClosed(inputIPCSocket, inputIPCToken, token); err != nil {
			log.Printf("transport close notification failed token=%s: %v", tokenLabel(token), err)
		}
	}()

	go func() {
		for {
			stream, err := sess.AcceptStream(ctx)
			if err != nil {
				return
			}
			go func() {
				handleControlStream(token, stream, inputIPCSocket, inputIPCToken)
			}()
		}
	}()

	<-sess.Context().Done()
}

func main() {
	addr := flag.String("addr", ":47992", "UDP HTTPS/WebTransport bind address")
	hosts := flag.String("hosts", "", "Comma-separated hostnames or IP addresses clients may use for WebTransport")
	ipcSocket := flag.String("ipc-socket", "", "Unix socket path for Polaris IPC")
	ipcToken := flag.String("ipc-token", "", "Random per-process IPC token")
	inputIPCSocket := flag.String("input-ipc-socket", "", "Unix socket path for Browser Stream input IPC")
	inputIPCToken := flag.String("input-ipc-token", "", "Random per-process Browser Stream input IPC token")
	certHashFile := flag.String("cert-hash-file", "", "Optional path to write the server certificate SHA-256 hex digest")
	flag.Parse()

	var extraHosts []string
	if *hosts != "" {
		extraHosts = strings.Split(*hosts, ",")
	}

	cert, der, _, err := generateCertificate(time.Now(), extraHosts)
	if err != nil {
		log.Fatalf("certificate generation failed: %v", err)
	}
	certHash := sha256.Sum256(der)
	certHashHex := hex.EncodeToString(certHash[:])

	if *certHashFile != "" {
		if err := os.WriteFile(*certHashFile, []byte(certHashHex), 0o600); err != nil {
			log.Fatalf("writing cert hash file failed: %v", err)
		}
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	registry := newSessionRegistry()
	if err := serveIPC(ctx, *ipcSocket, *ipcToken, registry); err != nil {
		log.Fatalf("IPC setup failed: %v", err)
	}

	mux := http.NewServeMux()
	server := webtransport.Server{
		H3: &http3.Server{
			Addr:    *addr,
			Handler: mux,
			TLSConfig: http3.ConfigureTLSConfig(&tls.Config{
				Certificates: []tls.Certificate{cert},
			}),
		},
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	webtransport.ConfigureHTTP3Server(server.H3)
	defer server.Close()

	mux.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(readyInfo{
			Status:     "ok",
			Addr:       *addr,
			CertSHA256: certHashHex,
		})
	})

	mux.HandleFunc("/browser-stream", func(w http.ResponseWriter, r *http.Request) {
		token := r.URL.Query().Get("token")
		if !registry.has(token) {
			log.Printf("webtransport rejected remote=%s token=%s reason=invalid-or-used", r.RemoteAddr, tokenLabel(token))
			http.Error(w, "invalid or already used Browser Stream token", http.StatusForbidden)
			return
		}

		log.Printf("webtransport upgrade requested remote=%s token=%s", r.RemoteAddr, tokenLabel(token))
		sess, err := server.Upgrade(w, r)
		if err != nil {
			log.Printf("webtransport upgrade failed: %v", err)
			return
		}
		if !registry.consume(token) {
			log.Printf("webtransport rejected after upgrade remote=%s token=%s reason=invalid-or-used", r.RemoteAddr, tokenLabel(token))
			_ = sess.CloseWithError(0, "invalid or already used Browser Stream token")
			return
		}
		log.Printf("webtransport upgrade accepted remote=%s token=%s", r.RemoteAddr, tokenLabel(token))
		go handleWebTransportSession(ctx, token, sess, registry, *inputIPCSocket, *inputIPCToken)
	})

	log.Printf("webtransport helper ready addr=%s hosts=%s cert_sha256=%s", *addr, *hosts, certHashHex)
	_ = json.NewEncoder(os.Stdout).Encode(readyInfo{
		Status:     "ready",
		Addr:       *addr,
		CertSHA256: certHashHex,
	})

	if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
		log.Fatalf("WebTransport server failed: %v", err)
	}
}