@@ -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+
1833type 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
4156type 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
5368func (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
7083type 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
104140func isSoftError (err error , ss ... string ) bool {
0 commit comments