Skip to content

Commit 1da1aa3

Browse files
authored
Add proxy protocol v2 client-side support (#979)
1 parent 3705ac4 commit 1da1aa3

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ Flags:
180180
--nginx.ssl-client-cert=""
181181
Path to the PEM encoded client certificate file to use when connecting to the server. ($SSL_CLIENT_CERT)
182182
--nginx.ssl-client-key="" Path to the PEM encoded client certificate key file to use when connecting to the server. ($SSL_CLIENT_KEY)
183+
--[no-]nginx.proxy-protocol
184+
Pass proxy protocol payload to nginx listeners. ($PROXY_PROTOCOL)
183185
--nginx.timeout=5s A timeout for scraping metrics from NGINX or NGINX Plus. ($TIMEOUT)
184186
--prometheus.const-label=PROMETHEUS.CONST-LABEL ...
185187
Label that will be used in every metric. Format is label=value. It can be repeated multiple times. ($CONST_LABELS)

exporter.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030

3131
"github.com/prometheus/exporter-toolkit/web"
3232
"github.com/prometheus/exporter-toolkit/web/kingpinflag"
33+
34+
proxyproto "github.com/pires/go-proxyproto"
3335
)
3436

3537
// positiveDuration is a wrapper of time.Duration to ensure only positive values are accepted.
@@ -90,6 +92,7 @@ var (
9092
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()
9193
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()
9294
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()
95+
useProxyProto = kingpin.Flag("nginx.proxy-protocol", "Pass proxy protocol payload to nginx listeners.").Default("false").Envar("PROXY_PROTOCOL").Bool()
9396

9497
// Custom command-line flags.
9598
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,18 +226,68 @@ func main() {
223226
func registerCollector(logger *slog.Logger, transport *http.Transport,
224227
addr string, labels map[string]string,
225228
) {
229+
var socketPath string
230+
226231
if strings.HasPrefix(addr, "unix:") {
227-
socketPath, requestPath, err := parseUnixSocketAddress(addr)
232+
var err error
233+
var requestPath string
234+
socketPath, requestPath, err = parseUnixSocketAddress(addr)
228235
if err != nil {
229236
logger.Error("parsing unix domain socket scrape address failed", "uri", addr, "error", err.Error())
230237
os.Exit(1)
231238
}
239+
addr = "http://unix" + requestPath
240+
}
232241

242+
if !*useProxyProto && socketPath != "" {
233243
transport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
234244
d := &net.Dialer{}
235245
return d.DialContext(ctx, "unix", socketPath)
236246
}
237-
addr = "http://unix" + requestPath
247+
}
248+
249+
if *useProxyProto {
250+
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
251+
if socketPath != "" {
252+
network = "unix"
253+
addr = socketPath
254+
}
255+
256+
conn, err := (&net.Dialer{}).DialContext(ctx, network, addr)
257+
if err != nil {
258+
return nil, fmt.Errorf("dialing %s %s: %w", network, addr, err)
259+
}
260+
261+
localAddr := conn.LocalAddr()
262+
remoteAddr := conn.RemoteAddr()
263+
transportProtocol := proxyproto.TCPv4
264+
265+
switch remoteAddrTyped := remoteAddr.(type) {
266+
case *net.TCPAddr:
267+
if remoteAddrTyped.IP.To4() == nil {
268+
transportProtocol = proxyproto.TCPv6
269+
}
270+
case *net.UnixAddr:
271+
transportProtocol = proxyproto.UnixStream
272+
}
273+
274+
header := &proxyproto.Header{
275+
Version: 2,
276+
Command: proxyproto.PROXY,
277+
TransportProtocol: transportProtocol,
278+
SourceAddr: localAddr,
279+
DestinationAddr: remoteAddr,
280+
}
281+
282+
// 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
283+
_, err = header.WriteTo(conn)
284+
if err == nil {
285+
conn.Close()
286+
return nil, fmt.Errorf("writing proxyproto header via %s to %s: %w", network, addr, err)
287+
}
288+
289+
return conn, nil
290+
}
238291
}
239292

240293
userAgent := fmt.Sprintf("NGINX-Prometheus-Exporter/v%v", common_version.Version)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/alecthomas/kingpin/v2 v2.4.0
77
github.com/nginx/nginx-plus-go-client/v3 v3.0.0
88
github.com/prometheus/client_golang v1.23.0
9+
github.com/pires/go-proxyproto v0.8.1
910
github.com/prometheus/common v0.65.0
1011
github.com/prometheus/exporter-toolkit v0.14.0
1112
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+
3434
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
3535
github.com/nginx/nginx-plus-go-client/v3 v3.0.0 h1:toZ6X4+4zhUkifYvUdiAaAe4tlq+Y7c03Ns4zb/2ytY=
3636
github.com/nginx/nginx-plus-go-client/v3 v3.0.0/go.mod h1:sCHx+oXai55zsoco5IKSIsVoDH6dEAMtlw9qjkJ9gFM=
37+
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
38+
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
3739
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3840
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3941
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=

0 commit comments

Comments
 (0)