diff --git a/README.md b/README.md index 2d868186..362a729d 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,8 @@ Flags: --nginx.ssl-client-cert="" Path to the PEM encoded client certificate file to use when connecting to the server. ($SSL_CLIENT_CERT) --nginx.ssl-client-key="" Path to the PEM encoded client certificate key file to use when connecting to the server. ($SSL_CLIENT_KEY) + --[no-]nginx.proxy-protocol + Pass proxy protocol payload to nginx listeners. ($PROXY_PROTOCOL) --nginx.timeout=5s A timeout for scraping metrics from NGINX or NGINX Plus. ($TIMEOUT) --prometheus.const-label=PROMETHEUS.CONST-LABEL ... Label that will be used in every metric. Format is label=value. It can be repeated multiple times. ($CONST_LABELS) diff --git a/exporter.go b/exporter.go index a403e9ad..4ad00b81 100644 --- a/exporter.go +++ b/exporter.go @@ -30,6 +30,8 @@ import ( "github.com/prometheus/exporter-toolkit/web" "github.com/prometheus/exporter-toolkit/web/kingpinflag" + + proxyproto "github.com/pires/go-proxyproto" ) // positiveDuration is a wrapper of time.Duration to ensure only positive values are accepted. @@ -90,6 +92,7 @@ var ( sslCaCert = kingpin.Flag("nginx.ssl-ca-cert", "Path to the PEM encoded CA certificate file used to validate the servers SSL certificate.").Default("").Envar("SSL_CA_CERT").String() sslClientCert = kingpin.Flag("nginx.ssl-client-cert", "Path to the PEM encoded client certificate file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_CERT").String() sslClientKey = kingpin.Flag("nginx.ssl-client-key", "Path to the PEM encoded client certificate key file to use when connecting to the server.").Default("").Envar("SSL_CLIENT_KEY").String() + useProxyProto = kingpin.Flag("nginx.proxy-protocol", "Pass proxy protocol payload to nginx listeners.").Default("false").Envar("PROXY_PROTOCOL").Bool() // Custom command-line flags. timeout = createPositiveDurationFlag(kingpin.Flag("nginx.timeout", "A timeout for scraping metrics from NGINX or NGINX Plus.").Default("5s").Envar("TIMEOUT").HintOptions("5s", "10s", "30s", "1m", "5m")) @@ -223,17 +226,65 @@ func main() { func registerCollector(logger *slog.Logger, transport *http.Transport, addr string, labels map[string]string, ) { + var socketPath string + if strings.HasPrefix(addr, "unix:") { - socketPath, requestPath, err := parseUnixSocketAddress(addr) + var err error + var requestPath string + socketPath, requestPath, err = parseUnixSocketAddress(addr) if err != nil { logger.Error("parsing unix domain socket scrape address failed", "uri", addr, "error", err.Error()) os.Exit(1) } + addr = "http://unix" + requestPath + } + if !*useProxyProto && socketPath != "" { transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { return net.Dial("unix", socketPath) } - addr = "http://unix" + requestPath + } + + if *useProxyProto { + transport.DialContext = func(_ context.Context, network, addr string) (net.Conn, error) { + if socketPath != "" { + network = "unix" + addr = socketPath + } + + conn, err := (&net.Dialer{}).Dial(network, addr) + if err != nil { + return nil, fmt.Errorf("dialing %s %s: %w", network, addr, err) + } + + localAddr := conn.LocalAddr() + remoteAddr := conn.RemoteAddr() + transportProtocol := proxyproto.TCPv4 + + switch addr := remoteAddr.(type) { + case *net.TCPAddr: + if addr.IP.To4() == nil { + transportProtocol = proxyproto.TCPv6 + } + case *net.UnixAddr: + transportProtocol = proxyproto.UnixStream + } + + header := &proxyproto.Header{ + Version: 2, + Command: proxyproto.PROXY, + TransportProtocol: transportProtocol, + SourceAddr: localAddr, + DestinationAddr: remoteAddr, + } + + _, err = header.WriteTo(conn) + if err != nil { + return nil, fmt.Errorf("writing proxyproto header: %w", err) + } + + return conn, nil + } } userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", common_version.Version) diff --git a/go.mod b/go.mod index 23109f95..fed9c497 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.2 require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/nginx/nginx-plus-go-client/v2 v2.4.0 + github.com/pires/go-proxyproto v0.8.1 github.com/prometheus/client_golang v1.22.0 github.com/prometheus/common v0.65.0 github.com/prometheus/exporter-toolkit v0.14.0 diff --git a/go.sum b/go.sum index 246ef46f..15005cf9 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nginx/nginx-plus-go-client/v2 v2.4.0 h1:4c7V57CLCZUOxQCUcS9G8a5MClzdmxByBm+f4zKMzAY= github.com/nginx/nginx-plus-go-client/v2 v2.4.0/go.mod h1:P+dIP2oKYzFoyf/zlLWQa8Sf+fHb+CclOKzxAjxpvug= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=