Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow source_ip as a parameter #1099

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ modules:
# How long the probe will wait before giving up.
[ timeout: <duration> ]

# Dynamic probes. Allow to pass dynamic configuration as parameters
[ dynamic: <boolean> ]

# The specific probe configuration - at most one of these should be specified.
[ http: <http_probe> ]
[ tcp: <tcp_probe> ]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ scrape_configs:
- target_label: __address__
replacement: 127.0.0.1:9115 # The blackbox exporter's real hostname:port.
```
ICMP and TCP probes can accept an additional `source_ip` parameter that will set the source IP for those probes. This is useful for hosts with multiple interfaces in more than one subnet.

HTTP probes can accept an additional `hostname` parameter that will set `Host` header and TLS SNI. This can be especially useful with `dns_sd_config`:
```yaml
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ func MustNewRegexp(s string) Regexp {
type Module struct {
Prober string `yaml:"prober,omitempty"`
Timeout time.Duration `yaml:"timeout,omitempty"`
Dynamic bool `yaml:"allow_dynamic,omitempty"`
HTTP HTTPProbe `yaml:"http,omitempty"`
TCP TCPProbe `yaml:"tcp,omitempty"`
ICMP ICMPProbe `yaml:"icmp,omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions prober/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ func Handler(w http.ResponseWriter, r *http.Request, c *config.Config, logger lo
}
}

sourceIp := params.Get("source_ip")
if module.Dynamic && (module.Prober == "tcp" || module.Prober == "icmp") && sourceIp != "" {
module.TCP.SourceIPAddress = sourceIp
module.ICMP.SourceIPAddress = sourceIp
}

sl := newScrapeLogger(logger, moduleName, target)
level.Info(sl).Log("msg", "Beginning probe", "probe", module.Prober, "timeout_seconds", timeoutSeconds)

Expand Down
90 changes: 90 additions & 0 deletions prober/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,96 @@ func TestHostnameParam(t *testing.T) {
}
}

func TestSourceIpParam(t *testing.T) {

c := &config.Config{
Modules: map[string]config.Module{
"tcp": {
Dynamic: true,
Prober: "tcp",
Timeout: 10 * time.Second,
TCP: config.TCPProbe{
IPProtocol: "ip4",
},
},
},
}

// check the the source ip is at the origin of the TCP probe
source_ip := "8.8.8.8" // Needs to be a local IP address
local_ip := "127.0.0.1" // Needs to be a local IP address

// List on TCP socket
ln, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Fatalf("Error listening on socket: %s", err)
}
defer ln.Close()

ch := make(chan (net.Addr))
go func() {
conn, err := ln.Accept()
if err != nil {
panic(fmt.Sprintf("Error accepting on socket: %s", err))
}
ch <- conn.RemoteAddr()
conn.Close()
}()

rr := httptest.NewRecorder()

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Handler(w, r, c, log.NewNopLogger(), &ResultHistory{}, 0.5, nil, nil)
})

requrl := fmt.Sprintf("?debug=true&module=tcp&source_ip=%s&target=%s", source_ip, ln.Addr().String())
req, err := http.NewRequest("GET", requrl, nil)
if err != nil {
t.Fatal(err)
}

handler.ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusOK {
t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusOK)
}
// We need to make sure the probe did NOT succees, as we're setting a source IP that the host can't route from
if !strings.Contains(rr.Body.String(), "probe_success 0") {
t.Errorf("probe failed, response body: %v", rr.Body.String())
}

tcpModule := c.Modules["tcp"]
tcpModule.TCP.SourceIPAddress = ""
tcpModule.Dynamic = false
c.Modules["tcp"] = tcpModule

handler.ServeHTTP(rr, req)

timer := time.NewTimer(5 * time.Second)
select {
case receivedAddr := <-ch:
ip, _, err := net.SplitHostPort(receivedAddr.String())
if err != nil {
t.Fatalf("Error splitting host and port: %s", err)
}
if ip != local_ip {
t.Errorf("Unexpected source IP: expected %q, got %q.", local_ip, ip)
}

case <-timer.C:
t.Errorf("Timeout waiting for TCP connection")
}

if status := rr.Code; status != http.StatusOK {
t.Errorf("probe request handler returned wrong status code: %v, want %v", status, http.StatusOK)
}
// We need to make sure the probe did NOT succees, as we're setting a source IP that the host can't route from
if !strings.Contains(rr.Body.String(), "probe_success 1") {
t.Errorf("probe failed, response body: %v", rr.Body.String())
}

}

func TestTCPHostnameParam(t *testing.T) {
c := &config.Config{
Modules: map[string]config.Module{
Expand Down