Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
56 changes: 54 additions & 2 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -223,18 +226,67 @@ 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(ctx context.Context, _, _ string) (net.Conn, error) {
d := &net.Dialer{}
return d.DialContext(ctx, "unix", socketPath)
}
addr = "http://unix" + requestPath
}

if *useProxyProto {
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
if socketPath != "" {
network = "unix"
addr = socketPath
}

conn, err := (&net.Dialer{}).DialContext(ctx, 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 remoteAddrTyped := remoteAddr.(type) {
case *net.TCPAddr:
if remoteAddrTyped.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,
}

// as we do not use any TLVs, header size should be pretty small, hence we only check for error, assuming the whole header went out in a single packet
_, 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)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/nginx/nginx-plus-go-client/v3 v3.0.0
github.com/prometheus/client_golang v1.23.0
github.com/pires/go-proxyproto v0.8.1
github.com/prometheus/common v0.65.0
github.com/prometheus/exporter-toolkit v0.14.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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/v3 v3.0.0 h1:toZ6X4+4zhUkifYvUdiAaAe4tlq+Y7c03Ns4zb/2ytY=
github.com/nginx/nginx-plus-go-client/v3 v3.0.0/go.mod h1:sCHx+oXai55zsoco5IKSIsVoDH6dEAMtlw9qjkJ9gFM=
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.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
Expand Down