-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathprotocol.go
140 lines (128 loc) · 3.2 KB
/
protocol.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package internal
import (
"context"
"fmt"
"net"
"net/http"
"time"
)
// Default timeout for the probes.
const timeout = 5 * time.Second
// Protocol defines a probe attempt.
type Protocol interface {
// Returns the identifier.
// Example: "http".
String() string
// Attempt to check the connectivity to the target.
// The target depends on the protocol. For example, for HTTP it's a URL.
// Returns the used target or error if the attempt failed. Some protocols
// include an additional string with extra information. For example, the
// HTTP protocol returns the status code.
Probe(target string) (string, string, error)
}
// HTTP protocol implementation.
type HTTP struct {
Timeout time.Duration
}
// String returns the identifier of the protocol.
func (h *HTTP) String() string {
return "http"
}
// Probe makes an HTTP request to a random captive portal.
//
// The target is a URL.
// The extra data is the status code.
func (h *HTTP) Probe(target string) (string, string, error) {
cli := &http.Client{Timeout: h.Timeout}
url := target
if url == "" {
var err error
url, err = RandomCaptivePortal()
if err != nil {
return "", "", fmt.Errorf("selecting captive portal: %w", err)
}
}
resp, err := cli.Get(url)
if err != nil {
return "", "", err
}
err = resp.Body.Close()
if err != nil {
return "", "", fmt.Errorf("closing response body: %w", err)
}
return url, resp.Status, nil
}
// TCP protocol implementation.
type TCP struct {
Timeout time.Duration
}
// String returns the identifier of the protocol.
func (t *TCP) String() string {
return "tcp"
}
// Probe makes a TCP request to a random server.
//
// The target is a host:port.
// The extra data is the local interface.
func (t *TCP) Probe(target string) (string, string, error) {
hostPort := target
if hostPort == "" {
var err error
hostPort, err = RandomTCPServer()
if err != nil {
return "", "", fmt.Errorf("selecting TCP server: %w", err)
}
}
conn, err := net.DialTimeout("tcp", hostPort, t.Timeout)
if err != nil {
return "", "", err
}
err = conn.Close()
if err != nil {
return "", "", fmt.Errorf("closing connection: %w", err)
}
return hostPort, conn.LocalAddr().String(), nil
}
// DNS protocol implementation.
type DNS struct {
Timeout time.Duration
// Custom DNS resolver.
Resolver string
}
// String returns the identifier of the protocol.
func (d *DNS) String() string {
return "dns"
}
// Probe resolves a random domain name.
//
// The target is a domain name.
// The extra data is the first resolved IP address.
func (d *DNS) Probe(target string) (string, string, error) {
var r net.Resolver
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
if d.Resolver != "" {
r.PreferGo = true
r.Dial = func(ctx context.Context, network, address string) (
net.Conn, error,
) {
nd := net.Dialer{Timeout: d.Timeout}
return nd.DialContext(ctx, network, fmt.Sprintf(
"%s:%s", d.Resolver, "53",
))
}
}
domain := target
if domain == "" {
var err error
domain, err = RandomDomain()
if err != nil {
return "", "", fmt.Errorf("selecting domain: %w", err)
}
}
addrs, err := r.LookupHost(ctx, domain)
if err != nil {
return "", "", err
}
return domain, addrs[0], nil
}