-
Notifications
You must be signed in to change notification settings - Fork 0
/
hosts.go
133 lines (119 loc) · 2.91 KB
/
hosts.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
// Copyright(C) 2021 github.com/fsgo All Rights Reserved.
// Author: fsgo
// Date: 2021/10/30
package fsdns
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"os"
"strings"
"sync"
"github.com/fsgo/fsgo/fsfs"
"github.com/fsgo/fsgo/fsnet/fsip"
"github.com/fsgo/fsgo/fsnet/fsresolver"
)
// DefaultHostsFile os default hosts file
var DefaultHostsFile fsresolver.LookupIPer = NewHostsFile("")
// LookupIPFromHosts find domain in hosts file
func LookupIPFromHosts(ctx context.Context, network, host string) ([]net.IP, error) {
return DefaultHostsFile.LookupIP(ctx, network, host)
}
// NewHostsFile create new HostsFile instance
//
// if start fail,then panic
// already start watch the hostPath
// when hostPath is empty, use the default path:
// on unix like system,the default path is /etc/hosts.
// the default hostsPath can set by Environment Variables 'FSGO_HOSTS',
// eg: export FSGO_HOSTS=./my_hosts
func NewHostsFile(hostsPath string) *HostsFile {
hf := &HostsFile{}
hf.FileName = hf.getPath(hostsPath)
hf.Parser = hf.parse
if err := hf.Start(); err != nil {
panic(err)
}
return hf
}
// HostsFile hosts file parser
type HostsFile struct {
domains map[string][]net.IP
fsfs.WatchFile
mux sync.RWMutex
}
func (hf *HostsFile) getPath(fileName string) string {
if len(fileName) == 0 {
return getDefaultHostsPath()
}
return fileName
}
func getDefaultHostsPath() string {
hostPath := os.Getenv("FSGO_HOSTS")
if len(hostPath) == 0 {
// todo other system,eg windows
return "/etc/hosts"
}
return hostPath
}
func (hf *HostsFile) parse(content []byte) error {
domains := ParseHosts(content)
hf.mux.Lock()
hf.domains = domains
hf.mux.Unlock()
return nil
}
var errNotFoundInHosts = errors.New("not found in hosts")
// LookupIP lookup ip from hosts file
func (hf *HostsFile) LookupIP(_ context.Context, network, host string) ([]net.IP, error) {
host = strings.ToLower(host)
hf.mux.RLock()
defer hf.mux.RUnlock()
if len(hf.domains) == 0 {
return nil, errNotFoundInHosts
}
ips := hf.domains[host]
if len(ips) == 0 {
return nil, errNotFoundInHosts
}
switch network {
case "ip":
return ips, nil
case "ip4":
ips = fsip.FilterList(ips, fsip.IsIPv4only)
case "ip6":
ips = fsip.FilterList(ips, fsip.IsIPv6only)
default:
return nil, fmt.Errorf("not support network=%q", network)
}
if len(ips) == 0 {
return nil, errNotFoundInHosts
}
return ips, nil
}
// ParseHosts hosts content
func ParseHosts(content []byte) map[string][]net.IP {
lines := bytes.Split(content, []byte("\n"))
domains := make(map[string][]net.IP)
for _, line := range lines {
line = bytes.TrimSpace(line)
if len(line) == 0 || line[0] == '#' {
continue
}
line = bytes.ToLower(line)
fields := strings.Fields(string(line))
if len(fields) < 2 { // 异常数据
continue
}
ip := net.ParseIP(fields[0])
if ip == nil {
continue
}
for _, h := range fields[1:] {
domains[h] = append(domains[h], ip)
}
}
return domains
}