Skip to content

Commit 307c698

Browse files
committed
simulator: rework interface binding for DNS sims; introduce BindAddr
Extend how flightsim finds a usable (up until now, only external) IP. An external IP is favoured, however, allow flightsim to fallback to a local/internal IP if necessary. This will correctly show failures in IP simulations if a user specifies '-iface lo'. For DNS simulations using systemd's stub resolver, this will allow the simulation to succeed. Further, DNS simulations should use the interface obtained via a DNS probe, unless overridden via the -iface flag.
1 parent 376628d commit 307c698

File tree

13 files changed

+190
-75
lines changed

13 files changed

+190
-75
lines changed

cmd/run/print.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ func printMsg(s *Simulation, msg string) {
2121
fmt.Printf("%s [%s] %s\n", time.Now().Format("15:04:05"), s.Name(), msg)
2222
}
2323

24-
func printWelcome(ip string) {
24+
func printWelcome(ip, dnsIntfIP string) {
2525
fmt.Printf(`
2626
AlphaSOC Network Flight Simulator™ %s (https://github.com/alphasoc/flightsim)
27-
The IP address of the network interface is %s
27+
The address of the network interface for IP traffic is %s
28+
The address of the network interface for DNS queries is %s
2829
The current time is %s
29-
`, Version, ip, time.Now().Format("02-Jan-06 15:04:05"))
30+
`, Version, ip, dnsIntfIP, time.Now().Format("02-Jan-06 15:04:05"))
3031
}
3132

3233
func printGoodbye() {

cmd/run/run.go

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,14 @@ func RunCmd(args []string) error {
7474
if *size < 0 {
7575
*size = 0
7676
}
77-
78-
extIP, err := utils.ExternalIP(*ifaceName)
77+
// Grab a "usable" IP address for ifaceName.
78+
bindIP, err := utils.UsableIP(*ifaceName)
7979
if err != nil {
80-
return err
80+
return fmt.Errorf("Unable to determine usable IP address for '%v': %v", *ifaceName, err)
81+
}
82+
bind := simulator.BindAddr{Addr: bindIP}
83+
if *ifaceName != "" {
84+
bind.UserSet = true
8185
}
8286

8387
sims, err := selectSimulations(modules)
@@ -90,7 +94,7 @@ func RunCmd(args []string) error {
9094
// sims[i].Timeout = 100 * time.Millisecond
9195
// }
9296
// }
93-
return run(sims, extIP, *size)
97+
return run(sims, bind, *size)
9498
}
9599

96100
func selectSimulations(names []string) ([]*Simulation, error) {
@@ -322,15 +326,59 @@ const (
322326
msgPrefixErrorRecover = "FATAL: Module terminated: "
323327
)
324328

325-
func run(sims []*Simulation, extIP net.IP, size int) error {
326-
printWelcome(extIP.String())
329+
// getDefaultDNSIntf runs a DNS probe using default system resolver and returns the IP of
330+
// the interface used and an error. Thanks @tg.
331+
func getDefaultDNSIntf() (string, error) {
332+
timeout := 10 * time.Second
333+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
334+
defer cancel()
335+
var defaultDNSServer string
336+
r := &net.Resolver{
337+
PreferGo: true,
338+
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
339+
// we can capture the address here
340+
defaultDNSServer = address
341+
// still use the default dialer
342+
var d net.Dialer
343+
return d.DialContext(ctx, network, address)
344+
},
345+
}
346+
_, err := r.LookupHost(ctx, "alphasoc.com")
347+
if err != nil {
348+
return "", err
349+
}
350+
conn, err := net.DialTimeout("udp", defaultDNSServer, timeout)
351+
if err != nil {
352+
return "", err
353+
}
354+
dnsIntfIP, _, err := net.SplitHostPort(conn.LocalAddr().String())
355+
if err != nil {
356+
return "", err
357+
}
358+
return dnsIntfIP, nil
359+
}
360+
361+
func run(sims []*Simulation, bind simulator.BindAddr, size int) error {
362+
// If user override on iface, both IP and DNS traffic will flow through bind.Addr.
363+
// NOTE: not passing the DNS server to printWelcome(), as it may be confusing in cases
364+
// where there are multiple nameservers configured (ie. resolver errors will carry
365+
// the address of the last queried nameserver).
366+
defaultDNSIntfIP, err := getDefaultDNSIntf()
367+
if err != nil {
368+
return fmt.Errorf("Failed DNS probe: %v", err)
369+
}
370+
if bind.UserSet {
371+
printWelcome(bind.String(), bind.String())
372+
} else {
373+
printWelcome(bind.String(), defaultDNSIntfIP)
374+
}
327375
printHeader()
328376

329377
for simN, sim := range sims {
330378
fmt.Print("\n")
331379

332380
okHosts := 0
333-
err := sim.Init(extIP)
381+
err := sim.Init(bind)
334382
if err != nil {
335383
printMsg(sim, msgPrefixErrorInit+fmt.Sprint(err))
336384
} else {

simulator/dga.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package simulator
22

33
import (
44
"math/rand"
5-
"net"
65
"strconv"
76
"strings"
87

@@ -21,7 +20,7 @@ func NewDGA() *DGA {
2120
return &DGA{}
2221
}
2322

24-
func (s *DGA) Init(bind net.IP) error {
23+
func (s *DGA) Init(bind BindAddr) error {
2524
return s.DNSResolveSimulator.Init(bind)
2625
}
2726

simulator/hijack.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ func NewHijack() *Hijack {
1616
}
1717

1818
// Simulate port scanning for given host.
19-
func (*Hijack) Simulate(ctx context.Context, extIP net.IP, host string) error {
20-
d := &net.Dialer{
21-
LocalAddr: &net.UDPAddr{IP: extIP},
19+
func (*Hijack) Simulate(ctx context.Context, bind BindAddr, host string) error {
20+
d := &net.Dialer{}
21+
// Set the user overridden bind iface.
22+
if bind.UserSet {
23+
d.LocalAddr = &net.UDPAddr{IP: bind.Addr}
2224
}
2325
r := &net.Resolver{
2426
PreferGo: true,

simulator/miner.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ const miningSubscribeBody string = `{"jsonrpc": "2.0", "id": 1, "method": "minin
1010

1111
//StratumMiner simulator
1212
type StratumMiner struct {
13-
bind net.IP
13+
bind BindAddr
1414
}
1515

1616
//NewStratumMiner creates new StratumMiner simulator
1717
func NewStratumMiner() *StratumMiner {
1818
return &StratumMiner{}
1919
}
2020

21-
func (s *StratumMiner) Init(bind net.IP) error {
21+
func (s *StratumMiner) Init(bind BindAddr) error {
2222
s.bind = bind
2323
return nil
2424
}
@@ -28,10 +28,7 @@ func (StratumMiner) Cleanup() {
2828

2929
//Simulate connection to mining pool using Stratum protocol
3030
func (s *StratumMiner) Simulate(ctx context.Context, dst string) error {
31-
d := &net.Dialer{}
32-
if s.bind != nil {
33-
d.LocalAddr = &net.TCPAddr{IP: s.bind}
34-
}
31+
d := &net.Dialer{LocalAddr: &net.TCPAddr{IP: s.bind.Addr}}
3532
conn, err := d.DialContext(ctx, "tcp", dst)
3633
if conn != nil {
3734
deadline, _ := ctx.Deadline()

simulator/scan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func NewPortScan() *PortScan {
6161
return &PortScan{}
6262
}
6363

64-
func (s *PortScan) Init(bind net.IP) error {
64+
func (s *PortScan) Init(bind BindAddr) error {
6565
return s.tcp.Init(bind)
6666
}
6767

simulator/simulator.go

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,23 @@ type HostMsgFormatter interface {
1515
HostMsg(host string) string
1616
}
1717

18+
// BindAddr wraps addr along with whether it was set by the user. The UserSet flag is
19+
// solely used by PipelineDNS simulators that specify their own dialer+LocalAddr. In such
20+
// cases, the usable/external IP is not always the correct choice (ie. on systems using
21+
// systemd's stub resolver running on 127.0.0.53:53), so explicitly setting the LocalAddr
22+
// is done only if the user has supplied an interface/address via flightsim's `-iface` flag.
23+
type BindAddr struct {
24+
Addr net.IP
25+
UserSet bool
26+
}
27+
28+
// String returns the string representation of the underlying address.
29+
func (b *BindAddr) String() string {
30+
return b.Addr.String()
31+
}
32+
1833
type Simulator interface {
19-
Init(bind net.IP) error
34+
Init(bind BindAddr) error
2035
Simulate(ctx context.Context, host string) error
2136
Cleanup()
2237
}
@@ -39,10 +54,10 @@ func CreateModule(src HostSource, sim Simulator) Module {
3954
}
4055

4156
type TCPConnectSimulator struct {
42-
bind net.IP
57+
bind BindAddr
4358
}
4459

45-
func (s *TCPConnectSimulator) Init(bind net.IP) error {
60+
func (s *TCPConnectSimulator) Init(bind BindAddr) error {
4661
s.bind = bind
4762
return nil
4863
}
@@ -51,27 +66,25 @@ func (TCPConnectSimulator) Cleanup() {
5166
}
5267

5368
func (s *TCPConnectSimulator) Simulate(ctx context.Context, dst string) error {
54-
d := &net.Dialer{}
55-
if s.bind != nil {
56-
d.LocalAddr = &net.TCPAddr{IP: s.bind}
57-
}
69+
d := &net.Dialer{LocalAddr: &net.TCPAddr{IP: s.bind.Addr}}
5870

5971
conn, err := d.DialContext(ctx, "tcp", dst)
6072
if conn != nil {
6173
conn.Close()
6274
}
63-
64-
if isSoftError(err, "connect: connection refused") {
65-
return nil
75+
// This will likely generate some superfluous io-timeout error messages, but if the
76+
// user specifies a misconfigured interface, they'll see the simulation failing.
77+
if err != nil && (!isSoftError(err, "connect: connection refused") || isDialError(err)) {
78+
return err
6679
}
67-
return err
80+
return nil
6881
}
6982

7083
type DNSResolveSimulator struct {
71-
bind net.IP
84+
bind BindAddr
7285
}
7386

74-
func (s *DNSResolveSimulator) Init(bind net.IP) error {
87+
func (s *DNSResolveSimulator) Init(bind BindAddr) error {
7588
s.bind = bind
7689
return nil
7790
}
@@ -86,19 +99,42 @@ func (s *DNSResolveSimulator) Simulate(ctx context.Context, dst string) error {
8699
}
87100

88101
d := &net.Dialer{}
89-
if s.bind != nil {
90-
d.LocalAddr = &net.UDPAddr{IP: s.bind}
102+
// Set the user overridden bind iface.
103+
if s.bind.UserSet {
104+
d.LocalAddr = &net.UDPAddr{IP: s.bind.Addr}
91105
}
92106
r := &net.Resolver{
93107
PreferGo: true,
94108
Dial: d.DialContext,
95109
}
96-
_, err := r.LookupHost(ctx, utils.FQDN(host))
97110

98-
if isSoftError(err, "no such host") {
99-
return nil
111+
host = utils.FQDN(host)
112+
113+
_, err := r.LookupHost(ctx, host)
114+
// Ignore "no such host". Will ignore timeouts as well, so check for dial errors.
115+
if err != nil && (!isSoftError(err, "no such host") || isDialError(err)) {
116+
return err
117+
}
118+
119+
return nil
120+
}
121+
122+
// isDialError scans an error message for signs of a dial error, returning a boolean.
123+
func isDialError(err error) bool {
124+
// Errors we're after are of the form:
125+
// lookup abc.sandbox.alphasoc.xyz. on A.B.C.D:53: dial udp E.F.G.H:0->A.B.C.D:53: i/o timeout
126+
// TODO: something more robust? I don't want to double-dial though.
127+
return isTimeout(err) &&
128+
strings.Contains(err.Error(), "dial") &&
129+
strings.Count(err.Error(), "->") == 1
130+
}
131+
132+
func isTimeout(err error) bool {
133+
netErr, ok := err.(net.Error)
134+
if !ok {
135+
return false
100136
}
101-
return err
137+
return netErr.Timeout()
102138
}
103139

104140
func isSoftError(err error, ss ...string) bool {

simulator/spambot.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func NewSpambot() *Spambot {
6969
return &Spambot{}
7070
}
7171

72-
func (s *Spambot) Init(bind net.IP) error {
72+
func (s *Spambot) Init(bind BindAddr) error {
7373
return s.TCPConnectSimulator.Init(bind)
7474
}
7575

@@ -88,8 +88,13 @@ func (s *Spambot) Hosts(scope string, size int) ([]string, error) {
8888

8989
for n := 0; len(hosts) < size && n < len(idx); n++ {
9090
ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond)
91-
mx, _ := rv.LookupMX(ctx, utils.FQDN(domains[idx[n]]))
91+
host := utils.FQDN(domains[idx[n]])
92+
mx, err := rv.LookupMX(ctx, host)
9293
cancel()
94+
// Check error message for sign of resolver/routing issue.
95+
if err != nil && isDialError(err) {
96+
return hosts, err
97+
}
9398
if len(mx) > 0 {
9499
host := strings.TrimSuffix(mx[0].Host, ".")
95100
if !seen[host] {

simulator/ssh-transfer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ func (s *SSHTransfer) HostMsg(host string) string {
5151
}
5252

5353
// Init sets the source IP for this simulation.
54-
func (s *SSHTransfer) Init(src net.IP) error {
55-
s.src = src
54+
func (s *SSHTransfer) Init(src BindAddr) error {
55+
s.src = src.Addr
5656
return nil
5757
}
5858

simulator/tor.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"math/rand"
8-
"net"
98
"net/http"
109
"os"
1110
"os/exec"
@@ -37,7 +36,7 @@ func NewTorSimulator() *TorSimulator {
3736

3837
// Tor creates tor connector;
3938
// There is no way to pass the bind IP to tor, so we ignore it.
40-
func (t *TorSimulator) Init(_ net.IP) error {
39+
func (t *TorSimulator) Init(_ BindAddr) error {
4140
tor, err := tor.Start(nil, &tor.StartConf{
4241
TempDataDirBase: os.TempDir(),
4342
RetainTempDataDir: false,

0 commit comments

Comments
 (0)