From c08002cd4e46b5530cd526090cdb354ce2dca035 Mon Sep 17 00:00:00 2001 From: Fijxu Date: Mon, 24 Mar 2025 18:15:38 -0300 Subject: [PATCH] style: move configuration to it's own package and more - Remove https, it's useless and reverse proxies like haproxy, caddy and nginx are better at handling TCP and SSL connections - Ability to disable the UDS and HTTP servers --- cmd/http3-ytproxy/main.go | 224 ++++++++------------------------ internal/config/config.go | 88 +++++++++++++ internal/httpc/client.go | 15 +-- internal/paths/consts.go | 4 - internal/paths/videoplayback.go | 3 +- internal/utils/utils.go | 7 - 6 files changed, 148 insertions(+), 193 deletions(-) create mode 100644 internal/config/config.go diff --git a/cmd/http3-ytproxy/main.go b/cmd/http3-ytproxy/main.go index d00d1ed..7e3a597 100644 --- a/cmd/http3-ytproxy/main.go +++ b/cmd/http3-ytproxy/main.go @@ -1,8 +1,6 @@ package main import ( - "crypto/tls" - "errors" "flag" "io" "log" @@ -10,18 +8,16 @@ import ( "net/http" "os" "runtime" - "strconv" "strings" "syscall" "time" + "git.nadeko.net/Fijxu/http3-ytproxy/internal/config" "git.nadeko.net/Fijxu/http3-ytproxy/internal/httpc" "git.nadeko.net/Fijxu/http3-ytproxy/internal/metrics" "git.nadeko.net/Fijxu/http3-ytproxy/internal/paths" "git.nadeko.net/Fijxu/http3-ytproxy/internal/utils" "github.com/prometheus/procfs" - "github.com/quic-go/quic-go" - "github.com/quic-go/quic-go/http3" ) type ConnectionWatcher struct { @@ -31,7 +27,6 @@ type ConnectionWatcher struct { idle int64 } -var h3s bool var version string var cw ConnectionWatcher var tx uint64 @@ -117,10 +112,6 @@ func beforeProxy(next http.HandlerFunc) http.HandlerFunc { w.Header().Set("Strict-Transport-Security", "max-age=86400") w.Header().Set("X-Powered-By", "http3-ytproxy "+version+"-"+runtime.GOARCH) - if h3s { - w.Header().Set("Alt-Svc", "h3=\":8443\"; ma=86400") - } - if req.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return @@ -137,104 +128,34 @@ func beforeProxy(next http.HandlerFunc) http.HandlerFunc { } } +func init() { + config.LoadConfig() +} + func main() { - defaultHost := "0.0.0.0" - defaultPort := "8080" - defaultSock := "/tmp/http-ytproxy.sock" - defaultTLSCert := "/data/cert.pem" - defaultTLSKey := "/data/key.key" - - var http_server bool = true - var https bool = false - var h3c bool = false - var h2c bool = false - var ipv6 bool = false - var bc bool = true - - if strings.ToLower(utils.Getenv("HTTP")) == "true" { - http_server = true - } - if strings.ToLower(utils.Getenv("HTTPS")) == "true" { - https = true - } - if strings.ToLower(utils.Getenv("H2C")) == "true" { - h2c = true - } - if strings.ToLower(utils.Getenv("H3C")) == "true" { - h3c = true - } - if strings.ToLower(utils.Getenv("H3S")) == "true" { - h3s = true - } - if strings.ToLower(utils.Getenv("IPV6_ONLY")) == "true" { - ipv6 = true - } - if strings.ToLower(utils.Getenv("BLOCK_CHECKER")) == "false" { - bc = false - } - - tls_cert := utils.Getenv("TLS_CERT") - if tls_cert == "" { - tls_cert = defaultTLSCert - } - tls_key := utils.Getenv("TLS_KEY") - if tls_key == "" { - tls_key = defaultTLSKey - } - sock := utils.Getenv("SOCK_PATH") - if sock == "" { - sock = defaultSock - } - port := utils.Getenv("PORT") - if port == "" { - port = defaultPort - } - host := utils.Getenv("HOST") - if host == "" { - host = defaultHost - } - // gh is where the gluetun api is located - gh := utils.Getenv("GLUETUN_HOSTNAME") - if gh == "" { - gh = "127.0.0.1:8000" - } - bc_cooldown := utils.Getenv("BLOCK_CHECKER_COOLDOWN") - if bc_cooldown == "" { - bc_cooldown = "60" - } - httpc.Proxy = utils.Getenv("PROXY") - - flag.BoolVar(&https, "https", https, "Use built-in https server (recommended)") - flag.BoolVar(&h3c, "h3c", h3c, "Use HTTP/3 for client requests (high CPU usage)") - flag.BoolVar(&h3s, "h3s", h3s, "Use HTTP/3 for server requests, (requires HTTPS)") - flag.BoolVar(&httpc.Ipv6_only, "ipv6_only", httpc.Ipv6_only, "Only use ipv6 for requests") - flag.StringVar(&tls_cert, "tls-cert", tls_cert, "TLS Certificate path") - flag.StringVar(&tls_key, "tls-key", tls_key, "TLS Certificate Key path") - flag.StringVar(&sock, "s", sock, "Specify a socket name") - flag.StringVar(&port, "p", port, "Specify a port number") - flag.StringVar(&host, "l", host, "Specify a listen address") + flag.BoolVar(&config.Cfg.Enable_http, "http", config.Cfg.Enable_http, "Enable HTTP Server") + flag.BoolVar(&config.Cfg.Uds, "uds", config.Cfg.Uds, "Enable UDS (Unix socket domain)") + flag.IntVar(&config.Cfg.Http_client_ver, "http-client-ver", config.Cfg.Http_client_ver, "Specify the HTTP Version that is going to be used on the client, accepted values are '1', '2 'and '3'") + flag.BoolVar(&config.Cfg.Ipv6_only, "ipv6-only", config.Cfg.Ipv6_only, "Only use ipv6 for requests") + flag.StringVar(&config.Cfg.Uds_path, "s", config.Cfg.Uds_path, "Specify the UDS (Unix socket domain) path\nExample: /run/http3-ytproxy.sock") + flag.StringVar(&config.Cfg.Proxy, "pr", config.Cfg.Proxy, "Specify the proxy that is going to be used for requests\nExample: http://127.0.0.1:8090") + flag.StringVar(&config.Cfg.Port, "p", config.Cfg.Port, "Specify a port number") + flag.StringVar(&config.Cfg.Host, "l", config.Cfg.Host, "Specify a listen address") flag.Parse() - httpc.Ipv6_only = ipv6 - if h3c { - log.Println("[INFO] Using HTTP/3 Client") - httpc.Client = httpc.H3client - } else if h2c { - log.Println("[INFO] Using HTTP/2 Client") - httpc.Client = httpc.H2client - } else { + switch config.Cfg.Http_client_ver { + case 1: + log.Println("[INFO] Using HTTP/1.1 Client") + httpc.Client = httpc.H1_1client + case 2: + log.Println("[INFO] Using HTTP/2 Client") + httpc.Client = httpc.H2client + case 3: + log.Println("[INFO] Using HTTP/3 Client") + httpc.Client = httpc.H3client + default: log.Println("[INFO] Using HTTP/1.1 Client") httpc.Client = httpc.H1_1client - } - - if https { - if len(tls_cert) <= 0 { - log.Fatal("tls-cert argument is missing, you need a TLS certificate for HTTPS") - } - - if len(tls_key) <= 0 { - log.Fatal("tls-key argument is missing, you need a TLS key for HTTPS") - } } mux := http.NewServeMux() @@ -256,12 +177,8 @@ func main() { mux.HandleFunc("/a/", beforeProxy(paths.Ggpht)) mux.HandleFunc("/ytc/", beforeProxy(paths.Ggpht)) - if bc { - num, err := strconv.Atoi(bc_cooldown) - if err != nil { - log.Fatalf("[FATAL] Error while setting BLOCK_CHECKER_COOLDOWN: %s", err) - } - go blockChecker(gh, num) + if config.Cfg.Gluetun.Block_checker { + go blockChecker(config.Cfg.Gluetun.Gluetun_api, config.Cfg.Gluetun.Block_checker_cooldown) } srv := &http.Server{ @@ -269,78 +186,39 @@ func main() { ReadTimeout: 5 * time.Second, WriteTimeout: 1 * time.Hour, ConnState: cw.OnStateChange, - Addr: host + ":" + port, + Addr: config.Cfg.Host + ":" + config.Cfg.Port, } - srvh3 := &http3.Server{ - Handler: mux, - // https://quic.video/blog/never-use-datagrams/ (Read it) - EnableDatagrams: false, - IdleTimeout: 120 * time.Second, - TLSConfig: http3.ConfigureTLSConfig(&tls.Config{}), - QUICConfig: &quic.Config{ - // I'm not sure if this is correct. - MaxIncomingStreams: 256, - // Same as above. - MaxIncomingUniStreams: 256, - }, - Addr: host + ":" + port, - } - - syscall.Unlink(sock) - socket_listener, err := net.Listen("unix", sock) - - if err != nil { - log.Println("Failed to bind to UDS, please check the socket name", err.Error()) - } else { - defer socket_listener.Close() - - // To allow everyone to access the socket - err = os.Chmod(sock, 0777) + if config.Cfg.Uds { + syscall.Unlink(config.Cfg.Uds_path) + socket_listener, err := net.Listen("unix", config.Cfg.Uds_path) if err != nil { - log.Println("Failed to set socket permissions to 777:", err.Error()) + log.Println("[ERROR] Failed to bind to UDS, please check the socket path", err.Error()) + } + defer socket_listener.Close() + err = os.Chmod(config.Cfg.Uds_path, 0777) + if err != nil { + log.Println("[ERROR] Failed to set socket permissions to 777:", err.Error()) return } else { - log.Println("Setting socket permissions to 777") + log.Println("[INFO] Setting socket permissions to 777") } - go srv.Serve(socket_listener) - log.Println("Unix socket listening at:", string(sock)) - - if http_server { - if https { - if _, err := os.Open(tls_cert); errors.Is(err, os.ErrNotExist) { - log.Panicf("Certificate file does not exist at path '%s'", tls_cert) - } - - if _, err := os.Open(tls_key); errors.Is(err, os.ErrNotExist) { - log.Panicf("Key file does not exist at path '%s'", tls_key) - } - - log.Println("Serving HTTPS at port", string(port)+"/tcp") - - go func() { - if err := srv.ListenAndServeTLS(tls_cert, tls_key); err != nil { - log.Fatal("Failed to serve HTTP/2", err.Error()) - } - }() - - if h3s { - log.Println("Serving HTTP/3 (HTTPS) via QUIC at port", string(port)+"/udp") - go func() { - if err := srvh3.ListenAndServeTLS(tls_cert, tls_key); err != nil { - log.Fatal("Failed to serve HTTP/3:", err.Error()) - } - }() - } - - select {} - } else { - log.Println("Serving HTTP at port", string(port)) - if err := srv.ListenAndServe(); err != nil { - log.Fatal(err) - } + go func() { + err := srv.Serve(socket_listener) + if err != nil { + log.Println("[ERROR] Failed to listen serve UDS:", err) } + }() + + // To allow everyone to access the socket + log.Println("[INFO] Unix socket listening at:", config.Cfg.Uds_path) + } + + if config.Cfg.Enable_http { + log.Println("[INFO] Serving HTTP server at port", config.Cfg.Port) + if err := srv.ListenAndServe(); err != nil { + log.Fatalf("[FATAL] Failed to listen on '%s:%s': %s\n", config.Cfg.Host, config.Cfg.Port, err) } } } diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..289f161 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,88 @@ +package config + +import ( + "log" + "strconv" + "strings" + "syscall" +) + +var Cfg *config + +type config struct { + Enable_http bool + Uds bool + Uds_path string + Host string + Port string + Proxy string + Http_client_ver int + Ipv6_only bool + Gluetun struct { + Gluetun_api string + Block_checker bool + Block_checker_cooldown int + } + Companion struct { + // Used for videoplayback query encryption + Secret_key string + } +} + +func getenv(key string) string { + // `YTPROXY_` as a prefix + v, _ := syscall.Getenv("YTPROXY_" + key) + return v +} + +func getEnvBool(key string, def bool) bool { + v := strings.ToLower(getenv(key)) + if v == "" { + return def + } + return v == "true" +} + +func getEnvString(key string, def string) string { + v := strings.ToLower(getenv(key)) + if v == "" { + return def + } + return v +} + +func getEnvInt(key string, def int) int { + v := strings.ToLower(getenv(key)) + if v == "" { + return def + } + i, err := strconv.Atoi(v) + if err != nil { + log.Panicf("[FATAL] Failed to convert env variable '%s' to int", v) + } + return int(i) +} + +func LoadConfig() { + Cfg = &config{ + Enable_http: getEnvBool("ENABLE_HTTP", true), + Uds: getEnvBool("ENABLE_UDS", true), + // I would use `/run/http3-proxy` here, but `/run` is not user writable + // which is kinda anoying when developing. + Uds_path: getEnvString("UDS_PATH", "/tmp/http-ytproxy.sock"), + Host: getEnvString("HOST", "0.0.0.0"), + Port: getEnvString("PORT", "8080"), + Proxy: getEnvString("PROXY", ""), + Http_client_ver: getEnvInt("HTTP_CLIENT_VER", 1), + Ipv6_only: getEnvBool("IPV6_ONLY", true), + Gluetun: struct { + Gluetun_api string + Block_checker bool + Block_checker_cooldown int + }{ + Gluetun_api: getEnvString("GLUETUN_API", "127.0.0.1:8000"), + Block_checker: getEnvBool("BLOCK_CHECKER", true), + Block_checker_cooldown: getEnvInt("BLOCK_CHECKER_COOLDOWN", 60), + }, + } +} diff --git a/internal/httpc/client.go b/internal/httpc/client.go index 2bcc1f3..67aff20 100644 --- a/internal/httpc/client.go +++ b/internal/httpc/client.go @@ -7,11 +7,10 @@ import ( "net/url" "time" + "git.nadeko.net/Fijxu/http3-ytproxy/internal/config" "github.com/quic-go/quic-go/http3" ) -var Ipv6_only = false -var Proxy string var Client *http.Client var dialer = &net.Dialer{ @@ -34,7 +33,7 @@ var H1_1client = &http.Client{ Transport: &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { var net string - if Ipv6_only { + if config.Cfg.Ipv6_only { net = "tcp6" } else { net = "tcp4" @@ -50,8 +49,8 @@ var H1_1client = &http.Client{ MaxIdleConnsPerHost: 10, MaxIdleConns: 0, Proxy: func(r *http.Request) (*url.URL, error) { - if Proxy != "" { - return url.Parse(Proxy) + if config.Cfg.Proxy != "" { + return url.Parse(config.Cfg.Proxy) } return nil, nil }, @@ -68,7 +67,7 @@ var H2client = &http.Client{ Transport: &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { var net string - if Ipv6_only { + if config.Cfg.Ipv6_only { net = "tcp6" } else { net = "tcp4" @@ -85,8 +84,8 @@ var H2client = &http.Client{ MaxIdleConnsPerHost: 10, MaxIdleConns: 0, Proxy: func(r *http.Request) (*url.URL, error) { - if Proxy != "" { - return url.Parse(Proxy) + if config.Cfg.Proxy != "" { + return url.Parse(config.Cfg.Proxy) } return nil, nil }, diff --git a/internal/paths/consts.go b/internal/paths/consts.go index 8ba44d9..1da12ea 100644 --- a/internal/paths/consts.go +++ b/internal/paths/consts.go @@ -3,8 +3,6 @@ package paths import ( "net/http" "regexp" - - "git.nadeko.net/Fijxu/http3-ytproxy/internal/utils" ) const ( @@ -33,5 +31,3 @@ var videoplayback_headers = &http.Header{ // https://github.com/FreeTubeApp/FreeTube/blob/5a4cd981cdf2c2a20ab68b001746658fd0c6484e/src/renderer/components/ft-shaka-video-player/ft-shaka-video-player.js#L1097 var protobuf_body = []byte{0x78, 0} // protobuf body - -var secret_key = utils.Getenv("SECRET_KEY") diff --git a/internal/paths/videoplayback.go b/internal/paths/videoplayback.go index 7ec07ea..d4ec2a1 100644 --- a/internal/paths/videoplayback.go +++ b/internal/paths/videoplayback.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "git.nadeko.net/Fijxu/http3-ytproxy/internal/config" "git.nadeko.net/Fijxu/http3-ytproxy/internal/httpc" "git.nadeko.net/Fijxu/http3-ytproxy/internal/metrics" "git.nadeko.net/Fijxu/http3-ytproxy/internal/utils" @@ -57,7 +58,7 @@ func Videoplayback(w http.ResponseWriter, req *http.Request) { q := req.URL.Query() if q.Get("enc") == "yes" { - deencryptedQueryParams, err := utils.DecryptQueryParams(req.URL.Query().Get("data"), secret_key) + deencryptedQueryParams, err := utils.DecryptQueryParams(req.URL.Query().Get("data"), config.Cfg.Companion.Secret_key) if err != nil { http.Error(w, "Internal Server Error:\nFailed to decrypt query parameters", http.StatusInternalServerError) return diff --git a/internal/utils/utils.go b/internal/utils/utils.go index d4a1c37..4d92465 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -7,7 +7,6 @@ import ( "net/http" "net/url" "strings" - "syscall" "git.nadeko.net/Fijxu/http3-ytproxy/internal/httpc" ) @@ -78,12 +77,6 @@ func PanicHandler(w http.ResponseWriter) { } } -func Getenv(key string) string { - // `YTPROXY_` as a prefix - v, _ := syscall.Getenv("YTPROXY_" + key) - return v -} - // https://stackoverflow.com/a/41652605 func DecryptQueryParams(encryptedQuery string, key string) (string, error) { se, err := base64.URLEncoding.DecodeString(encryptedQuery)