This repository has been archived by the owner on Apr 23, 2024. It is now read-only.
/
dialer.go
139 lines (112 loc) · 2.58 KB
/
dialer.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
package srvfunc
import (
"bufio"
"bytes"
"context"
"io"
"log"
"net"
"os"
"sync"
)
var (
emptyDialer = &net.Dialer{}
hostsCache = struct {
m map[string]string // hostname => ip
sync.RWMutex
}{
m: make(map[string]string),
}
)
func skipLongLine(rd *bufio.Reader) (eof bool) {
for {
_, isPrefix, err := rd.ReadLine()
if err == io.EOF {
return true
} else if err != nil {
log.Printf("Error while parsing /etc/hosts: %s", err.Error())
return true
}
if !isPrefix {
break
}
}
return false
}
func etcHostsLookup(hostname string) string {
hostsCache.RLock()
ip, ok := hostsCache.m[hostname]
hostsCache.RUnlock()
if ok {
return ip
}
hostsCache.Lock()
ip, ok = hostsCache.m[hostname]
if ok {
hostsCache.Unlock()
return ip
}
defer hostsCache.Unlock()
fp, err := os.Open("/etc/hosts")
if err != nil {
log.Printf("Could not open /etc/hosts: %s", err.Error())
return ""
}
defer fp.Close()
hostnameBytes := []byte(hostname)
rd := bufio.NewReader(fp)
for {
ln, isPrefix, err := rd.ReadLine()
if err == io.EOF {
break
} else if err != nil {
log.Printf("Error while reading /etc/hosts: %s", err.Error())
return ""
}
// do not attempt to parse long lines, we should not really have them in /etc/hosts
if isPrefix {
if eof := skipLongLine(rd); eof {
return ""
}
}
if len(ln) <= 1 || ln[0] == '#' {
continue
}
if !bytes.Contains(ln, hostnameBytes) {
continue
}
// 127.0.0.1 localhost loclahost loclhsot lolcahost
fields := bytes.Fields(ln)
if len(fields) <= 1 {
continue
}
// ensure that it is IPv4 address because we do not support dual stack in this resolver anyway
if ip := net.ParseIP(string(fields[0])); ip == nil || ip.To4() == nil {
continue
}
for _, f := range fields[1:] {
if bytes.Equal(hostnameBytes, f) {
return string(fields[0])
}
}
}
return ""
}
// CachingDialer should be used as DialContext function in http.Transport to speed up DNS resolution dramatically.
func CachingDialer(ctx context.Context, network, addr string) (net.Conn, error) {
if network != "tcp" && network != "udp" {
return emptyDialer.DialContext(ctx, network, addr)
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
// check if it already is an IP address, no need to resolve in this case
if ip := net.ParseIP(host); ip != nil {
return emptyDialer.DialContext(ctx, network, addr)
}
if hostIP := etcHostsLookup(host); hostIP != "" {
return emptyDialer.DialContext(ctx, network, hostIP+":"+port)
}
return emptyDialer.DialContext(ctx, network, addr)
}