Pull request: * all: move internal Go packages to internal/
Merge in DNS/adguard-home from 2234-move-to-internal to master Squashed commit of the following: commit d26a288cabeac86f9483fab307677b1027c78524 Author: Eugene Burkov <e.burkov@adguard.com> Date: Fri Oct 30 12:44:18 2020 +0300 * all: move internal Go packages to internal/ Closes #2234.
This commit is contained in:
337
internal/util/auto_hosts.go
Normal file
337
internal/util/auto_hosts.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
type onChangedT func()
|
||||
|
||||
// AutoHosts - automatic DNS records
|
||||
type AutoHosts struct {
|
||||
lock sync.Mutex // serialize access to table
|
||||
table map[string][]net.IP // 'hostname -> IP' table
|
||||
tableReverse map[string]string // "IP -> hostname" table for reverse lookup
|
||||
|
||||
hostsFn string // path to the main hosts-file
|
||||
hostsDirs []string // paths to OS-specific directories with hosts-files
|
||||
watcher *fsnotify.Watcher // file and directory watcher object
|
||||
updateChan chan bool // signal for 'updateLoop' goroutine
|
||||
|
||||
onChanged onChangedT // notification to other modules
|
||||
}
|
||||
|
||||
// SetOnChanged - set callback function that will be called when the data is changed
|
||||
func (a *AutoHosts) SetOnChanged(onChanged onChangedT) {
|
||||
a.onChanged = onChanged
|
||||
}
|
||||
|
||||
// Notify other modules
|
||||
func (a *AutoHosts) notify() {
|
||||
if a.onChanged == nil {
|
||||
return
|
||||
}
|
||||
a.onChanged()
|
||||
}
|
||||
|
||||
// Init - initialize
|
||||
// hostsFn: Override default name for the hosts-file (optional)
|
||||
func (a *AutoHosts) Init(hostsFn string) {
|
||||
a.table = make(map[string][]net.IP)
|
||||
a.updateChan = make(chan bool, 2)
|
||||
|
||||
a.hostsFn = "/etc/hosts"
|
||||
if runtime.GOOS == "windows" {
|
||||
a.hostsFn = os.ExpandEnv("$SystemRoot\\system32\\drivers\\etc\\hosts")
|
||||
}
|
||||
if len(hostsFn) != 0 {
|
||||
a.hostsFn = hostsFn
|
||||
}
|
||||
|
||||
if IsOpenWrt() {
|
||||
a.hostsDirs = append(a.hostsDirs, "/tmp/hosts") // OpenWRT: "/tmp/hosts/dhcp.cfg01411c"
|
||||
}
|
||||
|
||||
// Load hosts initially
|
||||
a.updateHosts()
|
||||
|
||||
var err error
|
||||
a.watcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Error("AutoHosts: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Start - start module
|
||||
func (a *AutoHosts) Start() {
|
||||
log.Debug("Start AutoHosts module")
|
||||
|
||||
go a.updateLoop()
|
||||
a.updateChan <- true
|
||||
|
||||
if a.watcher != nil {
|
||||
go a.watcherLoop()
|
||||
|
||||
err := a.watcher.Add(a.hostsFn)
|
||||
if err != nil {
|
||||
log.Error("Error while initializing watcher for a file %s: %s", a.hostsFn, err)
|
||||
}
|
||||
|
||||
for _, dir := range a.hostsDirs {
|
||||
err = a.watcher.Add(dir)
|
||||
if err != nil {
|
||||
log.Error("Error while initializing watcher for a directory %s: %s", dir, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close - close module
|
||||
func (a *AutoHosts) Close() {
|
||||
a.updateChan <- false
|
||||
close(a.updateChan)
|
||||
if a.watcher != nil {
|
||||
_ = a.watcher.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Process - get the list of IP addresses for the hostname
|
||||
// Return nil if not found
|
||||
func (a *AutoHosts) Process(host string, qtype uint16) []net.IP {
|
||||
if qtype == dns.TypePTR {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ipsCopy []net.IP
|
||||
a.lock.Lock()
|
||||
ips, _ := a.table[host]
|
||||
if len(ips) != 0 {
|
||||
ipsCopy = make([]net.IP, len(ips))
|
||||
copy(ipsCopy, ips)
|
||||
}
|
||||
a.lock.Unlock()
|
||||
|
||||
log.Debug("AutoHosts: answer: %s -> %v", host, ipsCopy)
|
||||
return ipsCopy
|
||||
}
|
||||
|
||||
// ProcessReverse - process PTR request
|
||||
// Return "" if not found or an error occurred
|
||||
func (a *AutoHosts) ProcessReverse(addr string, qtype uint16) string {
|
||||
if qtype != dns.TypePTR {
|
||||
return ""
|
||||
}
|
||||
|
||||
ipReal := DNSUnreverseAddr(addr)
|
||||
if ipReal == nil {
|
||||
return "" // invalid IP in question
|
||||
}
|
||||
ipStr := ipReal.String()
|
||||
|
||||
a.lock.Lock()
|
||||
host := a.tableReverse[ipStr]
|
||||
a.lock.Unlock()
|
||||
|
||||
if len(host) == 0 {
|
||||
return "" // not found
|
||||
}
|
||||
|
||||
log.Debug("AutoHosts: reverse-lookup: %s -> %s", addr, host)
|
||||
return host
|
||||
}
|
||||
|
||||
// List - get "IP -> hostname" table. Thread-safe.
|
||||
func (a *AutoHosts) List() map[string]string {
|
||||
table := make(map[string]string)
|
||||
a.lock.Lock()
|
||||
for k, v := range a.tableReverse {
|
||||
table[k] = v
|
||||
}
|
||||
a.lock.Unlock()
|
||||
return table
|
||||
}
|
||||
|
||||
// update table
|
||||
func (a *AutoHosts) updateTable(table map[string][]net.IP, host string, ipAddr net.IP) {
|
||||
ips, ok := table[host]
|
||||
if ok {
|
||||
for _, ip := range ips {
|
||||
if ip.Equal(ipAddr) {
|
||||
// IP already exists: don't add duplicates
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
ips = append(ips, ipAddr)
|
||||
table[host] = ips
|
||||
}
|
||||
} else {
|
||||
table[host] = []net.IP{ipAddr}
|
||||
ok = true
|
||||
}
|
||||
if ok {
|
||||
log.Debug("AutoHosts: added %s -> %s", ipAddr, host)
|
||||
}
|
||||
}
|
||||
|
||||
// update "reverse" table
|
||||
func (a *AutoHosts) updateTableRev(tableRev map[string]string, host string, ipAddr net.IP) {
|
||||
ipStr := ipAddr.String()
|
||||
_, ok := tableRev[ipStr]
|
||||
if !ok {
|
||||
tableRev[ipStr] = host
|
||||
log.Debug("AutoHosts: added reverse-address %s -> %s", ipStr, host)
|
||||
}
|
||||
}
|
||||
|
||||
// Read IP-hostname pairs from file
|
||||
// Multiple hostnames per line (per one IP) is supported.
|
||||
func (a *AutoHosts) load(table map[string][]net.IP, tableRev map[string]string, fn string) {
|
||||
f, err := os.Open(fn)
|
||||
if err != nil {
|
||||
log.Error("AutoHosts: %s", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
r := bufio.NewReader(f)
|
||||
log.Debug("AutoHosts: loading hosts from file %s", fn)
|
||||
|
||||
finish := false
|
||||
for !finish {
|
||||
line, err := r.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
finish = true
|
||||
} else if err != nil {
|
||||
log.Error("AutoHosts: %s", err)
|
||||
return
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
ipAddr := net.ParseIP(fields[0])
|
||||
if ipAddr == nil {
|
||||
continue
|
||||
}
|
||||
for i := 1; i != len(fields); i++ {
|
||||
host := fields[i]
|
||||
if len(host) == 0 {
|
||||
break
|
||||
}
|
||||
sharp := strings.IndexByte(host, '#')
|
||||
if sharp == 0 {
|
||||
break // skip the rest of the line after #
|
||||
} else if sharp > 0 {
|
||||
host = host[:sharp]
|
||||
}
|
||||
|
||||
a.updateTable(table, host, ipAddr)
|
||||
a.updateTableRev(tableRev, host, ipAddr)
|
||||
if sharp >= 0 {
|
||||
break // skip the rest of the line after #
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Receive notifications from fsnotify package
|
||||
func (a *AutoHosts) watcherLoop() {
|
||||
for {
|
||||
select {
|
||||
|
||||
case event, ok := <-a.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// skip duplicate events
|
||||
repeat := true
|
||||
for repeat {
|
||||
select {
|
||||
case _ = <-a.watcher.Events:
|
||||
// skip this event
|
||||
default:
|
||||
repeat = false
|
||||
}
|
||||
}
|
||||
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Debug("AutoHosts: modified: %s", event.Name)
|
||||
select {
|
||||
case a.updateChan <- true:
|
||||
// sent a signal to 'updateLoop' goroutine
|
||||
default:
|
||||
// queue is full
|
||||
}
|
||||
}
|
||||
|
||||
case err, ok := <-a.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Error("AutoHosts: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateLoop - read static hosts from system files
|
||||
func (a *AutoHosts) updateLoop() {
|
||||
for {
|
||||
select {
|
||||
case ok := <-a.updateChan:
|
||||
if !ok {
|
||||
log.Debug("Finished AutoHosts update loop")
|
||||
return
|
||||
}
|
||||
|
||||
a.updateHosts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateHosts - loads system hosts
|
||||
func (a *AutoHosts) updateHosts() {
|
||||
table := make(map[string][]net.IP)
|
||||
tableRev := make(map[string]string)
|
||||
|
||||
a.load(table, tableRev, a.hostsFn)
|
||||
|
||||
for _, dir := range a.hostsDirs {
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Error("AutoHosts: Opening directory: %s: %s", dir, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
a.load(table, tableRev, dir+"/"+fi.Name())
|
||||
}
|
||||
}
|
||||
|
||||
a.lock.Lock()
|
||||
a.table = table
|
||||
a.tableReverse = tableRev
|
||||
a.lock.Unlock()
|
||||
|
||||
a.notify()
|
||||
}
|
||||
112
internal/util/auto_hosts_test.go
Normal file
112
internal/util/auto_hosts_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func prepareTestDir() string {
|
||||
const dir = "./agh-test"
|
||||
_ = os.RemoveAll(dir)
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
return dir
|
||||
}
|
||||
|
||||
func TestAutoHostsResolution(t *testing.T) {
|
||||
ah := AutoHosts{}
|
||||
|
||||
dir := prepareTestDir()
|
||||
defer func() { _ = os.RemoveAll(dir) }()
|
||||
|
||||
f, _ := ioutil.TempFile(dir, "")
|
||||
defer func() { _ = os.Remove(f.Name()) }()
|
||||
defer f.Close()
|
||||
|
||||
_, _ = f.WriteString(" 127.0.0.1 host localhost # comment \n")
|
||||
_, _ = f.WriteString(" ::1 localhost#comment \n")
|
||||
|
||||
ah.Init(f.Name())
|
||||
|
||||
// Existing host
|
||||
ips := ah.Process("localhost", dns.TypeA)
|
||||
assert.NotNil(t, ips)
|
||||
assert.Equal(t, 1, len(ips))
|
||||
assert.Equal(t, net.ParseIP("127.0.0.1"), ips[0])
|
||||
|
||||
// Unknown host
|
||||
ips = ah.Process("newhost", dns.TypeA)
|
||||
assert.Nil(t, ips)
|
||||
|
||||
// Unknown host (comment)
|
||||
ips = ah.Process("comment", dns.TypeA)
|
||||
assert.Nil(t, ips)
|
||||
|
||||
// Test hosts file
|
||||
table := ah.List()
|
||||
name, ok := table["127.0.0.1"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "host", name)
|
||||
|
||||
// Test PTR
|
||||
a, _ := dns.ReverseAddr("127.0.0.1")
|
||||
a = strings.TrimSuffix(a, ".")
|
||||
assert.True(t, ah.ProcessReverse(a, dns.TypePTR) == "host")
|
||||
a, _ = dns.ReverseAddr("::1")
|
||||
a = strings.TrimSuffix(a, ".")
|
||||
assert.True(t, ah.ProcessReverse(a, dns.TypePTR) == "localhost")
|
||||
}
|
||||
|
||||
func TestAutoHostsFSNotify(t *testing.T) {
|
||||
ah := AutoHosts{}
|
||||
|
||||
dir := prepareTestDir()
|
||||
defer func() { _ = os.RemoveAll(dir) }()
|
||||
|
||||
f, _ := ioutil.TempFile(dir, "")
|
||||
defer func() { _ = os.Remove(f.Name()) }()
|
||||
defer f.Close()
|
||||
|
||||
// Init
|
||||
_, _ = f.WriteString(" 127.0.0.1 host localhost \n")
|
||||
ah.Init(f.Name())
|
||||
|
||||
// Unknown host
|
||||
ips := ah.Process("newhost", dns.TypeA)
|
||||
assert.Nil(t, ips)
|
||||
|
||||
// Stat monitoring for changes
|
||||
ah.Start()
|
||||
defer ah.Close()
|
||||
|
||||
// Update file
|
||||
_, _ = f.WriteString("127.0.0.2 newhost\n")
|
||||
_ = f.Sync()
|
||||
|
||||
// wait until fsnotify has triggerred and processed the file-modification event
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Check if we are notified about changes
|
||||
ips = ah.Process("newhost", dns.TypeA)
|
||||
assert.NotNil(t, ips)
|
||||
assert.Equal(t, 1, len(ips))
|
||||
assert.Equal(t, "127.0.0.2", ips[0].String())
|
||||
}
|
||||
|
||||
func TestIP(t *testing.T) {
|
||||
assert.Equal(t, "127.0.0.1", DNSUnreverseAddr("1.0.0.127.in-addr.arpa").String())
|
||||
assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String())
|
||||
assert.Equal(t, "::abcd:1234", DNSUnreverseAddr("4.3.2.1.d.c.B.A.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa").String())
|
||||
|
||||
assert.Nil(t, DNSUnreverseAddr("1.0.0.127.in-addr.arpa."))
|
||||
assert.Nil(t, DNSUnreverseAddr(".0.0.127.in-addr.arpa"))
|
||||
assert.Nil(t, DNSUnreverseAddr(".3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"))
|
||||
assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b.a.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0..ip6.arpa"))
|
||||
assert.Nil(t, DNSUnreverseAddr("4.3.2.1.d.c.b. .0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa"))
|
||||
}
|
||||
70
internal/util/dns.go
Normal file
70
internal/util/dns.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// convert character to hex number
|
||||
func charToHex(n byte) int8 {
|
||||
if n >= '0' && n <= '9' {
|
||||
return int8(n) - '0'
|
||||
} else if (n|0x20) >= 'a' && (n|0x20) <= 'f' {
|
||||
return (int8(n) | 0x20) - 'a' + 10
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// parse IPv6 reverse address
|
||||
func ipParseArpa6(s string) net.IP {
|
||||
if len(s) != 63 {
|
||||
return nil
|
||||
}
|
||||
ip6 := make(net.IP, 16)
|
||||
|
||||
for i := 0; i != 64; i += 4 {
|
||||
|
||||
// parse "0.1."
|
||||
n := charToHex(s[i])
|
||||
n2 := charToHex(s[i+2])
|
||||
if s[i+1] != '.' || (i != 60 && s[i+3] != '.') ||
|
||||
n < 0 || n2 < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ip6[16-i/4-1] = byte(n2<<4) | byte(n&0x0f)
|
||||
}
|
||||
return ip6
|
||||
}
|
||||
|
||||
// ipReverse - reverse IP address: 1.0.0.127 -> 127.0.0.1
|
||||
func ipReverse(ip net.IP) net.IP {
|
||||
n := len(ip)
|
||||
r := make(net.IP, n)
|
||||
for i := 0; i != n; i++ {
|
||||
r[i] = ip[n-i-1]
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// DNSUnreverseAddr - convert reversed ARPA address to a normal IP address
|
||||
func DNSUnreverseAddr(s string) net.IP {
|
||||
const arpaV4 = ".in-addr.arpa"
|
||||
const arpaV6 = ".ip6.arpa"
|
||||
|
||||
if strings.HasSuffix(s, arpaV4) {
|
||||
ip := strings.TrimSuffix(s, arpaV4)
|
||||
ip4 := net.ParseIP(ip).To4()
|
||||
if ip4 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ipReverse(ip4)
|
||||
|
||||
} else if strings.HasSuffix(s, arpaV6) {
|
||||
ip := strings.TrimSuffix(s, arpaV6)
|
||||
return ipParseArpa6(ip)
|
||||
}
|
||||
|
||||
return nil // unknown suffix
|
||||
}
|
||||
103
internal/util/helpers.go
Normal file
103
internal/util/helpers.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ContainsString checks if "v" is in the array "arr"
|
||||
func ContainsString(arr []string, v string) bool {
|
||||
for _, i := range arr {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fileExists returns TRUE if file exists
|
||||
func FileExists(fn string) bool {
|
||||
_, err := os.Stat(fn)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// runCommand runs shell command
|
||||
func RunCommand(command string, arguments ...string) (int, string, error) {
|
||||
cmd := exec.Command(command, arguments...)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 1, "", fmt.Errorf("exec.Command(%s) failed: %v: %s", command, err, string(out))
|
||||
}
|
||||
|
||||
return cmd.ProcessState.ExitCode(), string(out), nil
|
||||
}
|
||||
|
||||
func FuncName() string {
|
||||
pc := make([]uintptr, 10) // at least 1 entry needed
|
||||
runtime.Callers(2, pc)
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
return path.Base(f.Name())
|
||||
}
|
||||
|
||||
// SplitNext - split string by a byte and return the first chunk
|
||||
// Skip empty chunks
|
||||
// Whitespace is trimmed
|
||||
func SplitNext(str *string, splitBy byte) string {
|
||||
i := strings.IndexByte(*str, splitBy)
|
||||
s := ""
|
||||
if i != -1 {
|
||||
s = (*str)[0:i]
|
||||
*str = (*str)[i+1:]
|
||||
k := 0
|
||||
ch := rune(0)
|
||||
for k, ch = range *str {
|
||||
if byte(ch) != splitBy {
|
||||
break
|
||||
}
|
||||
}
|
||||
*str = (*str)[k:]
|
||||
} else {
|
||||
s = *str
|
||||
*str = ""
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
// MinInt - return the minimum value
|
||||
func MinInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// IsOpenWrt checks if OS is OpenWRT
|
||||
func IsOpenWrt() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return false
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadFile("/etc/os-release")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Contains(string(body), "OpenWrt")
|
||||
}
|
||||
|
||||
// IsFreeBSD checks if OS is FreeBSD
|
||||
func IsFreeBSD() bool {
|
||||
if runtime.GOOS == "freebsd" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
14
internal/util/helpers_test.go
Normal file
14
internal/util/helpers_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSplitNext(t *testing.T) {
|
||||
s := " a,b , c "
|
||||
assert.True(t, SplitNext(&s, ',') == "a")
|
||||
assert.True(t, SplitNext(&s, ',') == "b")
|
||||
assert.True(t, SplitNext(&s, ',') == "c" && len(s) == 0)
|
||||
}
|
||||
21
internal/util/net.go
Normal file
21
internal/util/net.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
// CanBindPort - checks if we can bind to this port or not
|
||||
func CanBindPort(port int) (bool, error) {
|
||||
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
l, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_ = l.Close()
|
||||
return true, nil
|
||||
}
|
||||
186
internal/util/network_utils.go
Normal file
186
internal/util/network_utils.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
|
||||
"github.com/joomcode/errorx"
|
||||
)
|
||||
|
||||
// NetInterface represents a list of network interfaces
|
||||
type NetInterface struct {
|
||||
Name string // Network interface name
|
||||
MTU int // MTU
|
||||
HardwareAddr string // Hardware address
|
||||
Addresses []string // Array with the network interface addresses
|
||||
Subnets []string // Array with CIDR addresses of this network interface
|
||||
Flags string // Network interface flags (up, broadcast, etc)
|
||||
}
|
||||
|
||||
// GetValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
||||
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
||||
func GetValidNetInterfaces() ([]net.Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get list of interfaces: %s", err)
|
||||
}
|
||||
|
||||
netIfaces := []net.Interface{}
|
||||
|
||||
for i := range ifaces {
|
||||
iface := ifaces[i]
|
||||
netIfaces = append(netIfaces, iface)
|
||||
}
|
||||
|
||||
return netIfaces, nil
|
||||
}
|
||||
|
||||
// GetValidNetInterfacesForWeb returns interfaces that are eligible for DNS and WEB only
|
||||
// we do not return link-local addresses here
|
||||
func GetValidNetInterfacesForWeb() ([]NetInterface, error) {
|
||||
ifaces, err := GetValidNetInterfaces()
|
||||
if err != nil {
|
||||
return nil, errorx.Decorate(err, "Couldn't get interfaces")
|
||||
}
|
||||
if len(ifaces) == 0 {
|
||||
return nil, errors.New("couldn't find any legible interface")
|
||||
}
|
||||
|
||||
var netInterfaces []NetInterface
|
||||
|
||||
for _, iface := range ifaces {
|
||||
addrs, e := iface.Addrs()
|
||||
if e != nil {
|
||||
return nil, errorx.Decorate(e, "Failed to get addresses for interface %s", iface.Name)
|
||||
}
|
||||
|
||||
netIface := NetInterface{
|
||||
Name: iface.Name,
|
||||
MTU: iface.MTU,
|
||||
HardwareAddr: iface.HardwareAddr.String(),
|
||||
}
|
||||
|
||||
if iface.Flags != 0 {
|
||||
netIface.Flags = iface.Flags.String()
|
||||
}
|
||||
|
||||
// Collect network interface addresses
|
||||
for _, addr := range addrs {
|
||||
ipNet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// not an IPNet, should not happen
|
||||
return nil, fmt.Errorf("got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
||||
}
|
||||
// ignore link-local
|
||||
if ipNet.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
netIface.Addresses = append(netIface.Addresses, ipNet.IP.String())
|
||||
netIface.Subnets = append(netIface.Subnets, ipNet.String())
|
||||
}
|
||||
|
||||
// Discard interfaces with no addresses
|
||||
if len(netIface.Addresses) != 0 {
|
||||
netInterfaces = append(netInterfaces, netIface)
|
||||
}
|
||||
}
|
||||
|
||||
return netInterfaces, nil
|
||||
}
|
||||
|
||||
// GetInterfaceByIP - Get interface name by its IP address.
|
||||
func GetInterfaceByIP(ip string) string {
|
||||
ifaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
for _, addr := range iface.Addresses {
|
||||
if ip == addr {
|
||||
return iface.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetSubnet - Get IP address with netmask for the specified interface
|
||||
// Returns an empty string if it fails to find it
|
||||
func GetSubnet(ifaceName string) string {
|
||||
netIfaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
log.Error("Could not get network interfaces info: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, netIface := range netIfaces {
|
||||
if netIface.Name == ifaceName && len(netIface.Subnets) > 0 {
|
||||
return netIface.Subnets[0]
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// CheckPortAvailable - check if TCP port is available
|
||||
func CheckPortAvailable(host string, port int) error {
|
||||
ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = ln.Close()
|
||||
|
||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||
// We wait for some time and hope that this fd will be closed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPacketPortAvailable - check if UDP port is available
|
||||
func CheckPacketPortAvailable(host string, port int) error {
|
||||
ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = ln.Close()
|
||||
|
||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||
// We wait for some time and hope that this fd will be closed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return err
|
||||
}
|
||||
|
||||
// ErrorIsAddrInUse - check if error is "address already in use"
|
||||
func ErrorIsAddrInUse(err error) bool {
|
||||
errOpError, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
errSyscallError, ok := errOpError.Err.(*os.SyscallError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
errErrno, ok := errSyscallError.Err.(syscall.Errno)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
const WSAEADDRINUSE = 10048
|
||||
return errErrno == WSAEADDRINUSE
|
||||
}
|
||||
|
||||
return errErrno == syscall.EADDRINUSE
|
||||
}
|
||||
24
internal/util/network_utils_test.go
Normal file
24
internal/util/network_utils_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetValidNetInterfacesForWeb(t *testing.T) {
|
||||
ifaces, err := GetValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot get net interfaces: %s", err)
|
||||
}
|
||||
if len(ifaces) == 0 {
|
||||
t.Fatalf("No net interfaces found")
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if len(iface.Addresses) == 0 {
|
||||
t.Fatalf("No addresses found for %s", iface.Name)
|
||||
}
|
||||
|
||||
log.Printf("%v", iface)
|
||||
}
|
||||
}
|
||||
32
internal/util/os_freebsd.go
Normal file
32
internal/util/os_freebsd.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build freebsd
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// Set user-specified limit of how many fd's we can use
|
||||
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/659
|
||||
func SetRlimit(val uint) {
|
||||
var rlim syscall.Rlimit
|
||||
rlim.Max = int64(val)
|
||||
rlim.Cur = int64(val)
|
||||
err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim)
|
||||
if err != nil {
|
||||
log.Error("Setrlimit() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current user has root (administrator) rights
|
||||
func HaveAdminRights() (bool, error) {
|
||||
return os.Getuid() == 0, nil
|
||||
}
|
||||
|
||||
// SendProcessSignal - send signal to a process
|
||||
func SendProcessSignal(pid int, sig syscall.Signal) error {
|
||||
return syscall.Kill(pid, sig)
|
||||
}
|
||||
32
internal/util/os_unix.go
Normal file
32
internal/util/os_unix.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build aix darwin dragonfly linux netbsd openbsd solaris
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// Set user-specified limit of how many fd's we can use
|
||||
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/659
|
||||
func SetRlimit(val uint) {
|
||||
var rlim syscall.Rlimit
|
||||
rlim.Max = uint64(val)
|
||||
rlim.Cur = uint64(val)
|
||||
err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim)
|
||||
if err != nil {
|
||||
log.Error("Setrlimit() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the current user has root (administrator) rights
|
||||
func HaveAdminRights() (bool, error) {
|
||||
return os.Getuid() == 0, nil
|
||||
}
|
||||
|
||||
// SendProcessSignal - send signal to a process
|
||||
func SendProcessSignal(pid int, sig syscall.Signal) error {
|
||||
return syscall.Kill(pid, sig)
|
||||
}
|
||||
37
internal/util/os_windows.go
Normal file
37
internal/util/os_windows.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Set user-specified limit of how many fd's we can use
|
||||
func SetRlimit(val uint) {
|
||||
}
|
||||
|
||||
func HaveAdminRights() (bool, error) {
|
||||
var token windows.Token
|
||||
h := windows.CurrentProcess()
|
||||
err := windows.OpenProcessToken(h, windows.TOKEN_QUERY, &token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
info := make([]byte, 4)
|
||||
var returnedLen uint32
|
||||
err = windows.GetTokenInformation(token, windows.TokenElevation, &info[0], uint32(len(info)), &returnedLen)
|
||||
token.Close()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if info[0] == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func SendProcessSignal(pid int, sig syscall.Signal) error {
|
||||
return fmt.Errorf("not supported on Windows")
|
||||
}
|
||||
352
internal/util/pprof.go
Normal file
352
internal/util/pprof.go
Normal file
@@ -0,0 +1,352 @@
|
||||
// Modified pprof package
|
||||
// The problem with the mainstream package is that it registers HTTP handlers
|
||||
// inside init() function.
|
||||
// This behaviour makes it impossible to enable pprof on demand in runtime.
|
||||
// But this package has a separate function for this -
|
||||
// PProfRegisterWebHandlers().
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package pprof serves via its HTTP server runtime profiling data
|
||||
// in the format expected by the pprof visualization tool.
|
||||
//
|
||||
// The package is typically only imported for the side effect of
|
||||
// registering its HTTP handlers.
|
||||
// The handled paths all begin with /debug/pprof/.
|
||||
//
|
||||
// To use pprof, link this package into your program:
|
||||
// import _ "net/http/pprof"
|
||||
//
|
||||
// If your application is not already running an http server, you
|
||||
// need to start one. Add "net/http" and "log" to your imports and
|
||||
// the following code to your main function:
|
||||
//
|
||||
// go func() {
|
||||
// log.Println(http.ListenAndServe("localhost:6060", nil))
|
||||
// }()
|
||||
//
|
||||
// If you are not using DefaultServeMux, you will have to register handlers
|
||||
// with the mux you are using.
|
||||
//
|
||||
// Then use the pprof tool to look at the heap profile:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/heap
|
||||
//
|
||||
// Or to look at a 30-second CPU profile:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
|
||||
//
|
||||
// Or to look at the goroutine blocking profile, after calling
|
||||
// runtime.SetBlockProfileRate in your program:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/block
|
||||
//
|
||||
// Or to collect a 5-second execution trace:
|
||||
//
|
||||
// wget http://localhost:6060/debug/pprof/trace?seconds=5
|
||||
//
|
||||
// Or to look at the holders of contended mutexes, after calling
|
||||
// runtime.SetMutexProfileFraction in your program:
|
||||
//
|
||||
// go tool pprof http://localhost:6060/debug/pprof/mutex
|
||||
//
|
||||
// To view all available profiles, open http://localhost:6060/debug/pprof/
|
||||
// in your browser.
|
||||
//
|
||||
// For a study of the facility in action, visit
|
||||
//
|
||||
// https://blog.golang.org/2011/06/profiling-go-programs.html
|
||||
//
|
||||
package util
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PProfRegisterWebHandlers - register HTTP handlers for pprof
|
||||
func PProfRegisterWebHandlers(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/debug/pprof/", Index)
|
||||
mux.HandleFunc("/debug/pprof/cmdline", Cmdline)
|
||||
mux.HandleFunc("/debug/pprof/profile", Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", Trace)
|
||||
}
|
||||
|
||||
// Cmdline responds with the running program's
|
||||
// command line, with arguments separated by NUL bytes.
|
||||
// The package initialization registers it as /debug/pprof/cmdline.
|
||||
func Cmdline(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
fmt.Fprintf(w, strings.Join(os.Args, "\x00"))
|
||||
}
|
||||
|
||||
func sleep(w http.ResponseWriter, d time.Duration) {
|
||||
var clientGone <-chan bool
|
||||
if cn, ok := w.(http.CloseNotifier); ok {
|
||||
clientGone = cn.CloseNotify()
|
||||
}
|
||||
select {
|
||||
case <-time.After(d):
|
||||
case <-clientGone:
|
||||
}
|
||||
}
|
||||
|
||||
func durationExceedsWriteTimeout(r *http.Request, seconds float64) bool {
|
||||
srv, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
|
||||
return ok && srv.WriteTimeout != 0 && seconds >= srv.WriteTimeout.Seconds()
|
||||
}
|
||||
|
||||
func serveError(w http.ResponseWriter, status int, txt string) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.Header().Set("X-Go-Pprof", "1")
|
||||
w.Header().Del("Content-Disposition")
|
||||
w.WriteHeader(status)
|
||||
fmt.Fprintln(w, txt)
|
||||
}
|
||||
|
||||
// Profile responds with the pprof-formatted cpu profile.
|
||||
// Profiling lasts for duration specified in seconds GET parameter, or for 30 seconds if not specified.
|
||||
// The package initialization registers it as /debug/pprof/profile.
|
||||
func Profile(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
sec, err := strconv.ParseInt(r.FormValue("seconds"), 10, 64)
|
||||
if sec <= 0 || err != nil {
|
||||
sec = 30
|
||||
}
|
||||
|
||||
if durationExceedsWriteTimeout(r, float64(sec)) {
|
||||
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
|
||||
return
|
||||
}
|
||||
|
||||
// Set Content Type assuming StartCPUProfile will work,
|
||||
// because if it does it starts writing.
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="profile"`)
|
||||
if err := pprof.StartCPUProfile(w); err != nil {
|
||||
// StartCPUProfile failed, so no writes yet.
|
||||
serveError(w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("Could not enable CPU profiling: %s", err))
|
||||
return
|
||||
}
|
||||
sleep(w, time.Duration(sec)*time.Second)
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Trace responds with the execution trace in binary form.
|
||||
// Tracing lasts for duration specified in seconds GET parameter, or for 1 second if not specified.
|
||||
// The package initialization registers it as /debug/pprof/trace.
|
||||
func Trace(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
sec, err := strconv.ParseFloat(r.FormValue("seconds"), 64)
|
||||
if sec <= 0 || err != nil {
|
||||
sec = 1
|
||||
}
|
||||
|
||||
if durationExceedsWriteTimeout(r, sec) {
|
||||
serveError(w, http.StatusBadRequest, "profile duration exceeds server's WriteTimeout")
|
||||
return
|
||||
}
|
||||
|
||||
// Set Content Type assuming trace.Start will work,
|
||||
// because if it does it starts writing.
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", `attachment; filename="trace"`)
|
||||
if err := trace.Start(w); err != nil {
|
||||
// trace.Start failed, so no writes yet.
|
||||
serveError(w, http.StatusInternalServerError,
|
||||
fmt.Sprintf("Could not enable tracing: %s", err))
|
||||
return
|
||||
}
|
||||
sleep(w, time.Duration(sec*float64(time.Second)))
|
||||
trace.Stop()
|
||||
}
|
||||
|
||||
// Symbol looks up the program counters listed in the request,
|
||||
// responding with a table mapping program counters to function names.
|
||||
// The package initialization registers it as /debug/pprof/symbol.
|
||||
func Symbol(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
// We have to read the whole POST body before
|
||||
// writing any output. Buffer the output here.
|
||||
var buf bytes.Buffer
|
||||
|
||||
// We don't know how many symbols we have, but we
|
||||
// do have symbol information. Pprof only cares whether
|
||||
// this number is 0 (no symbols available) or > 0.
|
||||
fmt.Fprintf(&buf, "num_symbols: 1\n")
|
||||
|
||||
var b *bufio.Reader
|
||||
if r.Method == "POST" {
|
||||
b = bufio.NewReader(r.Body)
|
||||
} else {
|
||||
b = bufio.NewReader(strings.NewReader(r.URL.RawQuery))
|
||||
}
|
||||
|
||||
for {
|
||||
word, err := b.ReadSlice('+')
|
||||
if err == nil {
|
||||
word = word[0 : len(word)-1] // trim +
|
||||
}
|
||||
pc, _ := strconv.ParseUint(string(word), 0, 64)
|
||||
if pc != 0 {
|
||||
f := runtime.FuncForPC(uintptr(pc))
|
||||
if f != nil {
|
||||
fmt.Fprintf(&buf, "%#x %s\n", pc, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until here to check for err; the last
|
||||
// symbol will have an err because it doesn't end in +.
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Fprintf(&buf, "reading request: %v\n", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// Handler returns an HTTP handler that serves the named profile.
|
||||
func Handler(name string) http.Handler {
|
||||
return handler(name)
|
||||
}
|
||||
|
||||
type handler string
|
||||
|
||||
func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
p := pprof.Lookup(string(name))
|
||||
if p == nil {
|
||||
serveError(w, http.StatusNotFound, "Unknown profile")
|
||||
return
|
||||
}
|
||||
gc, _ := strconv.Atoi(r.FormValue("gc"))
|
||||
if name == "heap" && gc > 0 {
|
||||
runtime.GC()
|
||||
}
|
||||
debug, _ := strconv.Atoi(r.FormValue("debug"))
|
||||
if debug != 0 {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
|
||||
}
|
||||
p.WriteTo(w, debug)
|
||||
}
|
||||
|
||||
var profileDescriptions = map[string]string{
|
||||
"allocs": "A sampling of all past memory allocations",
|
||||
"block": "Stack traces that led to blocking on synchronization primitives",
|
||||
"cmdline": "The command line invocation of the current program",
|
||||
"goroutine": "Stack traces of all current goroutines",
|
||||
"heap": "A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.",
|
||||
"mutex": "Stack traces of holders of contended mutexes",
|
||||
"profile": "CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.",
|
||||
"threadcreate": "Stack traces that led to the creation of new OS threads",
|
||||
"trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.",
|
||||
}
|
||||
|
||||
// Index responds with the pprof-formatted profile named by the request.
|
||||
// For example, "/debug/pprof/heap" serves the "heap" profile.
|
||||
// Index responds to a request for "/debug/pprof/" with an HTML page
|
||||
// listing the available profiles.
|
||||
func Index(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/debug/pprof/") {
|
||||
name := strings.TrimPrefix(r.URL.Path, "/debug/pprof/")
|
||||
if name != "" {
|
||||
handler(name).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type profile struct {
|
||||
Name string
|
||||
Href string
|
||||
Desc string
|
||||
Count int
|
||||
}
|
||||
var profiles []profile
|
||||
for _, p := range pprof.Profiles() {
|
||||
profiles = append(profiles, profile{
|
||||
Name: p.Name(),
|
||||
Href: p.Name() + "?debug=1",
|
||||
Desc: profileDescriptions[p.Name()],
|
||||
Count: p.Count(),
|
||||
})
|
||||
}
|
||||
|
||||
// Adding other profiles exposed from within this package
|
||||
for _, p := range []string{"cmdline", "profile", "trace"} {
|
||||
profiles = append(profiles, profile{
|
||||
Name: p,
|
||||
Href: p,
|
||||
Desc: profileDescriptions[p],
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(profiles, func(i, j int) bool {
|
||||
return profiles[i].Name < profiles[j].Name
|
||||
})
|
||||
|
||||
if err := indexTmpl.Execute(w, profiles); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
var indexTmpl = template.Must(template.New("index").Parse(`<html>
|
||||
<head>
|
||||
<title>/debug/pprof/</title>
|
||||
<style>
|
||||
.profile-name{
|
||||
display:inline-block;
|
||||
width:6rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
/debug/pprof/<br>
|
||||
<br>
|
||||
Types of profiles available:
|
||||
<table>
|
||||
<thead><td>Count</td><td>Profile</td></thead>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td>{{.Count}}</td><td><a href={{.Href}}>{{.Name}}</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
<a href="goroutine?debug=2">full goroutine stack dump</a>
|
||||
<br/>
|
||||
<p>
|
||||
Profile Descriptions:
|
||||
<ul>
|
||||
{{range .}}
|
||||
<li><div class=profile-name>{{.Name}}:</div> {{.Desc}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
18
internal/util/syslog_others.go
Normal file
18
internal/util/syslog_others.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// +build !windows,!nacl,!plan9
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
// ConfigureSyslog reroutes standard logger output to syslog
|
||||
func ConfigureSyslog(serviceName string) error {
|
||||
w, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.SetOutput(w)
|
||||
return nil
|
||||
}
|
||||
39
internal/util/syslog_windows.go
Normal file
39
internal/util/syslog_windows.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc/eventlog"
|
||||
)
|
||||
|
||||
type eventLogWriter struct {
|
||||
el *eventlog.Log
|
||||
}
|
||||
|
||||
// Write sends a log message to the Event Log.
|
||||
func (w *eventLogWriter) Write(b []byte) (int, error) {
|
||||
return len(b), w.el.Info(1, string(b))
|
||||
}
|
||||
|
||||
func ConfigureSyslog(serviceName string) error {
|
||||
// Note that the eventlog src is the same as the service name
|
||||
// Otherwise, we will get "the description for event id cannot be found" warning in every log record
|
||||
|
||||
// Continue if we receive "registry key already exists" or if we get
|
||||
// ERROR_ACCESS_DENIED so that we can log without administrative permissions
|
||||
// for pre-existing eventlog sources.
|
||||
if err := eventlog.InstallAsEventCreate(serviceName, eventlog.Info|eventlog.Warning|eventlog.Error); err != nil {
|
||||
if !strings.Contains(err.Error(), "registry key already exists") && err != windows.ERROR_ACCESS_DENIED {
|
||||
return err
|
||||
}
|
||||
}
|
||||
el, err := eventlog.Open(serviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.SetOutput(&eventLogWriter{el: el})
|
||||
return nil
|
||||
}
|
||||
97
internal/util/tls.go
Normal file
97
internal/util/tls.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// LoadSystemRootCAs - load root CAs from the system
|
||||
// Return the x509 certificate pool object
|
||||
// Return nil if nothing has been found.
|
||||
// This means that Go.crypto will use its default algorithm to find system root CA list.
|
||||
// https://github.com/AdguardTeam/AdGuardHome/internal/issues/1311
|
||||
func LoadSystemRootCAs() *x509.CertPool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Directories with the system root certificates, that aren't supported by Go.crypto
|
||||
dirs := []string{
|
||||
"/opt/etc/ssl/certs", // Entware
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
for _, dir := range dirs {
|
||||
fis, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Error("Opening directory: %s: %s", dir, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
rootsAdded := false
|
||||
for _, fi := range fis {
|
||||
data, err := ioutil.ReadFile(dir + "/" + fi.Name())
|
||||
if err == nil && roots.AppendCertsFromPEM(data) {
|
||||
rootsAdded = true
|
||||
}
|
||||
}
|
||||
if rootsAdded {
|
||||
return roots
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTLSCiphers - the same as initDefaultCipherSuites() from src/crypto/tls/common.go
|
||||
// but with the difference that we don't use so many other default ciphers.
|
||||
func InitTLSCiphers() []uint16 {
|
||||
var ciphers []uint16
|
||||
|
||||
// Check the cpu flags for each platform that has optimized GCM implementations.
|
||||
// Worst case, these variables will just all be false.
|
||||
var (
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
// Keep in sync with crypto/aes/cipher_s390x.go.
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
|
||||
|
||||
hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
|
||||
)
|
||||
|
||||
if hasGCMAsm {
|
||||
// If AES-GCM hardware is provided then prioritise AES-GCM
|
||||
// cipher suites.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
}
|
||||
} else {
|
||||
// Without AES-GCM hardware, we put the ChaCha20-Poly1305
|
||||
// cipher suites first.
|
||||
ciphers = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
}
|
||||
|
||||
otherCiphers := []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
}
|
||||
ciphers = append(ciphers, otherCiphers...)
|
||||
return ciphers
|
||||
}
|
||||
Reference in New Issue
Block a user