Merge pull request #126 in DNS/adguard-dns from feature/423 to master
* commit 'b0c4d88d5454f8dd5a92a73615cce3a31450f56b': (45 commits) Indicate that DHCP is experimental Update dnsproxy and dnscrypt, and run go mod tidy. Fix race conditions found by -race move log wrapper library outside into hmage/golibs/log Added check for active DHCP before enable Use new log wrapper and add more functions to it. Implement a log wrapper /dhcp/status -- give out hostname for UI dhcpd -- Remember hostname, for UI. Update comment why filter_conn.go is needed. Fixup of previous commit. /dhcp/find_active_dhcp -- use interface name from request body Don't try to start DHCP server if it's not enabled. Get rid of logrus, it's TTY output is not friendly or human parseable if we will want users to send us logs. Flag parser -- support options without values, move code for help and verbose into table. verbose output parameter Pretty-format leases so it shows human readable MAC address. Start DHCP on launch if it's enabled in config. Update makefile to detect changes in dhcpd/*.go DHCPD — don't forget to make Lease fields public. ...
This commit is contained in:
commit
cbe83e2053
2
Makefile
2
Makefile
|
@ -19,7 +19,7 @@ client/node_modules: client/package.json client/package-lock.json
|
|||
$(STATIC): $(JSFILES) client/node_modules
|
||||
npm --prefix client run build-prod
|
||||
|
||||
$(TARGET): $(STATIC) *.go dnsfilter/*.go dnsforward/*.go
|
||||
$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
|
||||
go get -d .
|
||||
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr -z
|
||||
|
|
59
app.go
59
app.go
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -14,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/hmage/golibs/log"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
|
@ -44,27 +44,30 @@ func main() {
|
|||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||
// therefore, we must do it manually instead of using a lib
|
||||
{
|
||||
var printHelp func()
|
||||
var configFilename *string
|
||||
var bindHost *string
|
||||
var bindPort *int
|
||||
var opts = []struct {
|
||||
longName string
|
||||
shortName string
|
||||
description string
|
||||
callback func(value string)
|
||||
longName string
|
||||
shortName string
|
||||
description string
|
||||
callbackWithValue func(value string)
|
||||
callbackNoValue func()
|
||||
}{
|
||||
{"config", "c", "path to config file", func(value string) { configFilename = &value }},
|
||||
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }},
|
||||
{"config", "c", "path to config file", func(value string) { configFilename = &value }, nil},
|
||||
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }, nil},
|
||||
{"port", "p", "port to serve HTTP pages on", func(value string) {
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
panic("Got port that is not a number")
|
||||
}
|
||||
bindPort = &v
|
||||
}},
|
||||
{"help", "h", "print this help", nil},
|
||||
}, nil},
|
||||
{"verbose", "v", "enable verbose output", nil, func() { log.Verbose = true }},
|
||||
{"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }},
|
||||
}
|
||||
printHelp := func() {
|
||||
printHelp = func() {
|
||||
fmt.Printf("Usage:\n\n")
|
||||
fmt.Printf("%s [options]\n\n", os.Args[0])
|
||||
fmt.Printf("Options:\n")
|
||||
|
@ -74,30 +77,19 @@ func main() {
|
|||
}
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
v := os.Args[i]
|
||||
// short-circuit for help
|
||||
if v == "--help" || v == "-h" {
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}
|
||||
knownParam := false
|
||||
for _, opt := range opts {
|
||||
if v == "--"+opt.longName {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
if v == "--"+opt.longName || v == "-"+opt.shortName {
|
||||
if opt.callbackWithValue != nil {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callbackWithValue(os.Args[i])
|
||||
} else if opt.callbackNoValue != nil {
|
||||
opt.callbackNoValue()
|
||||
}
|
||||
i++
|
||||
opt.callback(os.Args[i])
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
if v == "-"+opt.shortName {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callback(os.Args[i])
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
|
@ -192,6 +184,11 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = startDHCPServer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
URL := fmt.Sprintf("http://%s", address)
|
||||
log.Println("Go to " + URL)
|
||||
log.Fatal(http.ListenAndServe(address, nil))
|
||||
|
|
|
@ -4126,6 +4126,11 @@
|
|||
"next-tick": "1"
|
||||
}
|
||||
},
|
||||
"es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
|
||||
},
|
||||
"es6-iterator": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
|
@ -6588,7 +6593,7 @@
|
|||
},
|
||||
"html-webpack-plugin": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
|
||||
"resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
|
||||
"integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -6638,7 +6643,7 @@
|
|||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.34",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
|
||||
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -7387,8 +7392,7 @@
|
|||
"is-promise": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
|
||||
"dev": true
|
||||
"integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.0.4",
|
||||
|
@ -13202,6 +13206,21 @@
|
|||
"reduce-reducers": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"redux-form": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/redux-form/-/redux-form-7.4.2.tgz",
|
||||
"integrity": "sha512-QxC36s4Lelx5Cr8dbpxqvl23dwYOydeAX8c6YPmgkz/Dhj053C16S2qoyZN6LO6HJ2oUF00rKsAyE94GwOUhFA==",
|
||||
"requires": {
|
||||
"es6-error": "^4.1.1",
|
||||
"hoist-non-react-statics": "^2.5.4",
|
||||
"invariant": "^2.2.4",
|
||||
"is-promise": "^2.1.0",
|
||||
"lodash": "^4.17.10",
|
||||
"lodash-es": "^4.17.10",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"redux-thunk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
|
@ -15003,7 +15022,7 @@
|
|||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
|
||||
"dev": true
|
||||
},
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"react-transition-group": "^2.4.0",
|
||||
"redux": "^4.0.0",
|
||||
"redux-actions": "^2.4.0",
|
||||
"redux-form": "^7.4.2",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"whatwg-fetch": "2.0.3"
|
||||
|
|
|
@ -1,4 +1,30 @@
|
|||
{
|
||||
"check_dhcp_servers": "Check for DHCP servers",
|
||||
"save_config": "Save config",
|
||||
"enabled_dhcp": "DHCP server enabled",
|
||||
"disabled_dhcp": "DHCP server disabled",
|
||||
"dhcp_title": "DHCP server (experimental!)",
|
||||
"dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
|
||||
"dhcp_enable": "Enable DHCP server",
|
||||
"dhcp_disable": "Disable DHCP server",
|
||||
"dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.",
|
||||
"dhcp_found": "Found active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_leases_not_found": "No DHCP leases found",
|
||||
"dhcp_config_saved": "Saved DHCP server config",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip_format": "Invalid IPv4 format",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
"dhcp_form_range_start": "Range start",
|
||||
"dhcp_form_range_end": "Range end",
|
||||
"dhcp_form_lease_title": "DHCP lease time (in seconds)",
|
||||
"dhcp_form_lease_input": "Lease duration",
|
||||
"dhcp_interface_select": "Select DHCP interface",
|
||||
"dhcp_hardware_address": "Hardware address",
|
||||
"dhcp_ip_addresses": "IP addresses",
|
||||
"back": "Back",
|
||||
"dashboard": "Dashboard",
|
||||
"settings": "Settings",
|
||||
|
@ -89,7 +115,7 @@
|
|||
"example_upstream_regular": "regular DNS (over UDP)",
|
||||
"example_upstream_dot": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "you can use <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps</a> for <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt</a> or <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> resolvers",
|
||||
"example_upstream_sdns": "you can use <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> for <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> or <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> resolvers",
|
||||
"example_upstream_tcp": "regular DNS (over TCP)",
|
||||
"all_filters_up_to_date_toast": "All filters are already up-to-date",
|
||||
"updated_upstream_dns_toast": "Updated the upstream DNS servers",
|
||||
|
|
|
@ -1,10 +1,32 @@
|
|||
{
|
||||
"refresh_status": "\u30b9\u30c6\u30fc\u30bf\u30b9\u3092\u6700\u65b0\u306b\u3059\u308b",
|
||||
"save_config": "\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3059\u308b",
|
||||
"enabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"disabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"dhcp_title": "DHCP\u30b5\u30fc\u30d0",
|
||||
"dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002",
|
||||
"dhcp_enable": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b",
|
||||
"dhcp_disable": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3059\u308b",
|
||||
"dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002",
|
||||
"dhcp_leases": "DHCP\u5272\u5f53",
|
||||
"dhcp_leases_not_found": "DHCP\u5272\u5f53\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"dhcp_config_saved": "DHCP\u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f",
|
||||
"form_error_required": "\u5fc5\u9808\u9805\u76ee\u3067\u3059",
|
||||
"form_error_ip_format": "IPv4\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"form_error_positive": "0\u3088\u308a\u5927\u304d\u3044\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
|
||||
"dhcp_form_gateway_input": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4IP",
|
||||
"dhcp_form_subnet_input": "\u30b5\u30d6\u30cd\u30c3\u30c8\u30de\u30b9\u30af",
|
||||
"dhcp_form_range_title": "IP\u30a2\u30c9\u30ec\u30b9\u306e\u7bc4\u56f2",
|
||||
"dhcp_form_range_start": "\u7bc4\u56f2\u306e\u958b\u59cb",
|
||||
"dhcp_form_range_end": "\u7bc4\u56f2\u306e\u7d42\u4e86",
|
||||
"dhcp_form_lease_title": "DHCP\u5272\u5f53\u6642\u9593\uff08\u79d2\u5358\u4f4d\uff09",
|
||||
"dhcp_form_lease_input": "\u5272\u5f53\u671f\u9593",
|
||||
"back": "\u623b\u308b",
|
||||
"dashboard": "\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9",
|
||||
"settings": "\u8a2d\u5b9a",
|
||||
"filters": "\u30d5\u30a3\u30eb\u30bf",
|
||||
"query_log": "\u30af\u30a8\u30ea\u30fb\u30ed\u30b0",
|
||||
"faq": "FAQ",
|
||||
"faq": "\u3088\u304f\u3042\u308b\u8cea\u554f",
|
||||
"version": "\u30d0\u30fc\u30b8\u30e7\u30f3",
|
||||
"address": "\u30a2\u30c9\u30ec\u30b9",
|
||||
"on": "\u30aa\u30f3",
|
||||
|
@ -16,18 +38,18 @@
|
|||
"enabled_protection": "\u4fdd\u8b77\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"disable_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3059\u308b",
|
||||
"disabled_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"refresh_statics": "\u7d71\u8a08\u30c7\u30fc\u30bf\u3092\u66f4\u65b0\u3059\u308b",
|
||||
"refresh_statics": "\u7d71\u8a08\u30c7\u30fc\u30bf\u3092\u6700\u65b0\u306b\u3059\u308b",
|
||||
"dns_query": "DNS\u30af\u30a8\u30ea",
|
||||
"blocked_by": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea\u30d5\u30a3\u30eb\u30bf",
|
||||
"blocked_by": "\u30d5\u30a3\u30eb\u30bf\u306b\u3088\u308a\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea",
|
||||
"stats_malware_phishing": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30de\u30eb\u30a6\u30a7\u30a2\uff0f\u30d5\u30a3\u30c3\u30b7\u30f3\u30b0",
|
||||
"stats_adult": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30a2\u30c0\u30eb\u30c8\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8",
|
||||
"stats_query_domain": "\u6700\u3082\u554f\u5408\u305b\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3",
|
||||
"for_last_24_hours": "\u904e\u53bb24\u6642\u9593\u4ee5\u5185",
|
||||
"no_domains_found": "\u30c9\u30e1\u30a4\u30f3\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f",
|
||||
"no_domains_found": "\u30c9\u30e1\u30a4\u30f3\u60c5\u5831\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"requests_count": "\u30ea\u30af\u30a8\u30b9\u30c8\u6570",
|
||||
"top_blocked_domains": "\u6700\u3082\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3",
|
||||
"top_clients": "\u30c8\u30c3\u30d7\u30af\u30e9\u30a4\u30a2\u30f3\u30c8",
|
||||
"no_clients_found": "\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f",
|
||||
"no_clients_found": "\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u60c5\u5831\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"general_statistics": "\u5168\u822c\u7684\u306a\u7d71\u8a08",
|
||||
"number_of_dns_query_24_hours": "\u904e\u53bb24\u6642\u9593\u306b\u51e6\u7406\u3055\u308c\u305fDNS\u30af\u30a8\u30ea\u306e\u6570",
|
||||
"number_of_dns_query_blocked_24_hours": "\u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d6\u30ed\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u3088\u3063\u3066\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u6570",
|
||||
|
@ -39,18 +61,18 @@
|
|||
"average_processing_time_hint": "DNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u51e6\u7406\u306b\u304b\u304b\u308b\u5e73\u5747\u6642\u9593\uff08\u30df\u30ea\u79d2\u5358\u4f4d\uff09",
|
||||
"block_domain_use_filters_and_hosts": "\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d5\u30a1\u30a4\u30eb\u3092\u4f7f\u7528\u3057\u3066\u30c9\u30e1\u30a4\u30f3\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b",
|
||||
"filters_block_toggle_hint": "<a href='#filters'>\u30d5\u30a3\u30eb\u30bf<\/a>\u306e\u8a2d\u5b9a\u3067\u30d6\u30ed\u30c3\u30af\u3059\u308b\u30eb\u30fc\u30eb\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002",
|
||||
"use_adguard_browsing_sec": "AdGuard\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3Web\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home\u306f\u3001\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3Web\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u3063\u3066\u30c9\u30e1\u30a4\u30f3\u304c\u30d6\u30e9\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u3053\u308c\u306f\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3092\u8003\u616e\u3057\u305fAPI\u3092\u4f7f\u7528\u3057\u3066\u30c1\u30a7\u30c3\u30af\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002\u30c9\u30e1\u30a4\u30f3\u540dSHA256\u30cf\u30c3\u30b7\u30e5\u306e\u77ed\u3044\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306e\u307f\u304c\u30b5\u30fc\u30d0\u30fc\u306b\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002",
|
||||
"use_adguard_parental": "AdGuard\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30ebWeb\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b",
|
||||
"use_adguard_parental_hint": "AdGuard Home\u306f\u3001\u30c9\u30e1\u30a4\u30f3\u306b\u30a2\u30c0\u30eb\u30c8\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u542b\u307e\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3Web\u30b5\u30fc\u30d3\u30b9\u3068\u540c\u3058\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u306b\u512a\u3057\u3044API\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002",
|
||||
"use_adguard_browsing_sec": "AdGuard\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home\u306f\u3001\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u3063\u3066\u30c9\u30e1\u30a4\u30f3\u304c\u30d6\u30e9\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u3053\u308c\u306f\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3092\u8003\u616e\u3057\u305fAPI\u3092\u4f7f\u7528\u3057\u3066\u30c1\u30a7\u30c3\u30af\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002\u30c9\u30e1\u30a4\u30f3\u540dSHA256\u30cf\u30c3\u30b7\u30e5\u306e\u77ed\u3044\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306e\u307f\u304c\u30b5\u30fc\u30d0\u306b\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002",
|
||||
"use_adguard_parental": "AdGuard\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b",
|
||||
"use_adguard_parental_hint": "AdGuard Home\u306f\u3001\u30c9\u30e1\u30a4\u30f3\u306b\u30a2\u30c0\u30eb\u30c8\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u542b\u307e\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3068\u540c\u3058\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u306b\u512a\u3057\u3044API\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002",
|
||||
"enforce_safe_search": "\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u5f37\u5236\u3059\u308b",
|
||||
"enforce_save_search_hint": "AdGuard Home\u306f\u3001Google\u3001Youtube\u3001Bing\u3001Yandex\u306e\u691c\u7d22\u30a8\u30f3\u30b8\u30f3\u3067\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u5f37\u5236\u3067\u304d\u307e\u3059\u3002",
|
||||
"no_servers_specified": "\u30b5\u30fc\u30d0\u30fc\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093",
|
||||
"no_servers_specified": "\u30b5\u30fc\u30d0\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093",
|
||||
"no_settings": "\u8a2d\u5b9a\u306a\u3057",
|
||||
"general_settings": "\u4e00\u822c\u8a2d\u5b9a",
|
||||
"upstream_dns": "\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0DNS\u30b5\u30fc\u30d0\u30fc",
|
||||
"upstream_dns_hint": "\u3053\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u672a\u5165\u529b\u306e\u307e\u307e\u306b\u3059\u308b\u3068\u3001AdGuard Home\u306f<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u3092\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0\u3068\u3057\u3066\u4f7f\u7528\u3057\u307e\u3059\u3002DNS over TLS\u30b5\u30fc\u30d0\u30fc\u306b\u306f\u3001\uff62tls:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
|
||||
"test_upstream_btn": "\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0\u30b5\u30fc\u30d0\u30fc\u3092\u30c6\u30b9\u30c8\u3059\u308b",
|
||||
"upstream_dns": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0",
|
||||
"upstream_dns_hint": "\u3053\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u672a\u5165\u529b\u306e\u307e\u307e\u306b\u3059\u308b\u3068\u3001AdGuard Home\u306f\u4e0a\u6d41\u3068\u3057\u3066<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002DNS over TLS\u30b5\u30fc\u30d0\u306b\u306f\u3001\uff62tls:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
|
||||
"test_upstream_btn": "\u4e0a\u6d41\u30b5\u30fc\u30d0\u3092\u30c6\u30b9\u30c8\u3059\u308b",
|
||||
"apply_btn": "\u9069\u7528\u3059\u308b",
|
||||
"disabled_filtering_toast": "\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"enabled_filtering_toast": "\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
|
@ -66,7 +88,7 @@
|
|||
"rules_count_table_header": "\u30eb\u30fc\u30eb\u6570",
|
||||
"last_time_updated_table_header": "\u6700\u7d42\u66f4\u65b0\u6642\u523b",
|
||||
"actions_table_header": "\u64cd\u4f5c",
|
||||
"delete_table_action": "\u524a\u9664",
|
||||
"delete_table_action": "\u524a\u9664\u3059\u308b",
|
||||
"filters_and_hosts": "\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d6\u30ed\u30c3\u30af\u30ea\u30b9\u30c8",
|
||||
"filters_and_hosts_hint": "AdGuard Home\u306f\u3001\u57fa\u672c\u7684\u306a\u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30eb\u30fc\u30eb\u3068hosts\u30d5\u30a1\u30a4\u30eb\u306e\u69cb\u6587\u3092\u7406\u89e3\u3057\u307e\u3059\u3002",
|
||||
"no_filters_added": "\u30d5\u30a3\u30eb\u30bf\u306f\u8ffd\u52a0\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f",
|
||||
|
@ -82,17 +104,18 @@
|
|||
"examples_title": "\u4f8b",
|
||||
"example_meaning_filter_block": "example.org\u30c9\u30e1\u30a4\u30f3\u3068\u305d\u306e\u3059\u3079\u3066\u306e\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b",
|
||||
"example_meaning_filter_whitelist": "example.org\u30c9\u30e1\u30a4\u30f3\u3068\u305d\u306e\u3059\u3079\u3066\u306e\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3078\u306e\u30a2\u30af\u30bb\u30b9\u306e\u30d6\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3059\u308b",
|
||||
"example_meaning_host_block": "AdGuard Home\u306f\u3001example.org\u30c9\u30e1\u30a4\u30f3\uff08\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3092\u9664\u304f\uff09\u306b\u5bfe\u3057\u3066127.0.0.1\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u8fd4\u3059\u3088\u3046\u306b\u306a\u308a\u307e\u3057\u305f\u3002",
|
||||
"example_meaning_host_block": "AdGuard Home\u306f\u3001example.org\u30c9\u30e1\u30a4\u30f3\uff08\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3092\u9664\u304f\uff09\u306b\u5bfe\u3057\u3066127.0.0.1\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u8fd4\u3059\u3088\u3046\u306b\u306a\u308a\u307e\u3059\u3002",
|
||||
"example_comment": "! \u3053\u3053\u306b\u306f\u30b3\u30e1\u30f3\u30c8\u304c\u5165\u308a\u307e\u3059",
|
||||
"example_comment_meaning": "\u305f\u3060\u306e\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
|
||||
"example_comment_hash": "# \u3053\u3053\u3082\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
|
||||
"example_upstream_regular": "\u901a\u5e38\u306eDNS\uff08UDP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
|
||||
"example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u307e\u305f\u306f <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> \u3092\u4f7f\u3048\u307e\u3059",
|
||||
"example_upstream_tcp": "\u901a\u5e38\u306eDNS\uff08TCP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
|
||||
"all_filters_up_to_date_toast": "\u3059\u3079\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u306f\u65e2\u306b\u6700\u65b0\u3067\u3059",
|
||||
"updated_upstream_dns_toast": "\u30a2\u30c3\u30d7\u30b9\u30c8\u30ea\u30fc\u30e0DNS\u30b5\u30fc\u30d0\u30fc\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f",
|
||||
"dns_test_ok_toast": "\u6307\u5b9a\u3055\u308c\u305fDNS\u30b5\u30fc\u30d0\u30fc\u306f\u6b63\u3057\u304f\u52d5\u4f5c\u3057\u3066\u3044\u307e\u3059",
|
||||
"updated_upstream_dns_toast": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f",
|
||||
"dns_test_ok_toast": "\u6307\u5b9a\u3055\u308c\u305fDNS\u30b5\u30fc\u30d0\u306f\u6b63\u3057\u304f\u52d5\u4f5c\u3057\u3066\u3044\u307e\u3059",
|
||||
"dns_test_not_ok_toast": "\u30b5\u30fc\u30d0 \"{{key}}\": \u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u6b63\u3057\u304f\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044",
|
||||
"unblock_btn": "\u30d6\u30ed\u30c3\u30af\u89e3\u9664",
|
||||
"block_btn": "\u30d6\u30ed\u30c3\u30af",
|
||||
|
@ -104,7 +127,7 @@
|
|||
"empty_response_status": "\u672a\u5b9a\u7fa9",
|
||||
"show_all_filter_type": "\u3059\u3079\u3066\u8868\u793a",
|
||||
"show_filtered_type": "\u30d5\u30a3\u30eb\u30bf\u3055\u308c\u305f\u30ed\u30b0\u3092\u8868\u793a",
|
||||
"no_logs_found": "\u30ed\u30b0\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f",
|
||||
"no_logs_found": "\u30ed\u30b0\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"disabled_log_btn": "\u30ed\u30b0\u3092\u7121\u52b9\u306b\u3059\u308b",
|
||||
"download_log_file_btn": "\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b",
|
||||
"refresh_btn": "\u6700\u65b0\u306b\u3059\u308b",
|
||||
|
@ -125,5 +148,6 @@
|
|||
"found_in_known_domain_db": "\u65e2\u77e5\u306e\u30c9\u30e1\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002",
|
||||
"category_label": "\u30ab\u30c6\u30b4\u30ea",
|
||||
"rule_label": "\u30eb\u30fc\u30eb",
|
||||
"filter_label": "\u30d5\u30a3\u30eb\u30bf"
|
||||
"filter_label": "\u30d5\u30a3\u30eb\u30bf",
|
||||
"unknown_filter": "\u4e0d\u660e\u306a\u30d5\u30a3\u30eb\u30bf {{filterId}}"
|
||||
}
|
|
@ -1,4 +1,26 @@
|
|||
{
|
||||
"refresh_status": "Atualizar status",
|
||||
"save_config": "Salvar configura\u00e7\u00e3o",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
"disabled_dhcp": "Servidor DHCP desativado",
|
||||
"dhcp_title": "Servidor DHCP",
|
||||
"dhcp_description": "Se o seu roteador n\u00e3o fornecer configura\u00e7\u00f5es de DHCP, voc\u00ea poder\u00e1 usar o servidor DHCP integrado do AdGuard.",
|
||||
"dhcp_enable": "Ativar servidor DHCP",
|
||||
"dhcp_disable": "Desativar servidor DHCP",
|
||||
"dhcp_not_found": "Nenhum servidor DHCP ativo foi encontrado na sua rede. \u00c9 seguro ativar o servidor DHCP integrado.",
|
||||
"dhcp_leases": "Concess\u00f5es DHCP",
|
||||
"dhcp_leases_not_found": "Nenhuma concess\u00e3o DHCP encontrada",
|
||||
"dhcp_config_saved": "Salvar configura\u00e7\u00f5es do servidor DHCP",
|
||||
"form_error_required": "Campo obrigat\u00f3rio",
|
||||
"form_error_ip_format": "formato de endere\u00e7o IPv4 inv\u00e1lido",
|
||||
"form_error_positive": "Deve ser maior que 0",
|
||||
"dhcp_form_gateway_input": "IP do gateway",
|
||||
"dhcp_form_subnet_input": "M\u00e1scara de sub-rede",
|
||||
"dhcp_form_range_title": "Faixa de endere\u00e7os IP",
|
||||
"dhcp_form_range_start": "In\u00edcio da faixa",
|
||||
"dhcp_form_range_end": "Final da faixa",
|
||||
"dhcp_form_lease_title": "Tempo de concess\u00e3o do DHCP (em segundos)",
|
||||
"dhcp_form_lease_input": "Dura\u00e7\u00e3o da concess\u00e3o",
|
||||
"back": "Voltar",
|
||||
"dashboard": "Painel",
|
||||
"settings": "Configura\u00e7\u00f5es",
|
||||
|
@ -18,7 +40,7 @@
|
|||
"disabled_protection": "Prote\u00e7\u00e3o desativada",
|
||||
"refresh_statics": "Atualizar estat\u00edsticas",
|
||||
"dns_query": "Consultas de DNS",
|
||||
"blocked_by": "Bloqueador por Filtros",
|
||||
"blocked_by": "Bloqueador por filtros",
|
||||
"stats_malware_phishing": "Bloqueado malware\/phishing",
|
||||
"stats_adult": "Bloqueado sites adultos",
|
||||
"stats_query_domain": "Principais dom\u00ednios consultados",
|
||||
|
@ -89,6 +111,7 @@
|
|||
"example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)",
|
||||
"example_upstream_dot": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>atrav\u00e9s do TLS<\/a>",
|
||||
"example_upstream_doh": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>atrav\u00e9s do HTTPS<\/a>",
|
||||
"example_upstream_sdns": "Voc\u00ea pode usar <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a>para o<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a>ou usar resolvedores<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-sobre-HTTPS<\/a>",
|
||||
"example_upstream_tcp": "DNS regular (atrav\u00e9s do TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos os filtros j\u00e1 est\u00e3o atualizados",
|
||||
"updated_upstream_dns_toast": "Atualizado os servidores DNS upstream",
|
||||
|
@ -125,5 +148,6 @@
|
|||
"found_in_known_domain_db": "Encontrado no banco de dados de dom\u00ednios conhecidos.",
|
||||
"category_label": "Categoria",
|
||||
"rule_label": "Regra",
|
||||
"filter_label": "Filtro"
|
||||
"filter_label": "Filtro",
|
||||
"unknown_filter": "Filtro desconhecido {{filterId}}"
|
||||
}
|
|
@ -1,4 +1,26 @@
|
|||
{
|
||||
"refresh_status": "Uppdatera status",
|
||||
"save_config": "Spara inst\u00e4llningar",
|
||||
"enabled_dhcp": "DHCP-server aktiverad",
|
||||
"disabled_dhcp": "Dhcp-server avaktiverad",
|
||||
"dhcp_title": "DHCP-server",
|
||||
"dhcp_description": "Om din router inte har inst\u00e4llningar f\u00f6r DHCP kan du anv\u00e4nda AdGuards inbyggda server.",
|
||||
"dhcp_enable": "Aktivera DHCP.-server",
|
||||
"dhcp_disable": "Avaktivera DHCP-server",
|
||||
"dhcp_not_found": "Ingen aktiv DHCP-server hittades i n\u00e4tverkat.",
|
||||
"dhcp_leases": "DHCP-lease",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
||||
"dhcp_config_saved": "Sparade inst\u00e4llningar f\u00f6r DHCP-servern",
|
||||
"form_error_required": "Obligatoriskt f\u00e4lt",
|
||||
"form_error_ip_format": "Ogiltigt IPv4-format",
|
||||
"form_error_positive": "M\u00e5ste vara st\u00f6rre \u00e4n noll",
|
||||
"dhcp_form_gateway_input": "Gateway-IP",
|
||||
"dhcp_form_subnet_input": "Subnetmask",
|
||||
"dhcp_form_range_title": "IP-adressgr\u00e4nser",
|
||||
"dhcp_form_range_start": "Startgr\u00e4ns",
|
||||
"dhcp_form_range_end": "Gr\u00e4nsslut",
|
||||
"dhcp_form_lease_title": "DHCP-leasetid (i sekunder)",
|
||||
"dhcp_form_lease_input": "Leasetid",
|
||||
"back": "Tiilbaka",
|
||||
"dashboard": "Kontrollpanel",
|
||||
"settings": "Inst\u00e4llningar",
|
||||
|
@ -18,7 +40,7 @@
|
|||
"disabled_protection": "Kopplade bort skydd",
|
||||
"refresh_statics": "Uppdatera statistik",
|
||||
"dns_query": "DNS-f\u00f6rfr\u00e5gningar",
|
||||
"blocked_by": "Blockerat av Filter",
|
||||
"blocked_by": "Blockerat av filter",
|
||||
"stats_malware_phishing": "Blockerad skadekod\/phising",
|
||||
"stats_adult": "Blockerade vuxensajter",
|
||||
"stats_query_domain": "Mest efters\u00f6kta dom\u00e4ner",
|
||||
|
@ -89,6 +111,7 @@
|
|||
"example_upstream_regular": "vanlig DNS (\u00f6ver UDP)",
|
||||
"example_upstream_dot": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "Du kan anv\u00e4nda <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS-stamps<\/a> f\u00f6r <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> eller <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u00f6ver-HTTPS<\/a>\n-resolvers",
|
||||
"example_upstream_tcp": "vanlig DNS (\u00f6ver UDP)",
|
||||
"all_filters_up_to_date_toast": "Alla filter \u00e4r redan aktuella",
|
||||
"updated_upstream_dns_toast": "Uppdaterade uppstr\u00f6ms-dns-servrar",
|
||||
|
@ -125,5 +148,6 @@
|
|||
"found_in_known_domain_db": "Hittad i dom\u00e4ndatabas.",
|
||||
"category_label": "Kategori",
|
||||
"rule_label": "Regel",
|
||||
"filter_label": "Filter"
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Ok\u00e4nt filter {{filterId}}"
|
||||
}
|
|
@ -522,3 +522,130 @@ export const getLanguage = () => async (dispatch) => {
|
|||
dispatch(getLanguageFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getDhcpStatusRequest = createAction('GET_DHCP_STATUS_REQUEST');
|
||||
export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS');
|
||||
export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
|
||||
|
||||
export const getDhcpStatus = () => async (dispatch) => {
|
||||
dispatch(getDhcpStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getDhcpStatus();
|
||||
dispatch(getDhcpStatusSuccess(status));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDhcpStatusFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const getDhcpInterfacesRequest = createAction('GET_DHCP_INTERFACES_REQUEST');
|
||||
export const getDhcpInterfacesSuccess = createAction('GET_DHCP_INTERFACES_SUCCESS');
|
||||
export const getDhcpInterfacesFailure = createAction('GET_DHCP_INTERFACES_FAILURE');
|
||||
|
||||
export const getDhcpInterfaces = () => async (dispatch) => {
|
||||
dispatch(getDhcpInterfacesRequest());
|
||||
try {
|
||||
const interfaces = await apiClient.getDhcpInterfaces();
|
||||
dispatch(getDhcpInterfacesSuccess(interfaces));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDhcpInterfacesFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
|
||||
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
|
||||
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
|
||||
|
||||
export const findActiveDhcp = name => async (dispatch) => {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
|
||||
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
|
||||
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
|
||||
|
||||
// TODO rewrite findActiveDhcp part
|
||||
export const setDhcpConfig = config => async (dispatch) => {
|
||||
dispatch(setDhcpConfigRequest());
|
||||
try {
|
||||
if (config.interface_name) {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
|
||||
if (!activeDhcp.found) {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
dispatch(setDhcpConfigSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
} else {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
dispatch(setDhcpConfigSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDhcpConfigFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
|
||||
export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
|
||||
export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
|
||||
|
||||
// TODO rewrite findActiveDhcp part
|
||||
export const toggleDhcp = config => async (dispatch) => {
|
||||
dispatch(toggleDhcpRequest());
|
||||
|
||||
if (config.enabled) {
|
||||
dispatch(addSuccessToast('disabled_dhcp'));
|
||||
try {
|
||||
await apiClient.setDhcpConfig({ ...config, enabled: false });
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
}
|
||||
} else {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
|
||||
if (!activeDhcp.found) {
|
||||
try {
|
||||
await apiClient.setDhcpConfig({ ...config, enabled: true });
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
}
|
||||
dispatch(addSuccessToast('enabled_dhcp'));
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -302,4 +302,38 @@ export default class Api {
|
|||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// DHCP
|
||||
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
|
||||
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
|
||||
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
|
||||
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
|
||||
|
||||
getDhcpStatus() {
|
||||
const { path, method } = this.DHCP_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getDhcpInterfaces() {
|
||||
const { path, method } = this.DHCP_INTERFACES;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setDhcpConfig(config) {
|
||||
const { path, method } = this.DHCP_SET_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
findActiveDhcp(name) {
|
||||
const { path, method } = this.DHCP_FIND_ACTIVE;
|
||||
const parameters = {
|
||||
data: name,
|
||||
headers: { 'Content-Type': 'text/plain' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { R_IPV4 } from '../../../helpers/constants';
|
||||
|
||||
const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
|
||||
const ipv4 = (value) => {
|
||||
if (value && !new RegExp(R_IPV4).test(value)) {
|
||||
return <Trans>form_error_ip_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isPositive = (value) => {
|
||||
if ((value || value === 0) && (value <= 0)) {
|
||||
return <Trans>form_error_positive</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const toNumber = value => value && parseInt(value, 10);
|
||||
|
||||
const renderField = ({
|
||||
input, className, placeholder, type, disabled, meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<input
|
||||
{...input}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
pristine,
|
||||
submitting,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<Field
|
||||
name="gateway_ip"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_gateway_input')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--dhcp">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<Field
|
||||
name="subnet_mask"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_subnet_input')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="range_start"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_range_start')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="range_end"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_range_end')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__group form__group--dhcp">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="lease_duration"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_lease_input')}
|
||||
validate={[required, isPositive]}
|
||||
normalize={toNumber}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standart"
|
||||
disabled={pristine || submitting}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
pristine: PropTypes.bool,
|
||||
submitting: PropTypes.bool,
|
||||
interfaces: PropTypes.object,
|
||||
processing: PropTypes.bool,
|
||||
initialValues: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({ form: 'dhcpForm' }),
|
||||
])(Form);
|
|
@ -0,0 +1,113 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
const renderInterfaces = (interfaces => (
|
||||
Object.keys(interfaces).map((item) => {
|
||||
const option = interfaces[item];
|
||||
const { name } = option;
|
||||
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
|
||||
let interfaceIP = option.ip_addresses[0];
|
||||
|
||||
if (!onlyIPv6) {
|
||||
option.ip_addresses.forEach((ip) => {
|
||||
if (!ip.includes(':')) {
|
||||
interfaceIP = ip;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<option value={name} key={name} disabled={onlyIPv6}>
|
||||
{name} - {interfaceIP}
|
||||
</option>
|
||||
);
|
||||
})
|
||||
));
|
||||
|
||||
const renderInterfaceValues = (interfaceValues => (
|
||||
<ul className="list-unstyled mt-1 mb-0">
|
||||
<li>
|
||||
<span className="interface__title">MTU: </span>
|
||||
{interfaceValues.mtu}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
|
||||
{interfaceValues.hardware_address}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
|
||||
{
|
||||
interfaceValues.ip_addresses
|
||||
.map(ip => <span key={ip} className="interface__ip">{ip}</span>)
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
));
|
||||
|
||||
let Interface = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleChange,
|
||||
interfaces,
|
||||
processing,
|
||||
interfaceValue,
|
||||
enabled,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form>
|
||||
{!processing && interfaces &&
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-md-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<label>{t('dhcp_interface_select')}</label>
|
||||
<Field
|
||||
name="interface_name"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="" disabled={enabled}>{t('dhcp_interface_select')}</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
{interfaceValue &&
|
||||
<div className="col-sm-12 col-md-6">
|
||||
{renderInterfaceValues(interfaces[interfaceValue])}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Interface.propTypes = {
|
||||
handleChange: PropTypes.func,
|
||||
interfaces: PropTypes.object,
|
||||
processing: PropTypes.bool,
|
||||
interfaceValue: PropTypes.string,
|
||||
initialValues: PropTypes.object,
|
||||
enabled: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('dhcpInterface');
|
||||
|
||||
Interface = connect((state) => {
|
||||
const interfaceValue = selector(state, 'interface_name');
|
||||
return {
|
||||
interfaceValue,
|
||||
};
|
||||
})(Interface);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({ form: 'dhcpInterface' }),
|
||||
])(Interface);
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
const columns = [{
|
||||
Header: 'MAC',
|
||||
accessor: 'mac',
|
||||
}, {
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Hostname',
|
||||
accessor: 'hostname',
|
||||
}, {
|
||||
Header: 'Expires',
|
||||
accessor: 'expires',
|
||||
}];
|
||||
|
||||
const Leases = props => (
|
||||
<ReactTable
|
||||
data={props.leases || []}
|
||||
columns={columns}
|
||||
showPagination={false}
|
||||
noDataText={ props.t('dhcp_leases_not_found') }
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
/>
|
||||
);
|
||||
|
||||
Leases.propTypes = {
|
||||
leases: PropTypes.array,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Leases);
|
|
@ -0,0 +1,159 @@
|
|||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Form from './Form';
|
||||
import Leases from './Leases';
|
||||
import Interface from './Interface';
|
||||
import Card from '../../ui/Card';
|
||||
|
||||
class Dhcp extends Component {
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setDhcpConfig(values);
|
||||
};
|
||||
|
||||
handleFormChange = (value) => {
|
||||
this.props.setDhcpConfig(value);
|
||||
}
|
||||
|
||||
handleToggle = (config) => {
|
||||
this.props.toggleDhcp(config);
|
||||
this.props.findActiveDhcp(config.interface_name);
|
||||
}
|
||||
|
||||
getToggleDhcpButton = () => {
|
||||
const { config, active } = this.props.dhcp;
|
||||
const activeDhcpFound = active && active.found;
|
||||
const filledConfig = Object.keys(config).every((key) => {
|
||||
if (key === 'enabled') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return config[key];
|
||||
});
|
||||
|
||||
if (config.enabled) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standart mr-2 btn-gray"
|
||||
onClick={() => this.props.toggleDhcp(config)}
|
||||
>
|
||||
<Trans>dhcp_disable</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standart mr-2 btn-success"
|
||||
onClick={() => this.handleToggle(config)}
|
||||
disabled={!filledConfig || activeDhcpFound}
|
||||
>
|
||||
<Trans>dhcp_enable</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
getActiveDhcpMessage = () => {
|
||||
const { active } = this.props.dhcp;
|
||||
|
||||
if (active) {
|
||||
if (active.error) {
|
||||
return (
|
||||
<div className="text-danger">
|
||||
{active.error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{active.found ? (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_found</Trans>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-secondary">
|
||||
<Trans>dhcp_not_found</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t, dhcp } = this.props;
|
||||
const statusButtonClass = classnames({
|
||||
'btn btn-primary btn-standart': true,
|
||||
'btn btn-primary btn-standart btn-loading': dhcp.processingStatus,
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Card title={ t('dhcp_title') } subtitle={ t('dhcp_description') } bodyType="card-body box-body--settings">
|
||||
<div className="dhcp">
|
||||
{!dhcp.processing &&
|
||||
<Fragment>
|
||||
<Interface
|
||||
onChange={this.handleFormChange}
|
||||
initialValues={dhcp.config}
|
||||
interfaces={dhcp.interfaces}
|
||||
processing={dhcp.processingInterfaces}
|
||||
enabled={dhcp.config.enabled}
|
||||
/>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={dhcp.config}
|
||||
interfaces={dhcp.interfaces}
|
||||
processing={dhcp.processingInterfaces}
|
||||
/>
|
||||
<hr/>
|
||||
<div className="card-actions mb-3">
|
||||
{this.getToggleDhcpButton()}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() =>
|
||||
this.props.findActiveDhcp(dhcp.config.interface_name)
|
||||
}
|
||||
disabled={!dhcp.config.interface_name}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{this.getActiveDhcpMessage()}
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Card>
|
||||
{!dhcp.processing && dhcp.config.enabled &&
|
||||
<Card title={ t('dhcp_leases') } bodyType="card-body box-body--settings">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={dhcp.leases} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dhcp.propTypes = {
|
||||
dhcp: PropTypes.object,
|
||||
toggleDhcp: PropTypes.func,
|
||||
getDhcpStatus: PropTypes.func,
|
||||
setDhcpConfig: PropTypes.func,
|
||||
findActiveDhcp: PropTypes.func,
|
||||
handleSubmit: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Dhcp);
|
|
@ -1,4 +1,5 @@
|
|||
.form__group {
|
||||
position: relative;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
|
@ -6,6 +7,10 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form__group--dhcp:last-child {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn-standart {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
@ -18,3 +23,28 @@
|
|||
.form-control--textarea-large {
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
.form__message {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.form__message--error {
|
||||
color: #cd201f;
|
||||
}
|
||||
|
||||
.interface__title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.interface__ip:after {
|
||||
content: ", ";
|
||||
}
|
||||
|
||||
.interface__ip:last-child:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.dhcp {
|
||||
min-height: 450px;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import Upstream from './Upstream';
|
||||
import Dhcp from './Dhcp';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
|
@ -34,6 +35,8 @@ class Settings extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
this.props.initSettings(this.settings);
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
}
|
||||
|
||||
handleUpstreamChange = (value) => {
|
||||
|
@ -92,6 +95,13 @@ class Settings extends Component {
|
|||
handleUpstreamSubmit={this.handleUpstreamSubmit}
|
||||
handleUpstreamTest={this.handleUpstreamTest}
|
||||
/>
|
||||
<Dhcp
|
||||
dhcp={this.props.dhcp}
|
||||
toggleDhcp={this.props.toggleDhcp}
|
||||
getDhcpStatus={this.props.getDhcpStatus}
|
||||
findActiveDhcp={this.props.findActiveDhcp}
|
||||
setDhcpConfig={this.props.setDhcpConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { initSettings, toggleSetting, handleUpstreamChange, setUpstream, testUpstream, addErrorToast } from '../actions';
|
||||
import {
|
||||
initSettings,
|
||||
toggleSetting,
|
||||
handleUpstreamChange,
|
||||
setUpstream,
|
||||
testUpstream,
|
||||
addErrorToast,
|
||||
toggleDhcp,
|
||||
getDhcpStatus,
|
||||
getDhcpInterfaces,
|
||||
setDhcpConfig,
|
||||
findActiveDhcp,
|
||||
} from '../actions';
|
||||
import Settings from '../components/Settings';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { settings, dashboard } = state;
|
||||
const props = { settings, dashboard };
|
||||
const { settings, dashboard, dhcp } = state;
|
||||
const props = { settings, dashboard, dhcp };
|
||||
return props;
|
||||
};
|
||||
|
||||
|
@ -15,6 +27,11 @@ const mapDispatchToProps = {
|
|||
setUpstream,
|
||||
testUpstream,
|
||||
addErrorToast,
|
||||
toggleDhcp,
|
||||
getDhcpStatus,
|
||||
getDhcpInterfaces,
|
||||
setDhcpConfig,
|
||||
findActiveDhcp,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/;
|
||||
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/g;
|
||||
|
||||
export const STATS_NAMES = {
|
||||
avg_processing_time: 'average_processing_time',
|
||||
|
|
|
@ -2,6 +2,7 @@ import { combineReducers } from 'redux';
|
|||
import { handleActions } from 'redux-actions';
|
||||
import { loadingBarReducer } from 'react-redux-loading-bar';
|
||||
import nanoid from 'nanoid';
|
||||
import { reducer as formReducer } from 'redux-form';
|
||||
import versionCompare from '../helpers/versionCompare';
|
||||
|
||||
import * as actions from '../actions';
|
||||
|
@ -35,6 +36,7 @@ const settings = handleActions({
|
|||
processing: true,
|
||||
processingTestUpstream: false,
|
||||
processingSetUpstream: false,
|
||||
processingDhcpStatus: false,
|
||||
});
|
||||
|
||||
const dashboard = handleActions({
|
||||
|
@ -258,11 +260,61 @@ const toasts = handleActions({
|
|||
},
|
||||
}, { notices: [] });
|
||||
|
||||
const dhcp = handleActions({
|
||||
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.getDhcpStatusSuccess]: (state, { payload }) => {
|
||||
const newState = {
|
||||
...state,
|
||||
...payload,
|
||||
processing: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getDhcpInterfacesRequest]: state => ({ ...state, processingInterfaces: true }),
|
||||
[actions.getDhcpInterfacesFailure]: state => ({ ...state, processingInterfaces: false }),
|
||||
[actions.getDhcpInterfacesSuccess]: (state, { payload }) => {
|
||||
const newState = {
|
||||
...state,
|
||||
interfaces: payload,
|
||||
processingInterfaces: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.findActiveDhcpRequest]: state => ({ ...state, processingStatus: true }),
|
||||
[actions.findActiveDhcpFailure]: state => ({ ...state, processingStatus: false }),
|
||||
[actions.findActiveDhcpSuccess]: (state, { payload }) => ({
|
||||
...state,
|
||||
active: payload,
|
||||
processingStatus: false,
|
||||
}),
|
||||
|
||||
[actions.toggleDhcpSuccess]: (state) => {
|
||||
const { config } = state;
|
||||
const newConfig = { ...config, enabled: !config.enabled };
|
||||
const newState = { ...state, config: newConfig };
|
||||
return newState;
|
||||
},
|
||||
}, {
|
||||
processing: true,
|
||||
processingStatus: false,
|
||||
processingInterfaces: false,
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
active: null,
|
||||
leases: [],
|
||||
});
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
dashboard,
|
||||
queryLogs,
|
||||
filtering,
|
||||
toasts,
|
||||
dhcp,
|
||||
loadingBar: loadingBarReducer,
|
||||
form: formReducer,
|
||||
});
|
||||
|
|
20
config.go
20
config.go
|
@ -2,13 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||
"github.com/hmage/golibs/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
@ -23,14 +24,15 @@ type configuration struct {
|
|||
ourConfigFilename string // Config filename (can be overriden via the command line arguments)
|
||||
ourBinaryDir string // Location of our directory, used to protect against CWD being somewhere else
|
||||
|
||||
BindHost string `yaml:"bind_host"`
|
||||
BindPort int `yaml:"bind_port"`
|
||||
AuthName string `yaml:"auth_name"`
|
||||
AuthPass string `yaml:"auth_pass"`
|
||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||
DNS dnsConfig `yaml:"dns"`
|
||||
Filters []filter `yaml:"filters"`
|
||||
UserRules []string `yaml:"user_rules"`
|
||||
BindHost string `yaml:"bind_host"`
|
||||
BindPort int `yaml:"bind_port"`
|
||||
AuthName string `yaml:"auth_name"`
|
||||
AuthPass string `yaml:"auth_pass"`
|
||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||
DNS dnsConfig `yaml:"dns"`
|
||||
Filters []filter `yaml:"filters"`
|
||||
UserRules []string `yaml:"user_rules"`
|
||||
DHCP dhcpd.ServerConfig `yaml:"dhcp"`
|
||||
|
||||
sync.RWMutex `yaml:"-"`
|
||||
|
||||
|
|
10
control.go
10
control.go
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -12,11 +11,10 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"gopkg.in/asaskevich/govalidator.v4"
|
||||
)
|
||||
|
||||
|
@ -719,4 +717,8 @@ func registerControlHandlers() {
|
|||
http.HandleFunc("/control/safesearch/enable", optionalAuth(ensurePOST(handleSafeSearchEnable)))
|
||||
http.HandleFunc("/control/safesearch/disable", optionalAuth(ensurePOST(handleSafeSearchDisable)))
|
||||
http.HandleFunc("/control/safesearch/status", optionalAuth(ensureGET(handleSafeSearchStatus)))
|
||||
http.HandleFunc("/control/dhcp/status", optionalAuth(ensureGET(handleDHCPStatus)))
|
||||
http.HandleFunc("/control/dhcp/interfaces", optionalAuth(ensureGET(handleDHCPInterfaces)))
|
||||
http.HandleFunc("/control/dhcp/set_config", optionalAuth(ensurePOST(handleDHCPSetConfig)))
|
||||
http.HandleFunc("/control/dhcp/find_active_dhcp", optionalAuth(ensurePOST(handleDHCPFindActiveServer)))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
)
|
||||
|
||||
var dhcpServer = dhcpd.Server{}
|
||||
|
||||
func handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
|
||||
rawLeases := dhcpServer.Leases()
|
||||
leases := []map[string]string{}
|
||||
for i := range rawLeases {
|
||||
lease := map[string]string{
|
||||
"mac": rawLeases[i].HWAddr.String(),
|
||||
"ip": rawLeases[i].IP.String(),
|
||||
"hostname": rawLeases[i].Hostname,
|
||||
"expires": rawLeases[i].Expiry.Format(time.RFC3339),
|
||||
}
|
||||
leases = append(leases, lease)
|
||||
|
||||
}
|
||||
status := map[string]interface{}{
|
||||
"config": config.DHCP,
|
||||
"leases": leases,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(status)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Unable to marshal DHCP status json: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
newconfig := dhcpd.ServerConfig{}
|
||||
err := json.NewDecoder(r.Body).Decode(&newconfig)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if newconfig.Enabled {
|
||||
err := dhcpServer.Start(&newconfig)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "Failed to start DHCP server: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !newconfig.Enabled {
|
||||
dhcpServer.Stop()
|
||||
}
|
||||
config.DHCP = newconfig
|
||||
httpUpdateConfigReloadDNSReturnOK(w, r)
|
||||
}
|
||||
|
||||
func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
response := map[string]interface{}{}
|
||||
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Couldn't get list of interfaces: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
type address struct {
|
||||
IP string
|
||||
Netmask string
|
||||
}
|
||||
|
||||
type responseInterface struct {
|
||||
Name string `json:"name"`
|
||||
MTU int `json:"mtu"`
|
||||
HardwareAddr string `json:"hardware_address"`
|
||||
Addresses []string `json:"ip_addresses"`
|
||||
}
|
||||
|
||||
for i := range ifaces {
|
||||
if ifaces[i].Flags&net.FlagLoopback != 0 {
|
||||
// it's a loopback, skip it
|
||||
continue
|
||||
}
|
||||
if ifaces[i].Flags&net.FlagBroadcast == 0 {
|
||||
// this interface doesn't support broadcast, skip it
|
||||
continue
|
||||
}
|
||||
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
|
||||
// this interface is ppp, don't do dhcp over it
|
||||
continue
|
||||
}
|
||||
iface := responseInterface{
|
||||
Name: ifaces[i].Name,
|
||||
MTU: ifaces[i].MTU,
|
||||
HardwareAddr: ifaces[i].HardwareAddr.String(),
|
||||
}
|
||||
addrs, err := ifaces[i].Addrs()
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %v: %s", ifaces[i].Name, err)
|
||||
return
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
iface.Addresses = append(iface.Addresses, addr.String())
|
||||
}
|
||||
if len(iface.Addresses) == 0 {
|
||||
// this interface has no addresses, skip it
|
||||
continue
|
||||
}
|
||||
response[ifaces[i].Name] = iface
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(response)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Failed to marshal json with available interfaces: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorText := fmt.Sprintf("failed to read request body: %s", err)
|
||||
log.Println(errorText)
|
||||
http.Error(w, errorText, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
interfaceName := strings.TrimSpace(string(body))
|
||||
if interfaceName == "" {
|
||||
errorText := fmt.Sprintf("empty interface name specified")
|
||||
log.Println(errorText)
|
||||
http.Error(w, errorText, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
found, err := dhcpd.CheckIfOtherDHCPServersPresent(interfaceName)
|
||||
result := map[string]interface{}{}
|
||||
if err != nil {
|
||||
result["error"] = err.Error()
|
||||
} else {
|
||||
result["found"] = found
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(result)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func startDHCPServer() error {
|
||||
if config.DHCP.Enabled == false {
|
||||
// not enabled, don't do anything
|
||||
return nil
|
||||
}
|
||||
err := dhcpServer.Start(&config.DHCP)
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "Couldn't start DHCP server")
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package dhcpd
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/krolaw/dhcp4"
|
||||
)
|
||||
|
||||
func CheckIfOtherDHCPServersPresent(ifaceName string) (bool, error) {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't find interface by name %s", ifaceName)
|
||||
}
|
||||
|
||||
// get ipv4 address of an interface
|
||||
ifaceIPNet := getIfaceIPv4(iface)
|
||||
if ifaceIPNet == nil {
|
||||
return false, fmt.Errorf("Couldn't find IPv4 address of interface %s %+v", ifaceName, iface)
|
||||
}
|
||||
|
||||
srcIP := ifaceIPNet.IP
|
||||
src := net.JoinHostPort(srcIP.String(), "68")
|
||||
dst := "255.255.255.255:67"
|
||||
|
||||
// form a DHCP request packet, try to emulate existing client as much as possible
|
||||
xId := make([]byte, 8)
|
||||
n, err := rand.Read(xId)
|
||||
if n != 8 && err == nil {
|
||||
err = fmt.Errorf("Generated less than 8 bytes")
|
||||
}
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't generate 8 random bytes")
|
||||
}
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't get hostname")
|
||||
}
|
||||
requestList := []byte{
|
||||
byte(dhcp4.OptionSubnetMask),
|
||||
byte(dhcp4.OptionClasslessRouteFormat),
|
||||
byte(dhcp4.OptionRouter),
|
||||
byte(dhcp4.OptionDomainNameServer),
|
||||
byte(dhcp4.OptionDomainName),
|
||||
byte(dhcp4.OptionDomainSearch),
|
||||
252, // private/proxy autodiscovery
|
||||
95, // LDAP
|
||||
byte(dhcp4.OptionNetBIOSOverTCPIPNameServer),
|
||||
byte(dhcp4.OptionNetBIOSOverTCPIPNodeType),
|
||||
}
|
||||
maxUDPsizeRaw := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(maxUDPsizeRaw, 1500)
|
||||
leaseTimeRaw := make([]byte, 4)
|
||||
leaseTime := uint32(math.RoundToEven(time.Duration(time.Hour * 24 * 90).Seconds()))
|
||||
binary.BigEndian.PutUint32(leaseTimeRaw, leaseTime)
|
||||
options := []dhcp4.Option{
|
||||
{dhcp4.OptionParameterRequestList, requestList},
|
||||
{dhcp4.OptionMaximumDHCPMessageSize, maxUDPsizeRaw},
|
||||
{dhcp4.OptionClientIdentifier, append([]byte{0x01}, iface.HardwareAddr...)},
|
||||
{dhcp4.OptionIPAddressLeaseTime, leaseTimeRaw},
|
||||
{dhcp4.OptionHostName, []byte(hostname)},
|
||||
}
|
||||
packet := dhcp4.RequestPacket(dhcp4.Discover, iface.HardwareAddr, nil, xId, false, options)
|
||||
|
||||
// resolve 0.0.0.0:68
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", src)
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't resolve UDP address %s", src)
|
||||
}
|
||||
// spew.Dump(udpAddr, err)
|
||||
|
||||
if !udpAddr.IP.To4().Equal(srcIP) {
|
||||
return false, wrapErrPrint(err, "Resolved UDP address is not %s", src)
|
||||
}
|
||||
|
||||
// resolve 255.255.255.255:67
|
||||
dstAddr, err := net.ResolveUDPAddr("udp4", dst)
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't resolve UDP address %s", dst)
|
||||
}
|
||||
|
||||
// bind to 0.0.0.0:68
|
||||
log.Tracef("Listening to udp4 %+v", udpAddr)
|
||||
c, err := net.ListenPacket("udp4", src)
|
||||
if c != nil {
|
||||
defer c.Close()
|
||||
}
|
||||
// spew.Dump(c, err)
|
||||
// spew.Printf("net.ListenUDP returned %v, %v\n", c, err)
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't listen to %s", src)
|
||||
}
|
||||
|
||||
// send to 255.255.255.255:67
|
||||
n, err = c.WriteTo(packet, dstAddr)
|
||||
// spew.Dump(n, err)
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't send a packet to %s", dst)
|
||||
}
|
||||
|
||||
// wait for answer
|
||||
log.Tracef("Waiting %v for an answer", defaultDiscoverTime)
|
||||
// TODO: replicate dhclient's behaviour of retrying several times with progressively bigger timeouts
|
||||
b := make([]byte, 1500)
|
||||
c.SetReadDeadline(time.Now().Add(defaultDiscoverTime))
|
||||
n, _, err = c.ReadFrom(b)
|
||||
if isTimeout(err) {
|
||||
// timed out -- no DHCP servers
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, wrapErrPrint(err, "Couldn't receive packet")
|
||||
}
|
||||
if n > 0 {
|
||||
b = b[:n]
|
||||
}
|
||||
// spew.Dump(n, fromAddr, err, b)
|
||||
|
||||
if n < 240 {
|
||||
// packet too small for dhcp
|
||||
return false, wrapErrPrint(err, "got packet that's too small for DHCP")
|
||||
}
|
||||
|
||||
response := dhcp4.Packet(b[:n])
|
||||
if response.HLen() > 16 {
|
||||
// invalid size
|
||||
return false, wrapErrPrint(err, "got malformed packet with HLen() > 16")
|
||||
}
|
||||
|
||||
parsedOptions := response.ParseOptions()
|
||||
_, ok := parsedOptions[dhcp4.OptionDHCPMessageType]
|
||||
if !ok {
|
||||
return false, wrapErrPrint(err, "got malformed packet without DHCP message type")
|
||||
}
|
||||
|
||||
// that's a DHCP server there
|
||||
return true, nil
|
||||
}
|
|
@ -0,0 +1,398 @@
|
|||
package dhcpd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/krolaw/dhcp4"
|
||||
)
|
||||
|
||||
const defaultDiscoverTime = time.Second * 3
|
||||
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
type Lease struct {
|
||||
HWAddr net.HardwareAddr `json:"mac" yaml:"hwaddr"`
|
||||
IP net.IP `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
Expiry time.Time `json:"expires"`
|
||||
}
|
||||
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
type ServerConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
InterfaceName string `json:"interface_name" yaml:"interface_name"` // eth0, en0 and so on
|
||||
GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"`
|
||||
SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"`
|
||||
RangeStart string `json:"range_start" yaml:"range_start"`
|
||||
RangeEnd string `json:"range_end" yaml:"range_end"`
|
||||
LeaseDuration uint `json:"lease_duration" yaml:"lease_duration"` // in seconds
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
conn *filterConn // listening UDP socket
|
||||
|
||||
ipnet *net.IPNet // if interface name changes, this needs to be reset
|
||||
|
||||
// leases
|
||||
leases []*Lease
|
||||
leaseStart net.IP // parsed from config RangeStart
|
||||
leaseStop net.IP // parsed from config RangeEnd
|
||||
leaseTime time.Duration // parsed from config LeaseDuration
|
||||
leaseOptions dhcp4.Options // parsed from config GatewayIP and SubnetMask
|
||||
|
||||
// IP address pool -- if entry is in the pool, then it's attached to a lease
|
||||
IPpool map[[4]byte]net.HardwareAddr
|
||||
|
||||
ServerConfig
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Start will listen on port 67 and serve DHCP requests.
|
||||
// Even though config can be nil, it is not optional (at least for now), since there are no default values (yet).
|
||||
func (s *Server) Start(config *ServerConfig) error {
|
||||
if config != nil {
|
||||
s.ServerConfig = *config
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(s.InterfaceName)
|
||||
if err != nil {
|
||||
s.closeConn() // in case it was already started
|
||||
return wrapErrPrint(err, "Couldn't find interface by name %s", s.InterfaceName)
|
||||
}
|
||||
|
||||
// get ipv4 address of an interface
|
||||
s.ipnet = getIfaceIPv4(iface)
|
||||
if s.ipnet == nil {
|
||||
s.closeConn() // in case it was already started
|
||||
return wrapErrPrint(err, "Couldn't find IPv4 address of interface %s %+v", s.InterfaceName, iface)
|
||||
}
|
||||
|
||||
if s.LeaseDuration == 0 {
|
||||
s.leaseTime = time.Hour * 2
|
||||
s.LeaseDuration = uint(s.leaseTime.Seconds())
|
||||
} else {
|
||||
s.leaseTime = time.Second * time.Duration(s.LeaseDuration)
|
||||
}
|
||||
|
||||
s.leaseStart, err = parseIPv4(s.RangeStart)
|
||||
if err != nil {
|
||||
s.closeConn() // in case it was already started
|
||||
return wrapErrPrint(err, "Failed to parse range start address %s", s.RangeStart)
|
||||
}
|
||||
|
||||
s.leaseStop, err = parseIPv4(s.RangeEnd)
|
||||
if err != nil {
|
||||
s.closeConn() // in case it was already started
|
||||
return wrapErrPrint(err, "Failed to parse range end address %s", s.RangeEnd)
|
||||
}
|
||||
|
||||
subnet, err := parseIPv4(s.SubnetMask)
|
||||
if err != nil {
|
||||
s.closeConn() // in case it was already started
|
||||
return wrapErrPrint(err, "Failed to parse subnet mask %s", s.SubnetMask)
|
||||
}
|
||||
|
||||
// if !bytes.Equal(subnet, s.ipnet.Mask) {
|
||||
// s.closeConn() // in case it was already started
|
||||
// return wrapErrPrint(err, "specified subnet mask %s does not meatch interface %s subnet mask %s", s.SubnetMask, s.InterfaceName, s.ipnet.Mask)
|
||||
// }
|
||||
|
||||
router, err := parseIPv4(s.GatewayIP)
|
||||
if err != nil {
|
||||
s.closeConn() // in case it was already started
|
||||
return wrapErrPrint(err, "Failed to parse gateway IP %s", s.GatewayIP)
|
||||
}
|
||||
|
||||
s.leaseOptions = dhcp4.Options{
|
||||
dhcp4.OptionSubnetMask: subnet,
|
||||
dhcp4.OptionRouter: router,
|
||||
dhcp4.OptionDomainNameServer: s.ipnet.IP,
|
||||
}
|
||||
|
||||
// TODO: don't close if interface and addresses are the same
|
||||
if s.conn != nil {
|
||||
s.closeConn()
|
||||
}
|
||||
|
||||
c, err := newFilterConn(*iface, ":67") // it has to be bound to 0.0.0.0:67, otherwise it won't see DHCP discover/request packets
|
||||
if err != nil {
|
||||
return wrapErrPrint(err, "Couldn't start listening socket on 0.0.0.0:67")
|
||||
}
|
||||
|
||||
s.conn = c
|
||||
|
||||
go func() {
|
||||
// operate on c instead of c.conn because c.conn can change over time
|
||||
err := dhcp4.Serve(c, s)
|
||||
if err != nil {
|
||||
log.Printf("dhcp4.Serve() returned with error: %s", err)
|
||||
}
|
||||
c.Close() // in case Serve() exits for other reason than listening socket closure
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
if s.conn == nil {
|
||||
// nothing to do, return silently
|
||||
return nil
|
||||
}
|
||||
err := s.closeConn()
|
||||
if err != nil {
|
||||
return wrapErrPrint(err, "Couldn't close UDP listening socket")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeConn will close the connection and set it to zero
|
||||
func (s *Server) closeConn() error {
|
||||
if s.conn == nil {
|
||||
return nil
|
||||
}
|
||||
err := s.conn.Close()
|
||||
s.conn = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) reserveLease(p dhcp4.Packet) (*Lease, error) {
|
||||
// WARNING: do not remove copy()
|
||||
// the given hwaddr by p.CHAddr() in the packet survives only during ServeDHCP() call
|
||||
// since we need to retain it we need to make our own copy
|
||||
hwaddrCOW := p.CHAddr()
|
||||
hwaddr := make(net.HardwareAddr, len(hwaddrCOW))
|
||||
copy(hwaddr, hwaddrCOW)
|
||||
foundLease := s.locateLease(p)
|
||||
if foundLease != nil {
|
||||
// log.Tracef("found lease for %s: %+v", hwaddr, foundLease)
|
||||
return foundLease, nil
|
||||
}
|
||||
// not assigned a lease, create new one, find IP from LRU
|
||||
log.Tracef("Lease not found for %s: creating new one", hwaddr)
|
||||
ip, err := s.findFreeIP(p, hwaddr)
|
||||
if err != nil {
|
||||
return nil, wrapErrPrint(err, "Couldn't find free IP for the lease %s", hwaddr.String())
|
||||
}
|
||||
log.Tracef("Assigning to %s IP address %s", hwaddr, ip.String())
|
||||
hostname := p.ParseOptions()[dhcp4.OptionHostName]
|
||||
lease := &Lease{HWAddr: hwaddr, IP: ip, Hostname: string(hostname)}
|
||||
s.Lock()
|
||||
s.leases = append(s.leases, lease)
|
||||
s.Unlock()
|
||||
return lease, nil
|
||||
}
|
||||
|
||||
func (s *Server) locateLease(p dhcp4.Packet) *Lease {
|
||||
hwaddr := p.CHAddr()
|
||||
for i := range s.leases {
|
||||
if bytes.Equal([]byte(hwaddr), []byte(s.leases[i].HWAddr)) {
|
||||
// log.Tracef("bytes.Equal(%s, %s) returned true", hwaddr, s.leases[i].hwaddr)
|
||||
return s.leases[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) findFreeIP(p dhcp4.Packet, hwaddr net.HardwareAddr) (net.IP, error) {
|
||||
// if IP pool is nil, lazy initialize it
|
||||
if s.IPpool == nil {
|
||||
s.IPpool = make(map[[4]byte]net.HardwareAddr)
|
||||
}
|
||||
|
||||
// go from start to end, find unreserved IP
|
||||
var foundIP net.IP
|
||||
for i := 0; i < dhcp4.IPRange(s.leaseStart, s.leaseStop); i++ {
|
||||
newIP := dhcp4.IPAdd(s.leaseStart, i)
|
||||
foundHWaddr := s.getIPpool(newIP)
|
||||
log.Tracef("tried IP %v, got hwaddr %v", newIP, foundHWaddr)
|
||||
if foundHWaddr != nil && len(foundHWaddr) != 0 {
|
||||
// if !bytes.Equal(foundHWaddr, hwaddr) {
|
||||
// log.Tracef("SHOULD NOT HAPPEN: hwaddr in IP pool %s is not equal to hwaddr in lease %s", foundHWaddr, hwaddr)
|
||||
// }
|
||||
log.Tracef("will try again")
|
||||
continue
|
||||
}
|
||||
foundIP = newIP
|
||||
break
|
||||
}
|
||||
|
||||
if foundIP == nil {
|
||||
// TODO: LRU
|
||||
return nil, fmt.Errorf("Couldn't find free entry in IP pool")
|
||||
}
|
||||
|
||||
s.reserveIP(foundIP, hwaddr)
|
||||
|
||||
return foundIP, nil
|
||||
}
|
||||
|
||||
func (s *Server) getIPpool(ip net.IP) net.HardwareAddr {
|
||||
rawIP := []byte(ip)
|
||||
IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]}
|
||||
return s.IPpool[IP4]
|
||||
}
|
||||
|
||||
func (s *Server) reserveIP(ip net.IP, hwaddr net.HardwareAddr) {
|
||||
rawIP := []byte(ip)
|
||||
IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]}
|
||||
s.IPpool[IP4] = hwaddr
|
||||
}
|
||||
|
||||
func (s *Server) unreserveIP(ip net.IP) {
|
||||
rawIP := []byte(ip)
|
||||
IP4 := [4]byte{rawIP[0], rawIP[1], rawIP[2], rawIP[3]}
|
||||
delete(s.IPpool, IP4)
|
||||
}
|
||||
|
||||
func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dhcp4.Options) dhcp4.Packet {
|
||||
log.Tracef("Got %v message", msgType)
|
||||
log.Tracef("Leases:")
|
||||
for i, lease := range s.leases {
|
||||
log.Tracef("Lease #%d: hwaddr %s, ip %s, expiry %s", i, lease.HWAddr, lease.IP, lease.Expiry)
|
||||
}
|
||||
log.Tracef("IP pool:")
|
||||
for ip, hwaddr := range s.IPpool {
|
||||
log.Tracef("IP pool entry %s -> %s", net.IPv4(ip[0], ip[1], ip[2], ip[3]), hwaddr)
|
||||
}
|
||||
// spew.Dump(s.leases, s.IPpool)
|
||||
// log.Printf("Called with msgType = %v, options = %+v", msgType, options)
|
||||
// spew.Dump(p)
|
||||
// log.Printf("%14s %v", "p.Broadcast", p.Broadcast()) // false
|
||||
// log.Printf("%14s %v", "p.CHAddr", p.CHAddr()) // 2c:f0:a2:f2:31:00
|
||||
// log.Printf("%14s %v", "p.CIAddr", p.CIAddr()) // 0.0.0.0
|
||||
// log.Printf("%14s %v", "p.Cookie", p.Cookie()) // [99 130 83 99]
|
||||
// log.Printf("%14s %v", "p.File", p.File()) // []
|
||||
// log.Printf("%14s %v", "p.Flags", p.Flags()) // [0 0]
|
||||
// log.Printf("%14s %v", "p.GIAddr", p.GIAddr()) // 0.0.0.0
|
||||
// log.Printf("%14s %v", "p.HLen", p.HLen()) // 6
|
||||
// log.Printf("%14s %v", "p.HType", p.HType()) // 1
|
||||
// log.Printf("%14s %v", "p.Hops", p.Hops()) // 0
|
||||
// log.Printf("%14s %v", "p.OpCode", p.OpCode()) // BootRequest
|
||||
// log.Printf("%14s %v", "p.Options", p.Options()) // [53 1 1 55 10 1 121 3 6 15 119 252 95 44 46 57 2 5 220 61 7 1 44 240 162 242 49 0 51 4 0 118 167 0 12 4 119 104 109 100 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
|
||||
// log.Printf("%14s %v", "p.ParseOptions", p.ParseOptions()) // map[OptionParameterRequestList:[1 121 3 6 15 119 252 95 44 46] OptionDHCPMessageType:[1] OptionMaximumDHCPMessageSize:[5 220] OptionClientIdentifier:[1 44 240 162 242 49 0] OptionIPAddressLeaseTime:[0 118 167 0] OptionHostName:[119 104 109 100]]
|
||||
// log.Printf("%14s %v", "p.SIAddr", p.SIAddr()) // 0.0.0.0
|
||||
// log.Printf("%14s %v", "p.SName", p.SName()) // []
|
||||
// log.Printf("%14s %v", "p.Secs", p.Secs()) // [0 8]
|
||||
// log.Printf("%14s %v", "p.XId", p.XId()) // [211 184 20 44]
|
||||
// log.Printf("%14s %v", "p.YIAddr", p.YIAddr()) // 0.0.0.0
|
||||
|
||||
switch msgType {
|
||||
case dhcp4.Discover: // Broadcast Packet From Client - Can I have an IP?
|
||||
// find a lease, but don't update lease time
|
||||
log.Tracef("Got from client: Discover")
|
||||
lease, err := s.reserveLease(p)
|
||||
if err != nil {
|
||||
log.Tracef("Couldn't find free lease: %s", err)
|
||||
// couldn't find lease, don't respond
|
||||
return nil
|
||||
}
|
||||
reply := dhcp4.ReplyPacket(p, dhcp4.Offer, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]))
|
||||
log.Tracef("Replying with offer: offered IP %v for %v with options %+v", lease.IP, s.leaseTime, reply.ParseOptions())
|
||||
return reply
|
||||
case dhcp4.Request: // Broadcast From Client - I'll take that IP (Also start for renewals)
|
||||
// start/renew a lease -- update lease time
|
||||
// some clients (OSX) just go right ahead and do Request first from previously known IP, if they get NAK, they restart full cycle with Discover then Request
|
||||
log.Tracef("Got from client: Request")
|
||||
if server, ok := options[dhcp4.OptionServerIdentifier]; ok && !net.IP(server).Equal(s.ipnet.IP) {
|
||||
log.Tracef("Request message not for this DHCP server (%v vs %v)", server, s.ipnet.IP)
|
||||
return nil // Message not for this dhcp server
|
||||
}
|
||||
|
||||
reqIP := net.IP(options[dhcp4.OptionRequestedIPAddress])
|
||||
if reqIP == nil {
|
||||
reqIP = net.IP(p.CIAddr())
|
||||
}
|
||||
|
||||
if reqIP.To4() == nil {
|
||||
log.Tracef("Replying with NAK: request IP isn't valid IPv4: %s", reqIP)
|
||||
return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil)
|
||||
}
|
||||
|
||||
if reqIP.Equal(net.IPv4zero) {
|
||||
log.Tracef("Replying with NAK: request IP is 0.0.0.0")
|
||||
return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil)
|
||||
}
|
||||
|
||||
log.Tracef("requested IP is %s", reqIP)
|
||||
lease, err := s.reserveLease(p)
|
||||
if err != nil {
|
||||
log.Tracef("Couldn't find free lease: %s", err)
|
||||
// couldn't find lease, don't respond
|
||||
return nil
|
||||
}
|
||||
|
||||
if lease.IP.Equal(reqIP) {
|
||||
// IP matches lease IP, nothing else to do
|
||||
lease.Expiry = time.Now().Add(s.leaseTime)
|
||||
log.Tracef("Replying with ACK: request IP matches lease IP, nothing else to do. IP %v for %v", lease.IP, p.CHAddr())
|
||||
return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]))
|
||||
}
|
||||
|
||||
//
|
||||
// requested IP different from lease
|
||||
//
|
||||
|
||||
log.Tracef("lease IP is different from requested IP: %s vs %s", lease.IP, reqIP)
|
||||
|
||||
hwaddr := s.getIPpool(reqIP)
|
||||
if hwaddr == nil {
|
||||
// not in pool, check if it's in DHCP range
|
||||
if dhcp4.IPInRange(s.leaseStart, s.leaseStop, reqIP) {
|
||||
// okay, we can give it to our client -- it's in our DHCP range and not taken, so let them use their IP
|
||||
log.Tracef("Replying with ACK: request IP %v is not taken, so assigning lease IP %v to it, for %v", reqIP, lease.IP, p.CHAddr())
|
||||
s.unreserveIP(lease.IP)
|
||||
lease.IP = reqIP
|
||||
s.reserveIP(reqIP, p.CHAddr())
|
||||
lease.Expiry = time.Now().Add(s.leaseTime)
|
||||
return dhcp4.ReplyPacket(p, dhcp4.ACK, s.ipnet.IP, lease.IP, s.leaseTime, s.leaseOptions.SelectOrderOrAll(options[dhcp4.OptionParameterRequestList]))
|
||||
}
|
||||
}
|
||||
|
||||
if hwaddr != nil && !bytes.Equal(hwaddr, lease.HWAddr) {
|
||||
log.Printf("SHOULD NOT HAPPEN: IP pool hwaddr does not match lease hwaddr: %s vs %s", hwaddr, lease.HWAddr)
|
||||
}
|
||||
|
||||
// requsted IP is not sufficient, reply with NAK
|
||||
if hwaddr != nil {
|
||||
log.Tracef("Replying with NAK: request IP %s is taken, asked by %v", reqIP, p.CHAddr())
|
||||
return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil)
|
||||
}
|
||||
|
||||
// requested IP is outside of DHCP range
|
||||
log.Tracef("Replying with NAK: request IP %s is outside of DHCP range [%s, %s], asked by %v", reqIP, s.leaseStart, s.leaseStop, p.CHAddr())
|
||||
return dhcp4.ReplyPacket(p, dhcp4.NAK, s.ipnet.IP, nil, 0, nil)
|
||||
case dhcp4.Decline: // Broadcast From Client - Sorry I can't use that IP
|
||||
log.Tracef("Got from client: Decline")
|
||||
|
||||
case dhcp4.Release: // From Client, I don't need that IP anymore
|
||||
log.Tracef("Got from client: Release")
|
||||
|
||||
case dhcp4.Inform: // From Client, I have this IP and there's nothing you can do about it
|
||||
log.Tracef("Got from client: Inform")
|
||||
// do nothing
|
||||
|
||||
// from server -- ignore those but enumerate just in case
|
||||
case dhcp4.Offer: // Broadcast From Server - Here's an IP
|
||||
log.Printf("SHOULD NOT HAPPEN -- FROM ANOTHER DHCP SERVER: Offer")
|
||||
case dhcp4.ACK: // From Server, Yes you can have that IP
|
||||
log.Printf("SHOULD NOT HAPPEN -- FROM ANOTHER DHCP SERVER: ACK")
|
||||
case dhcp4.NAK: // From Server, No you cannot have that IP
|
||||
log.Printf("SHOULD NOT HAPPEN -- FROM ANOTHER DHCP SERVER: NAK")
|
||||
default:
|
||||
log.Printf("Unknown DHCP packet detected, ignoring: %v", msgType)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Leases() []*Lease {
|
||||
s.RLock()
|
||||
result := s.leases
|
||||
s.RUnlock()
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package dhcpd
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/joomcode/errorx"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
// filterConn listens to 0.0.0.0:67, but accepts packets only from specific interface
|
||||
// This is neccessary for DHCP daemon to work, since binding to IP address doesn't
|
||||
// us access to see Discover/Request packets from clients.
|
||||
//
|
||||
// TODO: on windows, controlmessage does not work, try to find out another way
|
||||
// https://github.com/golang/net/blob/master/ipv4/payload.go#L13
|
||||
type filterConn struct {
|
||||
iface net.Interface
|
||||
conn *ipv4.PacketConn
|
||||
}
|
||||
|
||||
func newFilterConn(iface net.Interface, address string) (*filterConn, error) {
|
||||
c, err := net.ListenPacket("udp4", address)
|
||||
if err != nil {
|
||||
return nil, errorx.Decorate(err, "Couldn't listen to %s on UDP4", address)
|
||||
}
|
||||
|
||||
p := ipv4.NewPacketConn(c)
|
||||
err = p.SetControlMessage(ipv4.FlagInterface, true)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, errorx.Decorate(err, "Couldn't set control message FlagInterface on connection")
|
||||
}
|
||||
|
||||
return &filterConn{iface: iface, conn: p}, nil
|
||||
}
|
||||
|
||||
func (f *filterConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
for { // read until we find a suitable packet
|
||||
n, cm, addr, err := f.conn.ReadFrom(b)
|
||||
if err != nil {
|
||||
return 0, addr, errorx.Decorate(err, "Error when reading from socket")
|
||||
}
|
||||
if cm == nil {
|
||||
// no controlmessage was passed, so pass the packet to the caller
|
||||
return n, addr, nil
|
||||
}
|
||||
if cm.IfIndex == f.iface.Index {
|
||||
return n, addr, nil
|
||||
}
|
||||
// packet doesn't match criteria, drop it
|
||||
}
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
func (f *filterConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
cm := ipv4.ControlMessage{
|
||||
IfIndex: f.iface.Index,
|
||||
}
|
||||
return f.conn.WriteTo(b, &cm, addr)
|
||||
}
|
||||
|
||||
func (f *filterConn) Close() error {
|
||||
return f.conn.Close()
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package dhcpd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
)
|
||||
|
||||
func isTimeout(err error) bool {
|
||||
operr, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return operr.Timeout()
|
||||
}
|
||||
|
||||
// return first IPv4 address of an interface, if there is any
|
||||
func getIfaceIPv4(iface *net.Interface) *net.IPNet {
|
||||
ifaceAddrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, addr := range ifaceAddrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// not an IPNet, should not happen
|
||||
log.Fatalf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet", addr)
|
||||
}
|
||||
|
||||
if ipnet.IP.To4() == nil {
|
||||
log.Printf("Got IP that is not IPv4: %v", ipnet.IP)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Got IP that is IPv4: %v", ipnet.IP)
|
||||
return &net.IPNet{
|
||||
IP: ipnet.IP.To4(),
|
||||
Mask: ipnet.Mask,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isConnClosed(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
nerr, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.Contains(nerr.Err.Error(), "use of closed network connection") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func wrapErrPrint(err error, message string, args ...interface{}) error {
|
||||
var errx error
|
||||
if err == nil {
|
||||
errx = fmt.Errorf(message, args...)
|
||||
} else {
|
||||
errx = errorx.Decorate(err, message, args...)
|
||||
}
|
||||
log.Println(errx.Error())
|
||||
return errx
|
||||
}
|
||||
|
||||
func parseIPv4(text string) (net.IP, error) {
|
||||
result := net.ParseIP(text)
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("%s is not an IP address", text)
|
||||
}
|
||||
if result.To4() == nil {
|
||||
return nil, fmt.Errorf("%s is not an IPv4 address", text)
|
||||
}
|
||||
return result.To4(), nil
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/krolaw/dhcp4"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Printf("Usage: %s <interface name>", os.Args[0])
|
||||
os.Exit(64)
|
||||
}
|
||||
|
||||
ifaceName := os.Args[1]
|
||||
present, err := dhcpd.CheckIfOtherDHCPServersPresent(ifaceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Printf("Found DHCP server? %v", present)
|
||||
if present {
|
||||
log.Printf("Will not start DHCP server because there's already running one on the network")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// get ipv4 address of an interface
|
||||
ifaceIPNet := getIfaceIPv4(iface)
|
||||
if ifaceIPNet == nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// append 10 to server's IP address as start
|
||||
start := dhcp4.IPAdd(ifaceIPNet.IP, 10)
|
||||
// lease range is 100 IP's, but TODO: don't go beyond end of subnet mask
|
||||
stop := dhcp4.IPAdd(start, 100)
|
||||
|
||||
server := dhcpd.Server{}
|
||||
config := dhcpd.ServerConfig{
|
||||
InterfaceName: ifaceName,
|
||||
RangeStart: start.String(),
|
||||
RangeEnd: stop.String(),
|
||||
SubnetMask: "255.255.255.0",
|
||||
GatewayIP: "192.168.7.1",
|
||||
}
|
||||
log.Printf("Starting DHCP server")
|
||||
err = server.Start(&config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
log.Printf("Stopping DHCP server")
|
||||
err = server.Stop()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Printf("Starting DHCP server")
|
||||
err = server.Start(&config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Printf("Starting DHCP server while it's already running")
|
||||
err = server.Start(&config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Printf("Now serving DHCP")
|
||||
signal_channel := make(chan os.Signal)
|
||||
signal.Notify(signal_channel, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-signal_channel
|
||||
|
||||
}
|
||||
|
||||
// return first IPv4 address of an interface, if there is any
|
||||
func getIfaceIPv4(iface *net.Interface) *net.IPNet {
|
||||
ifaceAddrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, addr := range ifaceAddrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// not an IPNet, should not happen
|
||||
log.Fatalf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet", addr)
|
||||
}
|
||||
|
||||
if ipnet.IP.To4() == nil {
|
||||
log.Printf("Got IP that is not IPv4: %v", ipnet.IP)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Got IP that is IPv4: %v", ipnet.IP)
|
||||
return &net.IPNet{
|
||||
IP: ipnet.IP.To4(),
|
||||
Mask: ipnet.Mask,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
2
dns.go
2
dns.go
|
@ -2,12 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
)
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
@ -18,6 +17,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/hmage/golibs/log"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/shirou/gopsutil/process"
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
@ -24,7 +25,7 @@ import (
|
|||
// first in file because it must be run first
|
||||
func TestLotsOfRulesMemoryUsage(t *testing.T) {
|
||||
start := getRSS()
|
||||
trace("RSS before loading rules - %d kB\n", start/1024)
|
||||
log.Tracef("RSS before loading rules - %d kB\n", start/1024)
|
||||
dumpMemProfile(_Func() + "1.pprof")
|
||||
|
||||
d := NewForTest()
|
||||
|
@ -35,7 +36,7 @@ func TestLotsOfRulesMemoryUsage(t *testing.T) {
|
|||
}
|
||||
|
||||
afterLoad := getRSS()
|
||||
trace("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024)
|
||||
log.Tracef("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024)
|
||||
dumpMemProfile(_Func() + "2.pprof")
|
||||
|
||||
tests := []struct {
|
||||
|
@ -58,7 +59,7 @@ func TestLotsOfRulesMemoryUsage(t *testing.T) {
|
|||
}
|
||||
}
|
||||
afterMatch := getRSS()
|
||||
trace("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024)
|
||||
log.Tracef("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024)
|
||||
dumpMemProfile(_Func() + "3.pprof")
|
||||
}
|
||||
|
||||
|
@ -88,20 +89,20 @@ func dumpMemProfile(name string) {
|
|||
const topHostsFilename = "../tests/top-1m.csv"
|
||||
|
||||
func fetchTopHostsFromNet() {
|
||||
trace("Fetching top hosts from network")
|
||||
log.Tracef("Fetching top hosts from network")
|
||||
resp, err := http.Get("http://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
trace("Reading zipfile body")
|
||||
log.Tracef("Reading zipfile body")
|
||||
zipfile, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
trace("Opening zipfile")
|
||||
log.Tracef("Opening zipfile")
|
||||
r, err := zip.NewReader(bytes.NewReader(zipfile), int64(len(zipfile)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -111,19 +112,19 @@ func fetchTopHostsFromNet() {
|
|||
panic(fmt.Errorf("zipfile must have only one entry: %+v", r))
|
||||
}
|
||||
f := r.File[0]
|
||||
trace("Unpacking file %s from zipfile", f.Name)
|
||||
log.Tracef("Unpacking file %s from zipfile", f.Name)
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
trace("Reading file %s contents", f.Name)
|
||||
log.Tracef("Reading file %s contents", f.Name)
|
||||
body, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rc.Close()
|
||||
|
||||
trace("Writing file %s contents to disk", f.Name)
|
||||
log.Tracef("Writing file %s contents to disk", f.Name)
|
||||
err = ioutil.WriteFile(topHostsFilename+".tmp", body, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -144,16 +145,16 @@ func getTopHosts() {
|
|||
|
||||
func TestLotsOfRulesLotsOfHostsMemoryUsage(t *testing.T) {
|
||||
start := getRSS()
|
||||
trace("RSS before loading rules - %d kB\n", start/1024)
|
||||
log.Tracef("RSS before loading rules - %d kB\n", start/1024)
|
||||
dumpMemProfile(_Func() + "1.pprof")
|
||||
|
||||
d := NewForTest()
|
||||
defer d.Destroy()
|
||||
mustLoadTestRules(d)
|
||||
trace("Have %d rules", d.Count())
|
||||
log.Tracef("Have %d rules", d.Count())
|
||||
|
||||
afterLoad := getRSS()
|
||||
trace("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024)
|
||||
log.Tracef("RSS after loading rules - %d kB (%d kB diff)\n", afterLoad/1024, (afterLoad-start)/1024)
|
||||
dumpMemProfile(_Func() + "2.pprof")
|
||||
|
||||
getTopHosts()
|
||||
|
@ -163,7 +164,7 @@ func TestLotsOfRulesLotsOfHostsMemoryUsage(t *testing.T) {
|
|||
}
|
||||
defer hostnames.Close()
|
||||
afterHosts := getRSS()
|
||||
trace("RSS after loading hosts - %d kB (%d kB diff)\n", afterHosts/1024, (afterHosts-afterLoad)/1024)
|
||||
log.Tracef("RSS after loading hosts - %d kB (%d kB diff)\n", afterHosts/1024, (afterHosts-afterLoad)/1024)
|
||||
dumpMemProfile(_Func() + "2.pprof")
|
||||
|
||||
{
|
||||
|
@ -182,7 +183,7 @@ func TestLotsOfRulesLotsOfHostsMemoryUsage(t *testing.T) {
|
|||
}
|
||||
|
||||
afterMatch := getRSS()
|
||||
trace("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024)
|
||||
log.Tracef("RSS after matching - %d kB (%d kB diff)\n", afterMatch/1024, (afterMatch-afterLoad)/1024)
|
||||
dumpMemProfile(_Func() + "3.pprof")
|
||||
}
|
||||
|
||||
|
@ -236,7 +237,7 @@ func TestSuffixRule(t *testing.T) {
|
|||
t.Errorf("Result suffix does not match for \"%s\": got \"%s\" expected \"%s\"", testcase.rule, suffix, testcase.suffix)
|
||||
continue
|
||||
}
|
||||
// trace("\"%s\": %v: %s", testcase.rule, isSuffix, suffix)
|
||||
// log.Tracef("\"%s\": %v: %s", testcase.rule, isSuffix, suffix)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package dnsfilter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
@ -62,17 +58,3 @@ func updateMax(valuePtr *int64, maxPtr *int64) {
|
|||
// swapping failed because value has changed after reading, try again
|
||||
}
|
||||
}
|
||||
|
||||
func trace(format string, args ...interface{}) {
|
||||
pc := make([]uintptr, 10) // at least 1 entry needed
|
||||
runtime.Callers(2, pc)
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
var buf strings.Builder
|
||||
buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name())))
|
||||
text := fmt.Sprintf(format, args...)
|
||||
buf.WriteString(text)
|
||||
if len(text) == 0 || text[len(text)-1] != '\n' {
|
||||
buf.WriteRune('\n')
|
||||
}
|
||||
fmt.Fprint(os.Stderr, buf.String())
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/joomcode/errorx"
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// DefaultTimeout is the default upstream timeout
|
||||
|
@ -283,7 +283,7 @@ func (s *Server) filterDNSRequest(d *proxy.DNSContext) (*dnsfilter.Result, error
|
|||
// Return immediately if there's an error
|
||||
return nil, errorx.Decorate(err, "dnsfilter failed to check host '%s'", host)
|
||||
} else if res.IsFiltered {
|
||||
log.Debugf("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
|
||||
// log.Tracef("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
|
||||
d.Res = s.genDNSFilterMessage(d, &res)
|
||||
}
|
||||
|
||||
|
@ -324,7 +324,7 @@ func (s *Server) genARecord(request *dns.Msg, ip net.IP) *dns.Msg {
|
|||
resp.SetReply(request)
|
||||
answer, err := dns.NewRR(fmt.Sprintf("%s %d A %s", request.Question[0].Name, s.BlockedResponseTTL, ip.String()))
|
||||
if err != nil {
|
||||
log.Warnf("Couldn't generate A record for up replacement host '%s': %s", ip.String(), err)
|
||||
log.Printf("Couldn't generate A record for replacement host '%s': %s", ip.String(), err)
|
||||
return s.genServerFailure(request)
|
||||
}
|
||||
resp.Answer = append(resp.Answer, answer)
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -9,9 +9,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hmage/golibs/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -222,7 +221,7 @@ func genericLoader(onEntry func(entry *logEntry) error, needMore func() bool, ti
|
|||
}
|
||||
|
||||
if now.Sub(entry.Time) > timeWindow {
|
||||
// trace("skipping entry") // debug logging
|
||||
// log.Tracef("skipping entry") // debug logging
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/hmage/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,9 +7,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/hmage/golibs/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -70,7 +69,7 @@ func purgeStats() {
|
|||
func (p *periodicStats) Inc(name string, when time.Time) {
|
||||
// calculate how many periods ago this happened
|
||||
elapsed := int64(time.Since(when) / p.period)
|
||||
// trace("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed)
|
||||
// log.Tracef("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed)
|
||||
if elapsed >= statsHistoryElements {
|
||||
return // outside of our timeframe
|
||||
}
|
||||
|
@ -84,7 +83,7 @@ func (p *periodicStats) Inc(name string, when time.Time) {
|
|||
func (p *periodicStats) Observe(name string, when time.Time, value float64) {
|
||||
// calculate how many periods ago this happened
|
||||
elapsed := int64(time.Since(when) / p.period)
|
||||
// trace("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed)
|
||||
// log.Tracef("%s: %v as %v -> [%v]", name, time.Since(when), p.period, elapsed)
|
||||
if elapsed >= statsHistoryElements {
|
||||
return // outside of our timeframe
|
||||
}
|
||||
|
@ -93,7 +92,7 @@ func (p *periodicStats) Observe(name string, when time.Time, value float64) {
|
|||
countname := name + "_count"
|
||||
currentValues := p.Entries[countname]
|
||||
value := currentValues[elapsed]
|
||||
// trace("Will change p.Entries[%s][%d] from %v to %v", countname, elapsed, value, value+1)
|
||||
// log.Tracef("Will change p.Entries[%s][%d] from %v to %v", countname, elapsed, value, value+1)
|
||||
value += 1
|
||||
currentValues[elapsed] = value
|
||||
p.Entries[countname] = currentValues
|
||||
|
@ -143,10 +142,12 @@ func statsRotator() {
|
|||
type counter struct {
|
||||
name string // used as key in periodic stats
|
||||
value int64
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func newDNSCounter(name string) *counter {
|
||||
// trace("called")
|
||||
// log.Tracef("called")
|
||||
return &counter{
|
||||
name: name,
|
||||
}
|
||||
|
@ -157,7 +158,9 @@ func (c *counter) IncWithTime(when time.Time) {
|
|||
statistics.PerMinute.Inc(c.name, when)
|
||||
statistics.PerHour.Inc(c.name, when)
|
||||
statistics.PerDay.Inc(c.name, when)
|
||||
c.Lock()
|
||||
c.value++
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
func (c *counter) Inc() {
|
||||
|
@ -168,6 +171,8 @@ type histogram struct {
|
|||
name string // used as key in periodic stats
|
||||
count int64
|
||||
total float64
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func newDNSHistogram(name string) *histogram {
|
||||
|
@ -181,8 +186,10 @@ func (h *histogram) ObserveWithTime(value float64, when time.Time) {
|
|||
statistics.PerMinute.Observe(h.name, when, value)
|
||||
statistics.PerHour.Observe(h.name, when, value)
|
||||
statistics.PerDay.Observe(h.name, when, value)
|
||||
h.Lock()
|
||||
h.count++
|
||||
h.total += value
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
func (h *histogram) Observe(value float64) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
@ -13,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/hmage/golibs/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
10
go.mod
10
go.mod
|
@ -1,22 +1,18 @@
|
|||
module github.com/AdguardTeam/AdGuardHome
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.9.3
|
||||
github.com/AdguardTeam/dnsproxy v0.9.9
|
||||
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f // indirect
|
||||
github.com/ameshkov/dnscrypt v1.0.1
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6
|
||||
github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7
|
||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||
github.com/go-test/deep v1.0.1
|
||||
github.com/gobuffalo/packr v1.19.0
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86
|
||||
github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4
|
||||
github.com/joomcode/errorx v0.1.0
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414
|
||||
github.com/miekg/dns v1.1.1
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/shirou/gopsutil v2.18.10+incompatible
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
|
||||
github.com/sirupsen/logrus v1.2.0
|
||||
go.uber.org/goleak v0.10.0
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3
|
||||
|
|
40
go.sum
40
go.sum
|
@ -1,21 +1,15 @@
|
|||
github.com/AdguardTeam/dnsproxy v0.9.0 h1:doHDmVE9bV1fhiBV8rX76WWaSAB9w1H3u8WIiez5OFs=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.0/go.mod h1:CKZVVknYdoHVirXqqbALEkC+DBY65yCQrzSKYS78GoE=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.1 h1:+F6jqrVOrUjpbzhALjtbwqHfxW4M2YS3mYdhGxLXQ08=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.1/go.mod h1:CKZVVknYdoHVirXqqbALEkC+DBY65yCQrzSKYS78GoE=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.2 h1:P3B2IECZejGv8sxjyLXDbCKMgWqUEFb5rq67lxXCKZ0=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.2/go.mod h1:CKZVVknYdoHVirXqqbALEkC+DBY65yCQrzSKYS78GoE=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.3 h1:zgLcsMEQ0hPhU0LjFwPMz4qeXDF+Yy1MO9xc9QaGjbk=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.3/go.mod h1:GsppU3a1x0hIRtIh7Te8CWHKNHtJaoRXQh08DSRqk0A=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.9 h1:eFBqEZbWi0IEhux7abNP5VaS91caqR7a1W44Tfi99As=
|
||||
github.com/AdguardTeam/dnsproxy v0.9.9/go.mod h1:IqBhopgNpzB168kMurbjXf86dn50geasBIuGVxY63j0=
|
||||
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f h1:5ZfJxyXo8KyX8DgGXC5B7ILL8y51fci/qYz2B4j8iLY=
|
||||
github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/ameshkov/dnscrypt v1.0.0 h1:Y7YexPCxtVCTDXlXu9n17+1H5YS25vftx8vV8Dhuu+E=
|
||||
github.com/ameshkov/dnscrypt v1.0.0/go.mod h1:EC7Z1GguyEEwhuLXrcgkRTE3GdyPDSWq2OXefhydGWo=
|
||||
github.com/ameshkov/dnscrypt v1.0.1 h1:Aoy/Sqiqk1b/AlBwdLb31QFUi+O02gzB+wDjhdePie0=
|
||||
github.com/ameshkov/dnscrypt v1.0.1/go.mod h1:fEeZ+/h8DTt4FxEv9sxN61ygy/8m/vFRqRJcNGJR+r0=
|
||||
github.com/ameshkov/dnscrypt v1.0.4 h1:vtwHm5m4R2dhcCx23wiI+gNBoy7qm4h7+kZ4Pucw/vE=
|
||||
github.com/ameshkov/dnscrypt v1.0.4/go.mod h1:hVW52S6r0QvUpIwsyfZ1ifYYpfGu5pewD3pl7afMJcQ=
|
||||
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
github.com/bluele/gcache v0.0.0-20171010155617-472614239ac7 h1:NpQ+gkFOH27AyDypSCJ/LdsIi/b4rdnEb1N5+IpFfYs=
|
||||
|
@ -32,9 +26,9 @@ github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264 h1:roWyi0eEdiFreSq
|
|||
github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI=
|
||||
github.com/gobuffalo/packr v1.19.0 h1:3UDmBDxesCOPF8iZdMDBBWKfkBoYujIMIZePnobqIUI=
|
||||
github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU=
|
||||
github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4 h1:FMAReGTEDNr4AdbScv/PqzjMQUpkkVHiF/t8sDHQQVQ=
|
||||
github.com/hmage/golibs v0.0.0-20181229160906-c8491df0bfc4/go.mod h1:H6Ev6svFxUVPFThxLtdnFfcE9e3GWufpfmcVFpqV6HM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86 h1:Olj4M6T1omUfx7yTTcnhLf4xo6gYMmRHSJIfeA1NZy0=
|
||||
github.com/jedisct1/go-dnsstamps v0.0.0-20180418170050-1e4999280f86/go.mod h1:j/ONpSHHmPgDwmFKXg9vhQvIjADe/ft1X4a3TVOmp9g=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff h1:6NvhExg4omUC9NfA+l4Oq3ibNNeJUdiAF3iBVB0PlDk=
|
||||
|
@ -43,8 +37,8 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
|||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/joomcode/errorx v0.1.0 h1:QmJMiI1DE1UFje2aI1ZWO/VMT5a32qBoXUclGOt8vsc=
|
||||
github.com/joomcode/errorx v0.1.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32BM74ph7bPgqMztWopMYKgVyEho=
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
||||
|
@ -59,39 +53,31 @@ github.com/shirou/gopsutil v2.18.10+incompatible h1:cy84jW6EVRPa5g9HAHrlbxMSIjBh
|
|||
github.com/shirou/gopsutil v2.18.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4=
|
||||
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6 h1:gT0Y6H7hbVPUtvtk0YGxMXPgN+p8fYlqWkgJeUCZcaQ=
|
||||
golang.org/x/net v0.0.0-20181213202711-891ebc4b82d6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06 h1:0oC8rFnE+74kEmuHZ46F6KHsMr5Gx2gUQPuNz28iQZM=
|
||||
golang.org/x/sys v0.0.0-20181213200352-4d1cda033e06/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb h1:zzdd4xkMwu/GRxhSUJaCPh4/jil9kAbsU7AUmXboO+A=
|
||||
golang.org/x/sys v0.0.0-20181217223516-dcdaa6325bcb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477 h1:5xUJw+lg4zao9W4HIDzlFbMYgSgtvNVHh00MEHvbGpQ=
|
||||
gopkg.in/asaskevich/govalidator.v4 v4.0.0-20160518190739-766470278477/go.mod h1:QDV1vrFSrowdoOba0UM8VJPUZONT7dnfdLsM+GG53Z8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
|
|
15
helpers.go
15
helpers.go
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -135,17 +134,3 @@ func _Func() string {
|
|||
f := runtime.FuncForPC(pc[0])
|
||||
return path.Base(f.Name())
|
||||
}
|
||||
|
||||
func trace(format string, args ...interface{}) {
|
||||
pc := make([]uintptr, 10) // at least 1 entry needed
|
||||
runtime.Callers(2, pc)
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
var buf strings.Builder
|
||||
buf.WriteString(fmt.Sprintf("%s(): ", path.Base(f.Name())))
|
||||
text := fmt.Sprintf(format, args...)
|
||||
buf.WriteString(text)
|
||||
if len(text) == 0 || text[len(text)-1] != '\n' {
|
||||
buf.WriteRune('\n')
|
||||
}
|
||||
fmt.Fprint(os.Stderr, buf.String())
|
||||
}
|
||||
|
|
3
i18n.go
3
i18n.go
|
@ -3,9 +3,10 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hmage/golibs/log"
|
||||
)
|
||||
|
||||
// --------------------
|
||||
|
|
622
openapi.yaml
622
openapi.yaml
|
@ -1,622 +0,0 @@
|
|||
swagger: '2.0'
|
||||
info:
|
||||
title: 'AdGuard Home'
|
||||
description: 'Control AdGuard Home server with this API'
|
||||
version: 0.0.0
|
||||
basePath: /control
|
||||
schemes:
|
||||
- http
|
||||
produces:
|
||||
- application/json
|
||||
tags:
|
||||
-
|
||||
name: global
|
||||
description: 'DNS server controls'
|
||||
-
|
||||
name: filtering
|
||||
description: 'Rule-based filtering'
|
||||
-
|
||||
name: safebrowsing
|
||||
description: 'Malware/hazardous sites'
|
||||
-
|
||||
name: parental
|
||||
description: 'Sites inappropriate for children'
|
||||
-
|
||||
name: safesearch
|
||||
description: 'Enforce family-friendly results in search engines'
|
||||
paths:
|
||||
/status:
|
||||
get:
|
||||
tags:
|
||||
- global
|
||||
operationId: status
|
||||
summary: 'Get DNS server status'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
dns_address: 127.0.0.1
|
||||
dns_port: 53
|
||||
protection_enabled: true
|
||||
querylog_enabled: true
|
||||
running: true
|
||||
bootstrap_dns: 8.8.8.8:53
|
||||
upstream_dns:
|
||||
- 1.1.1.1
|
||||
- 1.0.0.1
|
||||
version: "v0.1"
|
||||
language: "en"
|
||||
/enable_protection:
|
||||
post:
|
||||
tags:
|
||||
-global
|
||||
operationId: enableProtection
|
||||
summary: "Enable protection (turns on dnsfilter module in coredns)"
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/disable_protection:
|
||||
post:
|
||||
tags:
|
||||
-global
|
||||
operationId: disableProtection
|
||||
summary: "Disable protection (turns off filtering, sb, parental, safesearch temporarily by disabling dnsfilter module in coredns)"
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/querylog:
|
||||
get:
|
||||
tags:
|
||||
- global
|
||||
operationId: queryLog
|
||||
summary: 'Get DNS server query log'
|
||||
parameters:
|
||||
- in: query
|
||||
name: download
|
||||
type: boolean
|
||||
description: 'If any value is set, make the browser download the query instead of displaying it by setting Content-Disposition header'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
- answer:
|
||||
- ttl: 55
|
||||
type: A
|
||||
value: 217.69.139.201
|
||||
- ttl: 55
|
||||
type: A
|
||||
value: 94.100.180.200
|
||||
- ttl: 55
|
||||
type: A
|
||||
value: 94.100.180.201
|
||||
- ttl: 55
|
||||
type: A
|
||||
value: 217.69.139.200
|
||||
elapsedMs: '65.469556'
|
||||
question:
|
||||
class: IN
|
||||
host: mail.ru
|
||||
type: A
|
||||
reason: DNSFILTER_NOTFILTERED_NOTFOUND
|
||||
status: NOERROR
|
||||
time: '2018-07-16T22:24:02+03:00'
|
||||
- elapsedMs: '0.15716999999999998'
|
||||
question:
|
||||
class: IN
|
||||
host: doubleclick.net
|
||||
type: A
|
||||
reason: DNSFILTER_FILTERED_BLACKLIST
|
||||
rule: "||doubleclick.net^"
|
||||
status: NXDOMAIN
|
||||
time: '2018-07-16T22:24:02+03:00'
|
||||
- answer:
|
||||
- ttl: 299
|
||||
type: A
|
||||
value: 176.103.133.78
|
||||
elapsedMs: '132.110929'
|
||||
question:
|
||||
class: IN
|
||||
host: wmconvirus.narod.ru
|
||||
type: A
|
||||
reason: DNSFILTER_FILTERED_SAFEBROWSING
|
||||
rule: adguard-malware-shavar
|
||||
filterId: 1
|
||||
status: NOERROR
|
||||
time: '2018-07-16T22:24:02+03:00'
|
||||
/querylog_enable:
|
||||
post:
|
||||
tags:
|
||||
- global
|
||||
operationId: querylogEnable
|
||||
summary: 'Enable querylog'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/querylog_disable:
|
||||
post:
|
||||
tags:
|
||||
- global
|
||||
operationId: querylogDisable
|
||||
summary: 'Disable filtering'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/set_upstream_dns:
|
||||
post:
|
||||
tags:
|
||||
- global
|
||||
operationId: setUpstreamDNS
|
||||
summary: 'Set upstream DNS for coredns, empty value will reset it to default values'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: upstream
|
||||
description: 'Upstream servers, separated by newline or space, port is optional after colon'
|
||||
schema:
|
||||
type: string
|
||||
example: |
|
||||
1.1.1.1
|
||||
1.0.0.1
|
||||
8.8.8.8 8.8.4.4
|
||||
192.168.1.104:53535
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/test_upstream_dns:
|
||||
post:
|
||||
tags:
|
||||
- global
|
||||
operationId: testUpstreamDNS
|
||||
summary: 'Test upstream DNS'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: upstream
|
||||
description: 'Upstream servers, separated by newline or space, port is optional after colon'
|
||||
schema:
|
||||
type: string
|
||||
example: |
|
||||
1.1.1.1
|
||||
1.0.0.1
|
||||
8.8.8.8 8.8.4.4
|
||||
192.168.1.104:53535
|
||||
responses:
|
||||
200:
|
||||
description: 'Status of testing each requested server, with "OK" meaning that server works, any other text means an error.'
|
||||
examples:
|
||||
application/json:
|
||||
1.1.1.1: OK
|
||||
1.0.0.1: OK
|
||||
8.8.8.8: OK
|
||||
8.8.4.4: OK
|
||||
"192.168.1.104:53535": "Couldn't communicate with DNS server"
|
||||
/i18n/change_language:
|
||||
post:
|
||||
tags:
|
||||
- i18n
|
||||
operationId: changeLanguage
|
||||
summary: "Change current language. Argument must be an ISO 639-1 two-letter code"
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: language
|
||||
description: "New language. It must be known to the server and must be an ISO 639-1 two-letter code"
|
||||
schema:
|
||||
type: string
|
||||
example: en
|
||||
/i18n/current_language:
|
||||
get:
|
||||
tags:
|
||||
- i18n
|
||||
operationId: currentLanguage
|
||||
summary: "Get currently set language. Result is ISO 639-1 two-letter code. Empty result means default language."
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
text/plain:
|
||||
en
|
||||
/stats_top:
|
||||
get:
|
||||
tags:
|
||||
- global
|
||||
operationId: statusTop
|
||||
summary: 'Get DNS server top client, domain and blocked statistics'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
top_queried_domains:
|
||||
example.org: 12312
|
||||
example.com: 12312
|
||||
example.net: 12312
|
||||
example.ru: 12312
|
||||
top_clients:
|
||||
127.0.0.1: 12312
|
||||
192.168.0.1: 13211
|
||||
192.168.0.2: 13211
|
||||
192.168.0.3: 13211
|
||||
192.168.0.4: 13211
|
||||
192.168.0.5: 13211
|
||||
192.168.0.6: 13211
|
||||
top_blocked_domains:
|
||||
example.org: 12312
|
||||
example.com: 12312
|
||||
example.net: 12312
|
||||
example.ru: 12312
|
||||
/stats:
|
||||
get:
|
||||
tags:
|
||||
- global
|
||||
operationId: stats
|
||||
summary: 'Get DNS server statistics'
|
||||
responses:
|
||||
200:
|
||||
description: 'Gives statistics since start of the server'
|
||||
examples:
|
||||
application/json:
|
||||
dns_queries: 1201
|
||||
blocked_filtering: 123
|
||||
replaced_safebrowsing: 5
|
||||
replaced_parental: 18
|
||||
replaced_safesearch: 94
|
||||
avg_processing_time: 123
|
||||
/stats_history:
|
||||
get:
|
||||
tags:
|
||||
- global
|
||||
operationId: stats_history
|
||||
summary: 'Get historical DNS server statistics'
|
||||
parameters:
|
||||
-
|
||||
name: start_time
|
||||
in: query
|
||||
type: string
|
||||
description: 'Start time in ISO8601 (example: `2018-05-04T17:55:33+00:00`)'
|
||||
required: true
|
||||
-
|
||||
name: end_time
|
||||
in: query
|
||||
type: string
|
||||
description: 'End time in ISO8601 (example: `2018-05-04T17:55:33+00:00`)'
|
||||
required: true
|
||||
-
|
||||
name: time_unit
|
||||
in: query
|
||||
type: string
|
||||
description: 'Time unit (`minutes` or `hours`)'
|
||||
required: true
|
||||
enum:
|
||||
- minutes
|
||||
- hours
|
||||
responses:
|
||||
501:
|
||||
description: 'Requested time window is outside of supported range. It will be supported later, but not now.'
|
||||
200:
|
||||
description: 'Gives statistics since start of the server. Example below is for 5 minutes. Values are from oldest to newest.'
|
||||
examples:
|
||||
application/json:
|
||||
dns_queries:
|
||||
- 1201
|
||||
- 1201
|
||||
- 1201
|
||||
- 1201
|
||||
- 1201
|
||||
blocked_filtering:
|
||||
- 123
|
||||
- 123
|
||||
- 123
|
||||
- 123
|
||||
- 123
|
||||
replaced_safebrowsing:
|
||||
- 5
|
||||
- 5
|
||||
- 5
|
||||
- 5
|
||||
- 5
|
||||
replaced_parental:
|
||||
- 18
|
||||
- 18
|
||||
- 18
|
||||
- 18
|
||||
- 18
|
||||
replaced_safesearch:
|
||||
- 94
|
||||
- 94
|
||||
- 94
|
||||
- 94
|
||||
- 94
|
||||
avg_processing_time:
|
||||
- 123
|
||||
- 123
|
||||
- 123
|
||||
- 123
|
||||
- 123
|
||||
/stats_reset:
|
||||
post:
|
||||
tags:
|
||||
-global
|
||||
operationId: statsReset
|
||||
summary: "Reset all statistics to zeroes"
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/enable:
|
||||
post:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringEnable
|
||||
summary: 'Enable filtering'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/disable:
|
||||
post:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringDisable
|
||||
summary: 'Disable filtering'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/add_url:
|
||||
put:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringAddURL
|
||||
summary: 'Add filter URL'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: url
|
||||
description: 'URL containing filtering rules'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 'url=https://filters.adtidy.org/windows/filters/15.txt'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/remove_url:
|
||||
delete:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringRemoveURL
|
||||
summary: 'Remove filter URL'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: url
|
||||
description: 'Previously added URL containing filtering rules'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 'url=https://filters.adtidy.org/windows/filters/15.txt'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/enable_url:
|
||||
post:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringEnableURL
|
||||
summary: 'Enable filter URL'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: url
|
||||
description: 'Previously added URL containing filtering rules'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 'url=https://filters.adtidy.org/windows/filters/15.txt'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/disable_url:
|
||||
post:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringDisableURL
|
||||
summary: 'Disable filter URL'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: url
|
||||
description: 'Previously added URL containing filtering rules'
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: 'url=https://filters.adtidy.org/windows/filters/15.txt'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/filtering/refresh:
|
||||
post:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringRefresh
|
||||
summary: |
|
||||
Reload filtering rules from URLs
|
||||
|
||||
This might be needed if new URL was just added and you dont want to wait for automatic refresh to kick in.
|
||||
|
||||
This API request is ratelimited, so you can call it freely as often as you like, it wont create unneccessary burden on servers that host the URL.
|
||||
|
||||
This should work as intended, a `force` parameter is offered as last-resort attempt to make filter lists fresh.
|
||||
|
||||
If you ever find yourself using `force` to make something work that otherwise wont, this is a bug and report it accordingly.
|
||||
|
||||
parameters:
|
||||
-
|
||||
name: force
|
||||
in: query
|
||||
type: boolean
|
||||
description: 'If any value is set, ignore cache and force re-download of all filters'
|
||||
responses:
|
||||
200:
|
||||
description: OK with how many filters were actually updated
|
||||
/filtering/status:
|
||||
get:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringStatus
|
||||
summary: 'Get status of rules-based filter'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
enabled: false
|
||||
- filters:
|
||||
enabled: true
|
||||
id: 1
|
||||
lastUpdated: "2018-10-30T12:18:57.223101822+03:00"
|
||||
name: "AdGuard Simplified Domain Names filter"
|
||||
rulesCount: 24896
|
||||
url: "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt"
|
||||
rules:
|
||||
- '@@||yandex.ru^|'
|
||||
/filtering/set_rules:
|
||||
put:
|
||||
tags:
|
||||
- filtering
|
||||
operationId: filteringSetRules
|
||||
summary: 'Set user-defined filter rules'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: rules
|
||||
description: 'All filtering rules, one line per rule'
|
||||
schema:
|
||||
type: string
|
||||
example: '@@||yandex.ru^|'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/safebrowsing/enable:
|
||||
post:
|
||||
tags:
|
||||
- safebrowsing
|
||||
operationId: safebrowsingEnable
|
||||
summary: 'Enable safebrowsing'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/safebrowsing/disable:
|
||||
post:
|
||||
tags:
|
||||
- safebrowsing
|
||||
operationId: safebrowsingDisable
|
||||
summary: 'Disable safebrowsing'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/safebrowsing/status:
|
||||
get:
|
||||
tags:
|
||||
- safebrowsing
|
||||
operationId: safebrowsingStatus
|
||||
summary: 'Get safebrowsing status'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
enabled: false
|
||||
/parental/enable:
|
||||
post:
|
||||
tags:
|
||||
- parental
|
||||
operationId: parentalEnable
|
||||
summary: 'Enable parental filtering'
|
||||
consumes:
|
||||
- text/plain
|
||||
parameters:
|
||||
- in: body
|
||||
name: sensitivity
|
||||
description: |
|
||||
Age sensitivity for parental filtering,
|
||||
EARLY_CHILDHOOD is 3
|
||||
YOUNG is 10
|
||||
TEEN is 13
|
||||
MATURE is 17
|
||||
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- EARLY_CHILDHOOD
|
||||
- YOUNG
|
||||
- TEEN
|
||||
- MATURE
|
||||
example: 'sensitivity=TEEN'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/parental/disable:
|
||||
post:
|
||||
tags:
|
||||
- parental
|
||||
operationId: parentalDisable
|
||||
summary: 'Disable parental filtering'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/parental/status:
|
||||
get:
|
||||
tags:
|
||||
- parental
|
||||
operationId: parentalStatus
|
||||
summary: 'Get parental filtering status'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
enabled: true
|
||||
sensitivity: 13
|
||||
/safesearch/enable:
|
||||
post:
|
||||
tags:
|
||||
- safesearch
|
||||
operationId: safesearchEnable
|
||||
summary: 'Enable safesearch'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/safesearch/disable:
|
||||
post:
|
||||
tags:
|
||||
- safesearch
|
||||
operationId: safesearchDisable
|
||||
summary: 'Disable safesearch'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
/safesearch/status:
|
||||
get:
|
||||
tags:
|
||||
- safesearch
|
||||
operationId: safesearchStatus
|
||||
summary: 'Get safesearch status'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
examples:
|
||||
application/json:
|
||||
enabled: false
|
||||
definitions:
|
||||
rule:
|
||||
type: string
|
|
@ -0,0 +1,2 @@
|
|||
.DS_Store
|
||||
node_modules
|
|
@ -0,0 +1,13 @@
|
|||
## AdGuard Home OpenAPI
|
||||
|
||||
We are using [OpenAPI specification](https://swagger.io/docs/specification/about/) to generate AdGuard Home API specification.
|
||||
|
||||
### How to edit the API spec
|
||||
|
||||
The easiest way would be to use [Swagger Editor](http://editor.swagger.io/) and just copy/paste the YAML file there.
|
||||
|
||||
### How to read the API doc
|
||||
|
||||
1. `yarn install`
|
||||
2. `yarn start`
|
||||
3. Open `http://localhost:4000/`
|
|
@ -0,0 +1,60 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>AdGuard Home API</title>
|
||||
<link rel="stylesheet" type="text/css" href="./node_modules/swagger-ui-dist/swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="./node_modules/swagger-ui-dist/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./node_modules/swagger-ui-dist/favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./node_modules/swagger-ui-dist/swagger-ui-bundle.js"> </script>
|
||||
<script src="./node_modules/swagger-ui-dist/swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "./openapi.yaml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
const express = require('express')
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(express.static(__dirname))
|
||||
|
||||
console.log('Open http://localhost:4000/ to examine the API spec')
|
||||
app.listen(4000)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "adguard-home-api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.16.4",
|
||||
"swagger-ui-dist": "^3.20.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,349 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
accepts@~1.3.5:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
|
||||
integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I=
|
||||
dependencies:
|
||||
mime-types "~2.1.18"
|
||||
negotiator "0.6.1"
|
||||
|
||||
array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
|
||||
|
||||
body-parser@1.18.3:
|
||||
version "1.18.3"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
|
||||
integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
content-type "~1.0.4"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
http-errors "~1.6.3"
|
||||
iconv-lite "0.4.23"
|
||||
on-finished "~2.3.0"
|
||||
qs "6.5.2"
|
||||
raw-body "2.3.3"
|
||||
type-is "~1.6.16"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
|
||||
|
||||
content-disposition@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
|
||||
integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
|
||||
|
||||
content-type@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
|
||||
cookie-signature@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie@0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
depd@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
destroy@~1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
|
||||
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
encodeurl@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
|
||||
|
||||
etag@~1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
express@^4.16.4:
|
||||
version "4.16.4"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
|
||||
integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
|
||||
dependencies:
|
||||
accepts "~1.3.5"
|
||||
array-flatten "1.1.1"
|
||||
body-parser "1.18.3"
|
||||
content-disposition "0.5.2"
|
||||
content-type "~1.0.4"
|
||||
cookie "0.3.1"
|
||||
cookie-signature "1.0.6"
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
finalhandler "1.1.1"
|
||||
fresh "0.5.2"
|
||||
merge-descriptors "1.0.1"
|
||||
methods "~1.1.2"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.2"
|
||||
path-to-regexp "0.1.7"
|
||||
proxy-addr "~2.0.4"
|
||||
qs "6.5.2"
|
||||
range-parser "~1.2.0"
|
||||
safe-buffer "5.1.2"
|
||||
send "0.16.2"
|
||||
serve-static "1.13.2"
|
||||
setprototypeof "1.1.0"
|
||||
statuses "~1.4.0"
|
||||
type-is "~1.6.16"
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
finalhandler@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
|
||||
integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
on-finished "~2.3.0"
|
||||
parseurl "~1.3.2"
|
||||
statuses "~1.4.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
forwarded@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
||||
integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.0"
|
||||
statuses ">= 1.4.0 < 2"
|
||||
|
||||
iconv-lite@0.4.23:
|
||||
version "0.4.23"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
|
||||
integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ipaddr.js@1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
|
||||
integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4=
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||
|
||||
methods@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
|
||||
mime-db@~1.37.0:
|
||||
version "1.37.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
|
||||
integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
|
||||
|
||||
mime-types@~2.1.18:
|
||||
version "2.1.21"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
|
||||
integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
|
||||
dependencies:
|
||||
mime-db "~1.37.0"
|
||||
|
||||
mime@1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||
integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
negotiator@0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
|
||||
integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
parseurl@~1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
|
||||
integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=
|
||||
|
||||
path-to-regexp@0.1.7:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
|
||||
|
||||
proxy-addr@~2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93"
|
||||
integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==
|
||||
dependencies:
|
||||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.8.0"
|
||||
|
||||
qs@6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
range-parser@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
|
||||
integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
|
||||
|
||||
raw-body@2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
|
||||
integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==
|
||||
dependencies:
|
||||
bytes "3.0.0"
|
||||
http-errors "1.6.3"
|
||||
iconv-lite "0.4.23"
|
||||
unpipe "1.0.0"
|
||||
|
||||
safe-buffer@5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
send@0.16.2:
|
||||
version "0.16.2"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
|
||||
integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
depd "~1.1.2"
|
||||
destroy "~1.0.4"
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
etag "~1.8.1"
|
||||
fresh "0.5.2"
|
||||
http-errors "~1.6.2"
|
||||
mime "1.4.1"
|
||||
ms "2.0.0"
|
||||
on-finished "~2.3.0"
|
||||
range-parser "~1.2.0"
|
||||
statuses "~1.4.0"
|
||||
|
||||
serve-static@1.13.2:
|
||||
version "1.13.2"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
|
||||
integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==
|
||||
dependencies:
|
||||
encodeurl "~1.0.2"
|
||||
escape-html "~1.0.3"
|
||||
parseurl "~1.3.2"
|
||||
send "0.16.2"
|
||||
|
||||
setprototypeof@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
||||
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
|
||||
|
||||
"statuses@>= 1.4.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
|
||||
statuses@~1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
|
||||
integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
|
||||
|
||||
swagger-ui-dist@^3.20.1:
|
||||
version "3.20.1"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.20.1.tgz#2e871faf29984bc5f253b8641ba38b244001cfa4"
|
||||
integrity sha512-iqFNNmJWH24leUj/ohS5iZTHLZSPZse8c9F+WSCMi6ZJcRBgYKcT413c8BR5BEdKvU1kkIwvYy7C8DOjTRq9hQ==
|
||||
|
||||
type-is@~1.6.16:
|
||||
version "1.6.16"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
|
||||
integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==
|
||||
dependencies:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.18"
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
|
||||
|
||||
utils-merge@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
|
||||
|
||||
vary@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
|
@ -3,10 +3,10 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hmage/golibs/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue