-
Notifications
You must be signed in to change notification settings - Fork 50
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
Amir's Changes to Test Connectivity #275
base: main
Are you sure you want to change the base?
Changes from all commits
d7ed47d
089afc6
01d1495
7c69351
bf25e49
37cc3c5
404ef4f
0c6446d
2af18be
0aa4c43
1d017b7
291c376
5443859
47395ba
c486f24
b4fb6bc
866283a
7a93b7e
f0147d2
7fc9aa6
f3c2df3
ac76052
bc74500
bce576c
3f6b1a7
93533ed
0f1439a
8b3127e
204175f
ae97fd7
83ee12e
1117beb
e82c72d
d34c88f
395a8df
5739b47
18266bc
ffffa13
0e6f64c
9d20f52
c2c4354
d87f98f
ad1c930
371b31a
a6b4c31
7155047
e7e3407
69e426f
3d25b66
f23e8cf
50a1b7c
d3ddfd1
37db84b
9f80296
5239364
d5d3070
1f4f317
c2f6282
58e09cf
34e50b8
db46d9f
6eea6fb
20268ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,13 +24,15 @@ import ( | |
"log" | ||
"net" | ||
"net/http" | ||
"net/http/httptrace" | ||
"net/url" | ||
"os" | ||
"path" | ||
"strings" | ||
"time" | ||
|
||
"github.com/Jigsaw-Code/outline-sdk/dns" | ||
"github.com/Jigsaw-Code/outline-sdk/transport" | ||
"github.com/Jigsaw-Code/outline-sdk/x/config" | ||
"github.com/Jigsaw-Code/outline-sdk/x/connectivity" | ||
"github.com/Jigsaw-Code/outline-sdk/x/report" | ||
|
@@ -41,6 +43,12 @@ var debugLog log.Logger = *log.New(io.Discard, "", 0) | |
// var errorLog log.Logger = *log.New(os.Stderr, "[ERROR] ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) | ||
|
||
type connectivityReport struct { | ||
Test testReport `json:"test"` | ||
DNSQueries []dnsReport `json:"dns_queries,omitempty"` | ||
TCPConnections []tcpReport `json:"tcp_connections,omitempty"` | ||
} | ||
|
||
type testReport struct { | ||
// Inputs | ||
Resolver string `json:"resolver"` | ||
Proto string `json:"proto"` | ||
|
@@ -53,6 +61,23 @@ type connectivityReport struct { | |
Error *errorJSON `json:"error"` | ||
} | ||
|
||
type dnsReport struct { | ||
QueryName string `json:"query_name"` | ||
Time time.Time `json:"time"` | ||
DurationMs int64 `json:"duration_ms"` | ||
AnswerIPs []string `json:"answer_ips"` | ||
Error string `json:"error"` | ||
} | ||
|
||
type tcpReport struct { | ||
Hostname string `json:"hostname"` | ||
IP string `json:"ip"` | ||
Port string `json:"port"` | ||
Error string `json:"error"` | ||
Time time.Time `json:"time"` | ||
DurationMs int64 `json:"duration_ms"` | ||
} | ||
|
||
type errorJSON struct { | ||
// TODO: add Shadowsocks/Transport error | ||
Op string `json:"op,omitempty"` | ||
|
@@ -84,7 +109,7 @@ func unwrapAll(err error) error { | |
} | ||
|
||
func (r connectivityReport) IsSuccess() bool { | ||
if r.Error == nil { | ||
if r.Test.Error == nil { | ||
return true | ||
} else { | ||
return false | ||
|
@@ -98,6 +123,55 @@ func init() { | |
} | ||
} | ||
|
||
func getReportFromTrace(ctx context.Context, r *connectivityReport, hostname string) context.Context { | ||
var dnsStart, connectStart time.Time | ||
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ | ||
DNSStart: func(di httptrace.DNSStartInfo) { | ||
dnsStart = time.Now() | ||
}, | ||
DNSDone: func(di httptrace.DNSDoneInfo) { | ||
report := dnsReport{ | ||
QueryName: hostname, | ||
Time: dnsStart.UTC().Truncate(time.Second), | ||
DurationMs: time.Since(dnsStart).Milliseconds(), | ||
} | ||
if di.Err != nil { | ||
report.Error = di.Err.Error() | ||
} | ||
for _, ip := range di.Addrs { | ||
report.AnswerIPs = append(report.AnswerIPs, ip.IP.String()) | ||
} | ||
// TODO(fortuna): Use a Mutex. | ||
r.DNSQueries = append(r.DNSQueries, report) | ||
}, | ||
ConnectStart: func(network, addr string) { | ||
connectStart = time.Now() | ||
}, | ||
ConnectDone: func(network, addr string, connErr error) { | ||
ip, port, err := net.SplitHostPort(addr) | ||
if err != nil { | ||
return | ||
} | ||
if network == "tcp" { | ||
report := tcpReport{ | ||
Hostname: hostname, | ||
IP: ip, | ||
Port: port, | ||
Time: connectStart.UTC().Truncate(time.Second), | ||
DurationMs: time.Since(connectStart).Milliseconds(), | ||
} | ||
if connErr != nil { | ||
report.Error = connErr.Error() | ||
} | ||
// TODO(fortuna): Use a Mutex. | ||
r.TCPConnections = append(r.TCPConnections, report) | ||
} | ||
}, | ||
}) | ||
|
||
return ctx | ||
} | ||
|
||
func main() { | ||
verboseFlag := flag.Bool("v", false, "Enable debug output") | ||
transportFlag := flag.String("transport", "", "Transport config") | ||
|
@@ -161,21 +235,44 @@ func main() { | |
success := false | ||
jsonEncoder := json.NewEncoder(os.Stdout) | ||
jsonEncoder.SetEscapeHTML(false) | ||
configToDialer := config.NewDefaultConfigToDialer() | ||
for _, resolverHost := range strings.Split(*resolverFlag, ",") { | ||
resolverHost := strings.TrimSpace(resolverHost) | ||
resolverAddress := net.JoinHostPort(resolverHost, "53") | ||
for _, proto := range strings.Split(*protoFlag, ",") { | ||
r := &connectivityReport{ | ||
Test: testReport{}, | ||
DNSQueries: []dnsReport{}, | ||
TCPConnections: []tcpReport{}, | ||
} | ||
proto = strings.TrimSpace(proto) | ||
var resolver dns.Resolver | ||
switch proto { | ||
case "tcp": | ||
configToDialer := config.NewDefaultConfigToDialer() | ||
configToDialer.BaseStreamDialer = transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { | ||
hostname, _, err := net.SplitHostPort(addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ctx = getReportFromTrace(ctx, r, hostname) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both of our codes have a race condition. We need a mutex to append to the DNS queries and TCP connections. I was imagining a different function here, one that takes callbacks with the dns and tcp reports instead, and the function can then append to whatever we need. That way the function doesn't depend on the specific report format. BTW, it shouldn't depend on r, because the scope is too broad. It only needs the dns and tcp reports. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fortuna yes the race condition issue stands. I was planning to do that next. I can reduce the dependency on the report format |
||
return (&transport.TCPDialer{}).DialStream(ctx, addr) | ||
}) | ||
streamDialer, err := configToDialer.NewStreamDialer(*transportFlag) | ||
if err != nil { | ||
log.Fatalf("Failed to create StreamDialer: %v", err) | ||
} | ||
resolver = dns.NewTCPResolver(streamDialer, resolverAddress) | ||
|
||
case "udp": | ||
configToDialer := config.NewDefaultConfigToDialer() | ||
configToDialer.BasePacketDialer = transport.FuncPacketDialer(func(ctx context.Context, addr string) (net.Conn, error) { | ||
hostname, _, err := net.SplitHostPort(addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
ctx = getReportFromTrace(ctx, r, hostname) | ||
return (&transport.UDPDialer{}).DialPacket(ctx, addr) | ||
}) | ||
packetDialer, err := configToDialer.NewPacketDialer(*transportFlag) | ||
if err != nil { | ||
log.Fatalf("Failed to create PacketDialer: %v", err) | ||
|
@@ -198,7 +295,8 @@ func main() { | |
if err != nil { | ||
log.Fatalf("Failed to sanitize config: %v", err) | ||
} | ||
var r report.Report = connectivityReport{ | ||
|
||
r.Test = testReport{ | ||
Resolver: resolverAddress, | ||
Proto: proto, | ||
Time: startTime.UTC().Truncate(time.Second), | ||
|
@@ -207,6 +305,7 @@ func main() { | |
DurationMs: testDuration.Milliseconds(), | ||
Error: makeErrorRecord(result), | ||
} | ||
|
||
if reportCollector != nil { | ||
err = reportCollector.Collect(context.Background(), r) | ||
if err != nil { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the correct behavior. In Happy Eyeballs we can have overlapping connection attempts.
We need a map network, addr -> start time instead.