Loading config/proxyplus.yaml +5 −0 Changes for config/proxyplus.yaml: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -66,6 +66,11 @@ proxyplus: # load-balanced upstream connections, and each WebSocket tunnel's socket pair + its own # kqueue/epoll fd. 0 = leave the OS/login-class default alone. max_open_files: 65536 # Idle connections (no read/write activity) are closed after this many seconds -- # reclaims fds an upstream/client never properly closed (root cause of the same # fd-exhaustion incident max_open_files guards against). 0 = never time out idle # connections (the old, pre-fix behavior). idle_connection_timeout_seconds: 120 pid_file: "/run/proxyplus.pid" recovery_mode: false Loading src/config/BootstrapConfig.cpp +3 −0 Changes for src/config/BootstrapConfig.cpp: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ const std::set<std::string> &knownLeafPatterns() { "/proxyplus/process/user", "/proxyplus/process/group", "/proxyplus/process/max_open_files", "/proxyplus/process/idle_connection_timeout_seconds", "/proxyplus/pid_file", "/proxyplus/recovery_mode", "/proxyplus/maintenance_mode", Loading Loading @@ -223,6 +224,8 @@ void BootstrapConfig::loadAndValidate() { cfg.processUser = readValue(conf, "/proxyplus/process/user", ""); cfg.processGroup = readValue(conf, "/proxyplus/process/group", ""); cfg.maxOpenFiles = readInt(conf, "/proxyplus/process/max_open_files", 65536); cfg.idleConnectionTimeoutSeconds = readInt(conf, "/proxyplus/process/idle_connection_timeout_seconds", 120); cfg.pidFile = readValue(conf, "/proxyplus/pid_file", ""); cfg.recoveryMode = readBool(conf, "/proxyplus/recovery_mode", false) ? RecoveryMode::Recover Loading src/config/BootstrapConfig.h +11 −0 Changes for src/config/BootstrapConfig.h: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -88,6 +88,17 @@ struct AppConfig { // alone (opt-out escape hatch for a platform where this is managed externally). int maxOpenFiles = 65536; // Forwarded to libhttppp::HttpEvent's idleTimeoutSeconds ctor param (see httpd.h) -- // closes an accepted connection once it's gone this long with no read/write activity, // reclaiming exactly the fd leak that produced the "Too many open files" incident // maxOpenFiles's comment above references. Unlike maxOpenFiles's 0=opt-out convention, // this defaults to a positive value: the whole point is for proxyplus to fix its own // root-caused leak out of the box, not require every deployment to remember to opt in. // 2 minutes is generous enough not to kill a legitimately slow-but-alive client while // still being short enough to reclaim genuinely abandoned connections. 0 still means // "disabled" for anyone who explicitly wants the old always-open behavior. int idleConnectionTimeoutSeconds = 120; RecoveryMode recoveryMode = RecoveryMode::Normal; MaintenanceMode maintenanceMode = MaintenanceMode::Off; }; Loading src/http/HttpProxyServer.cpp +4 −2 Changes for src/http/HttpProxyServer.cpp: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -113,8 +113,10 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges) : libhttppp::HttpEvent(std::move(serverSockets)) tls::AcmeChallengeStore *acmeChallenges, int idleTimeoutSeconds) : libhttppp::HttpEvent(std::move(serverSockets), /*timeout=*/1000, /*h2OffloadThreads=*/0, idleTimeoutSeconds) , _listenerLabel(std::move(listenerLabel)) , _tlsActive(tlsActive) , _configStore(configStore) Loading src/http/HttpProxyServer.h +12 −3 Changes for src/http/HttpProxyServer.h: 12 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,9 @@ public: // acmeChallenges is nullptr unless this listener should answer ACME HTTP-01 challenges // (see docs/ARCHITECTURE.md's Let's Encrypt section) -- typically only the plain-HTTP // listener on port 80 needs it. // idleTimeoutSeconds is forwarded straight to the libhttppp::HttpEvent base (see its own // doc comment in httpd.h) -- default 0 keeps today's behavior for any caller that doesn't // pass it explicitly; main.cpp's real call sites pass AppConfig::idleConnectionTimeoutSeconds. HttpProxyController(std::vector<netplus::socket *> serverSockets, std::string listenerLabel, bool tlsActive, Loading @@ -32,7 +35,8 @@ public: auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges = nullptr); tls::AcmeChallengeStore *acmeChallenges = nullptr, int idleTimeoutSeconds = 0); void RequestEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) override; Loading Loading @@ -74,6 +78,8 @@ private: // from a worker thread after privileges have been dropped. class HttpProxyServer { public: // idleTimeoutSeconds: see HttpProxyController's own doc comment above -- threaded down // into the controller it constructs internally in run(). HttpProxyServer(config::ListenerConfig listener, std::string certPath, std::string keyPath, Loading @@ -81,7 +87,8 @@ public: auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges = nullptr); tls::AcmeChallengeStore *acmeChallenges = nullptr, int idleTimeoutSeconds = 0); // SNI virtual hosting variant: certsByHostname carries one CertificateBundle per domain // sharing this listener (see ListenerConfig::tlsProfiles) instead of the single cert/key Loading @@ -92,7 +99,8 @@ public: auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges = nullptr); tls::AcmeChallengeStore *acmeChallenges = nullptr, int idleTimeoutSeconds = 0); void run(); Loading @@ -119,6 +127,7 @@ private: plugin::PluginManager &_pluginManager; lb::LoadBalancer &_loadBalancer; tls::AcmeChallengeStore *_acmeChallenges; int _idleTimeoutSeconds; }; } // namespace proxyplus::httpproxy Loading
config/proxyplus.yaml +5 −0 Changes for config/proxyplus.yaml: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -66,6 +66,11 @@ proxyplus: # load-balanced upstream connections, and each WebSocket tunnel's socket pair + its own # kqueue/epoll fd. 0 = leave the OS/login-class default alone. max_open_files: 65536 # Idle connections (no read/write activity) are closed after this many seconds -- # reclaims fds an upstream/client never properly closed (root cause of the same # fd-exhaustion incident max_open_files guards against). 0 = never time out idle # connections (the old, pre-fix behavior). idle_connection_timeout_seconds: 120 pid_file: "/run/proxyplus.pid" recovery_mode: false Loading
src/config/BootstrapConfig.cpp +3 −0 Changes for src/config/BootstrapConfig.cpp: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ const std::set<std::string> &knownLeafPatterns() { "/proxyplus/process/user", "/proxyplus/process/group", "/proxyplus/process/max_open_files", "/proxyplus/process/idle_connection_timeout_seconds", "/proxyplus/pid_file", "/proxyplus/recovery_mode", "/proxyplus/maintenance_mode", Loading Loading @@ -223,6 +224,8 @@ void BootstrapConfig::loadAndValidate() { cfg.processUser = readValue(conf, "/proxyplus/process/user", ""); cfg.processGroup = readValue(conf, "/proxyplus/process/group", ""); cfg.maxOpenFiles = readInt(conf, "/proxyplus/process/max_open_files", 65536); cfg.idleConnectionTimeoutSeconds = readInt(conf, "/proxyplus/process/idle_connection_timeout_seconds", 120); cfg.pidFile = readValue(conf, "/proxyplus/pid_file", ""); cfg.recoveryMode = readBool(conf, "/proxyplus/recovery_mode", false) ? RecoveryMode::Recover Loading
src/config/BootstrapConfig.h +11 −0 Changes for src/config/BootstrapConfig.h: 11 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -88,6 +88,17 @@ struct AppConfig { // alone (opt-out escape hatch for a platform where this is managed externally). int maxOpenFiles = 65536; // Forwarded to libhttppp::HttpEvent's idleTimeoutSeconds ctor param (see httpd.h) -- // closes an accepted connection once it's gone this long with no read/write activity, // reclaiming exactly the fd leak that produced the "Too many open files" incident // maxOpenFiles's comment above references. Unlike maxOpenFiles's 0=opt-out convention, // this defaults to a positive value: the whole point is for proxyplus to fix its own // root-caused leak out of the box, not require every deployment to remember to opt in. // 2 minutes is generous enough not to kill a legitimately slow-but-alive client while // still being short enough to reclaim genuinely abandoned connections. 0 still means // "disabled" for anyone who explicitly wants the old always-open behavior. int idleConnectionTimeoutSeconds = 120; RecoveryMode recoveryMode = RecoveryMode::Normal; MaintenanceMode maintenanceMode = MaintenanceMode::Off; }; Loading
src/http/HttpProxyServer.cpp +4 −2 Changes for src/http/HttpProxyServer.cpp: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -113,8 +113,10 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges) : libhttppp::HttpEvent(std::move(serverSockets)) tls::AcmeChallengeStore *acmeChallenges, int idleTimeoutSeconds) : libhttppp::HttpEvent(std::move(serverSockets), /*timeout=*/1000, /*h2OffloadThreads=*/0, idleTimeoutSeconds) , _listenerLabel(std::move(listenerLabel)) , _tlsActive(tlsActive) , _configStore(configStore) Loading
src/http/HttpProxyServer.h +12 −3 Changes for src/http/HttpProxyServer.h: 12 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,9 @@ public: // acmeChallenges is nullptr unless this listener should answer ACME HTTP-01 challenges // (see docs/ARCHITECTURE.md's Let's Encrypt section) -- typically only the plain-HTTP // listener on port 80 needs it. // idleTimeoutSeconds is forwarded straight to the libhttppp::HttpEvent base (see its own // doc comment in httpd.h) -- default 0 keeps today's behavior for any caller that doesn't // pass it explicitly; main.cpp's real call sites pass AppConfig::idleConnectionTimeoutSeconds. HttpProxyController(std::vector<netplus::socket *> serverSockets, std::string listenerLabel, bool tlsActive, Loading @@ -32,7 +35,8 @@ public: auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges = nullptr); tls::AcmeChallengeStore *acmeChallenges = nullptr, int idleTimeoutSeconds = 0); void RequestEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) override; Loading Loading @@ -74,6 +78,8 @@ private: // from a worker thread after privileges have been dropped. class HttpProxyServer { public: // idleTimeoutSeconds: see HttpProxyController's own doc comment above -- threaded down // into the controller it constructs internally in run(). HttpProxyServer(config::ListenerConfig listener, std::string certPath, std::string keyPath, Loading @@ -81,7 +87,8 @@ public: auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges = nullptr); tls::AcmeChallengeStore *acmeChallenges = nullptr, int idleTimeoutSeconds = 0); // SNI virtual hosting variant: certsByHostname carries one CertificateBundle per domain // sharing this listener (see ListenerConfig::tlsProfiles) instead of the single cert/key Loading @@ -92,7 +99,8 @@ public: auth::IAuthService &authService, plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer, tls::AcmeChallengeStore *acmeChallenges = nullptr); tls::AcmeChallengeStore *acmeChallenges = nullptr, int idleTimeoutSeconds = 0); void run(); Loading @@ -119,6 +127,7 @@ private: plugin::PluginManager &_pluginManager; lb::LoadBalancer &_loadBalancer; tls::AcmeChallengeStore *_acmeChallenges; int _idleTimeoutSeconds; }; } // namespace proxyplus::httpproxy