From f2eda6d192c0037e93a77ab8727eafbc9a11c085 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 17 Nov 2020 19:56:58 +0300 Subject: [PATCH 01/54] Pull request: - client: 2326 Fix dhcp bug interfaces bug Merge in DNS/adguard-home from fix/2326 to master Closes #2326. Squashed commit of the following: commit 31f24d733eda747e161ddd44055591dbbc0752d7 Author: Artem Baskal Date: Tue Nov 17 18:16:19 2020 +0300 - client: 2326 Fix dhcp bug interfaces bug --- client/src/components/Settings/Dhcp/Interfaces.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Settings/Dhcp/Interfaces.js b/client/src/components/Settings/Dhcp/Interfaces.js index 987a84a9..d2760e3d 100644 --- a/client/src/components/Settings/Dhcp/Interfaces.js +++ b/client/src/components/Settings/Dhcp/Interfaces.js @@ -72,12 +72,12 @@ const Interfaces = () => { (store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name, ); - const interfaceValue = interface_name && interfaces[interface_name]; - if (processingInterfaces || !interfaces) { return null; } + const interfaceValue = interface_name && interfaces[interface_name]; + return
Date: Wed, 18 Nov 2020 15:43:28 +0300 Subject: [PATCH 02/54] Pull request: querylog bug fix Merge in DNS/adguard-home from 2324-querylog-bug-fix to master Closes #2324. Squashed commit of the following: commit fdd584a218e1edc3e45ab5b00ceed0a3be681e32 Merge: 8103f9e42 f2eda6d19 Author: Eugene Burkov Date: Wed Nov 18 15:35:42 2020 +0300 Merge branch 'master' into 2324-querylog-bug-fix commit 8103f9e42a398f43682ee30d09b3afdab0e9e177 Author: Eugene Burkov Date: Wed Nov 18 14:28:29 2020 +0300 querylog: fix the file ordering bug commit 2c4e8fcc5b8593be1614480508dfd600fc676e64 Author: Eugene Burkov Date: Tue Nov 17 20:57:45 2020 +0300 querylog: wrap errors to clarify error trace commit 3733062b494817696e4443f153774bb01cea1b06 Author: Eugene Burkov Date: Tue Nov 17 18:55:17 2020 +0300 querylog: fix logger output bug --- internal/agherr/agherr_test.go | 2 +- internal/querylog/qlog_file.go | 15 ++-- internal/querylog/qlog_reader.go | 23 +++--- internal/querylog/qlog_reader_test.go | 111 ++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 19 deletions(-) diff --git a/internal/agherr/agherr_test.go b/internal/agherr/agherr_test.go index 3940bdee..8ef2f51f 100644 --- a/internal/agherr/agherr_test.go +++ b/internal/agherr/agherr_test.go @@ -48,7 +48,7 @@ func TestError_Unwrap(t *testing.T) { ) errs := []error{ errSimple: errors.New("a"), - errWrapped: fmt.Errorf("%w", errors.New("nested")), + errWrapped: fmt.Errorf("err: %w", errors.New("nested")), errNil: nil, } testCases := []struct { diff --git a/internal/querylog/qlog_file.go b/internal/querylog/qlog_file.go index 5c5b8fc9..31f034ec 100644 --- a/internal/querylog/qlog_file.go +++ b/internal/querylog/qlog_file.go @@ -1,6 +1,7 @@ package querylog import ( + "fmt" "io" "os" "sync" @@ -102,13 +103,14 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { if err != nil { return 0, depth, err } - if lineIdx < start || lineEndIdx > end || lineIdx == lastProbeLineIdx { // If we're testing the same line twice then most likely - // the scope is too narrow and we won't find anything anymore - log.Error("querylog: didn't find timestamp:%v", timestamp) - return 0, depth, ErrSeekNotFound + // the scope is too narrow and we won't find anything + // anymore in any other file. + return 0, depth, fmt.Errorf("couldn't find timestamp %v: %w", timestamp, ErrSeekNotFound) } else if lineIdx == end && lineEndIdx == end { + // If both line beginning and line ending indices point + // at the end of the file, we apparently reached it. return 0, depth, ErrEndOfLog } @@ -119,7 +121,7 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { ts := readQLogTimestamp(line) if ts == 0 { - return 0, depth, ErrSeekNotFound + return 0, depth, fmt.Errorf("couldn't get timestamp: %w", ErrSeekNotFound) } if ts == timestamp { @@ -141,8 +143,7 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { depth++ if depth >= 100 { - log.Error("Seek depth is too high, aborting. File %s, ts %v", q.file.Name(), timestamp) - return 0, depth, ErrSeekNotFound + return 0, depth, fmt.Errorf("seek depth is too high, aborting. File %s, timestamp %v: %w", q.file.Name(), timestamp, ErrSeekNotFound) } } diff --git a/internal/querylog/qlog_reader.go b/internal/querylog/qlog_reader.go index 41677c3e..4cf65f7c 100644 --- a/internal/querylog/qlog_reader.go +++ b/internal/querylog/qlog_reader.go @@ -2,6 +2,7 @@ package querylog import ( "errors" + "fmt" "io" "github.com/AdguardTeam/AdGuardHome/internal/agherr" @@ -49,22 +50,22 @@ func NewQLogReader(files []string) (*QLogReader, error) { // // Returns nil if the record is successfully found. // Returns an error if for some reason we could not find a record with the specified timestamp. -func (r *QLogReader) Seek(timestamp int64) error { - for i := len(r.qFiles) - 1; i >= 0; i-- { - q := r.qFiles[i] - _, _, err := q.Seek(timestamp) - if err == nil || errors.Is(err, ErrEndOfLog) { - // Our search is finished, and we either found the - // element we were looking for or reached the end of the - // log. Update currentFile only, position is already - // set properly in QLogFile. +func (r *QLogReader) Seek(timestamp int64) (err error) { + for i, q := range r.qFiles { + _, _, err = q.Seek(timestamp) + if err == nil { + // Search is finished, and the searched element have + // been found. Update currentFile only, position is + // already set properly in QLogFile. r.currentFile = i - return err } + if errors.Is(err, ErrSeekNotFound) { + break + } } - return ErrSeekNotFound + return fmt.Errorf("querylog: %w", err) } // SeekStart changes the current position to the end of the newest file diff --git a/internal/querylog/qlog_reader_test.go b/internal/querylog/qlog_reader_test.go index 357b4f9d..af59eb10 100644 --- a/internal/querylog/qlog_reader_test.go +++ b/internal/querylog/qlog_reader_test.go @@ -1,6 +1,7 @@ package querylog import ( + "errors" "io" "os" "testing" @@ -90,6 +91,116 @@ func TestQLogReaderMultipleFiles(t *testing.T) { assert.Equal(t, io.EOF, err) } +func TestQLogReader_Seek(t *testing.T) { + count := 10000 + filesCount := 2 + + testDir := prepareTestDir() + t.Cleanup(func() { + _ = os.RemoveAll(testDir) + }) + testFiles := prepareTestFiles(testDir, filesCount, count) + + r, err := NewQLogReader(testFiles) + assert.Nil(t, err) + assert.NotNil(t, r) + t.Cleanup(func() { + _ = r.Close() + }) + + testCases := []struct { + name string + time string + want error + }{{ + name: "not_too_old", + time: "2020-02-19T04:04:56.920973+03:00", + want: nil, + }, { + name: "old", + time: "2020-02-19T01:28:16.920973+03:00", + want: nil, + }, { + name: "first", + time: "2020-02-19T04:09:55.920973+03:00", + want: nil, + }, { + name: "last", + time: "2020-02-19T01:23:16.920973+03:00", + want: nil, + }, { + name: "non-existent_long_ago", + time: "2000-02-19T01:23:16.920973+03:00", + want: ErrSeekNotFound, + }, { + name: "non-existent_far_ahead", + time: "2100-02-19T01:23:16.920973+03:00", + want: ErrEndOfLog, + }, { + name: "non-existent_but_could", + time: "2020-02-19T03:00:00.000000+03:00", + want: ErrSeekNotFound, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + timestamp, err := time.Parse(time.RFC3339Nano, tc.time) + assert.Nil(t, err) + + err = r.Seek(timestamp.UnixNano()) + assert.True(t, errors.Is(err, tc.want), err) + }) + } +} + +func TestQLogReader_ReadNext(t *testing.T) { + count := 10 + filesCount := 1 + + testDir := prepareTestDir() + t.Cleanup(func() { + _ = os.RemoveAll(testDir) + }) + testFiles := prepareTestFiles(testDir, filesCount, count) + + r, err := NewQLogReader(testFiles) + assert.Nil(t, err) + assert.NotNil(t, r) + t.Cleanup(func() { + _ = r.Close() + }) + + testCases := []struct { + name string + start int + want error + }{{ + name: "ok", + start: 0, + want: nil, + }, { + name: "too_big", + start: count + 1, + want: io.EOF, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := r.SeekStart() + assert.Nil(t, err, err) + + for i := 1; i < tc.start; i++ { + _, err := r.ReadNext() + assert.Nil(t, err) + } + + _, err = r.ReadNext() + assert.Equal(t, tc.want, err) + }) + } +} + +// TODO(e.burkov): Remove the tests below. Make tests above more compelling. func TestQLogReaderSeek(t *testing.T) { // more or less big file count := 10000 From de257b73aa459659e6085e3ffc064af640b50db3 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 18 Nov 2020 18:42:26 +0300 Subject: [PATCH 03/54] Pull request: update golangci-lint and golangci-lint-actions versions Merge in DNS/adguard-home from linter-version-update to master Squashed commit of the following: commit 3ab2cb198dd924d86f5e6b92d101846acc747e9b Author: Eugene Burkov Date: Wed Nov 18 16:42:13 2020 +0300 all: update golangci-lint and golangci-lint-actions versions --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2ddf108f..71de1b4f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,10 +12,10 @@ jobs: steps: - uses: actions/checkout@v2 - name: golangci-lint - uses: golangci/golangci-lint-action@v1 + uses: golangci/golangci-lint-action@v2.3.0 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.27 + version: v1.32 eslint: runs-on: ubuntu-latest From 4690229d815b5f6fc25410c8d85be6fa99c1db7b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 19 Nov 2020 12:53:31 +0300 Subject: [PATCH 04/54] Pull request: querylog: use better error signaling Merge in DNS/adguard-home from 2325-querylog-suffering to master Closes #2325. Squashed commit of the following: commit 90388050ed495286cdfed6574dd438abd4a33baa Author: Ainar Garipov Date: Thu Nov 19 12:37:00 2020 +0300 all: changelog commit bbdeabbb550c7e98f579e2a68c71de7a66624203 Author: Ainar Garipov Date: Thu Nov 19 12:33:21 2020 +0300 querylog: improve error reporting commit 807b23aa74d0e39f5ef51910e5b91c9b95a8c341 Author: Ainar Garipov Date: Wed Nov 18 19:39:22 2020 +0300 querylog: improve docs commit 65a8f4f3323192c872b3389d2b3420e072a01297 Author: Ainar Garipov Date: Wed Nov 18 19:36:28 2020 +0300 querylog: use better error signaling --- CHANGELOG.md | 2 ++ internal/querylog/qlog_file.go | 34 ++++++++++++------------ internal/querylog/qlog_file_test.go | 37 ++++++++++++++++++++++++--- internal/querylog/qlog_reader.go | 18 ++++++++++--- internal/querylog/qlog_reader_test.go | 8 +++--- internal/querylog/querylog_search.go | 4 --- 6 files changed, 71 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7e8f4d5..4502a81a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to ### Fixed +- Query logs from file not loading after the ones buffered in memory (#2325). +- Unnecessary errors in query logs when switching between log files (#2324). - `404 Not Found` errors on the DHCP settings page on *Windows*. The page now correctly shows that DHCP is not currently available on that OS (#2295). - Infinite loop in `/dhcp/find_active_dhcp` (#2301). diff --git a/internal/querylog/qlog_file.go b/internal/querylog/qlog_file.go index 31f034ec..13754427 100644 --- a/internal/querylog/qlog_file.go +++ b/internal/querylog/qlog_file.go @@ -11,12 +11,12 @@ import ( "github.com/AdguardTeam/golibs/log" ) -// ErrSeekNotFound is returned from Seek if when it fails to find the requested -// record. -const ErrSeekNotFound agherr.Error = "seek: record not found" - -// ErrEndOfLog is returned from Seek when the end of the current log is reached. -const ErrEndOfLog agherr.Error = "seek: end of log" +// Timestamp not found errors. +const ( + ErrTSNotFound agherr.Error = "ts not found" + ErrTSTooLate agherr.Error = "ts too late" + ErrTSTooEarly agherr.Error = "ts too early" +) // TODO: Find a way to grow buffer instead of relying on this value when reading strings const maxEntrySize = 16 * 1024 @@ -69,7 +69,7 @@ func NewQLogFile(path string) (*QLogFile, error) { // * It returns the position of the the line with the timestamp we were looking for // so that when we call "ReadNext" this line was returned. // * Depth of the search (how many times we compared timestamps). -// * If we could not find it, it returns ErrSeekNotFound +// * If we could not find it, it returns one of the errors described above. func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { q.lock.Lock() defer q.lock.Unlock() @@ -103,15 +103,18 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { if err != nil { return 0, depth, err } - if lineIdx < start || lineEndIdx > end || lineIdx == lastProbeLineIdx { + + if lineIdx == lastProbeLineIdx { + if lineIdx == 0 { + return 0, depth, ErrTSTooEarly + } + // If we're testing the same line twice then most likely // the scope is too narrow and we won't find anything // anymore in any other file. - return 0, depth, fmt.Errorf("couldn't find timestamp %v: %w", timestamp, ErrSeekNotFound) - } else if lineIdx == end && lineEndIdx == end { - // If both line beginning and line ending indices point - // at the end of the file, we apparently reached it. - return 0, depth, ErrEndOfLog + return 0, depth, fmt.Errorf("looking up timestamp %d in %q: %w", timestamp, q.file.Name(), ErrTSNotFound) + } else if lineIdx == fileInfo.Size() { + return 0, depth, ErrTSTooLate } // Save the last found idx @@ -119,9 +122,8 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { // Get the timestamp from the query log record ts := readQLogTimestamp(line) - if ts == 0 { - return 0, depth, fmt.Errorf("couldn't get timestamp: %w", ErrSeekNotFound) + return 0, depth, fmt.Errorf("looking up timestamp %d in %q: record %q has empty timestamp", timestamp, q.file.Name(), line) } if ts == timestamp { @@ -143,7 +145,7 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { depth++ if depth >= 100 { - return 0, depth, fmt.Errorf("seek depth is too high, aborting. File %s, timestamp %v: %w", q.file.Name(), timestamp, ErrSeekNotFound) + return 0, depth, fmt.Errorf("looking up timestamp %d in %q: depth %d too high: %w", timestamp, q.file.Name(), depth, ErrTSNotFound) } } diff --git a/internal/querylog/qlog_file_test.go b/internal/querylog/qlog_file_test.go index de402e8b..b458e071 100644 --- a/internal/querylog/qlog_file_test.go +++ b/internal/querylog/qlog_file_test.go @@ -243,10 +243,10 @@ func prepareTestFiles(dir string, filesCount, linesCount int) []string { lineTime, _ := time.Parse(time.RFC3339Nano, "2020-02-18T22:36:35.920973+03:00") lineIP := uint32(0) - files := make([]string, 0) + files := make([]string, filesCount) for j := 0; j < filesCount; j++ { f, _ := ioutil.TempFile(dir, "*.txt") - files = append(files, f.Name()) + files[filesCount-j-1] = f.Name() for i := 0; i < linesCount; i++ { lineIP += 1 @@ -289,7 +289,7 @@ func TestQLogSeek(t *testing.T) { assert.Equal(t, 1, depth) } -func TestQLogSeek_ErrEndOfLog(t *testing.T) { +func TestQLogSeek_ErrTSTooLate(t *testing.T) { testDir := prepareTestDir() t.Cleanup(func() { _ = os.RemoveAll(testDir) @@ -314,6 +314,35 @@ func TestQLogSeek_ErrEndOfLog(t *testing.T) { assert.Nil(t, err) _, depth, err := q.Seek(target.UnixNano() + int64(time.Second)) - assert.Equal(t, ErrEndOfLog, err) + assert.Equal(t, ErrTSTooLate, err) assert.Equal(t, 2, depth) } + +func TestQLogSeek_ErrTSTooEarly(t *testing.T) { + testDir := prepareTestDir() + t.Cleanup(func() { + _ = os.RemoveAll(testDir) + }) + + d := `{"T":"2020-08-31T18:44:23.911246629+03:00","QH":"wfqvjymurpwegyv","QT":"A","QC":"IN","CP":"","Answer":"","Result":{},"Elapsed":66286385,"Upstream":"tls://dns-unfiltered.adguard.com:853"} +{"T":"2020-08-31T18:44:25.376690873+03:00"} +{"T":"2020-08-31T18:44:25.382540454+03:00"} +` + f, err := ioutil.TempFile(testDir, "*.txt") + assert.Nil(t, err) + defer f.Close() + + _, err = f.WriteString(d) + assert.Nil(t, err) + + q, err := NewQLogFile(f.Name()) + assert.Nil(t, err) + defer q.Close() + + target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:23.911246629+03:00") + assert.Nil(t, err) + + _, depth, err := q.Seek(target.UnixNano() - int64(time.Second)) + assert.Equal(t, ErrTSTooEarly, err) + assert.Equal(t, 1, depth) +} diff --git a/internal/querylog/qlog_reader.go b/internal/querylog/qlog_reader.go index 4cf65f7c..dbb058c6 100644 --- a/internal/querylog/qlog_reader.go +++ b/internal/querylog/qlog_reader.go @@ -51,16 +51,26 @@ func NewQLogReader(files []string) (*QLogReader, error) { // Returns nil if the record is successfully found. // Returns an error if for some reason we could not find a record with the specified timestamp. func (r *QLogReader) Seek(timestamp int64) (err error) { - for i, q := range r.qFiles { + for i := len(r.qFiles) - 1; i >= 0; i-- { + q := r.qFiles[i] _, _, err = q.Seek(timestamp) if err == nil { // Search is finished, and the searched element have // been found. Update currentFile only, position is // already set properly in QLogFile. r.currentFile = i - return err - } - if errors.Is(err, ErrSeekNotFound) { + + return nil + } else if errors.Is(err, ErrTSTooEarly) { + // Look at the next file, since we've reached the end of + // this one. + continue + } else if errors.Is(err, ErrTSTooLate) { + // Just seek to the start then. timestamp is probably + // between the end of the previous one and the start of + // this one. + return r.SeekStart() + } else if errors.Is(err, ErrTSNotFound) { break } } diff --git a/internal/querylog/qlog_reader_test.go b/internal/querylog/qlog_reader_test.go index af59eb10..f1802a0a 100644 --- a/internal/querylog/qlog_reader_test.go +++ b/internal/querylog/qlog_reader_test.go @@ -131,15 +131,15 @@ func TestQLogReader_Seek(t *testing.T) { }, { name: "non-existent_long_ago", time: "2000-02-19T01:23:16.920973+03:00", - want: ErrSeekNotFound, + want: ErrTSTooEarly, }, { name: "non-existent_far_ahead", time: "2100-02-19T01:23:16.920973+03:00", - want: ErrEndOfLog, + want: nil, }, { name: "non-existent_but_could", - time: "2020-02-19T03:00:00.000000+03:00", - want: ErrSeekNotFound, + time: "2020-02-18T22:36:37.000000+03:00", + want: ErrTSNotFound, }} for _, tc := range testCases { diff --git a/internal/querylog/querylog_search.go b/internal/querylog/querylog_search.go index 133a1dba..2e8ce333 100644 --- a/internal/querylog/querylog_search.go +++ b/internal/querylog/querylog_search.go @@ -1,7 +1,6 @@ package querylog import ( - "errors" "io" "time" @@ -95,9 +94,6 @@ func (l *queryLog) searchFiles(params *searchParams) ([]*logEntry, time.Time, in // The one that was specified in the "oldest" param is not needed, // we need only the one next to it _, err = r.ReadNext() - } else if errors.Is(err, ErrEndOfLog) { - // We've reached the end of the log. - return entries, time.Time{}, 0 } } From b1c71a12847af0c0229f531c06b307816a6da4f1 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 19 Nov 2020 13:14:52 +0300 Subject: [PATCH 05/54] Pull request: all: prepare for v0.104.2 Merge in DNS/adguard-home from prepare-release to master Squashed commit of the following: commit cf4cad216fa259b260bc9cfbd99938979b10623c Merge: 5434ab5c1 4690229d8 Author: Ainar Garipov Date: Thu Nov 19 13:08:50 2020 +0300 Merge branch 'master' into prepare-release commit 5434ab5c178942e8db9e99758c86d22e977d092f Author: Ainar Garipov Date: Thu Nov 19 13:01:43 2020 +0300 all: prepare for v0.104.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4502a81a..7856c663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to ## [Unreleased] +## [v0.104.2] - 2020-11-19 + ### Added - This changelog :-) (#2294). @@ -25,3 +27,6 @@ and this project adheres to - `404 Not Found` errors on the DHCP settings page on *Windows*. The page now correctly shows that DHCP is not currently available on that OS (#2295). - Infinite loop in `/dhcp/find_active_dhcp` (#2301). + +[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...HEAD +[v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2 From 62a8fe0b73d16b9f71234f6b4efbba560ba470e2 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 19 Nov 2020 14:59:30 +0300 Subject: [PATCH 06/54] Pull request: home: add a patch against the global pprof handlers Merge in DNS/adguard-home from 2336-pprof to master Closes #2336. Squashed commit of the following: commit 855e133b17da4274bef7dec5c3b7db73486d97db Author: Ainar Garipov Date: Thu Nov 19 14:49:22 2020 +0300 home: add a patch against the global pprof handlers --- CHANGELOG.md | 36 +++++++++++++++++++++++++++++------- internal/home/web.go | 18 ++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7856c663..eb01e003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,24 +9,46 @@ and this project adheres to ## [Unreleased] + + +## [v0.104.3] - 2020-11-19 + +### Fixed + +- The accidentally exposed profiler HTTP API ([#2336]). + +[#2336]: https://github.com/AdguardTeam/AdGuardHome/issues/2336 + + + ## [v0.104.2] - 2020-11-19 ### Added -- This changelog :-) (#2294). +- This changelog :-) ([#2294]). - `HACKING.md`, a guide for developers. ### Changed -- Improved tests output (#2273). +- Improved tests output ([#2273]). ### Fixed -- Query logs from file not loading after the ones buffered in memory (#2325). -- Unnecessary errors in query logs when switching between log files (#2324). +- Query logs from file not loading after the ones buffered in memory ([#2325]). +- Unnecessary errors in query logs when switching between log files ([#2324]). - `404 Not Found` errors on the DHCP settings page on *Windows*. The page now - correctly shows that DHCP is not currently available on that OS (#2295). -- Infinite loop in `/dhcp/find_active_dhcp` (#2301). + correctly shows that DHCP is not currently available on that OS ([#2295]). +- Infinite loop in `/dhcp/find_active_dhcp` ([#2301]). -[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...HEAD +[#2273]: https://github.com/AdguardTeam/AdGuardHome/issues/2273 +[#2294]: https://github.com/AdguardTeam/AdGuardHome/issues/2294 +[#2295]: https://github.com/AdguardTeam/AdGuardHome/issues/2295 +[#2301]: https://github.com/AdguardTeam/AdGuardHome/issues/2301 +[#2324]: https://github.com/AdguardTeam/AdGuardHome/issues/2324 +[#2325]: https://github.com/AdguardTeam/AdGuardHome/issues/2325 + + + +[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...HEAD +[v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3 [v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2 diff --git a/internal/home/web.go b/internal/home/web.go index 976ea3ab..f8ceb296 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -7,6 +7,7 @@ import ( "net" "net/http" "strconv" + "strings" "sync" "github.com/AdguardTeam/AdGuardHome/internal/util" @@ -141,6 +142,7 @@ func (web *Web) Start() { web.httpServer = &http.Server{ ErrorLog: web.errLogger, Addr: address, + Handler: filterPPROF(http.DefaultServeMux), } err := web.httpServer.ListenAndServe() if err != http.ErrServerClosed { @@ -151,6 +153,22 @@ func (web *Web) Start() { } } +// TODO(a.garipov): We currently have to use this, because everything registers +// its HTTP handlers in http.DefaultServeMux. In the future, refactor our HTTP +// API initialization process and stop using the gosh darn http.DefaultServeMux +// for anything at all. Gosh darn global variables. +func filterPPROF(h http.Handler) (filtered http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/debug/pprof") { + http.NotFound(w, r) + + return + } + + h.ServeHTTP(w, r) + }) +} + // Close - stop HTTP server, possibly waiting for all active connections to be closed func (web *Web) Close() { log.Info("Stopping HTTP server...") From 642dcd647c0f753f7955499484a9ad01a4a36eca Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 20 Nov 2020 13:44:21 +0300 Subject: [PATCH 07/54] Pull request: all: update backend tools and dependencies Merge in DNS/adguard-home from 2275-update-tools to master Squashed commit of the following: commit 4de1cf91dc7accabeb2103d3c8ec424bee2a89ce Merge: 06b302c62 62a8fe0b7 Author: Ainar Garipov Date: Thu Nov 19 16:22:30 2020 +0300 Merge branch 'master' into 2275-update-tools commit 06b302c62958aa8ab4a9da423a32cd71037d58d7 Author: Ainar Garipov Date: Fri Nov 13 11:29:19 2020 +0300 all: update backend tools and dependencies --- Makefile | 22 ++-- go.mod | 25 ++-- go.sum | 289 ++++++++++++++++++++++++++++++++++++++++--- internal/dhcpd/v4.go | 10 +- 4 files changed, 303 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 24805894..f98d430f 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)" GPG_KEY := devteam@adguard.com GPG_KEY_PASSPHRASE := GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE) +VERBOSE := -v # See release target DIST_DIR=dist @@ -109,7 +110,7 @@ $(error DOCKER_IMAGE_NAME value is not set) endif # OS-specific flags -TEST_FLAGS := --race -v +TEST_FLAGS := --race $(VERBOSE) ifeq ($(OS),Windows_NT) TEST_FLAGS := endif @@ -177,20 +178,11 @@ dependencies: go mod download clean: - # make build output - rm -f AdGuardHome - rm -f AdGuardHome.exe - # tests output - rm -rf data - rm -f coverage.txt - # static build output - rm -rf build - # dist folder - rm -rf $(DIST_DIR) - # client deps - rm -rf client/node_modules - # packr-generated files - PATH=$(GOPATH)/bin:$(PATH) packr clean || true + rm -f ./AdGuardHome ./AdGuardHome.exe ./coverage.txt + rm -f -r ./build/ ./client/node_modules/ ./data/ $(DIST_DIR) +# Set the GOPATH explicitly in case make clean is called from under sudo +# after a Docker build. + env PATH="$(GOPATH)/bin:$$PATH" packr clean docker-multi-arch: DOCKER_CLI_EXPERIMENTAL=enabled \ diff --git a/go.mod b/go.mod index 467dfee6..fef5e380 100644 --- a/go.mod +++ b/go.mod @@ -9,29 +9,34 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect github.com/fsnotify/fsnotify v1.4.9 + github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 + github.com/gobuffalo/envy v1.9.0 // indirect github.com/gobuffalo/packr v1.30.1 + github.com/gobuffalo/packr/v2 v2.8.1 // indirect github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 - github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8 + github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 github.com/joomcode/errorx v1.0.3 // indirect - github.com/kardianos/service v1.1.0 + github.com/kardianos/service v1.2.0 + github.com/karrick/godirwalk v1.16.1 // indirect + github.com/lucas-clemente/quic-go v0.19.1 // indirect github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/miekg/dns v1.1.35 - github.com/rogpeppe/go-internal v1.5.2 // indirect + github.com/rogpeppe/go-internal v1.6.2 // indirect github.com/satori/go.uuid v1.2.0 - github.com/sirupsen/logrus v1.6.0 // indirect - github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c + github.com/sirupsen/logrus v1.7.0 // indirect + github.com/spf13/cobra v1.1.1 // indirect github.com/stretchr/testify v1.6.1 - github.com/u-root/u-root v6.0.0+incompatible - go.etcd.io/bbolt v1.3.4 - golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + github.com/u-root/u-root v7.0.0+incompatible + go.etcd.io/bbolt v1.3.5 + golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect - golang.org/x/sys v0.0.0-20201109165425-215b40eba54c + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 golang.org/x/text v0.3.4 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect - howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 + howett.net/plist v0.0.0-20201026045517-117a925f2150 ) diff --git a/go.sum b/go.sum index 65a1fd0c..ccbe48fa 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= @@ -19,14 +30,18 @@ github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwg github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/ameshkov/dnscrypt/v2 v2.0.0 h1:i83G8MeGLrAFgUL8GSu98TVhtFDEifF7SIS7Qi/RZ3U= github.com/ameshkov/dnscrypt/v2 v2.0.0/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI= github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug= @@ -34,30 +49,47 @@ github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaE github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 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/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g= github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -67,27 +99,47 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ping/ping v0.0.0-20201022122018-3977ed72668a h1:O9xspHB2yrvKfMQ1m6OQhqe37i5yvg0dXAYMuAjugmM= +github.com/go-ping/ping v0.0.0-20201022122018-3977ed72668a/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= +github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 h1:jI2GiiRh+pPbey52EVmbU6kuLiXqwy4CXZ4gwUBj8Y0= +github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.5 h1:AKODKU3pDH1RzZzm6YZu77YWtEAq6uh1rLIAQlay2qc= github.com/go-test/deep v1.0.5/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4= github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= +github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= @@ -105,6 +157,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -113,45 +166,87 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8 h1:u+vle+5E78+cT/CSMD5/Y3NUpMgA83Yu2KhG+Zbco/k= -github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw= +github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ= +github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= 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/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk= github.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ= github.com/joomcode/errorx v1.0.3 h1:3e1mi0u7/HTPNdg6d6DYyKGBhA5l9XpsfuVE29NxnWw= github.com/joomcode/errorx v1.0.3/go.mod h1:eQzdtdlNyN7etw6YCS4W4+lu442waxZYw5yvz0ULrRo= +github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= +github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= +github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= +github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0= github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc= +github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= +github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= +github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -160,38 +255,65 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= +github.com/lucas-clemente/quic-go v0.19.0 h1:IG5lB7DfHl6eZ7WTBVL8bnbDg0JGwDv906l6JffQbyg= +github.com/lucas-clemente/quic-go v0.19.0/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= +github.com/lucas-clemente/quic-go v0.19.1 h1:J9TkQJGJVOR3UmGhd4zdVYwKSA0EoXbLRf15uQJ6gT4= +github.com/lucas-clemente/quic-go v0.19.1/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg= github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= +github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= +github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= +github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= +github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= @@ -200,27 +322,45 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.20.3+incompatible h1:0JVooMPsT7A7HqEYdydp/OfjSOYSjhXV7w1hkKj/NPQ= github.com/shirou/gopsutil v2.20.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -244,24 +384,35 @@ github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= -github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c h1:gqEdF4VwBu3lTKGHS9rXE9x1/pEaSwCXRLOZRF6qtlw= -github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -272,58 +423,105 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE= -github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8= +github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= -go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -331,6 +529,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -342,22 +541,35 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -367,9 +579,16 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201109165425-215b40eba54c h1:+B+zPA6081G5cEb2triOIJpcvSW4AYzmIyWAqMn2JAc= -golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -381,17 +600,33 @@ golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -400,23 +635,38 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -430,6 +680,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= @@ -437,10 +688,13 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -457,9 +711,12 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc= -howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v0.0.0-20201026045517-117a925f2150 h1:s7O/9fwMNd6O1yXyQ8zv+U7dfl8k+zdiLWAY8h7XdVI= +howett.net/plist v0.0.0-20201026045517-117a925f2150/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 9ad032c1..d88272e4 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -11,9 +11,9 @@ import ( "time" "github.com/AdguardTeam/golibs/log" + "github.com/go-ping/ping" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4/server4" - "github.com/sparrc/go-ping" ) // v4Server - DHCPv4 server @@ -244,6 +244,7 @@ func (s *v4Server) addrAvailable(target net.IP) bool { pinger, err := ping.NewPinger(target.String()) if err != nil { log.Error("ping.NewPinger(): %v", err) + return true } @@ -255,7 +256,12 @@ func (s *v4Server) addrAvailable(target net.IP) bool { reply = true } log.Debug("dhcpv4: Sending ICMP Echo to %v", target) - pinger.Run() + + err = pinger.Run() + if err != nil { + log.Error("pinger.Run(): %v", err) + return true + } if reply { log.Info("dhcpv4: IP conflict: %v is already used by another device", target) From 3045da1742ee0e75392de1623900d7d88b08f0b4 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 20 Nov 2020 17:32:41 +0300 Subject: [PATCH 08/54] Pull request: 2271 handle nolint Merge in DNS/adguard-home from 2271-handle-nolint to master Closes #2271. Squashed commit of the following: commit fde5c8795ac79e1f7d02ba8c8e369b5a724a000e Merge: fc2acd898 642dcd647 Author: Eugene Burkov Date: Fri Nov 20 17:12:28 2020 +0300 Merge branch 'master' into 2271-handle-nolint commit fc2acd89871de08c39e80ace9e5bb8a7acb7afba Author: Eugene Burkov Date: Tue Nov 17 11:55:29 2020 +0300 dnsforward: fix test output strings commit c4ebae6ea9c293bad239519c44ca5a6c576bb921 Author: Eugene Burkov Date: Mon Nov 16 22:43:20 2020 +0300 dnsfilter: make package pass tests commit f2d98c6acabd8977f3b1b361987eaa31eb6eb9ad Author: Eugene Burkov Date: Mon Nov 16 20:05:00 2020 +0300 querylog: make decoding pass tests commit ab5850d24c50d53b8393f2de448cc340241351d7 Merge: 6ed2066bf 8a9c6e8a0 Author: Eugene Burkov Date: Mon Nov 16 19:48:31 2020 +0300 Merge branch 'master' into 2271-handle-nolint commit 6ed2066bf567e13dd14cfa16fc7b109b59fa39ef Author: Eugene Burkov Date: Mon Nov 16 18:13:45 2020 +0300 home: fix tests naming commit af691081fb02b7500a746b16492f01f7f9befe9a Author: Eugene Burkov Date: Mon Nov 16 12:15:49 2020 +0300 home: impove code quality commit 2914cd3cd23ef2a1964116baab9187d89b377f86 Author: Eugene Burkov Date: Wed Nov 11 15:46:39 2020 +0300 * querylog: remove useless check commit 9996840650e784ccc76d1f29964560435ba27dc7 Author: Eugene Burkov Date: Wed Nov 11 13:18:34 2020 +0300 * all: fix noticed defects commit 2b15293e59337f70302fbc0db81ebb26bee0bed2 Author: Eugene Burkov Date: Tue Nov 10 20:15:53 2020 +0300 * stats: remove last nolint directive commit b2e1ddf7b58196a2fdbf879f084edb41ca1aa1eb Author: Eugene Burkov Date: Tue Nov 10 18:35:41 2020 +0300 * all: remove another nolint directive commit c6fc5cfcc9c95ab9e570a95ab41c3e5c0125e62e Author: Eugene Burkov Date: Tue Nov 10 18:11:28 2020 +0300 * querylog: remove nolint directive commit 226ddbf2c92f737f085b44a4ddf6daec7b602153 Author: Eugene Burkov Date: Tue Nov 10 16:35:26 2020 +0300 * home: remove nolint directive commit 2ea3086ad41e9003282add7e996ae722d72d878b Author: Eugene Burkov Date: Tue Nov 10 16:13:57 2020 +0300 * home: reduce cyclomatic complexity of run function commit f479b480c48e0bb832ddef8f57586f56b8a55bab Author: Eugene Burkov Date: Tue Nov 10 15:35:46 2020 +0300 * home: use crypto/rand instead of math/rand commit a28d4a53e3b930136b036606fc7e78404f1d208b Author: Eugene Burkov Date: Tue Nov 10 14:11:07 2020 +0300 * dnsforward: remove gocyclo nolint directive commit 64a0a324cc2b20614ceec3ccc6505e960fe526e9 Author: Eugene Burkov Date: Tue Nov 10 11:45:49 2020 +0300 all *: remove some nolint directives Updates #2271. --- .golangci.yml | 1 - internal/dnsfilter/dnsfilter.go | 10 + internal/dnsfilter/sb_pc.go | 173 ++++----- internal/dnsforward/config.go | 19 +- internal/dnsforward/dnsforward_http.go | 407 +++++++++++--------- internal/dnsforward/dnsforward_http_test.go | 28 +- internal/home/auth.go | 137 ++++--- internal/home/auth_test.go | 6 +- internal/home/dns.go | 58 +-- internal/home/filter.go | 112 +++--- internal/home/home.go | 71 ++-- internal/home/ipdetector.go | 72 ++++ internal/home/ipdetector_test.go | 146 +++++++ internal/querylog/decode.go | 344 ++++++++++------- internal/querylog/decode_test.go | 27 -- internal/querylog/search_criteria.go | 123 +++--- internal/stats/stats_unit.go | 151 ++------ 17 files changed, 1053 insertions(+), 832 deletions(-) create mode 100644 internal/home/ipdetector.go create mode 100644 internal/home/ipdetector_test.go diff --git a/.golangci.yml b/.golangci.yml index be43ec74..29ec1f4e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,7 +39,6 @@ linters: - govet - ineffassign - staticcheck - - structcheck - unused - varcheck - bodyclose diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index a4ac31b8..b57d03fb 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -177,6 +177,16 @@ func (r Reason) String() string { return reasonNames[r] } +// In returns true if reasons include r. +func (r Reason) In(reasons ...Reason) bool { + for _, reason := range reasons { + if r == reason { + return true + } + } + return false +} + // GetConfig - get configuration func (d *Dnsfilter) GetConfig() RequestFilteringSettings { c := RequestFilteringSettings{} diff --git a/internal/dnsfilter/sb_pc.go b/internal/dnsfilter/sb_pc.go index eb5457a6..29a39fa2 100644 --- a/internal/dnsfilter/sb_pc.go +++ b/internal/dnsfilter/sb_pc.go @@ -71,31 +71,35 @@ func (c *sbCtx) setCache(prefix, hashes []byte) { log.Debug("%s: stored in cache: %v", c.svc, prefix) } +// findInHash returns 32-byte hash if it's found in hashToHost. +func (c *sbCtx) findInHash(val []byte) (hash32 [32]byte, found bool) { + for i := 4; i < len(val); i += 32 { + hash := val[i : i+32] + + copy(hash32[:], hash[0:32]) + + _, found = c.hashToHost[hash32] + if found { + return hash32, found + } + } + + return [32]byte{}, false +} + func (c *sbCtx) getCached() int { now := time.Now().Unix() hashesToRequest := map[[32]byte]string{} for k, v := range c.hashToHost { key := k[0:2] val := c.cache.Get(key) - if val != nil { - expire := binary.BigEndian.Uint32(val) - if now >= int64(expire) { - val = nil - } else { - for i := 4; i < len(val); i += 32 { - hash := val[i : i+32] - var hash32 [32]byte - copy(hash32[:], hash[0:32]) - _, found := c.hashToHost[hash32] - if found { - log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32) - return 1 - } - } - } - } - if val == nil { + if val == nil || now >= int64(binary.BigEndian.Uint32(val)) { hashesToRequest[k] = v + continue + } + if hash32, found := c.findInHash(val); found { + log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32) + return 1 } } @@ -254,106 +258,71 @@ func (c *sbCtx) storeCache(hashes [][]byte) { } } -// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data -// nolint:dupl +func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) { + c.hashToHost = hostnameToHashes(c.host) + switch c.getCached() { + case -1: + return Result{}, nil + case 1: + return r, nil + } + + question := c.getQuestion() + + log.Tracef("%s: checking %s: %s", c.svc, c.host, question) + req := (&dns.Msg{}).SetQuestion(question, dns.TypeTXT) + + resp, err := u.Exchange(req) + if err != nil { + return Result{}, err + } + + matched, receivedHashes := c.processTXT(resp) + + c.storeCache(receivedHashes) + if matched { + return r, nil + } + + return Result{}, nil +} + func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) { if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() defer timer.LogElapsed("SafeBrowsing lookup for %s", host) } - - result := Result{} - hashes := hostnameToHashes(host) - - c := &sbCtx{ - host: host, - svc: "SafeBrowsing", - hashToHost: hashes, - cache: gctx.safebrowsingCache, - cacheTime: d.Config.CacheTime, + ctx := &sbCtx{ + host: host, + svc: "SafeBrowsing", + cache: gctx.safebrowsingCache, + cacheTime: d.Config.CacheTime, } - - // check cache - match := c.getCached() - if match < 0 { - return result, nil - } else if match > 0 { - result.IsFiltered = true - result.Reason = FilteredSafeBrowsing - result.Rule = "adguard-malware-shavar" - return result, nil + res := Result{ + IsFiltered: true, + Reason: FilteredSafeBrowsing, + Rule: "adguard-malware-shavar", } - - question := c.getQuestion() - log.Tracef("SafeBrowsing: checking %s: %s", host, question) - - req := dns.Msg{} - req.SetQuestion(question, dns.TypeTXT) - resp, err := d.safeBrowsingUpstream.Exchange(&req) - if err != nil { - return result, err - } - - matched, receivedHashes := c.processTXT(resp) - if matched { - result.IsFiltered = true - result.Reason = FilteredSafeBrowsing - result.Rule = "adguard-malware-shavar" - } - c.storeCache(receivedHashes) - - return result, nil + return check(ctx, res, d.safeBrowsingUpstream) } -// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data -// nolint:dupl func (d *Dnsfilter) checkParental(host string) (Result, error) { if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() defer timer.LogElapsed("Parental lookup for %s", host) } - - result := Result{} - hashes := hostnameToHashes(host) - - c := &sbCtx{ - host: host, - svc: "Parental", - hashToHost: hashes, - cache: gctx.parentalCache, - cacheTime: d.Config.CacheTime, + ctx := &sbCtx{ + host: host, + svc: "Parental", + cache: gctx.parentalCache, + cacheTime: d.Config.CacheTime, } - - // check cache - match := c.getCached() - if match < 0 { - return result, nil - } else if match > 0 { - result.IsFiltered = true - result.Reason = FilteredParental - result.Rule = "parental CATEGORY_BLACKLISTED" - return result, nil + res := Result{ + IsFiltered: true, + Reason: FilteredParental, + Rule: "parental CATEGORY_BLACKLISTED", } - - question := c.getQuestion() - log.Tracef("Parental: checking %s: %s", host, question) - - req := dns.Msg{} - req.SetQuestion(question, dns.TypeTXT) - resp, err := d.parentalUpstream.Exchange(&req) - if err != nil { - return result, err - } - - matched, receivedHashes := c.processTXT(resp) - if matched { - result.IsFiltered = true - result.Reason = FilteredParental - result.Rule = "parental CATEGORY_BLACKLISTED" - } - c.storeCache(receivedHashes) - - return result, err + return check(ctx, res, d.parentalUpstream) } func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) { diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 93aa312b..73f5cb6d 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -94,19 +94,24 @@ type FilteringConfig struct { type TLSConfig struct { TLSListenAddr *net.TCPAddr `yaml:"-" json:"-"` QUICListenAddr *net.UDPAddr `yaml:"-" json:"-"` - StrictSNICheck bool `yaml:"strict_sni_check" json:"-"` // Reject connection if the client uses server name (in SNI) that doesn't match the certificate - CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` // PEM-encoded certificates chain - PrivateKey string `yaml:"private_key" json:"private_key"` // PEM-encoded private key + // Reject connection if the client uses server name (in SNI) that doesn't match the certificate + StrictSNICheck bool `yaml:"strict_sni_check" json:"-"` - CertificatePath string `yaml:"certificate_path" json:"certificate_path"` // certificate file name - PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"` // private key file name + // PEM-encoded certificates chain + CertificateChain string `yaml:"certificate_chain" json:"certificate_chain"` + // PEM-encoded private key + PrivateKey string `yaml:"private_key" json:"private_key"` + + CertificatePath string `yaml:"certificate_path" json:"certificate_path"` + PrivateKeyPath string `yaml:"private_key_path" json:"private_key_path"` CertificateChainData []byte `yaml:"-" json:"-"` PrivateKeyData []byte `yaml:"-" json:"-"` - cert tls.Certificate // nolint(structcheck) - linter thinks that this field is unused, while TLSConfig is directly included into ServerConfig - dnsNames []string // nolint(structcheck) // DNS names from certificate (SAN) or CN value from Subject + cert tls.Certificate + // DNS names from certificate (SAN) or CN value from Subject + dnsNames []string } // ServerConfig represents server configuration. diff --git a/internal/dnsforward/dnsforward_http.go b/internal/dnsforward/dnsforward_http.go index 1e156c50..c6c33c4e 100644 --- a/internal/dnsforward/dnsforward_http.go +++ b/internal/dnsforward/dnsforward_http.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/AdguardTeam/dnsproxy/upstream" - "github.com/AdguardTeam/golibs/jsonutil" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" "github.com/miekg/dns" @@ -21,232 +20,292 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } -type dnsConfigJSON struct { - Upstreams []string `json:"upstream_dns"` - UpstreamsFile string `json:"upstream_dns_file"` - Bootstraps []string `json:"bootstrap_dns"` +type dnsConfig struct { + Upstreams *[]string `json:"upstream_dns"` + UpstreamsFile *string `json:"upstream_dns_file"` + Bootstraps *[]string `json:"bootstrap_dns"` - ProtectionEnabled bool `json:"protection_enabled"` - RateLimit uint32 `json:"ratelimit"` - BlockingMode string `json:"blocking_mode"` - BlockingIPv4 string `json:"blocking_ipv4"` - BlockingIPv6 string `json:"blocking_ipv6"` - EDNSCSEnabled bool `json:"edns_cs_enabled"` - DNSSECEnabled bool `json:"dnssec_enabled"` - DisableIPv6 bool `json:"disable_ipv6"` - UpstreamMode string `json:"upstream_mode"` - CacheSize uint32 `json:"cache_size"` - CacheMinTTL uint32 `json:"cache_ttl_min"` - CacheMaxTTL uint32 `json:"cache_ttl_max"` + ProtectionEnabled *bool `json:"protection_enabled"` + RateLimit *uint32 `json:"ratelimit"` + BlockingMode *string `json:"blocking_mode"` + BlockingIPv4 *string `json:"blocking_ipv4"` + BlockingIPv6 *string `json:"blocking_ipv6"` + EDNSCSEnabled *bool `json:"edns_cs_enabled"` + DNSSECEnabled *bool `json:"dnssec_enabled"` + DisableIPv6 *bool `json:"disable_ipv6"` + UpstreamMode *string `json:"upstream_mode"` + CacheSize *uint32 `json:"cache_size"` + CacheMinTTL *uint32 `json:"cache_ttl_min"` + CacheMaxTTL *uint32 `json:"cache_ttl_max"` +} + +func (s *Server) getDNSConfig() dnsConfig { + s.RLock() + upstreams := stringArrayDup(s.conf.UpstreamDNS) + upstreamFile := s.conf.UpstreamDNSFileName + bootstraps := stringArrayDup(s.conf.BootstrapDNS) + protectionEnabled := s.conf.ProtectionEnabled + blockingMode := s.conf.BlockingMode + BlockingIPv4 := s.conf.BlockingIPv4 + BlockingIPv6 := s.conf.BlockingIPv6 + Ratelimit := s.conf.Ratelimit + EnableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet + EnableDNSSEC := s.conf.EnableDNSSEC + AAAADisabled := s.conf.AAAADisabled + CacheSize := s.conf.CacheSize + CacheMinTTL := s.conf.CacheMinTTL + CacheMaxTTL := s.conf.CacheMaxTTL + var upstreamMode string + if s.conf.FastestAddr { + upstreamMode = "fastest_addr" + } else if s.conf.AllServers { + upstreamMode = "parallel" + } + s.RUnlock() + return dnsConfig{ + Upstreams: &upstreams, + UpstreamsFile: &upstreamFile, + Bootstraps: &bootstraps, + ProtectionEnabled: &protectionEnabled, + BlockingMode: &blockingMode, + BlockingIPv4: &BlockingIPv4, + BlockingIPv6: &BlockingIPv6, + RateLimit: &Ratelimit, + EDNSCSEnabled: &EnableEDNSClientSubnet, + DNSSECEnabled: &EnableDNSSEC, + DisableIPv6: &AAAADisabled, + CacheSize: &CacheSize, + CacheMinTTL: &CacheMinTTL, + CacheMaxTTL: &CacheMaxTTL, + UpstreamMode: &upstreamMode, + } } func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { - resp := dnsConfigJSON{} - s.RLock() - resp.Upstreams = stringArrayDup(s.conf.UpstreamDNS) - resp.UpstreamsFile = s.conf.UpstreamDNSFileName - resp.Bootstraps = stringArrayDup(s.conf.BootstrapDNS) + resp := s.getDNSConfig() - resp.ProtectionEnabled = s.conf.ProtectionEnabled - resp.BlockingMode = s.conf.BlockingMode - resp.BlockingIPv4 = s.conf.BlockingIPv4 - resp.BlockingIPv6 = s.conf.BlockingIPv6 - resp.RateLimit = s.conf.Ratelimit - resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet - resp.DNSSECEnabled = s.conf.EnableDNSSEC - resp.DisableIPv6 = s.conf.AAAADisabled - resp.CacheSize = s.conf.CacheSize - resp.CacheMinTTL = s.conf.CacheMinTTL - resp.CacheMaxTTL = s.conf.CacheMaxTTL - if s.conf.FastestAddr { - resp.UpstreamMode = "fastest_addr" - } else if s.conf.AllServers { - resp.UpstreamMode = "parallel" - } - s.RUnlock() - - js, err := json.Marshal(resp) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "json.Marshal: %s", err) - return - } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(js) + + enc := json.NewEncoder(w) + if err := enc.Encode(resp); err != nil { + httpError(r, w, http.StatusInternalServerError, "json.Encoder: %s", err) + return + } } -func checkBlockingMode(req dnsConfigJSON) bool { - bm := req.BlockingMode - if !(bm == "default" || bm == "refused" || bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") { - return false +func (req *dnsConfig) checkBlockingMode() bool { + if req.BlockingMode == nil { + return true } + bm := *req.BlockingMode if bm == "custom_ip" { - ip := net.ParseIP(req.BlockingIPv4) - if ip == nil || ip.To4() == nil { + if req.BlockingIPv4 == nil || req.BlockingIPv6 == nil { return false } - ip = net.ParseIP(req.BlockingIPv6) - if ip == nil { + ip4 := net.ParseIP(*req.BlockingIPv4) + if ip4 == nil || ip4.To4() == nil { return false } + + ip6 := net.ParseIP(*req.BlockingIPv6) + return ip6 != nil + } + + for _, valid := range []string{ + "default", + "refused", + "nxdomain", + "null_ip", + } { + if bm == valid { + return true + } } - return true + return false } -// Validate bootstrap server address -func checkBootstrap(addr string) error { - if addr == "" { // additional check is required because NewResolver() allows empty address - return fmt.Errorf("invalid bootstrap server address: empty") +func (req *dnsConfig) checkUpstreamsMode() bool { + if req.UpstreamMode == nil { + return true } - _, err := upstream.NewResolver(addr, 0) - if err != nil { - return fmt.Errorf("invalid bootstrap server address: %w", err) + + for _, valid := range []string{ + "", + "fastest_addr", + "parallel", + } { + if *req.UpstreamMode == valid { + return true + } } - return nil + + return false +} + +func (req *dnsConfig) checkBootstrap() (string, error) { + if req.Bootstraps == nil { + return "", nil + } + + for _, boot := range *req.Bootstraps { + if boot == "" { + return boot, fmt.Errorf("invalid bootstrap server address: empty") + } + + if _, err := upstream.NewResolver(boot, 0); err != nil { + return boot, fmt.Errorf("invalid bootstrap server address: %w", err) + } + } + + return "", nil +} + +func (req *dnsConfig) checkCacheTTL() bool { + if req.CacheMinTTL == nil && req.CacheMaxTTL == nil { + return true + } + var min, max uint32 + if req.CacheMinTTL != nil { + min = *req.CacheMinTTL + } + if req.CacheMaxTTL == nil { + max = *req.CacheMaxTTL + } + + return min <= max } -// nolint(gocyclo) - we need to check each JSON field separately func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) { - req := dnsConfigJSON{} - js, err := jsonutil.DecodeObject(&req, r.Body) - if err != nil { - httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + req := dnsConfig{} + dec := json.NewDecoder(r.Body) + if err := dec.Decode(&req); err != nil { + httpError(r, w, http.StatusBadRequest, "json Encode: %s", err) return } - if js.Exists("upstream_dns") { - err = ValidateUpstreams(req.Upstreams) - if err != nil { + if req.Upstreams != nil { + if err := ValidateUpstreams(*req.Upstreams); err != nil { httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err) return } } - if js.Exists("bootstrap_dns") { - for _, boot := range req.Bootstraps { - if err := checkBootstrap(boot); err != nil { - httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", boot, err) - return - } - } + if errBoot, err := req.checkBootstrap(); err != nil { + httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", errBoot, err) + return } - if js.Exists("blocking_mode") && !checkBlockingMode(req) { + if !req.checkBlockingMode() { httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value") return } - if js.Exists("upstream_mode") && - !(req.UpstreamMode == "" || req.UpstreamMode == "fastest_addr" || req.UpstreamMode == "parallel") { + if !req.checkUpstreamsMode() { httpError(r, w, http.StatusBadRequest, "upstream_mode: incorrect value") return } - if req.CacheMinTTL > req.CacheMaxTTL { + if !req.checkCacheTTL() { httpError(r, w, http.StatusBadRequest, "cache_ttl_min must be less or equal than cache_ttl_max") return } - restart := false - s.Lock() - - if js.Exists("upstream_dns") { - s.conf.UpstreamDNS = req.Upstreams - restart = true - } - - if js.Exists("upstream_dns_file") { - s.conf.UpstreamDNSFileName = req.UpstreamsFile - restart = true - } - - if js.Exists("bootstrap_dns") { - s.conf.BootstrapDNS = req.Bootstraps - restart = true - } - - if js.Exists("protection_enabled") { - s.conf.ProtectionEnabled = req.ProtectionEnabled - } - - if js.Exists("blocking_mode") { - s.conf.BlockingMode = req.BlockingMode - if req.BlockingMode == "custom_ip" { - if js.Exists("blocking_ipv4") { - s.conf.BlockingIPv4 = req.BlockingIPv4 - s.conf.BlockingIPAddrv4 = net.ParseIP(req.BlockingIPv4) - } - if js.Exists("blocking_ipv6") { - s.conf.BlockingIPv6 = req.BlockingIPv6 - s.conf.BlockingIPAddrv6 = net.ParseIP(req.BlockingIPv6) - } - } - } - - if js.Exists("ratelimit") { - if s.conf.Ratelimit != req.RateLimit { - restart = true - } - s.conf.Ratelimit = req.RateLimit - } - - if js.Exists("edns_cs_enabled") { - s.conf.EnableEDNSClientSubnet = req.EDNSCSEnabled - restart = true - } - - if js.Exists("dnssec_enabled") { - s.conf.EnableDNSSEC = req.DNSSECEnabled - } - - if js.Exists("disable_ipv6") { - s.conf.AAAADisabled = req.DisableIPv6 - } - - if js.Exists("cache_size") { - s.conf.CacheSize = req.CacheSize - restart = true - } - - if js.Exists("cache_ttl_min") { - s.conf.CacheMinTTL = req.CacheMinTTL - restart = true - } - - if js.Exists("cache_ttl_max") { - s.conf.CacheMaxTTL = req.CacheMaxTTL - restart = true - } - - if js.Exists("upstream_mode") { - s.conf.FastestAddr = false - s.conf.AllServers = false - switch req.UpstreamMode { - case "": - // - - case "parallel": - s.conf.AllServers = true - - case "fastest_addr": - s.conf.FastestAddr = true - } - } - - s.Unlock() - s.conf.ConfigModified() - - if restart { - err = s.Reconfigure(nil) - if err != nil { + if s.setConfig(req) { + if err := s.Reconfigure(nil); err != nil { httpError(r, w, http.StatusInternalServerError, "%s", err) return } } } +func (s *Server) setConfig(dc dnsConfig) (restart bool) { + s.Lock() + + if dc.Upstreams != nil { + s.conf.UpstreamDNS = *dc.Upstreams + restart = true + } + + if dc.UpstreamsFile != nil { + s.conf.UpstreamDNSFileName = *dc.UpstreamsFile + restart = true + } + + if dc.Bootstraps != nil { + s.conf.BootstrapDNS = *dc.Bootstraps + restart = true + } + + if dc.ProtectionEnabled != nil { + s.conf.ProtectionEnabled = *dc.ProtectionEnabled + } + + if dc.BlockingMode != nil { + s.conf.BlockingMode = *dc.BlockingMode + if *dc.BlockingMode == "custom_ip" { + s.conf.BlockingIPv4 = *dc.BlockingIPv4 + s.conf.BlockingIPAddrv4 = net.ParseIP(*dc.BlockingIPv4) + s.conf.BlockingIPv6 = *dc.BlockingIPv6 + s.conf.BlockingIPAddrv6 = net.ParseIP(*dc.BlockingIPv6) + } + } + + if dc.RateLimit != nil { + if s.conf.Ratelimit != *dc.RateLimit { + restart = true + } + s.conf.Ratelimit = *dc.RateLimit + } + + if dc.EDNSCSEnabled != nil { + s.conf.EnableEDNSClientSubnet = *dc.EDNSCSEnabled + restart = true + } + + if dc.DNSSECEnabled != nil { + s.conf.EnableDNSSEC = *dc.DNSSECEnabled + } + + if dc.DisableIPv6 != nil { + s.conf.AAAADisabled = *dc.DisableIPv6 + } + + if dc.CacheSize != nil { + s.conf.CacheSize = *dc.CacheSize + restart = true + } + + if dc.CacheMinTTL != nil { + s.conf.CacheMinTTL = *dc.CacheMinTTL + restart = true + } + + if dc.CacheMaxTTL != nil { + s.conf.CacheMaxTTL = *dc.CacheMaxTTL + restart = true + } + + if dc.UpstreamMode != nil { + switch *dc.UpstreamMode { + case "parallel": + s.conf.AllServers = true + s.conf.FastestAddr = false + case "fastest_addr": + s.conf.AllServers = false + s.conf.FastestAddr = true + default: + s.conf.AllServers = false + s.conf.FastestAddr = false + } + } + s.Unlock() + s.conf.ConfigModified() + return restart +} + type upstreamJSON struct { Upstreams []string `json:"upstream_dns"` // Upstreams BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS diff --git a/internal/dnsforward/dnsforward_http_test.go b/internal/dnsforward/dnsforward_http_test.go index 19c4a0de..c8e2f9c5 100644 --- a/internal/dnsforward/dnsforward_http_test.go +++ b/internal/dnsforward/dnsforward_http_test.go @@ -29,7 +29,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf: func() ServerConfig { return defaultConf }, - want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "fastest_addr", conf: func() ServerConfig { @@ -37,7 +37,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf.FastestAddr = true return conf }, - want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "parallel", conf: func() ServerConfig { @@ -45,7 +45,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) { conf.AllServers = true return conf }, - want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }} for _, tc := range testCases { @@ -73,7 +73,7 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) { w := httptest.NewRecorder() - const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}" + const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n" testCases := []struct { name string req string @@ -83,52 +83,52 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) { name: "upstream_dns", req: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "bootstraps", req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "blocking_mode_good", req: "{\"blocking_mode\":\"refused\"}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "blocking_mode_bad", req: "{\"blocking_mode\":\"custom_ip\"}", wantSet: "blocking_mode: incorrect value\n", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "ratelimit", req: "{\"ratelimit\":6}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "edns_cs_enabled", req: "{\"edns_cs_enabled\":true}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "dnssec_enabled", req: "{\"dnssec_enabled\":true}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "cache_size", req: "{\"cache_size\":1024}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "upstream_mode_parallel", req: "{\"upstream_mode\":\"parallel\"}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "upstream_mode_fastest_addr", req: "{\"upstream_mode\":\"fastest_addr\"}", wantSet: "", - wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}", + wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}\n", }, { name: "upstream_dns_bad", req: "{\"upstream_dns\":[\"\"]}", diff --git a/internal/home/auth.go b/internal/home/auth.go index ca3f653a..de393f6f 100644 --- a/internal/home/auth.go +++ b/internal/home/auth.go @@ -1,12 +1,14 @@ package home import ( + "crypto/rand" "crypto/sha256" "encoding/binary" "encoding/hex" "encoding/json" "fmt" - "math/rand" + "math" + "math/big" "net/http" "strings" "sync" @@ -76,7 +78,6 @@ func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth { a := Auth{} a.sessionTTL = sessionTTL a.sessions = make(map[string]*session) - rand.Seed(time.Now().UTC().Unix()) var err error a.db, err = bbolt.Open(dbFilename, 0o644, nil) if err != nil { @@ -275,23 +276,28 @@ type loginJSON struct { Password string `json:"password"` } -func getSession(u *User) []byte { - // the developers don't currently believe that using a - // non-cryptographic RNG for the session hash salt is - // insecure - salt := rand.Uint32() //nolint:gosec - d := []byte(fmt.Sprintf("%d%s%s", salt, u.Name, u.PasswordHash)) - hash := sha256.Sum256(d) - return hash[:] -} - -func (a *Auth) httpCookie(req loginJSON) string { - u := a.UserFind(req.Name, req.Password) - if len(u.Name) == 0 { - return "" +func getSession(u *User) ([]byte, error) { + maxSalt := big.NewInt(math.MaxUint32) + salt, err := rand.Int(rand.Reader, maxSalt) + if err != nil { + return nil, err } - sess := getSession(&u) + d := []byte(fmt.Sprintf("%s%s%s", salt, u.Name, u.PasswordHash)) + hash := sha256.Sum256(d) + return hash[:], nil +} + +func (a *Auth) httpCookie(req loginJSON) (string, error) { + u := a.UserFind(req.Name, req.Password) + if len(u.Name) == 0 { + return "", nil + } + + sess, err := getSession(&u) + if err != nil { + return "", err + } now := time.Now().UTC() expire := now.Add(cookieTTL * time.Hour) @@ -305,7 +311,7 @@ func (a *Auth) httpCookie(req loginJSON) string { a.addSession(sess, &s) return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s", - sessionCookieName, hex.EncodeToString(sess), expstr) + sessionCookieName, hex.EncodeToString(sess), expstr), nil } func handleLogin(w http.ResponseWriter, r *http.Request) { @@ -316,7 +322,11 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { return } - cookie := Context.auth.httpCookie(req) + cookie, err := Context.auth.httpCookie(req) + if err != nil { + httpError(w, http.StatusBadRequest, "crypto rand reader: %s", err) + return + } if len(cookie) == 0 { log.Info("Auth: invalid user name or password: name=%q", req.Name) time.Sleep(1 * time.Second) @@ -369,7 +379,54 @@ func parseCookie(cookie string) string { return "" } -// nolint(gocyclo) +// optionalAuthThird return true if user should authenticate first. +func optionalAuthThird(w http.ResponseWriter, r *http.Request) (authFirst bool) { + authFirst = false + + // redirect to login page if not authenticated + ok := false + cookie, err := r.Cookie(sessionCookieName) + + if glProcessCookie(r) { + log.Debug("Auth: authentification was handled by GL-Inet submodule") + ok = true + + } else if err == nil { + r := Context.auth.CheckSession(cookie.Value) + if r == 0 { + ok = true + } else if r < 0 { + log.Debug("Auth: invalid cookie value: %s", cookie) + } + } else { + // there's no Cookie, check Basic authentication + user, pass, ok2 := r.BasicAuth() + if ok2 { + u := Context.auth.UserFind(user, pass) + if len(u.Name) != 0 { + ok = true + } else { + log.Info("Auth: invalid Basic Authorization value") + } + } + } + if !ok { + if r.URL.Path == "/" || r.URL.Path == "/index.html" { + if glProcessRedirect(w, r) { + log.Debug("Auth: redirected to login page by GL-Inet submodule") + } else { + w.Header().Set("Location", "/login.html") + w.WriteHeader(http.StatusFound) + } + } else { + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte("Forbidden")) + } + authFirst = true + } + return authFirst +} + func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/login.html" { @@ -392,45 +449,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re // process as usual // no additional auth requirements } else if Context.auth != nil && Context.auth.AuthRequired() { - // redirect to login page if not authenticated - ok := false - cookie, err := r.Cookie(sessionCookieName) - - if glProcessCookie(r) { - log.Debug("Auth: authentification was handled by GL-Inet submodule") - ok = true - - } else if err == nil { - r := Context.auth.CheckSession(cookie.Value) - if r == 0 { - ok = true - } else if r < 0 { - log.Debug("Auth: invalid cookie value: %s", cookie) - } - } else { - // there's no Cookie, check Basic authentication - user, pass, ok2 := r.BasicAuth() - if ok2 { - u := Context.auth.UserFind(user, pass) - if len(u.Name) != 0 { - ok = true - } else { - log.Info("Auth: invalid Basic Authorization value") - } - } - } - if !ok { - if r.URL.Path == "/" || r.URL.Path == "/index.html" { - if glProcessRedirect(w, r) { - log.Debug("Auth: redirected to login page by GL-Inet submodule") - } else { - w.Header().Set("Location", "/login.html") - w.WriteHeader(http.StatusFound) - } - } else { - w.WriteHeader(http.StatusForbidden) - _, _ = w.Write([]byte("Forbidden")) - } + if optionalAuthThird(w, r) { return } } diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index dd2b68b3..b88035b9 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -41,7 +41,8 @@ func TestAuth(t *testing.T) { assert.True(t, a.CheckSession("notfound") == -1) a.RemoveSession("notfound") - sess := getSession(&users[0]) + sess, err := getSession(&users[0]) + assert.Nil(t, err) sessStr := hex.EncodeToString(sess) now := time.Now().UTC().Unix() @@ -136,7 +137,8 @@ func TestAuthHTTP(t *testing.T) { assert.True(t, handlerCalled) // perform login - cookie := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"}) + cookie, err := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"}) + assert.Nil(t, err) assert.True(t, cookie != "") // get / diff --git a/internal/home/dns.go b/internal/home/dns.go index a93bc307..f4167f12 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -88,60 +88,6 @@ func isRunning() bool { return Context.dnsServer != nil && Context.dnsServer.IsRunning() } -// nolint (gocyclo) -// Return TRUE if IP is within public Internet IP range -func isPublicIP(ip net.IP) bool { - ip4 := ip.To4() - if ip4 != nil { - switch ip4[0] { - case 0: - return false // software - case 10: - return false // private network - case 127: - return false // loopback - case 169: - if ip4[1] == 254 { - return false // link-local - } - case 172: - if ip4[1] >= 16 && ip4[1] <= 31 { - return false // private network - } - case 192: - if (ip4[1] == 0 && ip4[2] == 0) || // private network - (ip4[1] == 0 && ip4[2] == 2) || // documentation - (ip4[1] == 88 && ip4[2] == 99) || // reserved - (ip4[1] == 168) { // private network - return false - } - case 198: - if (ip4[1] == 18 || ip4[2] == 19) || // private network - (ip4[1] == 51 || ip4[2] == 100) { // documentation - return false - } - case 203: - if ip4[1] == 0 && ip4[2] == 113 { // documentation - return false - } - case 224: - if ip4[1] == 0 && ip4[2] == 0 { // multicast - return false - } - case 255: - if ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255 { // subnet - return false - } - } - } else { - if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { - return false - } - } - - return true -} - func onDNSRequest(d *proxy.DNSContext) { ip := dnsforward.GetIPString(d.Addr) if ip == "" { @@ -153,7 +99,7 @@ func onDNSRequest(d *proxy.DNSContext) { if !ipAddr.IsLoopback() { Context.rdns.Begin(ip) } - if isPublicIP(ipAddr) { + if !Context.ipDetector.detectSpecialNetwork(ipAddr) { Context.whois.Begin(ip) } } @@ -327,7 +273,7 @@ func startDNSServer() error { if !ipAddr.IsLoopback() { Context.rdns.Begin(ip) } - if isPublicIP(ipAddr) { + if !Context.ipDetector.detectSpecialNetwork(ipAddr) { Context.whois.Begin(ip) } } diff --git a/internal/home/filter.go b/internal/home/filter.go index 5c6b0fa6..668af955 100644 --- a/internal/home/filter.go +++ b/internal/home/filter.go @@ -6,6 +6,7 @@ import ( "hash/crc32" "io" "io/ioutil" + "net/http" "os" "path/filepath" "regexp" @@ -497,46 +498,7 @@ func (f *Filtering) update(filter *filter) (bool, error) { return b, err } -// nolint(gocyclo) -func (f *Filtering) updateIntl(filter *filter) (bool, error) { - log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL) - - tmpFile, err := ioutil.TempFile(filepath.Join(Context.getDataDir(), filterDir), "") - if err != nil { - return false, err - } - defer func() { - if tmpFile != nil { - _ = tmpFile.Close() - _ = os.Remove(tmpFile.Name()) - } - }() - - var reader io.Reader - if filepath.IsAbs(filter.URL) { - f, err := os.Open(filter.URL) - if err != nil { - return false, fmt.Errorf("open file: %w", err) - } - defer f.Close() - reader = f - } else { - resp, err := Context.client.Get(filter.URL) - if resp != nil && resp.Body != nil { - defer resp.Body.Close() - } - if err != nil { - log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err) - return false, err - } - - if resp.StatusCode != 200 { - log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL) - return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode) - } - reader = resp.Body - } - +func (f *Filtering) read(reader io.Reader, tmpFile *os.File, filter *filter) (int, error) { htmlTest := true firstChunk := make([]byte, 4*1024) firstChunkLen := 0 @@ -556,12 +518,12 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) { if firstChunkLen == len(firstChunk) || err == io.EOF { if !isPrintableText(firstChunk, firstChunkLen) { - return false, fmt.Errorf("data contains non-printable characters") + return total, fmt.Errorf("data contains non-printable characters") } s := strings.ToLower(string(firstChunk)) if strings.Contains(s, "= '0' && s[1] <= '9') { - t = jsonTNum - } - s = s[1+sep+1:] - } - - *ps = s - return k, v, t -} diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index 35863890..e9c7c9f7 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -48,30 +48,3 @@ func TestDecode_decodeQueryLog(t *testing.T) { }) } } - -func TestJSON(t *testing.T) { - s := ` - {"keystr":"val","obj":{"keybool":true,"keyint":123456}} - ` - k, v, jtype := readJSON(&s) - assert.Equal(t, jtype, int32(jsonTStr)) - assert.Equal(t, "keystr", k) - assert.Equal(t, "val", v) - - k, _, jtype = readJSON(&s) - assert.Equal(t, jtype, int32(jsonTObj)) - assert.Equal(t, "obj", k) - - k, v, jtype = readJSON(&s) - assert.Equal(t, jtype, int32(jsonTBool)) - assert.Equal(t, "keybool", k) - assert.Equal(t, "true", v) - - k, v, jtype = readJSON(&s) - assert.Equal(t, jtype, int32(jsonTNum)) - assert.Equal(t, "keyint", k) - assert.Equal(t, "123456", v) - - _, _, jtype = readJSON(&s) - assert.True(t, jtype == jsonTErr) -} diff --git a/internal/querylog/search_criteria.go b/internal/querylog/search_criteria.go index a4213408..73f2497e 100644 --- a/internal/querylog/search_criteria.go +++ b/internal/querylog/search_criteria.go @@ -77,66 +77,79 @@ func (c *searchCriteria) quickMatchJSONValue(line string, propertyName string) b } // match - checks if the log entry matches this search criteria -// nolint (gocyclo) func (c *searchCriteria) match(entry *logEntry) bool { switch c.criteriaType { case ctDomainOrClient: - qhost := strings.ToLower(entry.QHost) - searchVal := strings.ToLower(c.value) - if c.strict && qhost == searchVal { - return true - } - if !c.strict && strings.Contains(qhost, searchVal) { - return true - } - - if c.strict && entry.IP == c.value { - return true - } - if !c.strict && strings.Contains(entry.IP, c.value) { - return true - } - - return false - + return c.ctDomainOrClientCase(entry) case ctFilteringStatus: - res := entry.Result - - switch c.value { - case filteringStatusAll: - return true - case filteringStatusFiltered: - return res.IsFiltered || - res.Reason == dnsfilter.NotFilteredWhiteList || - res.Reason == dnsfilter.ReasonRewrite || - res.Reason == dnsfilter.RewriteEtcHosts - case filteringStatusBlocked: - return res.IsFiltered && - (res.Reason == dnsfilter.FilteredBlackList || - res.Reason == dnsfilter.FilteredBlockedService) - case filteringStatusBlockedService: - return res.IsFiltered && res.Reason == dnsfilter.FilteredBlockedService - case filteringStatusBlockedParental: - return res.IsFiltered && res.Reason == dnsfilter.FilteredParental - case filteringStatusBlockedSafebrowsing: - return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeBrowsing - case filteringStatusWhitelisted: - return res.Reason == dnsfilter.NotFilteredWhiteList - case filteringStatusRewritten: - return res.Reason == dnsfilter.ReasonRewrite || - res.Reason == dnsfilter.RewriteEtcHosts - case filteringStatusSafeSearch: - return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch - - case filteringStatusProcessed: - return !(res.Reason == dnsfilter.FilteredBlackList || - res.Reason == dnsfilter.FilteredBlockedService || - res.Reason == dnsfilter.NotFilteredWhiteList) - - default: - return false - } + return c.ctFilteringStatusCase(entry.Result) } return false } + +func (c *searchCriteria) ctDomainOrClientCase(entry *logEntry) bool { + qhost := strings.ToLower(entry.QHost) + searchVal := strings.ToLower(c.value) + if c.strict && qhost == searchVal { + return true + } + if !c.strict && strings.Contains(qhost, searchVal) { + return true + } + + if c.strict && entry.IP == c.value { + return true + } + if !c.strict && strings.Contains(entry.IP, c.value) { + return true + } + return false +} + +func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { + switch c.value { + case filteringStatusAll: + return true + + case filteringStatusFiltered: + return res.IsFiltered || + res.Reason.In( + dnsfilter.NotFilteredWhiteList, + dnsfilter.ReasonRewrite, + dnsfilter.RewriteEtcHosts, + ) + + case filteringStatusBlocked: + return res.IsFiltered && + res.Reason.In(dnsfilter.FilteredBlackList, dnsfilter.FilteredBlockedService) + + case filteringStatusBlockedService: + return res.IsFiltered && res.Reason == dnsfilter.FilteredBlockedService + + case filteringStatusBlockedParental: + return res.IsFiltered && res.Reason == dnsfilter.FilteredParental + + case filteringStatusBlockedSafebrowsing: + return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeBrowsing + + case filteringStatusWhitelisted: + return res.Reason == dnsfilter.NotFilteredWhiteList + + case filteringStatusRewritten: + return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteEtcHosts) + + case filteringStatusSafeSearch: + return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch + + case filteringStatusProcessed: + return !res.Reason.In( + dnsfilter.FilteredBlackList, + dnsfilter.FilteredBlockedService, + dnsfilter.NotFilteredWhiteList, + ) + + default: + return false + } +} diff --git a/internal/stats/stats_unit.go b/internal/stats/stats_unit.go index 2c406972..a8bd224c 100644 --- a/internal/stats/stats_unit.go +++ b/internal/stats/stats_unit.go @@ -548,7 +548,6 @@ func (s *statsCtx) loadUnits(limit uint32) ([]*unitDB, uint32) { * parental-blocked These values are just the sum of data for all units. */ -// nolint (gocyclo) func (s *statsCtx) getData() map[string]interface{} { limit := s.conf.limit @@ -564,137 +563,63 @@ func (s *statsCtx) getData() map[string]interface{} { } // per time unit counters: - // 720 hours may span 31 days, so we skip data for the first day in this case firstDayID := (firstID + 24 - 1) / 24 * 24 // align_ceil(24) - a := []uint64{} - if timeUnit == Hours { - for _, u := range units { - a = append(a, u.NTotal) - } - } else { - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := firstDayID - firstID; int(i) != len(units); i++ { - sum += units[i].NTotal - if id == nextDayID { - a = append(a, sum) - sum = 0 - nextDayID += 24 + statsCollector := func(numsGetter func(u *unitDB) (num uint64)) (nums []uint64) { + if timeUnit == Hours { + for _, u := range units { + nums = append(nums, numsGetter(u)) } - id++ - } - if id <= nextDayID { - a = append(a, sum) - } - if len(a) != int(limit/24) { - log.Fatalf("len(a) != limit: %d %d", len(a), limit) - } - } - d["dns_queries"] = a - - a = []uint64{} - if timeUnit == Hours { - for _, u := range units { - a = append(a, u.NResult[RFiltered]) - } - } else { - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := firstDayID - firstID; int(i) != len(units); i++ { - sum += units[i].NResult[RFiltered] - if id == nextDayID { - a = append(a, sum) - sum = 0 - nextDayID += 24 + } else { + var sum uint64 + id := firstDayID + nextDayID := firstDayID + 24 + for i := int(firstDayID - firstID); i != len(units); i++ { + sum += numsGetter(units[i]) + if id == nextDayID { + nums = append(nums, sum) + sum = 0 + nextDayID += 24 + } + id++ } - id++ - } - if id <= nextDayID { - a = append(a, sum) - } - } - d["blocked_filtering"] = a - - a = []uint64{} - if timeUnit == Hours { - for _, u := range units { - a = append(a, u.NResult[RSafeBrowsing]) - } - } else { - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := firstDayID - firstID; int(i) != len(units); i++ { - sum += units[i].NResult[RSafeBrowsing] - if id == nextDayID { - a = append(a, sum) - sum = 0 - nextDayID += 24 + if id <= nextDayID { + nums = append(nums, sum) } - id++ - } - if id <= nextDayID { - a = append(a, sum) } + return nums } - d["replaced_safebrowsing"] = a - a = []uint64{} - if timeUnit == Hours { + topsCollector := func(max int, pairsGetter func(u *unitDB) (pairs []countPair)) []map[string]uint64 { + m := map[string]uint64{} for _, u := range units { - a = append(a, u.NResult[RParental]) - } - } else { - var sum uint64 - id := firstDayID - nextDayID := firstDayID + 24 - for i := firstDayID - firstID; int(i) != len(units); i++ { - sum += units[i].NResult[RParental] - if id == nextDayID { - a = append(a, sum) - sum = 0 - nextDayID += 24 + for _, it := range pairsGetter(u) { + m[it.Name] += it.Count } - id++ - } - if id <= nextDayID { - a = append(a, sum) } + a2 := convertMapToArray(m, max) + return convertTopArray(a2) } - d["replaced_parental"] = a - // top counters: - - m := map[string]uint64{} - for _, u := range units { - for _, it := range u.Domains { - m[it.Name] += it.Count - } + dnsQueries := statsCollector(func(u *unitDB) (num uint64) { return u.NTotal }) + if timeUnit != Hours && len(dnsQueries) != int(limit/24) { + log.Fatalf("len(dnsQueries) != limit: %d %d", len(dnsQueries), limit) } - a2 := convertMapToArray(m, maxDomains) - d["top_queried_domains"] = convertTopArray(a2) - m = map[string]uint64{} - for _, u := range units { - for _, it := range u.BlockedDomains { - m[it.Name] += it.Count - } + statsData := map[string]interface{}{ + "dns_queries": dnsQueries, + "blocked_filtering": statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RFiltered] }), + "replaced_safebrowsing": statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RSafeBrowsing] }), + "replaced_parental": statsCollector(func(u *unitDB) (num uint64) { return u.NResult[RParental] }), + "top_queried_domains": topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.Domains }), + "top_blocked_domains": topsCollector(maxDomains, func(u *unitDB) (pairs []countPair) { return u.BlockedDomains }), + "top_clients": topsCollector(maxClients, func(u *unitDB) (pairs []countPair) { return u.Clients }), } - a2 = convertMapToArray(m, maxDomains) - d["top_blocked_domains"] = convertTopArray(a2) - m = map[string]uint64{} - for _, u := range units { - for _, it := range u.Clients { - m[it.Name] += it.Count - } + for dataKey, dataValue := range statsData { + d[dataKey] = dataValue } - a2 = convertMapToArray(m, maxClients) - d["top_clients"] = convertTopArray(a2) // total counters: From 046ec13fdc5ec008fd4ac9b77cc943a96be2605c Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 20 Nov 2020 18:06:07 +0300 Subject: [PATCH 09/54] Pull request: all: reformat yaml, add yaml formatting standard Merge in DNS/adguard-home from 2297-yaml to master Closes #2297. Squashed commit of the following: commit 85df3a38a14adb1965944ddf14b197c12a213057 Author: Ainar Garipov Date: Fri Nov 20 17:52:22 2020 +0300 all: improve HACKING.md commit 079acdfe41cc12ab6aa13d7c28dcbf7b7b3c8380 Merge: 202ea078e 3045da174 Author: Ainar Garipov Date: Fri Nov 20 17:50:34 2020 +0300 Merge branch 'master' into 2297-yaml commit 202ea078e29d88871a32ac6e668dfae6db802bab Author: Ainar Garipov Date: Thu Nov 12 20:25:42 2020 +0300 all: reformat yaml, add yaml formatting standard --- .codecov.yml | 16 +- .github/stale.yml | 32 +- .github/workflows/build.yml | 296 ++- .github/workflows/lint.yml | 93 +- .golangci.yml | 115 +- .goreleaser.yml | 199 +- HACKING.md | 42 +- openapi/openapi.yaml | 4045 ++++++++++++++++++----------------- 8 files changed, 2427 insertions(+), 2411 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index f91e5c1f..8a3d121b 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,8 +1,8 @@ -coverage: - status: - project: - default: - target: 40% - threshold: null - patch: false - changes: false +'coverage': + 'status': + 'project': + 'default': + 'target': '40%' + 'threshold': null + 'patch': false + 'changes': false diff --git a/.github/stale.yml b/.github/stale.yml index fa56e823..90e24289 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,19 +1,19 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - 'bug' - - 'enhancement' - - 'feature request' - - 'localization' -# Label to use when marking an issue as stale -staleLabel: 'wontfix' -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > +# Number of days of inactivity before an issue becomes stale. +'daysUntilStale': 60 +# Number of days of inactivity before a stale issue is closed. +'daysUntilClose': 7 +# Issues with these labels will never be considered stale. +'exemptLabels': +- 'bug' +- 'enhancement' +- 'feature request' +- 'localization' +# Label to use when marking an issue as stale. +'staleLabel': 'wontfix' +# Comment to post when marking an issue as stale. Set to `false` to disable. +'markComment': > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false +# Comment to post when closing a stale issue. Set to `false` to disable. +'closeComment': false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0c599dbd..b59df4b1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,170 +1,136 @@ -name: build +'name': 'build' -env: - GO_VERSION: 1.14 - NODE_VERSION: 13 +'env': + 'GO_VERSION': '1.14' + 'NODE_VERSION': '13' -on: - push: - branches: - - '*' - tags: - - v* - pull_request: +'on': + 'push': + 'branches': + - '*' + 'tags': + - 'v*' + 'pull_request': -jobs: +'jobs': + 'test': + 'runs-on': '${{ matrix.os }}' + 'env': + 'GO111MODULE': 'on' + 'GOPROXY': 'https://goproxy.io' + 'strategy': + 'fail-fast': false + 'matrix': + 'os': + - 'ubuntu-latest' + - 'macOS-latest' + - 'windows-latest' + 'steps': + - 'name': 'Checkout' + 'uses': 'actions/checkout@v2' + 'with': + 'fetch-depth': 0 + - 'name': 'Set up Go' + 'uses': 'actions/setup-go@v2' + 'with': + 'go-version': '${{ env.GO_VERSION }}' + - 'name': 'Set up Node' + 'uses': 'actions/setup-node@v1' + 'with': + 'node-version': '${{ env.NODE_VERSION }}' + - 'name': 'Set up Go modules cache' + 'uses': 'actions/cache@v2' + 'with': + 'path': '~/go/pkg/mod' + 'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}" + 'restore-keys': '${{ runner.os }}-go-' + - 'name': 'Get npm cache directory' + 'id': 'npm-cache' + 'run': 'echo "::set-output name=dir::$(npm config get cache)"' + - 'name': 'Set up npm cache' + 'uses': 'actions/cache@v2' + 'with': + 'path': '${{ steps.npm-cache.outputs.dir }}' + 'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}" + 'restore-keys': '${{ runner.os }}-node-' + - 'name': 'Run make ci' + 'shell': 'bash' + 'run': 'make ci' + - 'name': 'Upload coverage' + 'uses': 'codecov/codecov-action@v1' + 'if': "success() && matrix.os == 'ubuntu-latest'" + 'with': + 'token': '${{ secrets.CODECOV_TOKEN }}' + 'file': './coverage.txt' + 'app': + 'runs-on': 'ubuntu-latest' + 'needs': 'test' + 'steps': + - 'name': 'Checkout' + 'uses': 'actions/checkout@v2' + 'with': + 'fetch-depth': 0 + - 'name': 'Set up Go' + 'uses': 'actions/setup-go@v2' + 'with': + 'go-version': '${{ env.GO_VERSION }}' + - 'name': 'Set up Node' + 'uses': 'actions/setup-node@v1' + 'with': + 'node-version': '${{ env.NODE_VERSION }}' + - 'name': 'Set up Go modules cache' + 'uses': 'actions/cache@v2' + 'with': + 'path': '~/go/pkg/mod' + 'key': "${{ runner.os }}-go-${{ hashFiles('go.sum') }}" + 'restore-keys': '${{ runner.os }}-go-' + - 'name': 'Get npm cache directory' + 'id': 'npm-cache' + 'run': 'echo "::set-output name=dir::$(npm config get cache)"' + - 'name': 'Set up node_modules cache' + 'uses': 'actions/cache@v2' + 'with': + 'path': '${{ steps.npm-cache.outputs.dir }}' + 'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}" + 'restore-keys': '${{ runner.os }}-node-' + - 'name': 'Set up Snapcraft' + 'run': 'sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft' + - 'name': 'Set up GoReleaser' + 'run': 'curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR="$(go env GOPATH)/bin" sh' + - 'name': 'Run snapshot build' + 'run': 'make release' - test: - runs-on: ${{ matrix.os }} - env: - GO111MODULE: on - GOPROXY: https://goproxy.io - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macOS-latest - - windows-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} + 'docker': + 'runs-on': 'ubuntu-latest' + 'needs': 'test' + 'steps': + - 'name': 'Checkout' + 'uses': 'actions/checkout@v2' + 'with': + 'fetch-depth': 0 + - 'name': 'Set up QEMU' + 'uses': 'docker/setup-qemu-action@v1' + - 'name': 'Set up Docker Buildx' + 'uses': 'docker/setup-buildx-action@v1' + - 'name': 'Docker Buildx (build)' + 'run': 'make docker-multi-arch' - - - name: Set up Node - uses: actions/setup-node@v1 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Set up Go modules cache - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Get npm cache directory - id: npm-cache - run: | - echo "::set-output name=dir::$(npm config get cache)" - - - name: Set up npm cache - uses: actions/cache@v2 - with: - path: ${{ steps.npm-cache.outputs.dir }} - key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Run make ci - shell: bash - run: | - make ci - - - name: Upload coverage - uses: codecov/codecov-action@v1 - if: success() && matrix.os == 'ubuntu-latest' - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.txt - - app: - runs-on: ubuntu-latest - needs: test - steps: - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: ${{ env.GO_VERSION }} - - - name: Set up Node - uses: actions/setup-node@v1 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: Set up Go modules cache - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Get npm cache directory - id: npm-cache - run: | - echo "::set-output name=dir::$(npm config get cache)" - - - name: Set up node_modules cache - uses: actions/cache@v2 - with: - path: ${{ steps.npm-cache.outputs.dir }} - key: ${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: Set up Snapcraft - run: | - sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft - - - name: Set up GoReleaser - run: | - curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | BINDIR="$(go env GOPATH)/bin" sh - - - name: Run snapshot build - run: | - make release - - docker: - runs-on: ubuntu-latest - needs: test - steps: - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Docker Buildx (build) - run: | - make docker-multi-arch - - notify: - needs: [app, docker] - # Secrets are not passed to workflows that are triggered by a pull request from a fork - if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-latest - steps: - - - name: Conclusion - uses: technote-space/workflow-conclusion-action@v1 - - - name: Send Slack notif - uses: 8398a7/action-slack@v3 - with: - status: ${{ env.WORKFLOW_CONCLUSION }} - fields: repo,message,commit,author - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + 'notify': + 'needs': + - 'app' + - 'docker' + # Secrets are not passed to workflows that are triggered by a pull request + # from a fork. + 'if': "${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}" + 'runs-on': 'ubuntu-latest' + 'steps': + - 'name': 'Conclusion' + 'uses': 'technote-space/workflow-conclusion-action@v1' + - 'name': 'Send Slack notif' + 'uses': '8398a7/action-slack@v3' + 'with': + 'status': '${{ env.WORKFLOW_CONCLUSION }}' + 'fields': 'repo, message, commit, author' + 'env': + 'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}' + 'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 71de1b4f..b48b508b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,47 +1,46 @@ -name: golangci-lint -on: - push: - tags: - - v* - branches: - - '*' - pull_request: -jobs: - golangci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2.3.0 - with: - # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.32 - - eslint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install modules - run: npm --prefix client ci - - name: Run ESLint - run: npm --prefix client run lint - - - notify: - needs: [golangci,eslint] - # Secrets are not passed to workflows that are triggered by a pull request from a fork - if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-latest - steps: - - - name: Conclusion - uses: technote-space/workflow-conclusion-action@v1 - - - name: Send Slack notif - uses: 8398a7/action-slack@v3 - with: - status: ${{ env.WORKFLOW_CONCLUSION }} - fields: repo,message,commit,author - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} +'name': 'golangci-lint' +'on': + 'push': + 'tags': + - 'v*' + 'branches': + - '*' + 'pull_request': +'jobs': + 'golangci': + 'runs-on': 'ubuntu-latest' + 'steps': + - 'uses': 'actions/checkout@v2' + - 'name': 'golangci-lint' + 'uses': 'golangci/golangci-lint-action@v2.3.0' + 'with': + # This field is required. Don't set the patch version to always use + # the latest patch version. + 'version': 'v1.32' + 'eslint': + 'runs-on': 'ubuntu-latest' + 'steps': + - 'uses': 'actions/checkout@v2' + - 'name': 'Install modules' + 'run': 'npm --prefix client ci' + - 'name': 'Run ESLint' + 'run': 'npm --prefix client run lint' + 'notify': + 'needs': + - 'golangci' + - 'eslint' + # Secrets are not passed to workflows that are triggered by a pull request + # from a fork. + 'if': "${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}" + 'runs-on': 'ubuntu-latest' + 'steps': + - 'name': 'Conclusion' + 'uses': 'technote-space/workflow-conclusion-action@v1' + - 'name': 'Send Slack notif' + 'uses': '8398a7/action-slack@v3' + 'with': + 'status': '${{ env.WORKFLOW_CONCLUSION }}' + 'fields': 'repo, message, commit, author' + 'env': + 'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}' + 'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}' diff --git a/.golangci.yml b/.golangci.yml index 29ec1f4e..19402bd9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,78 +1,77 @@ # options for analysis running -run: +'run': # default concurrency is a available CPU number - concurrency: 4 + 'concurrency': 4 # timeout for analysis, e.g. 30s, 5m, default is 1m - deadline: 2m + 'deadline': '2m' # which files to skip: they will be analyzed, but issues from them # won't be reported. Default value is empty list, but there is # no need to include all autogenerated files, we confidently recognize # autogenerated files. If it's not please let us know. - skip-files: - - ".*generated.*" - - dnsfilter/rule_to_regexp.go - - util/pprof.go - - ".*_test.go" - - client/.* - - build/.* - - dist/.* - + 'skip-files': + - '.*generated.*' + - 'dnsfilter/rule_to_regexp.go' + - 'util/pprof.go' + - '.*_test.go' + - 'client/.*' + - 'build/.*' + - 'dist/.*' # all available settings of specific linters -linters-settings: - errcheck: +'linters-settings': + 'errcheck': # [deprecated] comma-separated list of pairs of the form pkg:regex # the regex is used to ignore names within pkg. (default "fmt:.*"). # see https://github.com/kisielk/errcheck#the-deprecated-method for details - ignore: fmt:.*,net:SetReadDeadline,net/http:^Write - gocyclo: - min-complexity: 20 - lll: - line-length: 200 + 'ignore': 'fmt:.*,net:SetReadDeadline,net/http:^Write' + 'gocyclo': + 'min-complexity': 20 + 'lll': + 'line-length': 200 -linters: - enable: - - deadcode - - errcheck - - govet - - ineffassign - - staticcheck - - unused - - varcheck - - bodyclose - - depguard - - dupl - - gocyclo - - goimports - - golint - - gosec - - misspell - - stylecheck - - unconvert - disable-all: true - fast: true +'linters': + 'enable': + - 'bodyclose' + - 'deadcode' + - 'depguard' + - 'dupl' + - 'errcheck' + - 'gocyclo' + - 'goimports' + - 'golint' + - 'gosec' + - 'govet' + - 'ineffassign' + - 'misspell' + - 'staticcheck' + - 'stylecheck' + - 'unconvert' + - 'unused' + - 'varcheck' + 'disable-all': true + 'fast': true -issues: +'issues': # List of regexps of issue texts to exclude, empty list by default. # But independently from this option we use default exclude patterns, # it can be disabled by `exclude-use-default: false`. To list all # excluded by default patterns execute `golangci-lint run --help` - exclude: - # structcheck cannot detect usages while they're there - - .parentalServer. is unused - - .safeBrowsingServer. is unused - # errcheck - - Error return value of .s.closeConn. is not checked - - Error return value of ..*.Shutdown. - # goconst - - string .forcesafesearch.google.com. has 3 occurrences - # gosec: Profiling endpoint is automatically exposed on /debug/pprof - - G108 - # gosec: Subprocess launched with function call as argument or cmd arguments - - G204 - # gosec: Potential DoS vulnerability via decompression bomb - - G110 - # gosec: Expect WriteFile permissions to be 0600 or less - - G306 + 'exclude': + # structcheck cannot detect usages while they're there + - '.parentalServer. is unused' + - '.safeBrowsingServer. is unused' + # errcheck + - 'Error return value of .s.closeConn. is not checked' + - 'Error return value of ..*.Shutdown.' + # goconst + - 'string .forcesafesearch.google.com. has 3 occurrences' + # gosec: Profiling endpoint is automatically exposed on /debug/pprof + - 'G108' + # gosec: Subprocess launched with function call as argument or cmd arguments + - 'G204' + # gosec: Potential DoS vulnerability via decompression bomb + - 'G110' + # gosec: Expect WriteFile permissions to be 0600 or less + - 'G306' diff --git a/.goreleaser.yml b/.goreleaser.yml index edb4bd2b..14a6b3d7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,106 +1,107 @@ -project_name: AdGuardHome +'project_name': 'AdGuardHome' -env: - - GO111MODULE=on - - GOPROXY=https://goproxy.io +'env': +- 'GO111MODULE=on' +- 'GOPROXY=https://goproxy.io' -before: - hooks: - - go mod download - - go generate ./... +'before': + 'hooks': + - 'go mod download' + - 'go generate ./...' -builds: - - main: ./main.go - ldflags: - - -s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}} - env: - - CGO_ENABLED=0 - goos: - - darwin - - linux - - freebsd - - windows - goarch: - - 386 - - amd64 - - arm - - arm64 - - mips - - mipsle - - mips64 - - mips64le - goarm: - - 5 - - 6 - - 7 - gomips: - - softfloat - ignore: - - goos: freebsd - goarch: mips - - goos: freebsd - goarch: mipsle +'builds': +- 'main': './main.go' + 'ldflags': + - '-s -w -X main.version={{.Version}} -X main.channel={{.Env.CHANNEL}} -X main.goarm={{.Env.GOARM}}' + 'env': + - 'CGO_ENABLED=0' + 'goos': + - 'darwin' + - 'linux' + - 'freebsd' + - 'windows' + 'goarch': + - '386' + - 'amd64' + - 'arm' + - 'arm64' + - 'mips' + - 'mipsle' + - 'mips64' + - 'mips64le' + 'goarm': + - '5' + - '6' + - '7' + 'gomips': + - 'softfloat' + 'ignore': + - 'goos': 'freebsd' + 'goarch': 'mips' + - 'goos': 'freebsd' + 'goarch': 'mipsle' -archives: - - # Archive name template. - # Defaults: - # - if format is `tar.gz`, `tar.xz`, `gz` or `zip`: - # - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` - # - if format is `binary`: - # - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` - name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" - wrap_in_directory: "AdGuardHome" - format_overrides: - - goos: windows - format: zip - - goos: darwin - format: zip - files: - - LICENSE.txt - - README.md +'archives': +- # Archive name template. + # Defaults: + # - if format is `tar.gz`, `tar.xz`, `gz` or `zip`: + # - `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` + # - if format is `binary`: + # - `{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}` + 'name_template': '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}' + 'wrap_in_directory': 'AdGuardHome' + 'format_overrides': + - 'goos': 'windows' + 'format': 'zip' + - 'goos': 'darwin' + 'format': 'zip' + 'files': + - 'LICENSE.txt' + - 'README.md' -snapcrafts: - - name: adguard-home - base: core18 - name_template: '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' - summary: Network-wide ads & trackers blocking DNS server - description: | - AdGuard Home is a network-wide software for blocking ads & tracking. After - you set it up, it'll cover ALL your home devices, and you don't need any - client-side software for that. +'snapcrafts': +- 'name': 'adguard-home' + 'base': 'core18' + 'name_template': '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' + 'summary': 'Network-wide ads & trackers blocking DNS server' + 'description': | + AdGuard Home is a network-wide software for blocking ads & tracking. After + you set it up, it'll cover ALL your home devices, and you don't need any + client-side software for that. - It operates as a DNS server that re-routes tracking domains to a "black hole," - thus preventing your devices from connecting to those servers. It's based - on software we use for our public AdGuard DNS servers -- both share a lot - of common code. - grade: stable - confinement: strict - publish: false - license: GPL-3.0 - extra_files: - - source: scripts/snap/local/adguard-home-web.sh - destination: adguard-home-web.sh - mode: 0755 - - source: scripts/snap/gui/adguard-home-web.desktop - destination: meta/gui/adguard-home-web.desktop - mode: 0644 - - source: scripts/snap/gui/adguard-home-web.png - destination: meta/gui/adguard-home-web.png - mode: 0644 - apps: - adguard-home: - command: AdGuardHome -w $SNAP_DATA --no-check-update - plugs: - # Add the "netrwork-bind" plug to bind to interfaces. - - network-bind - # Add the "netrwork-control" plug to be able to bind to ports below - # 1024 (cap_net_bind_service) and also to bind to a particular - # interface using SO_BINDTODEVICE (cap_net_raw). - - network-control - daemon: simple - adguard-home-web: - command: adguard-home-web.sh - plugs: [ desktop ] + It operates as a DNS server that re-routes tracking domains to a "black hole," + thus preventing your devices from connecting to those servers. It's based + on software we use for our public AdGuard DNS servers -- both share a lot + of common code. + 'grade': 'stable' + 'confinement': 'strict' + 'publish': false + 'license': 'GPL-3.0' + 'extra_files': + - 'source': 'scripts/snap/local/adguard-home-web.sh' + 'destination': 'adguard-home-web.sh' + 'mode': 0755 + - 'source': 'scripts/snap/gui/adguard-home-web.desktop' + 'destination': 'meta/gui/adguard-home-web.desktop' + 'mode': 0644 + - 'source': 'scripts/snap/gui/adguard-home-web.png' + 'destination': 'meta/gui/adguard-home-web.png' + 'mode': 0644 + 'apps': + 'adguard-home': + 'command': 'AdGuardHome -w $SNAP_DATA --no-check-update' + 'plugs': + # Add the "netrwork-bind" plug to bind to interfaces. + - 'network-bind' + # Add the "netrwork-control" plug to be able to bind to ports below + # 1024 (cap_net_bind_service) and also to bind to a particular + # interface using SO_BINDTODEVICE (cap_net_raw). + - 'network-control' + 'daemon': 'simple' + 'adguard-home-web': + 'command': 'adguard-home-web.sh' + 'plugs': + - 'desktop' -checksum: - name_template: 'checksums.txt' +'checksum': + 'name_template': 'checksums.txt' diff --git a/HACKING.md b/HACKING.md index 8df23efd..fbd56cc4 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,9 +1,11 @@ # AdGuardHome Developer Guidelines -As of **2020-11-12**, this document is still a work-in-progress. Some of the +As of **2020-11-20**, this document is still a work-in-progress. Some of the rules aren't enforced, and others might change. Still, this is a good place to find out about how we **want** our code to look like. +The rules are mostly sorted in the alphabetical order. + ## Git * Follow the commit message header format: @@ -13,11 +15,14 @@ find out about how we **want** our code to look like. ``` Where `pkg` is the package where most changes took place. If there are - several such packages, just write `all`. + several such packages, or the change is top-level only, write `all`. - * Keep your commit messages to be no wider than eighty (**80**) columns. + * Keep your commit messages, including headers, to eighty (**80**) columns. - * Only use lowercase letters in your commit message headers. + * Only use lowercase letters in your commit message headers. The rest of the + message should follow the plain text conventions below. + + The only exception are direct mentions of identifiers from the source code. ## Go @@ -98,8 +103,20 @@ find out about how we **want** our code to look like. [constant errors]: https://dave.cheney.net/2016/04/07/constant-errors [Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation +## Markdown + + * **TODO(a.garipov):** Define our Markdown conventions. + ## Text, Including Comments + * End sentences with appropriate punctuation. + + * Headers should be written with all initial letters capitalized, except for + references to variable names that start with a lowercase letter. + + * Start sentences with a capital letter, unless the first word is a reference + to a variable name that starts with a lowercase letter. + * Text should wrap at eighty (**80**) columns to be more readable, to use a common standard, and to allow editing or diffing side-by-side without wrapping. @@ -126,17 +143,17 @@ find out about how we **want** our code to look like. // TODO(usr1, usr2): Fix the frobulation issue. ``` -## Markdown - - * **TODO(a.garipov):** Define our Markdown conventions. - ## YAML + * **TODO(a.garipov):** Define naming conventions for schema names in our + OpenAPI YAML file. And just generally OpenAPI conventions. + * **TODO(a.garipov):** Find a YAML formatter or write our own. * All strings, including keys, must be quoted. Reason: the [NO-rway Law]. - * Indent with two (**2**) spaces. + * Indent with two (**2**) spaces. YAML documents can get pretty + deeply-nested. * No extra indentation in multiline arrays: @@ -147,7 +164,10 @@ find out about how we **want** our code to look like. - 'value-3' ``` - * Prefer single quotes for string to prevent accidental escaping, unless - escaping is required. + * Prefer single quotes for strings to prevent accidental escaping, unless + escaping is required or there are single quotes inside the string (e.g. for + *GitHub Actions*). + + * Use `>` for multiline strings, unless you need to keep the line breaks. [NO-rway Law]: https://news.ycombinator.com/item?id=17359376 diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d42ee24c..05552218 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,2021 +1,2052 @@ -openapi: 3.0.3 -info: - title: AdGuard Home - description: AdGuard Home REST API. Admin web interface is built on top of this REST API. - version: "0.104" - contact: - name: "AdGuard Home" - url: "https://github.com/AdguardTeam/AdGuardHome" +'openapi': '3.0.3' +'info': + 'title': 'AdGuard Home' + 'description': > + AdGuard Home REST API. Our admin web interface is built on top of this REST + API. + 'version': '0.104' + 'contact': + 'name': 'AdGuard Home' + 'url': 'https://github.com/AdguardTeam/AdGuardHome' -servers: - - url: /control +'servers': +- 'url': '/control' -tags: - - name: clients - description: Clients list operations - - name: dhcp - description: Built-in DHCP server controls - - name: filtering - description: Rule-based filtering - - name: global - description: AdGuard Home server general settings and controls - - name: i18n - description: Application localization - - name: install - description: First-time install configuration handlers - - name: log - description: AdGuard Home query log - - name: parental - description: Blocking adult and explicit materials - - name: safebrowsing - description: Blocking malware/phishing sites - - name: safesearch - description: Enforce family-friendly results in search engines - - name: stats - description: AdGuard Home statistics - - name: tls - description: AdGuard Home HTTPS/DOH/DOT settings - - name: mobileconfig - description: Apple .mobileconfig +'tags': +- 'name': 'clients' + 'description': 'Clients list operations' +- 'name': 'dhcp' + 'description': 'Built-in DHCP server controls' +- 'name': 'filtering' + 'description': 'Rule-based filtering' +- 'name': 'global' + 'description': 'AdGuard Home server general settings and controls' +- 'name': 'i18n' + 'description': 'Application localization' +- 'name': 'install' + 'description': 'First-time install configuration handlers' +- 'name': 'log' + 'description': 'AdGuard Home query log' +- 'name': 'mobileconfig' + 'description': 'Apple .mobileconfig' +- 'name': 'parental' + 'description': 'Blocking adult and explicit materials' +- 'name': 'safebrowsing' + 'description': 'Blocking malware/phishing sites' +- 'name': 'safesearch' + 'description': 'Enforce family-friendly results in search engines' +- 'name': 'stats' + 'description': 'AdGuard Home statistics' +- 'name': 'tls' + 'description': 'AdGuard Home HTTPS/DOH/DOT settings' -paths: - /status: - get: - tags: - - global - operationId: status - summary: Get DNS server current status and general settings - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ServerStatus" - /dns_info: - get: - tags: - - global - operationId: dnsInfo - summary: Get general DNS parameters - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DNSConfig" - /dns_config: - post: - tags: - - global - operationId: dnsConfig - summary: Set general DNS parameters - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/DNSConfig" - responses: - "200": - description: OK - /test_upstream_dns: - post: - tags: - - global - operationId: testUpstreamDNS - summary: Test upstream configuration - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UpstreamsConfig" - description: Upstream configuration to be tested - responses: - "200": - description: Status of testing each requested server, with "OK" meaning that - server works, any other text means an error. - content: - application/json: - examples: - response: - value: - 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 - /version.json: - post: - tags: - - global - operationId: getVersionJson - summary: Gets information about the latest available version of AdGuard - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetVersionRequest" - required: true - responses: - "200": - description: Version info. If response message is empty, UI does not show a - version update message. - content: - application/json: - schema: - $ref: "#/components/schemas/VersionInfo" - "500": - description: Cannot write answer - "502": - description: Cannot retrieve the version.json file contents - /update: - post: - tags: - - global - operationId: beginUpdate - summary: Begin auto-upgrade procedure - responses: - "200": - description: OK - "500": - description: Failed - /querylog: - get: - tags: - - log - operationId: queryLog - summary: Get DNS server query log. - parameters: - - name: older_than - in: query - description: Filter by older than - schema: - type: string - - name: offset - in: query - description: - Specify the ranking number of the first item on the page. - Even though it is possible to use "offset" and "older_than", - we recommend choosing one of them and sticking to it. - schema: - type: integer - - name: limit - in: query - description: Limit the number of records to be returned - schema: - type: integer - - name: search - in: query - description: Filter by domain name or client IP - schema: - type: string - - name: response_status - in: query - description: Filter by response status - schema: - type: string - enum: - - all - - filtered - - blocked - - blocked_safebrowsing - - blocked_parental - - whitelisted - - rewritten - - safe_search - - processed - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/QueryLog" - /querylog_info: - get: - tags: - - log - operationId: queryLogInfo - summary: Get query log parameters - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/QueryLogConfig" - /querylog_config: - post: - tags: - - log - operationId: queryLogConfig - summary: Set query log parameters - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/QueryLogConfig" - responses: - "200": - description: OK - /querylog_clear: - post: - tags: - - log - operationId: querylogClear - summary: Clear query log - responses: - "200": - description: OK - /stats: - get: - tags: - - stats - operationId: stats - summary: Get DNS server statistics - responses: - "200": - description: Returns statistics data - content: - application/json: - schema: - $ref: "#/components/schemas/Stats" - /stats_reset: - post: - tags: - - stats - operationId: statsReset - summary: Reset all statistics to zeroes - responses: - "200": - description: OK - /stats_info: - get: - tags: - - stats - operationId: statsInfo - summary: Get statistics parameters - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StatsConfig" - /stats_config: - post: - tags: - - stats - operationId: statsConfig - summary: Set statistics parameters - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/StatsConfig" - responses: - "200": - description: OK - /tls/status: - get: - tags: - - tls - operationId: tlsStatus - summary: Returns TLS configuration and its status - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/TlsConfig" - /tls/configure: - post: - tags: - - tls - operationId: tlsConfigure - summary: Updates current TLS configuration - requestBody: - $ref: "#/components/requestBodies/TlsConfig" - responses: - "200": - description: TLS configuration and its status - content: - application/json: - schema: - $ref: "#/components/schemas/TlsConfig" - "400": - description: Invalid configuration or unavailable port - "500": - description: Error occurred while applying configuration - /tls/validate: - post: - tags: - - tls - operationId: tlsValidate - summary: Checks if the current TLS configuration is valid - requestBody: - $ref: "#/components/requestBodies/TlsConfig" - responses: - "200": - description: TLS configuration and its status - content: - application/json: - schema: - $ref: "#/components/schemas/TlsConfig" - "400": - description: Invalid configuration or unavailable port - /dhcp/status: - get: - tags: - - dhcp - operationId: dhcpStatus - summary: Gets the current DHCP settings and status - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DhcpStatus" - "501": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Not implemented (for example, on Windows). - /dhcp/set_config: - post: - tags: - - dhcp - operationId: dhcpSetConfig - summary: Updates the current DHCP server configuration - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/DhcpConfig" - responses: - "200": - description: OK - "501": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Not implemented (for example, on Windows). - /dhcp/find_active_dhcp: - post: - tags: - - dhcp - operationId: checkActiveDhcp - summary: Searches for an active DHCP server on the network - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DhcpSearchResult" - "501": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Not implemented (for example, on Windows). - /dhcp/add_static_lease: - post: - tags: - - dhcp - operationId: dhcpAddStaticLease - summary: Adds a static lease - requestBody: - $ref: "#/components/requestBodies/DhcpStaticLease" - responses: - "200": - description: OK - "501": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Not implemented (for example, on Windows). - /dhcp/remove_static_lease: - post: - tags: - - dhcp - operationId: dhcpRemoveStaticLease - summary: Removes a static lease - requestBody: - $ref: "#/components/requestBodies/DhcpStaticLease" - responses: - "200": - description: OK - "501": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Not implemented (for example, on Windows). - /dhcp/reset: - post: - tags: - - dhcp - operationId: dhcpReset - summary: Reset DHCP configuration - responses: - "200": - description: OK - "501": - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - description: Not implemented (for example, on Windows). - /filtering/status: - get: - tags: - - filtering - operationId: filteringStatus - summary: Get filtering parameters - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FilterStatus" - /filtering/config: - post: - tags: - - filtering - operationId: filteringConfig - summary: Set filtering parameters - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/FilterConfig" - required: true - responses: - "200": - description: OK - /filtering/add_url: - post: - tags: - - filtering - operationId: filteringAddURL - summary: Add filter URL or an absolute file path - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/AddUrlRequest" - required: true - responses: - "200": - description: OK - /filtering/remove_url: - post: - tags: - - filtering - operationId: filteringRemoveURL - summary: Remove filter URL - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RemoveUrlRequest" - required: true - responses: - "200": - description: OK - /filtering/set_url: - post: - tags: - - filtering - operationId: filteringSetURL - summary: Set URL parameters - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/FilterSetUrl" - responses: - "200": - description: OK - /filtering/refresh: - post: - tags: - - filtering - operationId: filteringRefresh - summary: > - Reload filtering rules from URLs +'paths': + '/status': + 'get': + 'tags': + - 'global' + 'operationId': 'status' + 'summary': 'Get DNS server current status and general settings' + 'responses': + '200': + 'description': 'OK' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/ServerStatus' + '/dns_info': + 'get': + 'tags': + - 'global' + 'operationId': 'dnsInfo' + 'summary': 'Get general DNS parameters' + 'responses': + '200': + 'description': 'OK' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/DNSConfig' + '/dns_config': + 'post': + 'tags': + - 'global' + 'operationId': 'dnsConfig' + 'summary': 'Set general DNS parameters' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/DNSConfig' + 'responses': + '200': + 'description': 'OK' + '/test_upstream_dns': + 'post': + 'tags': + - 'global' + 'operationId': 'testUpstreamDNS' + 'summary': 'Test upstream configuration' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/UpstreamsConfig' + 'description': 'Upstream configuration to be tested' + 'responses': + '200': + 'description': > + Status of testing each requested server, with "OK" meaning that + server works, any other text means an error. + 'content': + 'application/json': + 'examples': + 'response': + 'value': + '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 + '/version.json': + 'post': + 'tags': + - 'global' + 'operationId': 'getVersionJson' + 'summary': > + Gets information about the latest available version of AdGuard + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/GetVersionRequest' + 'required': true + 'responses': + '200': + 'description': > + Version info. If response message is empty, UI does not show + a version update message. + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/VersionInfo' + '500': + 'description': 'Cannot write answer' + '502': + 'description': 'Cannot retrieve the version.json file contents' + '/update': + 'post': + 'tags': + - 'global' + 'operationId': 'beginUpdate' + 'summary': 'Begin auto-upgrade procedure' + 'responses': + '200': + 'description': 'OK.' + '500': + 'description': 'Failed' + '/querylog': + 'get': + 'tags': + - 'log' + 'operationId': 'queryLog' + 'summary': 'Get DNS server query log.' + 'parameters': + - 'name': 'older_than' + 'in': 'query' + 'description': 'Filter by older than' + 'schema': + 'type': 'string' + - 'name': 'offset' + 'in': 'query' + 'description': > + Specify the ranking number of the first item on the page. Even + though it is possible to use "offset" and "older_than", we recommend + choosing one of them and sticking to it. + 'schema': + 'type': 'integer' + - 'name': 'limit' + 'in': 'query' + 'description': 'Limit the number of records to be returned' + 'schema': + 'type': 'integer' + - 'name': 'search' + 'in': 'query' + 'description': 'Filter by domain name or client IP' + 'schema': + 'type': 'string' + - 'name': 'response_status' + 'in': 'query' + 'description': 'Filter by response status' + 'schema': + 'type': 'string' + 'enum': + - 'all' + - 'filtered' + - 'blocked' + - 'blocked_safebrowsing' + - 'blocked_parental' + - 'whitelisted' + - 'rewritten' + - 'safe_search' + - 'processed' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/QueryLog' + '/querylog_info': + 'get': + 'tags': + - 'log' + 'operationId': 'queryLogInfo' + 'summary': 'Get query log parameters' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/QueryLogConfig' + '/querylog_config': + 'post': + 'tags': + - 'log' + 'operationId': 'queryLogConfig' + 'summary': 'Set query log parameters' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/QueryLogConfig' + 'responses': + '200': + 'description': 'OK.' + '/querylog_clear': + 'post': + 'tags': + - 'log' + 'operationId': 'querylogClear' + 'summary': 'Clear query log' + 'responses': + '200': + 'description': 'OK.' + '/stats': + 'get': + 'tags': + - 'stats' + 'operationId': 'stats' + 'summary': 'Get DNS server statistics' + 'responses': + '200': + 'description': 'Returns statistics data' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Stats' + '/stats_reset': + 'post': + 'tags': + - 'stats' + 'operationId': 'statsReset' + 'summary': 'Reset all statistics to zeroes' + 'responses': + '200': + 'description': 'OK.' + '/stats_info': + 'get': + 'tags': + - 'stats' + 'operationId': 'statsInfo' + 'summary': 'Get statistics parameters' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/StatsConfig' + '/stats_config': + 'post': + 'tags': + - 'stats' + 'operationId': 'statsConfig' + 'summary': 'Set statistics parameters' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/StatsConfig' + 'responses': + '200': + 'description': 'OK.' + '/tls/status': + 'get': + 'tags': + - 'tls' + 'operationId': 'tlsStatus' + 'summary': 'Returns TLS configuration and its status' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/TlsConfig' + '/tls/configure': + 'post': + 'tags': + - 'tls' + 'operationId': 'tlsConfigure' + 'summary': 'Updates current TLS configuration' + 'requestBody': + '$ref': '#/components/requestBodies/TlsConfig' + 'responses': + '200': + 'description': 'TLS configuration and its status' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/TlsConfig' + '400': + 'description': 'Invalid configuration or unavailable port' + '500': + 'description': 'Error occurred while applying configuration' + '/tls/validate': + 'post': + 'tags': + - 'tls' + 'operationId': 'tlsValidate' + 'summary': 'Checks if the current TLS configuration is valid' + 'requestBody': + '$ref': '#/components/requestBodies/TlsConfig' + 'responses': + '200': + 'description': 'TLS configuration and its status' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/TlsConfig' + '400': + 'description': 'Invalid configuration or unavailable port' + '/dhcp/status': + 'get': + 'tags': + - 'dhcp' + 'operationId': 'dhcpStatus' + 'summary': 'Gets the current DHCP settings and status' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/DhcpStatus' + '501': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/dhcp/set_config': + 'post': + 'tags': + - 'dhcp' + 'operationId': 'dhcpSetConfig' + 'summary': 'Updates the current DHCP server configuration' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/DhcpConfig' + 'responses': + '200': + 'description': 'OK.' + '501': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/dhcp/find_active_dhcp': + 'post': + 'tags': + - 'dhcp' + 'operationId': 'checkActiveDhcp' + 'summary': 'Searches for an active DHCP server on the network' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/DhcpSearchResult' + '501': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/dhcp/add_static_lease': + 'post': + 'tags': + - 'dhcp' + 'operationId': 'dhcpAddStaticLease' + 'summary': 'Adds a static lease' + 'requestBody': + '$ref': '#/components/requestBodies/DhcpStaticLease' + 'responses': + '200': + 'description': 'OK.' + '501': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/dhcp/remove_static_lease': + 'post': + 'tags': + - 'dhcp' + 'operationId': 'dhcpRemoveStaticLease' + 'summary': 'Removes a static lease' + 'requestBody': + '$ref': '#/components/requestBodies/DhcpStaticLease' + 'responses': + '200': + 'description': 'OK.' + '501': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/dhcp/reset': + 'post': + 'tags': + - 'dhcp' + 'operationId': 'dhcpReset' + 'summary': 'Reset DHCP configuration' + 'responses': + '200': + 'description': 'OK.' + '501': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Not implemented (for example, on Windows).' + '/filtering/status': + 'get': + 'tags': + - 'filtering' + 'operationId': 'filteringStatus' + 'summary': 'Get filtering parameters' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/FilterStatus' + '/filtering/config': + 'post': + 'tags': + - 'filtering' + 'operationId': 'filteringConfig' + 'summary': 'Set filtering parameters' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/FilterConfig' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/filtering/add_url': + 'post': + 'tags': + - 'filtering' + 'operationId': 'filteringAddURL' + 'summary': 'Add filter URL or an absolute file path' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/AddUrlRequest' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/filtering/remove_url': + 'post': + 'tags': + - 'filtering' + 'operationId': 'filteringRemoveURL' + 'summary': 'Remove filter URL' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/RemoveUrlRequest' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/filtering/set_url': + 'post': + 'tags': + - 'filtering' + 'operationId': 'filteringSetURL' + 'summary': 'Set URL parameters' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/FilterSetUrl' + '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. + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/FilterRefreshRequest' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/FilterRefreshResponse' + '/filtering/set_rules': + 'post': + 'tags': + - 'filtering' + 'operationId': 'filteringSetRules' + 'summary': 'Set user-defined filter rules' + 'requestBody': + 'content': + 'text/plain': + 'schema': + 'type': 'string' + 'example': '@@||yandex.ru^|' + 'description': 'All filtering rules, one line per rule' + 'responses': + '200': + 'description': 'OK.' + '/filtering/check_host': + 'get': + 'tags': + - 'filtering' + 'operationId': 'filteringCheckHost' + 'summary': 'Check if host name is filtered' + 'parameters': + - 'name': 'name' + 'in': 'query' + 'description': 'Filter by host name' + 'schema': + 'type': 'string' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/FilterCheckHostResponse' + '/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.' + 'content': + 'application/json': + 'examples': + 'response': + 'value': + 'enabled': false + '/parental/enable': + 'post': + 'tags': + - 'parental' + 'operationId': 'parentalEnable' + 'summary': 'Enable parental filtering' + 'requestBody': + 'content': + 'text/plain': + 'schema': + 'type': 'string' + 'enum': + - 'EARLY_CHILDHOOD' + - 'YOUNG' + - 'TEEN' + - 'MATURE' + 'example': 'sensitivity=TEEN' + 'description': | + Age sensitivity for parental filtering, + EARLY_CHILDHOOD is 3 + YOUNG is 10 + TEEN is 13 + MATURE is 17 + 'required': true + '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.' + 'content': + 'application/json': + 'examples': + 'response': + 'value': + '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.' + 'content': + 'application/json': + 'examples': + 'response': + 'value': + 'enabled': false + '/clients': + 'get': + 'tags': + - 'clients' + 'operationId': 'clientsStatus' + 'summary': 'Get information about configured clients' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Clients' + '/clients/add': + 'post': + 'tags': + - 'clients' + 'operationId': 'clientsAdd' + 'summary': 'Add a new client' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Client' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/clients/delete': + 'post': + 'tags': + - 'clients' + 'operationId': 'clientsDelete' + 'summary': 'Remove a client' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/ClientDelete' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/clients/update': + 'post': + 'tags': + - 'clients' + 'operationId': 'clientsUpdate' + 'summary': 'Update client information' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/ClientUpdate' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/clients/find': + 'get': + 'tags': + - 'clients' + 'operationId': 'clientsFind' + 'summary': 'Get information about selected clients by their IP address' + 'parameters': + - 'name': 'ip0' + 'in': 'query' + 'description': 'Filter by IP address' + 'schema': + 'type': 'string' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/ClientsFindResponse' + '/blocked_services/list': + 'get': + 'tags': + - 'blocked_services' + 'operationId': 'blockedServicesList' + 'summary': 'Get blocked services list' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BlockedServicesArray' + '/blocked_services/set': + 'post': + 'tags': + - 'blocked_services' + 'operationId': 'blockedServicesSet' + 'summary': 'Set blocked services list' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/BlockedServicesArray' + 'responses': + '200': + 'description': 'OK.' + '/rewrite/list': + 'get': + 'tags': + - 'rewrite' + 'operationId': 'rewriteList' + 'summary': 'Get list of Rewrite rules' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/RewriteList' + '/rewrite/add': + 'post': + 'tags': + - 'rewrite' + 'operationId': 'rewriteAdd' + 'summary': 'Add a new Rewrite rule' + 'requestBody': + '$ref': '#/components/requestBodies/RewriteEntry' + 'responses': + '200': + 'description': 'OK.' + '/rewrite/delete': + 'post': + 'tags': + - 'rewrite' + 'operationId': 'rewriteDelete' + 'summary': 'Remove a Rewrite rule' + 'requestBody': + '$ref': '#/components/requestBodies/RewriteEntry' + 'responses': + '200': + 'description': 'OK.' + '/i18n/change_language': + 'post': + 'tags': + - 'i18n' + 'operationId': 'changeLanguage' + 'summary': > + Change current language. Argument must be an ISO 639-1 two-letter code. + 'requestBody': + 'content': + 'text/plain': + 'schema': + 'type': 'string' + 'example': 'en' + 'description': > + New language. It must be known to the server and must be an ISO 639-1 + two-letter code. + 'responses': + '200': + 'description': 'OK.' + '/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.' + 'content': + 'text/plain': + 'examples': + 'response': + 'value': 'en' + '/install/get_addresses': + 'get': + 'tags': + - 'install' + 'operationId': 'installGetAddresses' + 'summary': 'Gets the network interfaces information.' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/AddressesInfo' + '/install/check_config': + 'post': + 'tags': + - 'install' + 'operationId': 'installCheckConfig' + 'summary': 'Checks configuration' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/CheckConfigRequest' + 'description': 'Configuration to be checked' + 'required': true + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/CheckConfigResponse' + '400': + 'description': > + Failed to parse JSON or cannot listen on the specified address. + '/install/configure': + 'post': + 'tags': + - 'install' + 'operationId': 'installConfigure' + 'summary': 'Applies the initial configuration.' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/InitialConfiguration' + 'description': 'Initial configuration JSON' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '400': + 'description': > + Failed to parse initial configuration or cannot listen to the + specified addresses. + '500': + 'description': 'Cannot start the DNS server' + '/login': + 'post': + 'tags': + - 'global' + 'operationId': 'login' + 'summary': 'Perform administrator log-in' + 'requestBody': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Login' + 'required': true + 'responses': + '200': + 'description': 'OK.' + '/logout': + 'get': + 'tags': + - 'global' + 'operationId': 'logout' + 'summary': 'Perform administrator log-out' + 'responses': + '302': + 'description': 'OK.' + '/profile': + 'get': + 'tags': + - 'global' + 'operationId': 'getProfile' + 'summary': '' + 'responses': + '200': + 'description': 'OK.' + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/ProfileInfo' + '/apple/doh.mobileconfig': + 'get': + 'tags': + - 'mobileconfig' + - 'global' + 'operationId': 'mobileConfigDoH' + 'summary': 'Get DNS over HTTPS .mobileconfig' + 'responses': + '200': + 'description': 'DNS over HTTPS plist file' + '/apple/dot.mobileconfig': + 'get': + 'tags': + - 'mobileconfig' + - 'global' + 'operationId': 'mobileConfigDoT' + 'summary': 'Get TLS over TLS .mobileconfig' + 'responses': + '200': + 'description': 'DNS over TLS plist file' - This might be needed if new URL was just added and you dont want to wait for automatic refresh to kick in. +'components': + 'requestBodies': + 'TlsConfig': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/TlsConfig' + 'description': 'TLS configuration JSON' + 'required': true + 'DhcpStaticLease': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/DhcpStaticLease' + 'required': true + 'RewriteEntry': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/RewriteEntry' + 'required': true + 'schemas': + 'ServerStatus': + 'type': 'object' + 'description': 'AdGuard Home server status and configuration' + 'required': + - 'dns_address' + - 'dns_port' + - 'protection_enabled' + - 'querylog_enabled' + - 'running' + - 'bootstrap_dns' + - 'upstream_dns' + - 'version' + - 'language' + 'properties': + 'dns_address': + 'type': 'string' + 'example': '127.0.0.1' + 'dns_port': + 'type': 'integer' + 'format': 'int32' + 'example': 53 + 'minimum': 1 + 'maximum': 65535 + 'protection_enabled': + 'type': 'boolean' + 'dhcp_available': + 'type': 'boolean' + 'querylog_enabled': + 'type': 'boolean' + 'running': + 'type': 'boolean' + 'version': + 'type': 'string' + 'example': '0.1' + 'language': + 'type': 'string' + 'example': 'en' + 'DNSConfig': + 'type': 'object' + 'description': 'Query log configuration' + 'properties': + 'bootstrap_dns': + 'type': 'array' + 'description': > + Bootstrap servers, port is optional after colon. Empty value will + reset it to default values. + 'items': + 'type': 'string' + 'example': + - '8.8.8.8:53' + - '1.1.1.1:53' + 'upstream_dns': + 'type': 'array' + 'description': > + Upstream servers, port is optional after colon. Empty value will + reset it to default values. + 'items': + 'type': 'string' + 'example': + - 'tls://1.1.1.1' + - 'tls://1.0.0.1' + 'upstream_dns_file': + 'type': 'string' + 'protection_enabled': + 'type': 'boolean' + 'dhcp_available': + 'type': 'boolean' + 'ratelimit': + 'type': 'integer' + 'blocking_mode': + 'type': 'string' + 'enum': + - 'default' + - 'refused' + - 'nxdomain' + - 'null_ip' + - 'custom_ip' + 'blocking_ipv4': + 'type': 'string' + 'blocking_ipv6': + 'type': 'string' + 'edns_cs_enabled': + 'type': 'boolean' + 'dnssec_enabled': + 'type': 'boolean' + 'cache_size': + 'type': 'integer' + 'cache_ttl_min': + 'type': 'integer' + 'cache_ttl_max': + 'type': 'integer' + 'upstream_mode': + 'enum': + - '' + - 'parallel' + - 'fastest_addr' + 'UpstreamsConfig': + 'type': 'object' + 'description': 'Upstreams configuration' + 'required': + - 'bootstrap_dns' + - 'upstream_dns' + 'properties': + 'bootstrap_dns': + 'type': 'array' + 'description': > + Bootstrap servers, port is optional after colon. Empty value will + reset it to default values. + 'items': + 'type': 'string' + 'example': + - '8.8.8.8:53' + - '1.1.1.1:53' + 'upstream_dns': + 'type': 'array' + 'description': > + Upstream servers, port is optional after colon. Empty value will + reset it to default values. + 'items': + 'type': 'string' + 'example': + - 'tls://1.1.1.1' + - 'tls://1.0.0.1' + 'Filter': + 'type': 'object' + 'description': 'Filter subscription info' + 'required': + - 'enabled' + - 'id' + - 'lastUpdated' + - 'name' + - 'rulesCount' + - 'url' + 'properties': + 'enabled': + 'type': 'boolean' + 'id': + 'type': 'integer' + 'example': 1234 + 'lastUpdated': + 'type': 'string' + 'format': 'date-time' + 'example': '2018-10-30T12:18:57+03:00' + 'name': + 'type': 'string' + 'example': 'AdGuard Simplified Domain Names filter' + 'rulesCount': + 'type': 'integer' + 'example': 5912 + 'url': + 'type': 'string' + 'example': > + https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt + 'FilterStatus': + 'type': 'object' + 'description': 'Filtering settings' + 'properties': + 'enabled': + 'type': 'boolean' + 'interval': + 'type': 'integer' + 'filters': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/Filter' + 'user_rules': + 'type': 'array' + 'items': + 'type': 'string' + 'FilterConfig': + 'type': 'object' + 'description': 'Filtering settings' + 'properties': + 'enabled': + 'type': 'boolean' + 'interval': + 'type': 'integer' + 'FilterSetUrl': + 'type': 'object' + 'description': 'Filtering URL settings' + 'properties': + 'data': + 'properties': + 'enabled': + 'type': 'boolean' + 'name': + 'type': 'string' + 'url': + 'type': 'string' + 'type': 'object' + 'url': + 'type': 'string' + 'whitelist': + 'type': 'boolean' + 'FilterRefreshRequest': + 'type': 'object' + 'description': 'Refresh Filters request data' + 'properties': + 'whitelist': + 'type': 'boolean' + 'FilterCheckHostResponse': + 'type': 'object' + 'description': 'Check Host Result' + 'properties': + 'reason': + 'type': 'string' + 'description': 'DNS filter status' + 'enum': + - 'NotFilteredNotFound' + - 'NotFilteredWhiteList' + - 'NotFilteredError' + - 'FilteredBlackList' + - 'FilteredSafeBrowsing' + - 'FilteredParental' + - 'FilteredInvalid' + - 'FilteredSafeSearch' + - 'FilteredBlockedService' + - 'ReasonRewrite' + 'filter_id': + 'type': 'integer' + 'rule': + 'type': 'string' + 'example': '||example.org^' + 'description': 'Filtering rule applied to the request (if any)' + 'service_name': + 'type': 'string' + 'description': 'Set if reason=FilteredBlockedService' + 'cname': + 'type': 'string' + 'description': 'Set if reason=ReasonRewrite' + 'ip_addrs': + 'type': 'array' + 'items': + 'type': 'string' + 'description': 'Set if reason=ReasonRewrite' + 'FilterRefreshResponse': + 'type': 'object' + 'description': '/filtering/refresh response data' + 'properties': + 'updated': + 'type': 'integer' + 'GetVersionRequest': + 'type': 'object' + 'description': '/version.json request data' + 'properties': + 'recheck_now': + 'description': > + If false, server will check for a new version data only once in + several hours. + 'type': 'boolean' + 'VersionInfo': + 'type': 'object' + 'description': > + Information about the latest available version of AdGuard Home. + 'properties': + 'new_version': + 'type': 'string' + 'example': 'v0.9' + 'announcement': + 'type': 'string' + 'example': 'AdGuard Home v0.9 is now available!' + 'announcement_url': + 'type': 'string' + 'example': > + https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.9 + 'can_autoupdate': + 'type': 'boolean' + 'Stats': + 'type': 'object' + 'description': 'Server statistics data' + 'properties': + 'time_units': + 'type': 'string' + 'description': 'Time units (hours | days)' + 'example': 'hours' + 'num_dns_queries': + 'type': 'integer' + 'description': 'Total number of DNS queries' + 'example': 123 + 'num_blocked_filtering': + 'type': 'integer' + 'description': 'Number of requests blocked by filtering rules' + 'example': 50 + 'num_replaced_safebrowsing': + 'type': 'integer' + 'description': 'Number of requests blocked by safebrowsing module' + 'example': 5 + 'num_replaced_safesearch': + 'type': 'integer' + 'description': 'Number of requests blocked by safesearch module' + 'example': 5 + 'num_replaced_parental': + 'type': 'integer' + 'description': 'Number of blocked adult websites' + 'example': 15 + 'avg_processing_time': + 'type': 'number' + 'format': 'float' + 'description': 'Average time in milliseconds on processing a DNS' + 'example': 0.34 + 'top_queried_domains': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/TopArrayEntry' + 'top_clients': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/TopArrayEntry' + 'top_blocked_domains': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/TopArrayEntry' + 'dns_queries': + 'type': 'array' + 'items': + 'type': 'integer' + 'blocked_filtering': + 'type': 'array' + 'items': + 'type': 'integer' + 'replaced_safebrowsing': + 'type': 'array' + 'items': + 'type': 'integer' + 'replaced_parental': + 'type': 'array' + 'items': + 'type': 'integer' + 'TopArrayEntry': + 'type': 'object' + 'description': > + Represent the number of hits per key (domain or client IP). + 'properties': + 'domain_or_ip': + 'type': 'integer' + 'StatsConfig': + 'type': 'object' + 'description': 'Statistics configuration' + 'properties': + 'interval': + 'type': 'integer' + 'description': 'Time period to keep data (1 | 7 | 30 | 90)' + 'DhcpConfig': + 'type': 'object' + 'properties': + 'enabled': + 'type': 'boolean' + 'interface_name': + 'type': 'string' + 'v4': + '$ref': '#/components/schemas/DhcpConfigV4' + 'v6': + '$ref': '#/components/schemas/DhcpConfigV6' + 'DhcpConfigV4': + 'type': 'object' + 'properties': + 'gateway_ip': + 'type': 'string' + 'example': '192.168.1.1' + 'subnet_mask': + 'type': 'string' + 'example': '255.255.255.0' + 'range_start': + 'type': 'string' + 'example': '192.168.1.2' + 'range_end': + 'type': 'string' + 'example': '192.168.10.50' + 'lease_duration': + 'type': 'integer' + 'DhcpConfigV6': + 'type': 'object' + 'properties': + 'range_start': + 'type': 'string' + 'lease_duration': + 'type': 'integer' + 'DhcpLease': + 'type': 'object' + 'description': 'DHCP lease information' + 'required': + - 'mac' + - 'ip' + - 'hostname' + - 'expires' + 'properties': + 'mac': + 'type': 'string' + 'example': '00:11:09:b3:b3:b8' + 'ip': + 'type': 'string' + 'example': '192.168.1.22' + 'hostname': + 'type': 'string' + 'example': 'dell' + 'expires': + 'type': 'string' + 'example': '2017-07-21T17:32:28Z' + 'DhcpStaticLease': + 'type': 'object' + 'description': 'DHCP static lease information' + 'required': + - 'mac' + - 'ip' + - 'hostname' + - 'expires' + 'properties': + 'mac': + 'type': 'string' + 'example': '00:11:09:b3:b3:b8' + 'ip': + 'type': 'string' + 'example': '192.168.1.22' + 'hostname': + 'type': 'string' + 'example': 'dell' + 'DhcpStatus': + 'type': 'object' + 'description': 'Built-in DHCP server configuration and status' + 'required': + - 'config' + - 'leases' + 'properties': + 'enabled': + 'type': 'boolean' + 'interface_name': + 'type': 'string' + 'v4': + '$ref': '#/components/schemas/DhcpConfigV4' + 'v6': + '$ref': '#/components/schemas/DhcpConfigV6' + 'leases': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/DhcpLease' + 'static_leases': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/DhcpStaticLease' + 'DhcpSearchResult': + 'type': 'object' + 'description': > + Information about a DHCP server discovered in the current network. + 'properties': + 'v4': + '$ref': '#/components/schemas/DhcpSearchV4' + 'v6': + '$ref': '#/components/schemas/DhcpSearchV6' - 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. + 'DhcpSearchV4': + 'type': 'object' + 'properties': + 'other_server': + '$ref': '#/components/schemas/DhcpSearchResultOtherServer' + 'static_ip': + '$ref': '#/components/schemas/DhcpSearchResultStaticIP' + 'DhcpSearchV6': + 'type': 'object' + 'properties': + 'other_server': + '$ref': '#/components/schemas/DhcpSearchResultOtherServer' - This should work as intended, a `force` parameter is offered as last-resort attempt to make filter lists fresh. + 'DhcpSearchResultOtherServer': + 'type': 'object' + 'properties': + 'found': + 'type': 'string' + 'description': 'yes|no|error' + 'example': 'no' + 'error': + 'type': 'string' + 'description': 'Set if found=error' + 'example': '' + 'DhcpSearchResultStaticIP': + 'type': 'object' + 'properties': + 'static': + 'type': 'string' + 'description': 'yes|no|error' + 'example': 'yes' + 'ip': + 'type': 'string' + 'description': 'Set if static=no' + 'example': '' - If you ever find yourself using `force` to make something work that otherwise wont, this is a bug and report it accordingly. - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/FilterRefreshRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FilterRefreshResponse" - /filtering/set_rules: - post: - tags: - - filtering - operationId: filteringSetRules - summary: Set user-defined filter rules - requestBody: - content: - text/plain: - schema: - type: string - example: "@@||yandex.ru^|" - description: All filtering rules, one line per rule - responses: - "200": - description: OK - /filtering/check_host: - get: - tags: - - filtering - operationId: filteringCheckHost - summary: Check if host name is filtered - parameters: - - name: name - in: query - description: Filter by host name - schema: - type: string - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FilterCheckHostResponse" - /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 - content: - application/json: - examples: - response: - value: - enabled: false - /parental/enable: - post: - tags: - - parental - operationId: parentalEnable - summary: Enable parental filtering - requestBody: - content: - text/plain: - schema: - type: string - enum: - - EARLY_CHILDHOOD - - YOUNG - - TEEN - - MATURE - example: sensitivity=TEEN - description: | - Age sensitivity for parental filtering, - EARLY_CHILDHOOD is 3 - YOUNG is 10 - TEEN is 13 - MATURE is 17 - required: true - 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 - content: - application/json: - examples: - response: - value: - 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 - content: - application/json: - examples: - response: - value: - enabled: false - /clients: - get: - tags: - - clients - operationId: clientsStatus - summary: Get information about configured clients - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Clients" - /clients/add: - post: - tags: - - clients - operationId: clientsAdd - summary: Add a new client - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Client" - required: true - responses: - "200": - description: OK - /clients/delete: - post: - tags: - - clients - operationId: clientsDelete - summary: Remove a client - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ClientDelete" - required: true - responses: - "200": - description: OK - /clients/update: - post: - tags: - - clients - operationId: clientsUpdate - summary: Update client information - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ClientUpdate" - required: true - responses: - "200": - description: OK - /clients/find: - get: - tags: - - clients - operationId: clientsFind - summary: Get information about selected clients by their IP address - parameters: - - name: ip0 - in: query - description: Filter by IP address - schema: - type: string - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ClientsFindResponse" - /blocked_services/list: - get: - tags: - - blocked_services - operationId: blockedServicesList - summary: Get blocked services list - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/BlockedServicesArray" - /blocked_services/set: - post: - tags: - - blocked_services - operationId: blockedServicesSet - summary: Set blocked services list - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/BlockedServicesArray" - responses: - "200": - description: OK - /rewrite/list: - get: - tags: - - rewrite - operationId: rewriteList - summary: Get list of Rewrite rules - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/RewriteList" - /rewrite/add: - post: - tags: - - rewrite - operationId: rewriteAdd - summary: Add a new Rewrite rule - requestBody: - $ref: "#/components/requestBodies/RewriteEntry" - responses: - "200": - description: OK - /rewrite/delete: - post: - tags: - - rewrite - operationId: rewriteDelete - summary: Remove a Rewrite rule - requestBody: - $ref: "#/components/requestBodies/RewriteEntry" - responses: - "200": - description: OK - /i18n/change_language: - post: - tags: - - i18n - operationId: changeLanguage - summary: Change current language. Argument must be an ISO 639-1 two-letter code - requestBody: - content: - text/plain: - schema: - type: string - example: en - description: New language. It must be known to the server and must be an ISO 639-1 - two-letter code - responses: - "200": - description: OK - /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 - content: - text/plain: - examples: - response: - value: en - /install/get_addresses: - get: - tags: - - install - operationId: installGetAddresses - summary: Gets the network interfaces information. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/AddressesInfo" - /install/check_config: - post: - tags: - - install - operationId: installCheckConfig - summary: Checks configuration - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CheckConfigRequest" - description: Configuration to be checked - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CheckConfigResponse" - "400": - description: Failed to parse JSON or cannot listen on the specified address - /install/configure: - post: - tags: - - install - operationId: installConfigure - summary: Applies the initial configuration. - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/InitialConfiguration" - description: Initial configuration JSON - required: true - responses: - "200": - description: OK - "400": - description: Failed to parse initial configuration or cannot listen to the - specified addresses - "500": - description: Cannot start the DNS server - /login: - post: - tags: - - global - operationId: login - summary: Perform administrator log-in - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Login" - required: true - responses: - "200": - description: OK - /logout: - get: - tags: - - global - operationId: logout - summary: Perform administrator log-out - responses: - "302": - description: OK - /profile: - get: - tags: - - global - operationId: getProfile - summary: "" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ProfileInfo" - /apple/doh.mobileconfig: - get: - tags: - - mobileconfig - - global - operationId: mobileConfigDoH - summary: Get DNS over HTTPS .mobileconfig - responses: - "200": - description: DNS over HTTPS plist file + 'DnsAnswer': + 'type': 'object' + 'description': 'DNS answer section' + 'properties': + 'ttl': + 'type': 'integer' + 'example': 55 + 'type': + 'type': 'string' + 'example': 'A' + 'value': + 'type': 'string' + 'example': '217.69.139.201' + 'DnsQuestion': + 'type': 'object' + 'description': 'DNS question section' + 'properties': + 'class': + 'type': 'string' + 'example': 'IN' + 'host': + 'type': 'string' + 'example': 'example.org' + 'type': + 'type': 'string' + 'example': 'A' + 'AddUrlRequest': + 'type': 'object' + 'description': '/add_url request data' + 'properties': + 'name': + 'type': 'string' + 'url': + 'description': > + URL or an absolute path to the file containing filtering rules. + 'type': 'string' + 'example': 'https://filters.adtidy.org/windows/filters/15.txt' + 'whitelist': + 'type': 'boolean' + 'RemoveUrlRequest': + 'type': 'object' + 'description': '/remove_url request data' + 'properties': + 'url': + 'description': 'Previously added URL containing filtering rules' + 'type': 'string' + 'example': 'https://filters.adtidy.org/windows/filters/15.txt' + 'QueryLogItem': + 'type': 'object' + 'description': 'Query log item' + 'properties': + 'answer': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/DnsAnswer' + 'original_answer': + 'type': 'array' + 'description': 'Answer from upstream server (optional)' + 'items': + '$ref': '#/components/schemas/DnsAnswer' + 'upstream': + 'type': 'string' + 'description': > + Upstream URL starting with tcp://, tls://, https://, or with an IP + address. + 'answer_dnssec': + 'type': 'boolean' + 'client': + 'type': 'string' + 'example': '192.168.0.1' + 'client_proto': + 'enum': + - 'dot' + - 'doh' + - 'doq' + - '' + 'elapsedMs': + 'type': 'string' + 'example': '54.023928' + 'question': + '$ref': '#/components/schemas/DnsQuestion' + 'filterId': + 'type': 'integer' + 'example': 123123 + 'description': > + In case if there's a rule applied to this DNS request, this is ID of + the filter that rule belongs to. + 'rule': + 'type': 'string' + 'example': '||example.org^' + 'description': 'Filtering rule applied to the request (if any)' + 'reason': + 'type': 'string' + 'description': 'DNS filter status' + 'enum': + - 'NotFilteredNotFound' + - 'NotFilteredWhiteList' + - 'NotFilteredError' + - 'FilteredBlackList' + - 'FilteredSafeBrowsing' + - 'FilteredParental' + - 'FilteredInvalid' + - 'FilteredSafeSearch' + - 'FilteredBlockedService' + - 'ReasonRewrite' + 'service_name': + 'type': 'string' + 'description': 'Set if reason=FilteredBlockedService' + 'status': + 'type': 'string' + 'description': 'DNS response status' + 'example': 'NOERROR' + 'time': + 'type': 'string' + 'description': 'DNS request processing start time' + 'example': '2018-11-26T00:02:41+03:00' + 'QueryLog': + 'type': 'object' + 'description': 'Query log' + 'properties': + 'oldest': + 'type': 'string' + 'example': '2018-11-26T00:02:41+03:00' + 'data': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/QueryLogItem' + 'QueryLogConfig': + 'type': 'object' + 'description': 'Query log configuration' + 'properties': + 'enabled': + 'type': 'boolean' + 'description': 'Is query log enabled' + 'interval': + 'type': 'integer' + 'description': 'Time period to keep data (1 | 7 | 30 | 90)' + 'anonymize_client_ip': + 'type': 'boolean' + 'description': "Anonymize clients' IP addresses" + 'TlsConfig': + 'type': 'object' + 'description': 'TLS configuration settings and status' + 'properties': + 'enabled': + 'type': 'boolean' + 'example': true + 'description': 'enabled is the encryption (DOT/DOH/HTTPS) status' + 'server_name': + 'type': 'string' + 'example': 'example.org' + 'description': 'server_name is the hostname of your HTTPS/TLS server' + 'force_https': + 'type': 'boolean' + 'example': true + 'description': 'if true, forces HTTP->HTTPS redirect' + 'port_https': + 'type': 'integer' + 'format': 'int32' + 'example': 443 + 'description': 'HTTPS port. If 0, HTTPS will be disabled.' + 'port_dns_over_tls': + 'type': 'integer' + 'format': 'int32' + 'example': 853 + 'description': 'DNS-over-TLS port. If 0, DOT will be disabled.' + 'port_dns_over_quic': + 'type': 'integer' + 'format': 'int32' + 'example': 784 + 'description': 'DNS-over-QUIC port. If 0, DOQ will be disabled.' + 'certificate_chain': + 'type': 'string' + 'description': 'Base64 string with PEM-encoded certificates chain' + 'private_key': + 'type': 'string' + 'description': 'Base64 string with PEM-encoded private key' + 'certificate_path': + 'type': 'string' + 'description': 'Path to certificate file' + 'private_key_path': + 'type': 'string' + 'description': 'Path to private key file' + 'valid_cert': + 'type': 'boolean' + 'example': true + 'description': > + Set to true if the specified certificates chain is a valid chain of + X509 certificates. + 'valid_chain': + 'type': 'boolean' + 'example': true + 'description': > + Set to true if the specified certificates chain is verified and + issued by a known CA. + 'subject': + 'type': 'string' + 'example': 'CN=example.org' + 'description': 'The subject of the first certificate in the chain.' + 'issuer': + 'type': 'string' + 'example': "CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US" + 'description': 'The issuer of the first certificate in the chain.' + 'not_before': + 'type': 'string' + 'example': '2019-01-31T10:47:32Z' + 'description': > + The NotBefore field of the first certificate in the chain. + 'not_after': + 'type': 'string' + 'example': '2019-05-01T10:47:32Z' + 'description': > + The NotAfter field of the first certificate in the chain. + 'dns_names': + 'type': 'array' + 'items': + 'type': 'string' + 'description': > + The value of SubjectAltNames field of the first certificate in the + chain. + 'example': + - '*.example.org' + 'valid_key': + 'type': 'boolean' + 'example': true + 'description': 'Set to true if the key is a valid private key.' + 'key_type': + 'type': 'string' + 'example': 'RSA' + 'enum': + - 'RSA' + - 'ECDSA' + 'description': 'Key type.' + 'warning_validation': + 'type': 'string' + 'example': 'You have specified an empty certificate' + 'description': > + A validation warning message with the issue description. + 'valid_pair': + 'type': 'boolean' + 'example': true + 'description': > + Set to true if both certificate and private key are correct. + 'NetInterface': + 'type': 'object' + 'description': 'Network interface info' + 'properties': + 'flags': + 'type': 'string' + 'example': 'up|broadcast|multicast' + 'hardware_address': + 'type': 'string' + 'example': '52:54:00:11:09:ba' + 'name': + 'type': 'string' + 'example': 'eth0' + 'ipv4_addresses': + 'type': 'array' + 'items': + 'type': 'string' + 'ipv6_addresses': + 'type': 'array' + 'items': + 'type': 'string' + 'gateway_ip': + 'type': 'string' + 'AddressInfo': + 'type': 'object' + 'description': 'Port information' + 'properties': + 'ip': + 'type': 'string' + 'example': '127.0.0.1' + 'port': + 'type': 'integer' + 'format': 'int32' + 'example': 53 + 'AddressesInfo': + 'type': 'object' + 'description': 'AdGuard Home addresses configuration' + 'properties': + 'dns_port': + 'type': 'integer' + 'format': 'int32' + 'example': 53 + 'web_port': + 'type': 'integer' + 'format': 'int32' + 'example': 80 + 'interfaces': + 'type': 'object' + 'description': > + Network interfaces dictionary, keys are interface names. + 'additionalProperties': + '$ref': '#/components/schemas/NetInterface' + 'ProfileInfo': + 'type': 'object' + 'description': 'Information about the current user' + 'properties': + 'name': + 'type': 'string' + 'Client': + 'type': 'object' + 'description': 'Client information' + 'properties': + 'name': + 'type': 'string' + 'description': 'Name' + 'example': 'localhost' + 'ids': + 'type': 'array' + 'description': 'IP, CIDR or MAC address' + 'items': + 'type': 'string' + 'use_global_settings': + 'type': 'boolean' + 'filtering_enabled': + 'type': 'boolean' + 'parental_enabled': + 'type': 'boolean' + 'safebrowsing_enabled': + 'type': 'boolean' + 'safesearch_enabled': + 'type': 'boolean' + 'use_global_blocked_services': + 'type': 'boolean' + 'blocked_services': + 'type': 'array' + 'items': + 'type': 'string' + 'upstreams': + 'type': 'array' + 'items': + 'type': 'string' + 'ClientAuto': + 'type': 'object' + 'description': 'Auto-Client information' + 'properties': + 'ip': + 'type': 'string' + 'description': 'IP address' + 'example': '127.0.0.1' + 'name': + 'type': 'string' + 'description': 'Name' + 'example': 'localhost' + 'source': + 'type': 'string' + 'description': 'The source of this information' + 'example': 'etc/hosts' + 'ClientUpdate': + 'type': 'object' + 'description': 'Client update request' + 'properties': + 'name': + 'type': 'string' + 'data': + '$ref': '#/components/schemas/Client' + 'ClientDelete': + 'type': 'object' + 'description': 'Client delete request' + 'properties': + 'name': + 'type': 'string' + 'ClientsFindResponse': + 'type': 'array' + 'description': 'Response to clients find operation' + 'items': + '$ref': '#/components/schemas/ClientsFindEntry' + 'ClientsFindEntry': + 'type': 'object' + 'properties': + '1.2.3.4': + 'items': + '$ref': '#/components/schemas/ClientFindSubEntry' - /apple/dot.mobileconfig: - get: - tags: - - mobileconfig - - global - operationId: mobileConfigDoT - summary: Get TLS over TLS .mobileconfig - responses: - "200": - description: DNS over TLS plist file + 'ClientFindSubEntry': + 'type': 'object' + 'properties': + 'name': + 'type': 'string' + 'description': 'Name' + 'example': 'localhost' + 'ids': + 'type': 'array' + 'description': 'IP, CIDR or MAC address' + 'items': + 'type': 'string' + 'use_global_settings': + 'type': 'boolean' + 'filtering_enabled': + 'type': 'boolean' + 'parental_enabled': + 'type': 'boolean' + 'safebrowsing_enabled': + 'type': 'boolean' + 'safesearch_enabled': + 'type': 'boolean' + 'use_global_blocked_services': + 'type': 'boolean' + 'blocked_services': + 'type': 'array' + 'items': + 'type': 'string' + 'upstreams': + 'type': 'array' + 'items': + 'type': 'string' + 'whois_info': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/WhoisInfo' + 'disallowed': + 'type': 'boolean' + 'description': > + Whether the client's IP is blocked or not. + 'disallowed_rule': + 'type': 'string' + 'description': > + The rule due to which the client is disallowed. If disallowed is + set to true, and this string is empty, then the client IP is + disallowed by the "allowed IP list", that is it is not included in + the allowed list. -components: - requestBodies: - TlsConfig: - content: - application/json: - schema: - $ref: "#/components/schemas/TlsConfig" - description: TLS configuration JSON - required: true - DhcpStaticLease: - content: - application/json: - schema: - $ref: "#/components/schemas/DhcpStaticLease" - required: true - RewriteEntry: - content: - application/json: - schema: - $ref: "#/components/schemas/RewriteEntry" - required: true - schemas: - ServerStatus: - type: object - description: AdGuard Home server status and configuration - required: - - dns_address - - dns_port - - protection_enabled - - querylog_enabled - - running - - bootstrap_dns - - upstream_dns - - version - - language - properties: - dns_address: - type: string - example: 127.0.0.1 - dns_port: - type: integer - format: int32 - example: 53 - minimum: 1 - maximum: 65535 - protection_enabled: - type: boolean - dhcp_available: - type: boolean - querylog_enabled: - type: boolean - running: - type: boolean - version: - type: string - example: "0.1" - language: - type: string - example: en - DNSConfig: - type: object - description: Query log configuration - properties: - bootstrap_dns: - type: array - description: Bootstrap servers, port is optional after colon. Empty value will - reset it to default values - items: - type: string - example: - - 8.8.8.8:53 - - 1.1.1.1:53 - upstream_dns: - type: array - description: Upstream servers, port is optional after colon. Empty value will - reset it to default values - items: - type: string - example: - - tls://1.1.1.1 - - tls://1.0.0.1 - upstream_dns_file: - type: string - protection_enabled: - type: boolean - dhcp_available: - type: boolean - ratelimit: - type: integer - blocking_mode: - type: string - enum: - - default - - refused - - nxdomain - - null_ip - - custom_ip - blocking_ipv4: - type: string - blocking_ipv6: - type: string - edns_cs_enabled: - type: boolean - dnssec_enabled: - type: boolean - cache_size: - type: integer - cache_ttl_min: - type: integer - cache_ttl_max: - type: integer - upstream_mode: - enum: - - "" - - parallel - - fastest_addr - UpstreamsConfig: - type: object - description: Upstreams configuration - required: - - bootstrap_dns - - upstream_dns - properties: - bootstrap_dns: - type: array - description: Bootstrap servers, port is optional after colon. Empty value will - reset it to default values - items: - type: string - example: - - 8.8.8.8:53 - - 1.1.1.1:53 - upstream_dns: - type: array - description: Upstream servers, port is optional after colon. Empty value will - reset it to default values - items: - type: string - example: - - tls://1.1.1.1 - - tls://1.0.0.1 - Filter: - type: object - description: Filter subscription info - required: - - enabled - - id - - lastUpdated - - name - - rulesCount - - url - properties: - enabled: - type: boolean - id: - type: integer - example: 1234 - lastUpdated: - type: string - format: date-time - example: 2018-10-30T12:18:57+03:00 - name: - type: string - example: AdGuard Simplified Domain Names filter - rulesCount: - type: integer - example: 5912 - url: - type: string - example: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt - FilterStatus: - type: object - description: Filtering settings - properties: - enabled: - type: boolean - interval: - type: integer - filters: - type: array - items: - $ref: "#/components/schemas/Filter" - user_rules: - type: array - items: - type: string - FilterConfig: - type: object - description: Filtering settings - properties: - enabled: - type: boolean - interval: - type: integer - FilterSetUrl: - type: object - description: Filtering URL settings - properties: - data: - properties: - enabled: - type: boolean - name: - type: string - url: - type: string - type: object - url: - type: string - whitelist: - type: boolean - FilterRefreshRequest: - type: object - description: Refresh Filters request data - properties: - whitelist: - type: boolean - FilterCheckHostResponse: - type: object - description: Check Host Result - properties: - reason: - type: string - description: DNS filter status - enum: - - NotFilteredNotFound - - NotFilteredWhiteList - - NotFilteredError - - FilteredBlackList - - FilteredSafeBrowsing - - FilteredParental - - FilteredInvalid - - FilteredSafeSearch - - FilteredBlockedService - - ReasonRewrite - filter_id: - type: integer - rule: - type: string - example: "||example.org^" - description: Filtering rule applied to the request (if any) - service_name: - type: string - description: Set if reason=FilteredBlockedService - cname: - type: string - description: Set if reason=ReasonRewrite - ip_addrs: - type: array - items: - type: string - description: Set if reason=ReasonRewrite - FilterRefreshResponse: - type: object - description: /filtering/refresh response data - properties: - updated: - type: integer - GetVersionRequest: - type: object - description: /version.json request data - properties: - recheck_now: - description: If false, server will check for a new version data only once in - several hours - type: boolean - VersionInfo: - type: object - description: Information about the latest available version of AdGuard Home - properties: - new_version: - type: string - example: v0.9 - announcement: - type: string - example: AdGuard Home v0.9 is now available! - announcement_url: - type: string - example: https://github.com/AdguardTeam/AdGuardHome/releases/tag/v0.9 - can_autoupdate: - type: boolean - Stats: - type: object - description: Server statistics data - properties: - time_units: - type: string - description: Time units (hours | days) - example: hours - num_dns_queries: - type: integer - description: Total number of DNS queries - example: 123 - num_blocked_filtering: - type: integer - description: Number of requests blocked by filtering rules - example: 50 - num_replaced_safebrowsing: - type: integer - description: Number of requests blocked by safebrowsing module - example: 5 - num_replaced_safesearch: - type: integer - description: Number of requests blocked by safesearch module - example: 5 - num_replaced_parental: - type: integer - description: Number of blocked adult websites - example: 15 - avg_processing_time: - type: number - format: float - description: Average time in milliseconds on processing a DNS - example: 0.34 - top_queried_domains: - type: array - items: - $ref: "#/components/schemas/TopArrayEntry" - top_clients: - type: array - items: - $ref: "#/components/schemas/TopArrayEntry" - top_blocked_domains: - type: array - items: - $ref: "#/components/schemas/TopArrayEntry" - dns_queries: - type: array - items: - type: integer - blocked_filtering: - type: array - items: - type: integer - replaced_safebrowsing: - type: array - items: - type: integer - replaced_parental: - type: array - items: - type: integer - TopArrayEntry: - type: object - description: Represent the number of hits per key (domain or client IP) - properties: - domain_or_ip: - type: integer - StatsConfig: - type: object - description: Statistics configuration - properties: - interval: - type: integer - description: Time period to keep data (1 | 7 | 30 | 90) - DhcpConfig: - type: object - properties: - enabled: - type: boolean - interface_name: - type: string - v4: - $ref: "#/components/schemas/DhcpConfigV4" - v6: - $ref: "#/components/schemas/DhcpConfigV6" - DhcpConfigV4: - type: object - properties: - gateway_ip: - type: string - example: 192.168.1.1 - subnet_mask: - type: string - example: 255.255.255.0 - range_start: - type: string - example: 192.168.1.2 - range_end: - type: string - example: 192.168.10.50 - lease_duration: - type: integer - DhcpConfigV6: - type: object - properties: - range_start: - type: string - lease_duration: - type: integer - DhcpLease: - type: object - description: DHCP lease information - required: - - mac - - ip - - hostname - - expires - properties: - mac: - type: string - example: 00:11:09:b3:b3:b8 - ip: - type: string - example: 192.168.1.22 - hostname: - type: string - example: dell - expires: - type: string - example: "2017-07-21T17:32:28Z" - DhcpStaticLease: - type: object - description: DHCP static lease information - required: - - mac - - ip - - hostname - - expires - properties: - mac: - type: string - example: 00:11:09:b3:b3:b8 - ip: - type: string - example: 192.168.1.22 - hostname: - type: string - example: dell - DhcpStatus: - type: object - description: Built-in DHCP server configuration and status - required: - - config - - leases - properties: - enabled: - type: boolean - interface_name: - type: string - v4: - $ref: "#/components/schemas/DhcpConfigV4" - v6: - $ref: "#/components/schemas/DhcpConfigV6" - leases: - type: array - items: - $ref: "#/components/schemas/DhcpLease" - static_leases: - type: array - items: - $ref: "#/components/schemas/DhcpStaticLease" + 'WhoisInfo': + 'type': 'object' + 'properties': + 'key': + 'type': 'string' - DhcpSearchResult: - type: object - description: Information about a DHCP server discovered in the current network - properties: - v4: - $ref: "#/components/schemas/DhcpSearchV4" - v6: - $ref: "#/components/schemas/DhcpSearchV6" - - DhcpSearchV4: - type: object - properties: - other_server: - $ref: "#/components/schemas/DhcpSearchResultOtherServer" - static_ip: - $ref: "#/components/schemas/DhcpSearchResultStaticIP" - - DhcpSearchV6: - type: object - properties: - other_server: - $ref: "#/components/schemas/DhcpSearchResultOtherServer" - - DhcpSearchResultOtherServer: - type: object - properties: - found: - type: string - description: yes|no|error - example: no - error: - type: string - description: Set if found=error - example: "" - - DhcpSearchResultStaticIP: - type: object - properties: - static: - type: string - description: yes|no|error - example: yes - ip: - type: string - description: Set if static=no - example: "" - - DnsAnswer: - type: object - description: DNS answer section - properties: - ttl: - type: integer - example: 55 - type: - type: string - example: A - value: - type: string - example: 217.69.139.201 - DnsQuestion: - type: object - description: DNS question section - properties: - class: - type: string - example: IN - host: - type: string - example: example.org - type: - type: string - example: A - AddUrlRequest: - type: object - description: /add_url request data - properties: - name: - type: string - url: - description: URL or an absolute path to the file containing filtering rules - type: string - example: https://filters.adtidy.org/windows/filters/15.txt - whitelist: - type: boolean - RemoveUrlRequest: - type: object - description: /remove_url request data - properties: - url: - description: Previously added URL containing filtering rules - type: string - example: https://filters.adtidy.org/windows/filters/15.txt - QueryLogItem: - type: object - description: Query log item - properties: - answer: - type: array - items: - $ref: "#/components/schemas/DnsAnswer" - original_answer: - type: array - description: Answer from upstream server (optional) - items: - $ref: "#/components/schemas/DnsAnswer" - upstream: - type: string - description: Upstream URL starting with tcp://, tls://, https://, or with an IP address - answer_dnssec: - type: boolean - client: - type: string - example: 192.168.0.1 - client_proto: - enum: - - dot - - doh - - doq - - "" - elapsedMs: - type: string - example: "54.023928" - question: - $ref: "#/components/schemas/DnsQuestion" - filterId: - type: integer - example: 123123 - description: In case if there's a rule applied to this DNS request, this is ID of - the filter that rule belongs to. - rule: - type: string - example: "||example.org^" - description: Filtering rule applied to the request (if any) - reason: - type: string - description: DNS filter status - enum: - - NotFilteredNotFound - - NotFilteredWhiteList - - NotFilteredError - - FilteredBlackList - - FilteredSafeBrowsing - - FilteredParental - - FilteredInvalid - - FilteredSafeSearch - - FilteredBlockedService - - ReasonRewrite - service_name: - type: string - description: Set if reason=FilteredBlockedService - status: - type: string - description: DNS response status - example: NOERROR - time: - type: string - description: DNS request processing start time - example: 2018-11-26T00:02:41+03:00 - QueryLog: - type: object - description: Query log - properties: - oldest: - type: string - example: 2018-11-26T00:02:41+03:00 - data: - type: array - items: - $ref: "#/components/schemas/QueryLogItem" - QueryLogConfig: - type: object - description: Query log configuration - properties: - enabled: - type: boolean - description: Is query log enabled - interval: - type: integer - description: Time period to keep data (1 | 7 | 30 | 90) - anonymize_client_ip: - type: boolean - description: Anonymize clients' IP addresses - TlsConfig: - type: object - description: TLS configuration settings and status - properties: - enabled: - type: boolean - example: "true" - description: enabled is the encryption (DOT/DOH/HTTPS) status - server_name: - type: string - example: example.org - description: server_name is the hostname of your HTTPS/TLS server - force_https: - type: boolean - example: "true" - description: if true, forces HTTP->HTTPS redirect - port_https: - type: integer - format: int32 - example: 443 - description: HTTPS port. If 0, HTTPS will be disabled. - port_dns_over_tls: - type: integer - format: int32 - example: 853 - description: DNS-over-TLS port. If 0, DOT will be disabled. - port_dns_over_quic: - type: integer - format: int32 - example: 784 - description: DNS-over-QUIC port. If 0, DOQ will be disabled. - certificate_chain: - type: string - description: Base64 string with PEM-encoded certificates chain - private_key: - type: string - description: Base64 string with PEM-encoded private key - certificate_path: - type: string - description: Path to certificate file - private_key_path: - type: string - description: Path to private key file - valid_cert: - type: boolean - example: "true" - description: valid_cert is true if the specified certificates chain is a valid - chain of X509 certificates - valid_chain: - type: boolean - example: "true" - description: valid_chain is true if the specified certificates chain is verified - and issued by a known CA - subject: - type: string - example: CN=example.org - description: subject is the subject of the first certificate in the chain - issuer: - type: string - example: CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US - description: issuer is the issuer of the first certificate in the chain - not_before: - type: string - example: 2019-01-31T10:47:32Z - description: not_before is the NotBefore field of the first certificate in the - chain - not_after: - type: string - example: 2019-05-01T10:47:32Z - description: not_after is the NotAfter field of the first certificate in the chain - dns_names: - type: array - items: - type: string - description: dns_names is the value of SubjectAltNames field of the first - certificate in the chain - example: - - "*.example.org" - valid_key: - type: boolean - example: "true" - description: valid_key is true if the key is a valid private key - key_type: - type: string - example: RSA - description: key_type is either RSA or ECDSA - warning_validation: - type: string - example: You have specified an empty certificate - description: warning_validation is a validation warning message with the issue - description - valid_pair: - type: boolean - example: "true" - description: valid_pair is true if both certificate and private key are correct - NetInterface: - type: object - description: Network interface info - properties: - flags: - type: string - example: up|broadcast|multicast - hardware_address: - type: string - example: 52:54:00:11:09:ba - name: - type: string - example: eth0 - ipv4_addresses: - type: array - items: - type: string - ipv6_addresses: - type: array - items: - type: string - gateway_ip: - type: string - AddressInfo: - type: object - description: Port information - properties: - ip: - type: string - example: 127.0.0.1 - port: - type: integer - format: int32 - example: 53 - AddressesInfo: - type: object - description: AdGuard Home addresses configuration - properties: - dns_port: - type: integer - format: int32 - example: 53 - web_port: - type: integer - format: int32 - example: 80 - interfaces: - type: object - description: Network interfaces dictionary (key is the interface name) - additionalProperties: - $ref: "#/components/schemas/NetInterface" - ProfileInfo: - type: object - description: Information about the current user - properties: - name: - type: string - Client: - type: object - description: Client information - properties: - name: - type: string - description: Name - example: localhost - ids: - type: array - description: IP, CIDR or MAC address - items: - type: string - use_global_settings: - type: boolean - filtering_enabled: - type: boolean - parental_enabled: - type: boolean - safebrowsing_enabled: - type: boolean - safesearch_enabled: - type: boolean - use_global_blocked_services: - type: boolean - blocked_services: - type: array - items: - type: string - upstreams: - type: array - items: - type: string - ClientAuto: - type: object - description: Auto-Client information - properties: - ip: - type: string - description: IP address - example: 127.0.0.1 - name: - type: string - description: Name - example: localhost - source: - type: string - description: The source of this information - example: etc/hosts - ClientUpdate: - type: object - description: Client update request - properties: - name: - type: string - data: - $ref: "#/components/schemas/Client" - ClientDelete: - type: object - description: Client delete request - properties: - name: - type: string - ClientsFindResponse: - type: array - description: Response to clients find operation - items: - $ref: "#/components/schemas/ClientsFindEntry" - ClientsFindEntry: - type: object - properties: - 1.2.3.4: - items: - $ref: "#/components/schemas/ClientFindSubEntry" - - ClientFindSubEntry: - type: object - properties: - name: - type: string - description: Name - example: localhost - ids: - type: array - description: IP, CIDR or MAC address - items: - type: string - use_global_settings: - type: boolean - filtering_enabled: - type: boolean - parental_enabled: - type: boolean - safebrowsing_enabled: - type: boolean - safesearch_enabled: - type: boolean - use_global_blocked_services: - type: boolean - blocked_services: - type: array - items: - type: string - upstreams: - type: array - items: - type: string - whois_info: - type: array - items: - $ref: "#/components/schemas/WhoisInfo" - disallowed: - type: boolean - description: > - Whether the client's IP is blocked or not. - disallowed_rule: - type: string - description: > - The rule due to which the client is disallowed. - If `disallowed` is `true`, and this string is empty - it means that - the client IP is disallowed by the "allowed IP list", i.e. it is not included in allowed list. - - WhoisInfo: - type: object - properties: - key: - type: string - - Clients: - type: object - properties: - clients: - $ref: "#/components/schemas/ClientsArray" - auto_clients: - $ref: "#/components/schemas/ClientsAutoArray" - ClientsArray: - type: array - items: - $ref: "#/components/schemas/Client" - description: Clients array - ClientsAutoArray: - type: array - items: - $ref: "#/components/schemas/ClientAuto" - description: Auto-Clients array - RewriteList: - type: array - items: - $ref: "#/components/schemas/RewriteEntry" - description: Rewrite rules array - RewriteEntry: - type: object - description: Rewrite rule - properties: - domain: - type: string - description: Domain name - example: example.org - answer: - type: string - description: value of A, AAAA or CNAME DNS record - example: 127.0.0.1 - BlockedServicesArray: - type: array - items: - type: string - CheckConfigRequest: - type: object - description: Configuration to be checked - properties: - dns: - $ref: "#/components/schemas/CheckConfigRequestInfo" - web: - $ref: "#/components/schemas/CheckConfigRequestInfo" - set_static_ip: - type: boolean - example: false - CheckConfigRequestInfo: - type: object - properties: - ip: - type: string - example: 127.0.0.1 - port: - type: integer - format: int32 - example: 53 - autofix: - type: boolean - example: false - CheckConfigResponse: - type: object - properties: - dns: - $ref: "#/components/schemas/CheckConfigResponseInfo" - web: - $ref: "#/components/schemas/CheckConfigResponseInfo" - static_ip: - $ref: "#/components/schemas/CheckConfigStaticIpInfo" - CheckConfigResponseInfo: - type: object - properties: - status: - type: string - example: "" - can_autofix: - type: boolean - example: false - CheckConfigStaticIpInfo: - type: object - properties: - static: - type: string - example: no - description: "Can be: yes, no, error" - ip: - type: string - example: 192.168.1.1 - description: Current dynamic IP address. Set if static=no - error: - type: string - example: "" - description: Error text. Set if static=error - InitialConfiguration: - type: object - description: AdGuard Home initial configuration (for the first-install wizard) - properties: - dns: - $ref: "#/components/schemas/AddressInfo" - web: - $ref: "#/components/schemas/AddressInfo" - username: - type: string - description: Basic auth username - example: admin - password: - type: string - description: Basic auth password - example: password - Login: - type: object - description: Login request data - properties: - username: - type: string - description: User name - password: - type: string - description: Password - Error: - description: A generic JSON error response. - properties: - message: - type: string - description: The error message, an opaque string. - type: object + 'Clients': + 'type': 'object' + 'properties': + 'clients': + '$ref': '#/components/schemas/ClientsArray' + 'auto_clients': + '$ref': '#/components/schemas/ClientsAutoArray' + 'ClientsArray': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/Client' + 'description': 'Clients array' + 'ClientsAutoArray': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/ClientAuto' + 'description': 'Auto-Clients array' + 'RewriteList': + 'type': 'array' + 'items': + '$ref': '#/components/schemas/RewriteEntry' + 'description': 'Rewrite rules array' + 'RewriteEntry': + 'type': 'object' + 'description': 'Rewrite rule' + 'properties': + 'domain': + 'type': 'string' + 'description': 'Domain name' + 'example': 'example.org' + 'answer': + 'type': 'string' + 'description': 'value of A, AAAA or CNAME DNS record' + 'example': '127.0.0.1' + 'BlockedServicesArray': + 'type': 'array' + 'items': + 'type': 'string' + 'CheckConfigRequest': + 'type': 'object' + 'description': 'Configuration to be checked' + 'properties': + 'dns': + '$ref': '#/components/schemas/CheckConfigRequestInfo' + 'web': + '$ref': '#/components/schemas/CheckConfigRequestInfo' + 'set_static_ip': + 'type': 'boolean' + 'example': false + 'CheckConfigRequestInfo': + 'type': 'object' + 'properties': + 'ip': + 'type': 'string' + 'example': '127.0.0.1' + 'port': + 'type': 'integer' + 'format': 'int32' + 'example': 53 + 'autofix': + 'type': 'boolean' + 'example': false + 'CheckConfigResponse': + 'type': 'object' + 'properties': + 'dns': + '$ref': '#/components/schemas/CheckConfigResponseInfo' + 'web': + '$ref': '#/components/schemas/CheckConfigResponseInfo' + 'static_ip': + '$ref': '#/components/schemas/CheckConfigStaticIpInfo' + 'CheckConfigResponseInfo': + 'type': 'object' + 'properties': + 'status': + 'type': 'string' + 'example': '' + 'can_autofix': + 'type': 'boolean' + 'example': false + 'CheckConfigStaticIpInfo': + 'type': 'object' + 'properties': + 'static': + 'type': 'string' + 'example': 'no' + 'description': 'Can be: yes, no, error' + 'ip': + 'type': 'string' + 'example': '192.168.1.1' + 'description': 'Current dynamic IP address. Set if static=no' + 'error': + 'type': 'string' + 'example': '' + 'description': 'Error text. Set if static=error' + 'InitialConfiguration': + 'type': 'object' + 'description': > + AdGuard Home initial configuration for the first-install wizard. + 'properties': + 'dns': + '$ref': '#/components/schemas/AddressInfo' + 'web': + '$ref': '#/components/schemas/AddressInfo' + 'username': + 'type': 'string' + 'description': 'Basic auth username' + 'example': 'admin' + 'password': + 'type': 'string' + 'description': 'Basic auth password' + 'example': 'password' + 'Login': + 'type': 'object' + 'description': 'Login request data' + 'properties': + 'username': + 'type': 'string' + 'description': 'User name' + 'password': + 'type': 'string' + 'description': 'Password' + 'Error': + 'description': 'A generic JSON error response.' + 'properties': + 'message': + 'description': 'The error message, an opaque string.' + 'type': 'string' + 'type': 'object' From c129361e55aea50c3454feca90025937261214ee Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 23 Nov 2020 14:14:08 +0300 Subject: [PATCH 10/54] Pull request: 2305 limit message size Merge in DNS/adguard-home from 2305-limit-message-size to master Closes #2305. Squashed commit of the following: commit 6edd1e0521277a680f0053308efcf3d9cacc8e62 Author: Eugene Burkov Date: Mon Nov 23 14:03:36 2020 +0300 aghio: fix final inaccuracies commit 4dd382aaf25132b31eb269749a2cd36daf0cb792 Author: Eugene Burkov Date: Mon Nov 23 13:59:10 2020 +0300 all: improve code quality commit 060f923f6023d0e6f26441559b7023d5e5f96843 Author: Eugene Burkov Date: Mon Nov 23 13:10:57 2020 +0300 aghio: add validation to constructor commit f57a2f596f5dc578548241c315c68dce7fc93905 Author: Eugene Burkov Date: Fri Nov 20 19:19:26 2020 +0300 all: fix minor inaccuracies commit 93462c71725d3d00655a4bd565b77e64451fff60 Author: Eugene Burkov Date: Fri Nov 20 19:13:23 2020 +0300 home: make test name follow convention commit 4922986ad84481b054479c43b4133a1b97bee86b Merge: 1f5472abc 046ec13fd Author: Eugene Burkov Date: Fri Nov 20 19:09:01 2020 +0300 Merge branch 'master' into 2305-limit-message-size commit 1f5472abcfa7427f389825fc59eb4253e1e2bfb7 Author: Eugene Burkov Date: Fri Nov 20 19:08:21 2020 +0300 aghio: improve readability commit 60dc706b093fa22bbf62f13b2341934364ddc4df Author: Eugene Burkov Date: Fri Nov 20 18:44:08 2020 +0300 home: cover middleware with test commit bedf436b947ca1fa4493af2fc94f1f40beec7c35 Author: Eugene Burkov Date: Fri Nov 20 17:10:23 2020 +0300 aghio: improved error informativeness commit 682c5da9f21fa330fb3536bb1c112129c91b9990 Author: Eugene Burkov Date: Fri Nov 20 13:37:51 2020 +0300 all: limit readers for ReadAll dealing with miscellanious data. commit 78c6dd8d90a0a43fe6ee3f9ed4d5fc637b15ba74 Author: Eugene Burkov Date: Thu Nov 19 20:07:43 2020 +0300 all: handle ReadAll calls dealing with request's bodies. commit bfe1a6faf6468eb44515e2b0ecffa8c51f90b7e8 Author: Eugene Burkov Date: Thu Nov 19 17:25:34 2020 +0300 home: add middlewares commit bbd1d491b318e6ba07f8af23ad546183383783a8 Merge: 7b77c2cad 62a8fe0b7 Author: Eugene Burkov Date: Thu Nov 19 16:44:04 2020 +0300 Merge branch 'master' into 2305-limit-message-size commit 7b77c2cad03154177392460982e1d73ee2a30177 Author: Eugene Burkov Date: Tue Nov 17 15:33:33 2020 +0300 aghio: create package --- CHANGELOG.md | 13 +++ HACKING.md | 46 +++++++--- internal/aghio/limitedreadcloser.go | 59 +++++++++++++ internal/aghio/limitedreadcloser_test.go | 108 +++++++++++++++++++++++ internal/dhcpd/dhcphttp.go | 1 + internal/home/auth_glinet.go | 23 ++++- internal/home/clients_http.go | 37 +++----- internal/home/control_filtering.go | 1 + internal/home/i18n.go | 1 + internal/home/middlewares.go | 59 +++++++++++++ internal/home/middlewares_test.go | 64 ++++++++++++++ internal/home/web.go | 19 +--- internal/home/whois.go | 13 ++- internal/update/check.go | 18 +++- internal/update/updater.go | 15 +++- 15 files changed, 413 insertions(+), 64 deletions(-) create mode 100644 internal/aghio/limitedreadcloser.go create mode 100644 internal/aghio/limitedreadcloser_test.go create mode 100644 internal/home/middlewares.go create mode 100644 internal/home/middlewares_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index eb01e003..bb2f1be7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ and this project adheres to ## [Unreleased] +### Added + +- HTTP API request body size limit [#2305]. + +[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 + +### Changed + +- Various internal improvements ([#2271], [#2297]). + +[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 +[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 + ## [v0.104.3] - 2020-11-19 diff --git a/HACKING.md b/HACKING.md index fbd56cc4..bbd4dfbd 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,4 +1,4 @@ - # AdGuardHome Developer Guidelines + # *AdGuardHome* Developer Guidelines As of **2020-11-20**, this document is still a work-in-progress. Some of the rules aren't enforced, and others might change. Still, this is a good place to @@ -6,7 +6,11 @@ find out about how we **want** our code to look like. The rules are mostly sorted in the alphabetical order. -## Git +## *Git* + + * Call your branches either `NNNN-fix-foo` (where `NNNN` is the ID of the + *GitHub* issue you worked on in this branch) or just `fix-foo` if there was + no *GitHub* issue. * Follow the commit message header format: @@ -22,9 +26,10 @@ The rules are mostly sorted in the alphabetical order. * Only use lowercase letters in your commit message headers. The rest of the message should follow the plain text conventions below. - The only exception are direct mentions of identifiers from the source code. + The only exceptions are direct mentions of identifiers from the source code + and filenames like `HACKING.md`. -## Go +## *Go* * . @@ -32,6 +37,9 @@ The rules are mostly sorted in the alphabetical order. * + * Add an empty line before `break`, `continue`, and `return`, unless it's the + only statement in that block. + * Avoid `init` and use explicit initialization functions instead. * Avoid `new`, especially with structs. @@ -53,6 +61,18 @@ The rules are mostly sorted in the alphabetical order. * Eschew external dependencies, including transitive, unless absolutely necessary. + * Name benchmarks and tests using the same convention as examples. For + example: + + ```go + func TestFunction(t *testing.T) { /* … */ } + func TestFunction_suffix(t *testing.T) { /* … */ } + func TestType_Method(t *testing.T) { /* … */ } + func TestType_Method_suffix(t *testing.T) { /* … */ } + ``` + + * Name the deferred errors (e.g. when closing something) `cerr`. + * No `goto`. * No shadowing, since it can often lead to subtle bugs, especially with @@ -103,9 +123,9 @@ The rules are mostly sorted in the alphabetical order. [constant errors]: https://dave.cheney.net/2016/04/07/constant-errors [Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation -## Markdown +## *Markdown* - * **TODO(a.garipov):** Define our Markdown conventions. + * **TODO(a.garipov):** Define our *Markdown* conventions. ## Text, Including Comments @@ -128,7 +148,7 @@ The rules are mostly sorted in the alphabetical order. * Use double spacing between sentences to make sentence borders more clear. - * Use the serial comma (a.k.a. Oxford comma) to improve comprehension, + * Use the serial comma (a.k.a. *Oxford* comma) to improve comprehension, decrease ambiguity, and use a common standard. * Write todos like this: @@ -143,16 +163,16 @@ The rules are mostly sorted in the alphabetical order. // TODO(usr1, usr2): Fix the frobulation issue. ``` -## YAML +## *YAML* * **TODO(a.garipov):** Define naming conventions for schema names in our - OpenAPI YAML file. And just generally OpenAPI conventions. + *OpenAPI* *YAML* file. And just generally OpenAPI conventions. - * **TODO(a.garipov):** Find a YAML formatter or write our own. + * **TODO(a.garipov):** Find a *YAML* formatter or write our own. - * All strings, including keys, must be quoted. Reason: the [NO-rway Law]. + * All strings, including keys, must be quoted. Reason: the [*NO-rway Law*]. - * Indent with two (**2**) spaces. YAML documents can get pretty + * Indent with two (**2**) spaces. *YAML* documents can get pretty deeply-nested. * No extra indentation in multiline arrays: @@ -170,4 +190,4 @@ The rules are mostly sorted in the alphabetical order. * Use `>` for multiline strings, unless you need to keep the line breaks. -[NO-rway Law]: https://news.ycombinator.com/item?id=17359376 +[*NO-rway Law*]: https://news.ycombinator.com/item?id=17359376 diff --git a/internal/aghio/limitedreadcloser.go b/internal/aghio/limitedreadcloser.go new file mode 100644 index 00000000..7690705a --- /dev/null +++ b/internal/aghio/limitedreadcloser.go @@ -0,0 +1,59 @@ +// Package aghio contains extensions for io package's types and methods +package aghio + +import ( + "fmt" + "io" +) + +// LimitReachedError records the limit and the operation that caused it. +type LimitReachedError struct { + Limit int64 +} + +// Error implements error interface for LimitReachedError. +// TODO(a.garipov): Think about error string format. +func (lre *LimitReachedError) Error() string { + return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit) +} + +// limitedReadCloser is a wrapper for io.ReadCloser with limited reader and +// dealing with agherr package. +type limitedReadCloser struct { + limit int64 + n int64 + rc io.ReadCloser +} + +// Read implements Reader interface. +func (lrc *limitedReadCloser) Read(p []byte) (n int, err error) { + if lrc.n == 0 { + return 0, &LimitReachedError{ + Limit: lrc.limit, + } + } + if int64(len(p)) > lrc.n { + p = p[0:lrc.n] + } + n, err = lrc.rc.Read(p) + lrc.n -= int64(n) + return n, err +} + +// Close implements Closer interface. +func (lrc *limitedReadCloser) Close() error { + return lrc.rc.Close() +} + +// LimitReadCloser wraps ReadCloser to make it's Reader stop with +// ErrLimitReached after n bytes read. +func LimitReadCloser(rc io.ReadCloser, n int64) (limited io.ReadCloser, err error) { + if n < 0 { + return nil, fmt.Errorf("aghio: invalid n in LimitReadCloser: %d", n) + } + return &limitedReadCloser{ + limit: n, + n: n, + rc: rc, + }, nil +} diff --git a/internal/aghio/limitedreadcloser_test.go b/internal/aghio/limitedreadcloser_test.go new file mode 100644 index 00000000..1f10e32b --- /dev/null +++ b/internal/aghio/limitedreadcloser_test.go @@ -0,0 +1,108 @@ +package aghio + +import ( + "fmt" + "io" + "io/ioutil" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLimitReadCloser(t *testing.T) { + testCases := []struct { + name string + n int64 + want error + }{{ + name: "positive", + n: 1, + want: nil, + }, { + name: "zero", + n: 0, + want: nil, + }, { + name: "negative", + n: -1, + want: fmt.Errorf("aghio: invalid n in LimitReadCloser: -1"), + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, err := LimitReadCloser(nil, tc.n) + assert.Equal(t, tc.want, err) + }) + } +} + +func TestLimitedReadCloser_Read(t *testing.T) { + testCases := []struct { + name string + limit int64 + rStr string + want int + err error + }{{ + name: "perfectly_match", + limit: 3, + rStr: "abc", + want: 3, + err: nil, + }, { + name: "eof", + limit: 3, + rStr: "", + want: 0, + err: io.EOF, + }, { + name: "limit_reached", + limit: 0, + rStr: "abc", + want: 0, + err: &LimitReachedError{ + Limit: 0, + }, + }, { + name: "truncated", + limit: 2, + rStr: "abc", + want: 2, + err: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + readCloser := ioutil.NopCloser(strings.NewReader(tc.rStr)) + buf := make([]byte, tc.limit+1) + + lreader, err := LimitReadCloser(readCloser, tc.limit) + assert.Nil(t, err) + + n, err := lreader.Read(buf) + assert.Equal(t, n, tc.want) + assert.Equal(t, tc.err, err) + }) + } +} + +func TestLimitedReadCloser_LimitReachedError(t *testing.T) { + testCases := []struct { + name string + want string + err error + }{{ + name: "simplest", + want: "attempted to read more than 0 bytes", + err: &LimitReachedError{ + Limit: 0, + }, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, tc.err.Error()) + }) + } +} diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index f4ce801b..1cacd83c 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -299,6 +299,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { // . Check if a static IP is configured for the network interface // Respond with results func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { + // This use of ReadAll is safe, because request's body is now limited. body, err := ioutil.ReadAll(r.Body) if err != nil { msg := fmt.Sprintf("failed to read request body: %s", err) diff --git a/internal/home/auth_glinet.go b/internal/home/auth_glinet.go index 7dd2790d..228843fa 100644 --- a/internal/home/auth_glinet.go +++ b/internal/home/auth_glinet.go @@ -10,6 +10,7 @@ import ( "time" "unsafe" + "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/golibs/log" ) @@ -18,8 +19,10 @@ var GLMode bool var glFilePrefix = "/tmp/gl_token_" -const glTokenTimeoutSeconds = 3600 -const glCookieName = "Admin-Token" +const ( + glTokenTimeoutSeconds = 3600 + glCookieName = "Admin-Token" +) func glProcessRedirect(w http.ResponseWriter, r *http.Request) bool { if !GLMode { @@ -71,14 +74,28 @@ func archIsLittleEndian() bool { return (b == 0x04) } +// MaxFileSize is a maximum file length in bytes. +const MaxFileSize = 1024 * 1024 + func glGetTokenDate(file string) uint32 { f, err := os.Open(file) if err != nil { log.Error("os.Open: %s", err) return 0 } + defer f.Close() + + fileReadCloser, err := aghio.LimitReadCloser(f, MaxFileSize) + if err != nil { + log.Error("LimitReadCloser: %s", err) + return 0 + } + defer fileReadCloser.Close() + var dateToken uint32 - bs, err := ioutil.ReadAll(f) + + // This use of ReadAll is now safe, because we limited reader. + bs, err := ioutil.ReadAll(fileReadCloser) if err != nil { log.Error("ioutil.ReadAll: %s", err) return 0 diff --git a/internal/home/clients_http.go b/internal/home/clients_http.go index 42fa6f2a..752b3c6f 100644 --- a/internal/home/clients_http.go +++ b/internal/home/clients_http.go @@ -3,7 +3,6 @@ package home import ( "encoding/json" "fmt" - "io/ioutil" "net/http" ) @@ -150,16 +149,11 @@ func clientHostToJSON(ip string, ch ClientHost) clientJSON { // Add a new client func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - httpError(w, http.StatusBadRequest, "failed to read request body: %s", err) - return - } - cj := clientJSON{} - err = json.Unmarshal(body, &cj) + err := json.NewDecoder(r.Body).Decode(&cj) if err != nil { - httpError(w, http.StatusBadRequest, "JSON parse: %s", err) + httpError(w, http.StatusBadRequest, "failed to process request body: %s", err) + return } @@ -183,16 +177,17 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http. // Remove client func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + cj := clientJSON{} + err := json.NewDecoder(r.Body).Decode(&cj) if err != nil { - httpError(w, http.StatusBadRequest, "failed to read request body: %s", err) + httpError(w, http.StatusBadRequest, "failed to process request body: %s", err) + return } - cj := clientJSON{} - err = json.Unmarshal(body, &cj) - if err != nil || len(cj.Name) == 0 { - httpError(w, http.StatusBadRequest, "JSON parse: %s", err) + if len(cj.Name) == 0 { + httpError(w, http.StatusBadRequest, "client's name must be non-empty") + return } @@ -211,18 +206,14 @@ type updateJSON struct { // Update client's properties func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) + dj := updateJSON{} + err := json.NewDecoder(r.Body).Decode(&dj) if err != nil { - httpError(w, http.StatusBadRequest, "failed to read request body: %s", err) + httpError(w, http.StatusBadRequest, "failed to process request body: %s", err) + return } - var dj updateJSON - err = json.Unmarshal(body, &dj) - if err != nil { - httpError(w, http.StatusBadRequest, "JSON parse: %s", err) - return - } if len(dj.Name) == 0 { httpError(w, http.StatusBadRequest, "Invalid request") return diff --git a/internal/home/control_filtering.go b/internal/home/control_filtering.go index 37f9af81..1794cce7 100644 --- a/internal/home/control_filtering.go +++ b/internal/home/control_filtering.go @@ -214,6 +214,7 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request } func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) { + // This use of ReadAll is safe, because request's body is now limited. body, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err) diff --git a/internal/home/i18n.go b/internal/home/i18n.go index 6ddfe549..adbc95aa 100644 --- a/internal/home/i18n.go +++ b/internal/home/i18n.go @@ -66,6 +66,7 @@ func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) { } func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) { + // This use of ReadAll is safe, because request's body is now limited. body, err := ioutil.ReadAll(r.Body) if err != nil { msg := fmt.Sprintf("failed to read request body: %s", err) diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go new file mode 100644 index 00000000..4a38160d --- /dev/null +++ b/internal/home/middlewares.go @@ -0,0 +1,59 @@ +package home + +import ( + "net/http" + "strings" + + "github.com/AdguardTeam/AdGuardHome/internal/aghio" + + "github.com/AdguardTeam/golibs/log" +) + +// middlerware is a wrapper function signature. +type middleware func(http.Handler) http.Handler + +// withMiddlewares consequently wraps h with all the middlewares. +func withMiddlewares(h http.Handler, middlewares ...middleware) (wrapped http.Handler) { + wrapped = h + + for _, mw := range middlewares { + wrapped = mw(wrapped) + } + + return wrapped +} + +// RequestBodySizeLimit is maximum request body length in bytes. +const RequestBodySizeLimit = 64 * 1024 + +// limitRequestBody wraps underlying handler h, making it's request's body Read +// method limited. +func limitRequestBody(h http.Handler) (limited http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var err error + r.Body, err = aghio.LimitReadCloser(r.Body, RequestBodySizeLimit) + if err != nil { + log.Error("limitRequestBody: %s", err) + + return + } + + h.ServeHTTP(w, r) + }) +} + +// TODO(a.garipov): We currently have to use this, because everything registers +// its HTTP handlers in http.DefaultServeMux. In the future, refactor our HTTP +// API initialization process and stop using the gosh darn http.DefaultServeMux +// for anything at all. Gosh darn global variables. +func filterPProf(h http.Handler) (filtered http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/debug/pprof") { + http.NotFound(w, r) + + return + } + + h.ServeHTTP(w, r) + }) +} diff --git a/internal/home/middlewares_test.go b/internal/home/middlewares_test.go new file mode 100644 index 00000000..4d6a33d0 --- /dev/null +++ b/internal/home/middlewares_test.go @@ -0,0 +1,64 @@ +package home + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/aghio" + "github.com/stretchr/testify/assert" +) + +func TestLimitRequestBody(t *testing.T) { + errReqLimitReached := &aghio.LimitReachedError{ + Limit: RequestBodySizeLimit, + } + + testCases := []struct { + name string + body string + want []byte + wantErr error + }{{ + name: "not_so_big", + body: "somestr", + want: []byte("somestr"), + wantErr: nil, + }, { + name: "so_big", + body: string(make([]byte, RequestBodySizeLimit+1)), + want: make([]byte, RequestBodySizeLimit), + wantErr: errReqLimitReached, + }, { + name: "empty", + body: "", + want: []byte(nil), + wantErr: nil, + }} + + makeHandler := func(err *error) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var b []byte + b, *err = ioutil.ReadAll(r.Body) + w.Write(b) + }) + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var err error + handler := makeHandler(&err) + lim := limitRequestBody(handler) + + req := httptest.NewRequest(http.MethodPost, "https://www.example.com", strings.NewReader(tc.body)) + res := httptest.NewRecorder() + + lim.ServeHTTP(res, req) + + assert.Equal(t, tc.want, res.Body.Bytes()) + assert.Equal(t, tc.wantErr, err) + }) + } +} diff --git a/internal/home/web.go b/internal/home/web.go index f8ceb296..0d6a1628 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "strconv" - "strings" "sync" "github.com/AdguardTeam/AdGuardHome/internal/util" @@ -142,7 +141,7 @@ func (web *Web) Start() { web.httpServer = &http.Server{ ErrorLog: web.errLogger, Addr: address, - Handler: filterPPROF(http.DefaultServeMux), + Handler: withMiddlewares(http.DefaultServeMux, filterPProf, limitRequestBody), } err := web.httpServer.ListenAndServe() if err != http.ErrServerClosed { @@ -153,22 +152,6 @@ func (web *Web) Start() { } } -// TODO(a.garipov): We currently have to use this, because everything registers -// its HTTP handlers in http.DefaultServeMux. In the future, refactor our HTTP -// API initialization process and stop using the gosh darn http.DefaultServeMux -// for anything at all. Gosh darn global variables. -func filterPPROF(h http.Handler) (filtered http.Handler) { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/debug/pprof") { - http.NotFound(w, r) - - return - } - - h.ServeHTTP(w, r) - }) -} - // Close - stop HTTP server, possibly waiting for all active connections to be closed func (web *Web) Close() { log.Info("Stopping HTTP server...") diff --git a/internal/home/whois.go b/internal/home/whois.go index 1fcff3dc..4884d776 100644 --- a/internal/home/whois.go +++ b/internal/home/whois.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/cache" @@ -115,6 +116,9 @@ func whoisParse(data string) map[string]string { return m } +// MaxConnReadSize is an upper limit in bytes for reading from net.Conn. +const MaxConnReadSize = 64 * 1024 + // Send request to a server and receive the response func (w *Whois) query(target, serverAddr string) (string, error) { addr, _, _ := net.SplitHostPort(serverAddr) @@ -127,13 +131,20 @@ func (w *Whois) query(target, serverAddr string) (string, error) { } defer conn.Close() + connReadCloser, err := aghio.LimitReadCloser(conn, MaxConnReadSize) + if err != nil { + return "", err + } + defer connReadCloser.Close() + _ = conn.SetReadDeadline(time.Now().Add(time.Duration(w.timeoutMsec) * time.Millisecond)) _, err = conn.Write([]byte(target + "\r\n")) if err != nil { return "", err } - data, err := ioutil.ReadAll(conn) + // This use of ReadAll is now safe, because we limited the conn Reader. + data, err := ioutil.ReadAll(connReadCloser) if err != nil { return "", err } diff --git a/internal/update/check.go b/internal/update/check.go index b10cec73..e83ab5c2 100644 --- a/internal/update/check.go +++ b/internal/update/check.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "strings" "time" + + "github.com/AdguardTeam/AdGuardHome/internal/aghio" ) const versionCheckPeriod = 8 * 60 * 60 @@ -19,6 +21,9 @@ type VersionInfo struct { CanAutoUpdate bool // If true - we can auto-update } +// MaxResponseSize is responses on server's requests maximum length in bytes. +const MaxResponseSize = 64 * 1024 + // GetVersionResponse - downloads version.json (if needed) and deserializes it func (u *Updater) GetVersionResponse(forceRecheck bool) (VersionInfo, error) { if !forceRecheck && @@ -27,14 +32,19 @@ func (u *Updater) GetVersionResponse(forceRecheck bool) (VersionInfo, error) { } resp, err := u.Client.Get(u.VersionURL) - if resp != nil && resp.Body != nil { - defer resp.Body.Close() - } - if err != nil { return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", u.VersionURL, err) } + defer resp.Body.Close() + resp.Body, err = aghio.LimitReadCloser(resp.Body, MaxResponseSize) + if err != nil { + return VersionInfo{}, fmt.Errorf("updater: LimitReadCloser: %w", err) + } + defer resp.Body.Close() + + // This use of ReadAll is safe, because we just limited the appropriate + // ReadCloser. body, err := ioutil.ReadAll(resp.Body) if err != nil { return VersionInfo{}, fmt.Errorf("updater: HTTP GET %s: %w", u.VersionURL, err) diff --git a/internal/update/updater.go b/internal/update/updater.go index f78f85c5..34d66819 100644 --- a/internal/update/updater.go +++ b/internal/update/updater.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/AdguardTeam/AdGuardHome/internal/aghio" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/log" ) @@ -217,17 +218,27 @@ func (u *Updater) clean() { _ = os.RemoveAll(u.updateDir) } +// MaxPackageFileSize is a maximum package file length in bytes. The largest +// package whose size is limited by this constant currently has the size of +// approximately 9 MiB. +const MaxPackageFileSize = 32 * 1024 * 1024 + // Download package file and save it to disk func (u *Updater) downloadPackageFile(url string, filename string) error { resp, err := u.Client.Get(url) if err != nil { return fmt.Errorf("http request failed: %w", err) } - if resp != nil && resp.Body != nil { - defer resp.Body.Close() + defer resp.Body.Close() + + resp.Body, err = aghio.LimitReadCloser(resp.Body, MaxPackageFileSize) + if err != nil { + return fmt.Errorf("http request failed: %w", err) } + defer resp.Body.Close() log.Debug("updater: reading HTTP body") + // This use of ReadAll is now safe, because we limited body's Reader. body, err := ioutil.ReadAll(resp.Body) if err != nil { return fmt.Errorf("ioutil.ReadAll() failed: %w", err) From 1cf9848044fc549788b42373bb0b46295fb2720d Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 23 Nov 2020 18:05:45 +0300 Subject: [PATCH 11/54] Pull request: update snap core version Merge in DNS/adguard-home from 2306-update-snap-core to master Closes #2306. Squashed commit of the following: commit e02c083ede35e27e1273d3fa2c1d033ccd749718 Author: Eugene Burkov Date: Mon Nov 23 16:15:59 2020 +0300 all: update snap core version --- .goreleaser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 14a6b3d7..ac9c57cf 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -61,7 +61,7 @@ 'snapcrafts': - 'name': 'adguard-home' - 'base': 'core18' + 'base': 'core20' 'name_template': '{{ .ProjectName }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 'summary': 'Network-wide ads & trackers blocking DNS server' 'description': | From e685d81c92a0eb56d0799d08cb983a805112b065 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 24 Nov 2020 19:55:05 +0300 Subject: [PATCH 12/54] Pull request #847: dnsfilter: add $dnstype handling Merge in DNS/adguard-home from 2337-dnstype to master Updates #2102. Updates #2337. Squashed commit of the following: commit ac4b7522c732c0bf8ee06539fd4c95b5dc1c87b8 Author: Ainar Garipov Date: Tue Nov 24 17:50:33 2020 +0300 dnsfilter: add $dnstype handling --- go.mod | 2 +- go.sum | 2 + internal/dnsfilter/dnsfilter.go | 12 +++-- internal/dnsfilter/dnsfilter_test.go | 74 +++++++++++++++------------- internal/dnsfilter/rewrites.go | 3 -- internal/dnsfilter/rewrites_test.go | 60 +++++++++++----------- 6 files changed, 81 insertions(+), 72 deletions(-) diff --git a/go.mod b/go.mod index fef5e380..c20a9da1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/AdguardTeam/dnsproxy v0.33.2 github.com/AdguardTeam/golibs v0.4.3 - github.com/AdguardTeam/urlfilter v0.12.3 + github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect github.com/fsnotify/fsnotify v1.4.9 diff --git a/go.sum b/go.sum index ccbe48fa..9a7f298f 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs= github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0= +github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c= +github.com/AdguardTeam/urlfilter v0.13.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index b57d03fb..2b58b02e 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -596,11 +596,13 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS // but also while using the rules returned by it. defer d.engineLock.RUnlock() - ureq := urlfilter.DNSRequest{} - ureq.Hostname = host - ureq.ClientIP = setts.ClientIP - ureq.ClientName = setts.ClientName - ureq.SortedClientTags = setts.ClientTags + ureq := urlfilter.DNSRequest{ + Hostname: host, + SortedClientTags: setts.ClientTags, + ClientIP: setts.ClientIP, + ClientName: setts.ClientName, + DNSType: qtype, + } if d.filteringEngineWhite != nil { rr, ok := d.filteringEngineWhite.MatchRequest(ureq) diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index bfe06caa..1eb7a31c 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -368,6 +368,7 @@ const ( importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl maskRules = `test*.example.org^` + nl + `exam*.com` + nl + dnstypeRules = `||example.org^$dnstype=AAAA` + nl + `@@||test.example.org^` + nl ) var tests = []struct { @@ -376,44 +377,51 @@ var tests = []struct { hostname string isFiltered bool reason Reason + dnsType uint16 }{ - {"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList}, - {"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound}, - {"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound}, - {"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound}, + {"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList, dns.TypeA}, + {"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA}, + {"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA}, + {"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA}, - {"blocking", blockingRules, "example.org", true, FilteredBlackList}, - {"blocking", blockingRules, "test.example.org", true, FilteredBlackList}, - {"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList}, - {"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound}, - {"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound}, + {"blocking", blockingRules, "example.org", true, FilteredBlackList, dns.TypeA}, + {"blocking", blockingRules, "test.example.org", true, FilteredBlackList, dns.TypeA}, + {"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA}, + {"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"whitelist", whitelistRules, "example.org", true, FilteredBlackList}, - {"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList}, - {"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList}, - {"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound}, - {"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound}, + {"whitelist", whitelistRules, "example.org", true, FilteredBlackList, dns.TypeA}, + {"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA}, + {"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA}, + {"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"important", importantRules, "example.org", false, NotFilteredWhiteList}, - {"important", importantRules, "test.example.org", true, FilteredBlackList}, - {"important", importantRules, "test.test.example.org", true, FilteredBlackList}, - {"important", importantRules, "testexample.org", false, NotFilteredNotFound}, - {"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound}, + {"important", importantRules, "example.org", false, NotFilteredWhiteList, dns.TypeA}, + {"important", importantRules, "test.example.org", true, FilteredBlackList, dns.TypeA}, + {"important", importantRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA}, + {"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"regex", regexRules, "example.org", true, FilteredBlackList}, - {"regex", regexRules, "test.example.org", false, NotFilteredWhiteList}, - {"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList}, - {"regex", regexRules, "testexample.org", true, FilteredBlackList}, - {"regex", regexRules, "onemoreexample.org", true, FilteredBlackList}, + {"regex", regexRules, "example.org", true, FilteredBlackList, dns.TypeA}, + {"regex", regexRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA}, + {"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA}, + {"regex", regexRules, "testexample.org", true, FilteredBlackList, dns.TypeA}, + {"regex", regexRules, "onemoreexample.org", true, FilteredBlackList, dns.TypeA}, - {"mask", maskRules, "test.example.org", true, FilteredBlackList}, - {"mask", maskRules, "test2.example.org", true, FilteredBlackList}, - {"mask", maskRules, "example.com", true, FilteredBlackList}, - {"mask", maskRules, "exampleeee.com", true, FilteredBlackList}, - {"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList}, - {"mask", maskRules, "example.org", false, NotFilteredNotFound}, - {"mask", maskRules, "testexample.org", false, NotFilteredNotFound}, - {"mask", maskRules, "example.co.uk", false, NotFilteredNotFound}, + {"mask", maskRules, "test.example.org", true, FilteredBlackList, dns.TypeA}, + {"mask", maskRules, "test2.example.org", true, FilteredBlackList, dns.TypeA}, + {"mask", maskRules, "example.com", true, FilteredBlackList, dns.TypeA}, + {"mask", maskRules, "exampleeee.com", true, FilteredBlackList, dns.TypeA}, + {"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList, dns.TypeA}, + {"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, + {"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA}, + + {"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, + {"dnstype", dnstypeRules, "example.org", true, FilteredBlackList, dns.TypeAAAA}, + {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA}, + {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeAAAA}, } func TestMatching(t *testing.T) { @@ -425,7 +433,7 @@ func TestMatching(t *testing.T) { d := NewForTest(nil, filters) defer d.Close() - ret, err := d.CheckHost(test.hostname, dns.TypeA, &setts) + ret, err := d.CheckHost(test.hostname, test.dnsType, &setts) if err != nil { t.Errorf("Error while matching host %s: %s", test.hostname, err) } diff --git a/internal/dnsfilter/rewrites.go b/internal/dnsfilter/rewrites.go index 9c042228..0092344e 100644 --- a/internal/dnsfilter/rewrites.go +++ b/internal/dnsfilter/rewrites.go @@ -149,7 +149,6 @@ type rewriteEntryJSON struct { } func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { - arr := []*rewriteEntryJSON{} d.confLock.Lock() @@ -171,7 +170,6 @@ func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { } func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { - jsent := rewriteEntryJSON{} err := json.NewDecoder(r.Body).Decode(&jsent) if err != nil { @@ -194,7 +192,6 @@ func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { } func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { - jsent := rewriteEntryJSON{} err := json.NewDecoder(r.Body).Decode(&jsent) if err != nil { diff --git a/internal/dnsfilter/rewrites_test.go b/internal/dnsfilter/rewrites_test.go index 2ed8210e..31b5cc0d 100644 --- a/internal/dnsfilter/rewrites_test.go +++ b/internal/dnsfilter/rewrites_test.go @@ -12,13 +12,13 @@ func TestRewrites(t *testing.T) { d := Dnsfilter{} // CNAME, A, AAAA d.Rewrites = []RewriteEntry{ - RewriteEntry{"somecname", "somehost.com", 0, nil}, - RewriteEntry{"somehost.com", "0.0.0.0", 0, nil}, + {"somecname", "somehost.com", 0, nil}, + {"somehost.com", "0.0.0.0", 0, nil}, - RewriteEntry{"host.com", "1.2.3.4", 0, nil}, - RewriteEntry{"host.com", "1.2.3.5", 0, nil}, - RewriteEntry{"host.com", "1:2:3::4", 0, nil}, - RewriteEntry{"www.host.com", "host.com", 0, nil}, + {"host.com", "1.2.3.4", 0, nil}, + {"host.com", "1.2.3.5", 0, nil}, + {"host.com", "1:2:3::4", 0, nil}, + {"www.host.com", "host.com", 0, nil}, } d.prepareRewrites() r := d.processRewrites("host2.com", dns.TypeA) @@ -39,8 +39,8 @@ func TestRewrites(t *testing.T) { // wildcard d.Rewrites = []RewriteEntry{ - RewriteEntry{"host.com", "1.2.3.4", 0, nil}, - RewriteEntry{"*.host.com", "1.2.3.5", 0, nil}, + {"host.com", "1.2.3.4", 0, nil}, + {"*.host.com", "1.2.3.5", 0, nil}, } d.prepareRewrites() r = d.processRewrites("host.com", dns.TypeA) @@ -56,8 +56,8 @@ func TestRewrites(t *testing.T) { // override a wildcard d.Rewrites = []RewriteEntry{ - RewriteEntry{"a.host.com", "1.2.3.4", 0, nil}, - RewriteEntry{"*.host.com", "1.2.3.5", 0, nil}, + {"a.host.com", "1.2.3.4", 0, nil}, + {"*.host.com", "1.2.3.5", 0, nil}, } d.prepareRewrites() r = d.processRewrites("a.host.com", dns.TypeA) @@ -67,8 +67,8 @@ func TestRewrites(t *testing.T) { // wildcard + CNAME d.Rewrites = []RewriteEntry{ - RewriteEntry{"host.com", "1.2.3.4", 0, nil}, - RewriteEntry{"*.host.com", "host.com", 0, nil}, + {"host.com", "1.2.3.4", 0, nil}, + {"*.host.com", "host.com", 0, nil}, } d.prepareRewrites() r = d.processRewrites("www.host.com", dns.TypeA) @@ -78,9 +78,9 @@ func TestRewrites(t *testing.T) { // 2 CNAMEs d.Rewrites = []RewriteEntry{ - RewriteEntry{"b.host.com", "a.host.com", 0, nil}, - RewriteEntry{"a.host.com", "host.com", 0, nil}, - RewriteEntry{"host.com", "1.2.3.4", 0, nil}, + {"b.host.com", "a.host.com", 0, nil}, + {"a.host.com", "host.com", 0, nil}, + {"host.com", "1.2.3.4", 0, nil}, } d.prepareRewrites() r = d.processRewrites("b.host.com", dns.TypeA) @@ -91,9 +91,9 @@ func TestRewrites(t *testing.T) { // 2 CNAMEs + wildcard d.Rewrites = []RewriteEntry{ - RewriteEntry{"b.host.com", "a.host.com", 0, nil}, - RewriteEntry{"a.host.com", "x.somehost.com", 0, nil}, - RewriteEntry{"*.somehost.com", "1.2.3.4", 0, nil}, + {"b.host.com", "a.host.com", 0, nil}, + {"a.host.com", "x.somehost.com", 0, nil}, + {"*.somehost.com", "1.2.3.4", 0, nil}, } d.prepareRewrites() r = d.processRewrites("b.host.com", dns.TypeA) @@ -107,9 +107,9 @@ func TestRewritesLevels(t *testing.T) { d := Dnsfilter{} // exact host, wildcard L2, wildcard L3 d.Rewrites = []RewriteEntry{ - RewriteEntry{"host.com", "1.1.1.1", 0, nil}, - RewriteEntry{"*.host.com", "2.2.2.2", 0, nil}, - RewriteEntry{"*.sub.host.com", "3.3.3.3", 0, nil}, + {"host.com", "1.1.1.1", 0, nil}, + {"*.host.com", "2.2.2.2", 0, nil}, + {"*.sub.host.com", "3.3.3.3", 0, nil}, } d.prepareRewrites() @@ -136,8 +136,8 @@ func TestRewritesExceptionCNAME(t *testing.T) { d := Dnsfilter{} // wildcard; exception for a sub-domain d.Rewrites = []RewriteEntry{ - RewriteEntry{"*.host.com", "2.2.2.2", 0, nil}, - RewriteEntry{"sub.host.com", "sub.host.com", 0, nil}, + {"*.host.com", "2.2.2.2", 0, nil}, + {"sub.host.com", "sub.host.com", 0, nil}, } d.prepareRewrites() @@ -156,8 +156,8 @@ func TestRewritesExceptionWC(t *testing.T) { d := Dnsfilter{} // wildcard; exception for a sub-wildcard d.Rewrites = []RewriteEntry{ - RewriteEntry{"*.host.com", "2.2.2.2", 0, nil}, - RewriteEntry{"*.sub.host.com", "*.sub.host.com", 0, nil}, + {"*.host.com", "2.2.2.2", 0, nil}, + {"*.sub.host.com", "*.sub.host.com", 0, nil}, } d.prepareRewrites() @@ -176,11 +176,11 @@ func TestRewritesExceptionIP(t *testing.T) { d := Dnsfilter{} // exception for AAAA record d.Rewrites = []RewriteEntry{ - RewriteEntry{"host.com", "1.2.3.4", 0, nil}, - RewriteEntry{"host.com", "AAAA", 0, nil}, - RewriteEntry{"host2.com", "::1", 0, nil}, - RewriteEntry{"host2.com", "A", 0, nil}, - RewriteEntry{"host3.com", "A", 0, nil}, + {"host.com", "1.2.3.4", 0, nil}, + {"host.com", "AAAA", 0, nil}, + {"host2.com", "::1", 0, nil}, + {"host2.com", "A", 0, nil}, + {"host3.com", "A", 0, nil}, } d.prepareRewrites() From 284da7c91b78724a066e646b33b81272706e9b6b Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 25 Nov 2020 12:02:21 +0300 Subject: [PATCH 13/54] Pull request: improve docs Merge in DNS/adguard-home from update-docs to master Squashed commit of the following: commit 228c432adecf9f7927a692780a4762f1135b8cd6 Author: Ainar Garipov Date: Fri Nov 20 18:18:10 2020 +0300 improve docs --- CHANGELOG.md | 8 ++++++++ HACKING.md | 3 +++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2f1be7..f12d2670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,18 +9,26 @@ and this project adheres to ## [Unreleased] + + ### Added +- `$dnstype` modifier for filters [#2337]. - HTTP API request body size limit [#2305]. [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 +[#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 ### Changed +- Our snap package now uses the `core20` image as its base [#2306]. - Various internal improvements ([#2271], [#2297]). [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 +[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 diff --git a/HACKING.md b/HACKING.md index bbd4dfbd..bf56c270 100644 --- a/HACKING.md +++ b/HACKING.md @@ -47,6 +47,9 @@ The rules are mostly sorted in the alphabetical order. * Document everything, including unexported top-level identifiers, to build a habit of writing documentation. + * Constructors should validate their arguments and return meaningful errors. + As a corollary, avoid lazy initialization. + * Don't put variable names into any kind of quotes. * Don't use naked `return`s. From 36c7735b855f4388d0cf36fb2f26f1979809c217 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 25 Nov 2020 14:26:26 +0300 Subject: [PATCH 14/54] Pull request: dhcpd: fix interface ipv6 check Merge in DNS/adguard-home from 2355-dhcpcheck-ipv6 to master Updates #2335. Squashed commit of the following: commit 5ce1cc7bc244ba5dd4a065d47dec8884fa3d45e7 Author: Ainar Garipov Date: Wed Nov 25 14:03:24 2020 +0300 dhcpd: fix loop exit condition commit 32b4b946bfa30159326dc295fa1a2607b78172af Author: Ainar Garipov Date: Wed Nov 25 13:26:50 2020 +0300 dhcpd: fix interface ipv6 check --- CHANGELOG.md | 7 ++++ internal/dhcpd/check_other_dhcp.go | 10 ++--- internal/dhcpd/db.go | 1 - internal/dhcpd/nclient4/client_test.go | 8 ++-- internal/dhcpd/nclient4/conn_unix.go | 18 ++++----- internal/dhcpd/nclient4/ipv4.go | 6 +-- internal/dhcpd/v46_windows.go | 51 ++++++------------------ internal/dhcpd/v6.go | 13 ++++-- internal/dhcpd/v6_test.go | 55 ++++++++++++++++++++++++++ 9 files changed, 103 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f12d2670..798bf950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,13 @@ and this project adheres to [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 +### Fixed + +- Incorrect detection of the IPv6 address of an interface as well as another + infinite loop in the `/dhcp/find_active_dhcp` HTTP API [#2355]. + +[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 + ## [v0.104.3] - 2020-11-19 diff --git a/internal/dhcpd/check_other_dhcp.go b/internal/dhcpd/check_other_dhcp.go index aba9e446..e77a7801 100644 --- a/internal/dhcpd/check_other_dhcp.go +++ b/internal/dhcpd/check_other_dhcp.go @@ -94,12 +94,11 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) { continue } - if ok { - return true, nil - } if err != nil { return false, err } + + return ok, nil } } @@ -216,12 +215,11 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) { continue } - if ok { - return true, nil - } if err != nil { return false, err } + + return ok, nil } } diff --git a/internal/dhcpd/db.go b/internal/dhcpd/db.go index b01f9635..2618f12e 100644 --- a/internal/dhcpd/db.go +++ b/internal/dhcpd/db.go @@ -74,7 +74,6 @@ func (s *Server) dbLoad() { } else { v6DynLeases = append(v6DynLeases, &lease) } - } else { if obj[i].Expiry == leaseExpireStatic { staticLeases = append(staticLeases, &lease) diff --git a/internal/dhcpd/nclient4/client_test.go b/internal/dhcpd/nclient4/client_test.go index 353a9ed7..99f99640 100644 --- a/internal/dhcpd/nclient4/client_test.go +++ b/internal/dhcpd/nclient4/client_test.go @@ -79,7 +79,7 @@ func serveAndClient(ctx context.Context, responses [][]*dhcpv4.DHCPv4, opts ...C return mc, serverConn } -func ComparePacket(got *dhcpv4.DHCPv4, want *dhcpv4.DHCPv4) error { +func ComparePacket(got, want *dhcpv4.DHCPv4) error { if got == nil && got == want { return nil } @@ -92,7 +92,7 @@ func ComparePacket(got *dhcpv4.DHCPv4, want *dhcpv4.DHCPv4) error { return nil } -func pktsExpected(got []*dhcpv4.DHCPv4, want []*dhcpv4.DHCPv4) error { +func pktsExpected(got, want []*dhcpv4.DHCPv4) error { if len(got) != len(want) { return fmt.Errorf("got %d packets, want %d packets", len(got), len(want)) } @@ -309,10 +309,10 @@ func TestMultipleSendAndRead(t *testing.T) { newPacket(dhcpv4.OpcodeBootRequest, [4]byte{0x44, 0x44, 0x44, 0x44}), }, server: [][]*dhcpv4.DHCPv4{ - []*dhcpv4.DHCPv4{ // Response for first packet. + { // Response for first packet. newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x33, 0x33, 0x33, 0x33}), }, - []*dhcpv4.DHCPv4{ // Response for second packet. + { // Response for second packet. newPacket(dhcpv4.OpcodeBootReply, [4]byte{0x44, 0x44, 0x44, 0x44}), }, }, diff --git a/internal/dhcpd/nclient4/conn_unix.go b/internal/dhcpd/nclient4/conn_unix.go index 51ec98cb..39009d69 100644 --- a/internal/dhcpd/nclient4/conn_unix.go +++ b/internal/dhcpd/nclient4/conn_unix.go @@ -17,17 +17,13 @@ import ( "github.com/u-root/u-root/pkg/uio" ) -var ( - // BroadcastMac is the broadcast MAC address. - // - // Any UDP packet sent to this address is broadcast on the subnet. - BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255}) -) +// BroadcastMac is the broadcast MAC address. +// +// Any UDP packet sent to this address is broadcast on the subnet. +var BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255}) -var ( - // ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr". - ErrUDPAddrIsRequired = errors.New("must supply UDPAddr") -) +// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr". +var ErrUDPAddrIsRequired = errors.New("must supply UDPAddr") // NewRawUDPConn returns a UDP connection bound to the interface and port // given based on a raw packet socket. All packets are broadcasted. @@ -68,7 +64,7 @@ func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) n } } -func udpMatch(addr *net.UDPAddr, bound *net.UDPAddr) bool { +func udpMatch(addr, bound *net.UDPAddr) bool { if bound == nil { return true } diff --git a/internal/dhcpd/nclient4/ipv4.go b/internal/dhcpd/nclient4/ipv4.go index 5d961852..50a2d684 100644 --- a/internal/dhcpd/nclient4/ipv4.go +++ b/internal/dhcpd/nclient4/ipv4.go @@ -281,7 +281,7 @@ func (b UDP) Checksum() uint16 { // CalculateChecksum calculates the checksum of the udp packet, given the total // length of the packet and the checksum of the network-layer pseudo-header // (excluding the total length) and the checksum of the payload. -func (b UDP) CalculateChecksum(partialChecksum uint16, totalLen uint16) uint16 { +func (b UDP) CalculateChecksum(partialChecksum, totalLen uint16) uint16 { // Add the length portion of the checksum to the pseudo-checksum. tmp := make([]byte, 2) binary.BigEndian.PutUint16(tmp, totalLen) @@ -336,13 +336,13 @@ func ChecksumCombine(a, b uint16) uint16 { // given destination protocol and network address, ignoring the length // field. Pseudo-headers are needed by transport layers when calculating // their own checksum. -func PseudoHeaderChecksum(protocol TransportProtocolNumber, srcAddr net.IP, dstAddr net.IP) uint16 { +func PseudoHeaderChecksum(protocol TransportProtocolNumber, srcAddr, dstAddr net.IP) uint16 { xsum := Checksum([]byte(srcAddr), 0) xsum = Checksum([]byte(dstAddr), xsum) return Checksum([]byte{0, uint8(protocol)}, xsum) } -func udp4pkt(packet []byte, dest *net.UDPAddr, src *net.UDPAddr) []byte { +func udp4pkt(packet []byte, dest, src *net.UDPAddr) []byte { ipLen := IPv4MinimumSize udpLen := UDPMinimumSize diff --git a/internal/dhcpd/v46_windows.go b/internal/dhcpd/v46_windows.go index 152899b9..ebae25af 100644 --- a/internal/dhcpd/v46_windows.go +++ b/internal/dhcpd/v46_windows.go @@ -7,41 +7,16 @@ import "net" type winServer struct { } -func (s *winServer) ResetLeases(leases []*Lease) { -} -func (s *winServer) GetLeases(flags int) []Lease { - return nil -} -func (s *winServer) GetLeasesRef() []*Lease { - return nil -} -func (s *winServer) AddStaticLease(lease Lease) error { - return nil -} -func (s *winServer) RemoveStaticLease(l Lease) error { - return nil -} -func (s *winServer) FindMACbyIP(ip net.IP) net.HardwareAddr { - return nil -} - -func (s *winServer) WriteDiskConfig4(c *V4ServerConf) { -} -func (s *winServer) WriteDiskConfig6(c *V6ServerConf) { -} - -func (s *winServer) Start() error { - return nil -} -func (s *winServer) Stop() { -} -func (s *winServer) Reset() { -} - -func v4Create(conf V4ServerConf) (DHCPServer, error) { - return &winServer{}, nil -} - -func v6Create(conf V6ServerConf) (DHCPServer, error) { - return &winServer{}, nil -} +func (s *winServer) ResetLeases(leases []*Lease) {} +func (s *winServer) GetLeases(flags int) []Lease { return nil } +func (s *winServer) GetLeasesRef() []*Lease { return nil } +func (s *winServer) AddStaticLease(lease Lease) error { return nil } +func (s *winServer) RemoveStaticLease(l Lease) error { return nil } +func (s *winServer) FindMACbyIP(ip net.IP) net.HardwareAddr { return nil } +func (s *winServer) WriteDiskConfig4(c *V4ServerConf) {} +func (s *winServer) WriteDiskConfig6(c *V6ServerConf) {} +func (s *winServer) Start() error { return nil } +func (s *winServer) Stop() {} +func (s *winServer) Reset() {} +func v4Create(conf V4ServerConf) (DHCPServer, error) { return &winServer{}, nil } +func v6Create(conf V6ServerConf) (DHCPServer, error) { return &winServer{}, nil } diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index b24be499..f1ec57b3 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -537,8 +537,12 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6. } } +type netIface interface { + Addrs() ([]net.Addr, error) +} + // ifaceIPv6Addrs returns the interface's IPv6 addresses. -func ifaceIPv6Addrs(iface *net.Interface) (ips []net.IP, err error) { +func ifaceIPv6Addrs(iface netIface) (ips []net.IP, err error) { addrs, err := iface.Addrs() if err != nil { return nil, err @@ -550,8 +554,11 @@ func ifaceIPv6Addrs(iface *net.Interface) (ips []net.IP, err error) { continue } - if ip := ipnet.IP.To16(); ip != nil { - ips = append(ips, ip) + if ip := ipnet.IP.To4(); ip == nil { + // Assume that net.(*Interface).Addrs can only return + // valid IPv4 and IPv6 addresses. Since this isn't an + // IPv4 address, it must be an IPv6 one. + ips = append(ips, ipnet.IP) } } diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index 7d7dd678..d51c695c 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -6,6 +6,7 @@ import ( "net" "testing" + "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/assert" @@ -223,3 +224,57 @@ func TestV6GetDynamicLease(t *testing.T) { assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::2"))) assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::3"))) } + +type fakeIface struct { + addrs []net.Addr + err error +} + +// Addrs implements the netIface interface for *fakeIface. +func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) { + if iface.err != nil { + return nil, iface.err + } + + return iface.addrs, nil +} + +func TestIfaceIPv6Addrs(t *testing.T) { + ip := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + ip4 := net.IP{1, 2, 3, 4} + addr := &net.IPNet{IP: ip} + errTest := agherr.Error("test error") + + testCases := []struct { + name string + iface netIface + want []net.IP + wantErr error + }{{ + name: "success", + iface: &fakeIface{addrs: []net.Addr{addr}, err: nil}, + want: []net.IP{ip}, + wantErr: nil, + }, { + name: "success_with_ipv4", + iface: &fakeIface{ + addrs: []net.Addr{addr, &net.IPNet{IP: ip4}}, + err: nil, + }, + want: []net.IP{ip}, + wantErr: nil, + }, { + name: "error", + iface: &fakeIface{addrs: []net.Addr{addr}, err: errTest}, + want: nil, + wantErr: errTest, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, gotErr := ifaceIPv6Addrs(tc.iface) + assert.Equal(t, tc.want, got) + assert.Equal(t, tc.wantErr, gotErr) + }) + } +} From b4a35fa887737e737145edd5ce42dd7fdfea2ee4 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 25 Nov 2020 15:50:59 +0300 Subject: [PATCH 15/54] Pull request: 2343 http server Merge in DNS/adguard-home from 2343-http-server to master Closes #2343. Squashed commit of the following: commit f4ebfc129484fc3489409069b3580eb70d71cc74 Merge: b13ec7002 36c7735b8 Author: Eugene Burkov Date: Wed Nov 25 15:37:27 2020 +0300 Merge branch 'master' into 2343-http-server commit b13ec70024f24f6b68b13a1ec6f27c89535feaf8 Author: Eugene Burkov Date: Wed Nov 25 15:31:36 2020 +0300 all: record changes commit ce44aac9d43e32db3f68746dec7a4f21b0a9dea4 Author: Eugene Burkov Date: Wed Nov 25 14:00:45 2020 +0300 home: set http servers timeouts commit 7f3e7385d1df39b39713b8ec443da5d9374d0bc8 Author: Eugene Burkov Date: Tue Nov 24 19:58:56 2020 +0300 home: replace default ServeMux with custom one. --- CHANGELOG.md | 2 ++ internal/home/auth.go | 2 +- internal/home/control.go | 11 ++++---- internal/home/control_install.go | 6 ++--- internal/home/filter_test.go | 5 ++-- internal/home/home.go | 9 +++++++ internal/home/middlewares.go | 17 ------------ internal/home/web.go | 44 ++++++++++++++++++++++++++++---- 8 files changed, 62 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 798bf950..99183e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,12 +23,14 @@ and this project adheres to ### Changed +- Improved HTTP requests handling and timeouts. ([#2343]). - Our snap package now uses the `core20` image as its base [#2306]. - Various internal improvements ([#2271], [#2297]). [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 +[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 ### Fixed diff --git a/internal/home/auth.go b/internal/home/auth.go index de393f6f..00407fa0 100644 --- a/internal/home/auth.go +++ b/internal/home/auth.go @@ -360,7 +360,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) { // RegisterAuthHandlers - register handlers func RegisterAuthHandlers() { - http.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin))) + Context.mux.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin))) httpRegister("GET", "/control/logout", handleLogout) } diff --git a/internal/home/control.go b/internal/home/control.go index 00334b62..5c8bc1cb 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -107,24 +107,24 @@ func registerControlHandlers() { httpRegister(http.MethodGet, "/control/status", handleStatus) httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage) httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) - http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON))) + Context.mux.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON))) httpRegister(http.MethodPost, "/control/update", handleUpdate) httpRegister(http.MethodGet, "/control/profile", handleGetProfile) // No auth is necessary for DOH/DOT configurations - http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh)) - http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot)) + Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh)) + Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot)) RegisterAuthHandlers() } func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) { if len(method) == 0 { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method - http.HandleFunc(url, postInstall(handler)) + Context.mux.HandleFunc(url, postInstall(handler)) return } - http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler))))) + Context.mux.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler))))) } // ---------------------------------- @@ -201,7 +201,6 @@ func preInstallHandler(handler http.Handler) http.Handler { // it also enforces HTTPS if it is enabled and configured func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - if Context.firstRun && !strings.HasPrefix(r.URL.Path, "/install.") && !strings.HasPrefix(r.URL.Path, "/assets/") { diff --git a/internal/home/control_install.go b/internal/home/control_install.go index fcb8fcea..06f3bf43 100644 --- a/internal/home/control_install.go +++ b/internal/home/control_install.go @@ -372,7 +372,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) { } func (web *Web) registerInstallHandlers() { - http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) - http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) - http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) + Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) + Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) + Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) } diff --git a/internal/home/filter_test.go b/internal/home/filter_test.go index 317741d8..2bc23be1 100644 --- a/internal/home/filter_test.go +++ b/internal/home/filter_test.go @@ -12,7 +12,8 @@ import ( ) func testStartFilterListener() net.Listener { - http.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) { + mux := http.NewServeMux() + mux.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) { content := `||example.org^$third-party # Inline comment example ||example.com^$third-party @@ -26,7 +27,7 @@ func testStartFilterListener() net.Listener { panic(err) } - go func() { _ = http.Serve(listener, nil) }() + go func() { _ = http.Serve(listener, mux) }() return listener } diff --git a/internal/home/home.go b/internal/home/home.go index 493736f8..96bc4d68 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -67,6 +67,9 @@ type homeContext struct { ipDetector *ipDetector + // mux is our custom http.ServeMux. + mux *http.ServeMux + // Runtime properties // -- @@ -187,6 +190,8 @@ func setupContext(args options) { os.Exit(0) } } + + Context.mux = http.NewServeMux() } func setupConfig(args options) { @@ -306,6 +311,10 @@ func run(args options) { firstRun: Context.firstRun, BindHost: config.BindHost, BindPort: config.BindPort, + + ReadTimeout: ReadTimeout, + ReadHeaderTimeout: ReadHeaderTimeout, + WriteTimeout: WriteTimeout, } Context.web = CreateWeb(&webConf) if Context.web == nil { diff --git a/internal/home/middlewares.go b/internal/home/middlewares.go index 4a38160d..a5758985 100644 --- a/internal/home/middlewares.go +++ b/internal/home/middlewares.go @@ -2,7 +2,6 @@ package home import ( "net/http" - "strings" "github.com/AdguardTeam/AdGuardHome/internal/aghio" @@ -41,19 +40,3 @@ func limitRequestBody(h http.Handler) (limited http.Handler) { h.ServeHTTP(w, r) }) } - -// TODO(a.garipov): We currently have to use this, because everything registers -// its HTTP handlers in http.DefaultServeMux. In the future, refactor our HTTP -// API initialization process and stop using the gosh darn http.DefaultServeMux -// for anything at all. Gosh darn global variables. -func filterPProf(h http.Handler) (filtered http.Handler) { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/debug/pprof") { - http.NotFound(w, r) - - return - } - - h.ServeHTTP(w, r) - }) -} diff --git a/internal/home/web.go b/internal/home/web.go index 0d6a1628..974016da 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" "sync" + "time" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/log" @@ -15,11 +16,37 @@ import ( "github.com/gobuffalo/packr" ) +const ( + // ReadTimeout is the maximum duration for reading the entire request, + // including the body. + ReadTimeout = 10 * time.Second + + // ReadHeaderTimeout is the amount of time allowed to read request + // headers. + ReadHeaderTimeout = 10 * time.Second + + // WriteTimeout is the maximum duration before timing out writes of the + // response. + WriteTimeout = 10 * time.Second +) + type WebConfig struct { firstRun bool BindHost string BindPort int PortHTTPS int + + // ReadTimeout is an option to pass to http.Server for setting an + // appropriate field. + ReadTimeout time.Duration + + // ReadHeaderTimeout is an option to pass to http.Server for setting an + // appropriate field. + ReadHeaderTimeout time.Duration + + // WriteTimeout is an option to pass to http.Server for setting an + // appropriate field. + WriteTimeout time.Duration } // HTTPSServer - HTTPS Server @@ -66,12 +93,12 @@ func CreateWeb(conf *WebConfig) *Web { box := packr.NewBox("../../build/static") // if not configured, redirect / to /install.html, otherwise redirect /install.html to / - http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box))))) + Context.mux.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box))))) // add handlers for /install paths, we only need them when we're not configured yet if conf.firstRun { log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ") - http.Handle("/install.html", preInstallHandler(http.FileServer(box))) + Context.mux.Handle("/install.html", preInstallHandler(http.FileServer(box))) w.registerInstallHandlers() } else { registerControlHandlers() @@ -139,9 +166,12 @@ func (web *Web) Start() { // we need to have new instance, because after Shutdown() the Server is not usable address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort)) web.httpServer = &http.Server{ - ErrorLog: web.errLogger, - Addr: address, - Handler: withMiddlewares(http.DefaultServeMux, filterPProf, limitRequestBody), + ErrorLog: web.errLogger, + Addr: address, + Handler: withMiddlewares(Context.mux, limitRequestBody), + ReadTimeout: web.conf.ReadTimeout, + ReadHeaderTimeout: web.conf.ReadHeaderTimeout, + WriteTimeout: web.conf.WriteTimeout, } err := web.httpServer.ListenAndServe() if err != http.ErrServerClosed { @@ -198,6 +228,10 @@ func (web *Web) tlsServerLoop() { RootCAs: Context.tlsRoots, CipherSuites: Context.tlsCiphers, }, + Handler: Context.mux, + ReadTimeout: web.conf.ReadTimeout, + ReadHeaderTimeout: web.conf.ReadHeaderTimeout, + WriteTimeout: web.conf.WriteTimeout, } printHTTPAddresses("https") From 96e83a133f066a5f8b9cb812476aa31e2923ba1e Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 25 Nov 2020 18:09:41 +0300 Subject: [PATCH 16/54] Pull request: home: improve mobileconfig http api Merge in DNS/adguard-home from 2358-mobileconfig to master Updates #2358. Squashed commit of the following: commit ab3c7a75ae21f6978904f2dc237cb84cbedff7ab Merge: fa002e400 b4a35fa88 Author: Ainar Garipov Date: Wed Nov 25 16:11:06 2020 +0300 Merge branch 'master' into 2358-mobileconfig commit fa002e40004656db08d32c926892c6c820fb1338 Author: Ainar Garipov Date: Wed Nov 25 15:19:00 2020 +0300 home: improve mobileconfig http api --- CHANGELOG.md | 3 + internal/dhcpd/dhcphttp.go | 3 + internal/home/control.go | 4 +- internal/home/home.go | 9 ++ internal/home/mobileconfig.go | 54 ++++++----- internal/home/mobileconfig_test.go | 139 +++++++++++++++++++++++++---- openapi/openapi.yaml | 52 +++++++++-- 7 files changed, 217 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99183e01..f0dfe8c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to ### Changed +- Make the mobileconfig HTTP API more robust and predictable, add parameters and + improve error response ([#2358]). - Improved HTTP requests handling and timeouts. ([#2343]). - Our snap package now uses the `core20` image as its base [#2306]. - Various internal improvements ([#2271], [#2297]). @@ -31,6 +33,7 @@ and this project adheres to [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 [#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 +[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 ### Fixed diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index 1cacd83c..c7584f8b 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -509,6 +509,9 @@ func (s *Server) registerHandlers() { } // jsonError is a generic JSON error response. +// +// TODO(a.garipov): Merge together with the implementations in .../home and +// other packages after refactoring the web handler registering. type jsonError struct { // Message is the error message, an opaque string. Message string `json:"message"` diff --git a/internal/home/control.go b/internal/home/control.go index 5c8bc1cb..732fa919 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -112,8 +112,8 @@ func registerControlHandlers() { httpRegister(http.MethodGet, "/control/profile", handleGetProfile) // No auth is necessary for DOH/DOT configurations - Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh)) - Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot)) + Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDOH)) + Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDOT)) RegisterAuthHandlers() } diff --git a/internal/home/home.go b/internal/home/home.go index 96bc4d68..82f5ccf4 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -683,3 +683,12 @@ func getHTTPProxy(req *http.Request) (*url.URL, error) { } return url.Parse(config.ProxyURL) } + +// jsonError is a generic JSON error response. +// +// TODO(a.garipov): Merge together with the implementations in .../dhcpd and +// other packages after refactoring the web handler registering. +type jsonError struct { + // Message is the error message, an opaque string. + Message string `json:"message"` +} diff --git a/internal/home/mobileconfig.go b/internal/home/mobileconfig.go index 81fe4b1a..58bdea88 100644 --- a/internal/home/mobileconfig.go +++ b/internal/home/mobileconfig.go @@ -1,10 +1,11 @@ package home import ( + "encoding/json" "fmt" - "net" "net/http" + "github.com/AdguardTeam/golibs/log" uuid "github.com/satori/go.uuid" "howett.net/plist" ) @@ -51,6 +52,7 @@ func getMobileConfig(d DNSSettings) ([]byte, error) { switch d.DNSProtocol { case dnsProtoHTTPS: name = fmt.Sprintf("%s DoH", d.ServerName) + d.ServerURL = fmt.Sprintf("https://%s/dns-query", d.ServerName) case dnsProtoTLS: name = fmt.Sprintf("%s DoT", d.ServerName) default: @@ -80,34 +82,46 @@ func getMobileConfig(d DNSSettings) ([]byte, error) { return plist.MarshalIndent(data, plist.XMLFormat, "\t") } -func handleMobileConfig(w http.ResponseWriter, d DNSSettings) { +func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) { + host := r.URL.Query().Get("host") + if host == "" { + host = Context.tls.conf.ServerName + } + + if host == "" { + w.WriteHeader(http.StatusInternalServerError) + + const msg = "no host in query parameters and no server_name" + err := json.NewEncoder(w).Encode(&jsonError{ + Message: msg, + }) + if err != nil { + log.Debug("writing 500 json response: %s", err) + } + + return + } + + d := DNSSettings{ + DNSProtocol: dnsp, + ServerName: host, + } + mobileconfig, err := getMobileConfig(d) if err != nil { httpError(w, http.StatusInternalServerError, "plist.MarshalIndent: %s", err) + + return } w.Header().Set("Content-Type", "application/xml") _, _ = w.Write(mobileconfig) } -func handleMobileConfigDoh(w http.ResponseWriter, r *http.Request) { - handleMobileConfig(w, DNSSettings{ - DNSProtocol: dnsProtoHTTPS, - ServerURL: fmt.Sprintf("https://%s/dns-query", r.Host), - }) +func handleMobileConfigDOH(w http.ResponseWriter, r *http.Request) { + handleMobileConfig(w, r, dnsProtoHTTPS) } -func handleMobileConfigDot(w http.ResponseWriter, r *http.Request) { - var err error - - var host string - host, _, err = net.SplitHostPort(r.Host) - if err != nil { - httpError(w, http.StatusBadRequest, "getting host: %s", err) - } - - handleMobileConfig(w, DNSSettings{ - DNSProtocol: dnsProtoTLS, - ServerName: host, - }) +func handleMobileConfigDOT(w http.ResponseWriter, r *http.Request) { + handleMobileConfig(w, r, dnsProtoTLS) } diff --git a/internal/home/mobileconfig_test.go b/internal/home/mobileconfig_test.go index f58f4e99..520d70e1 100644 --- a/internal/home/mobileconfig_test.go +++ b/internal/home/mobileconfig_test.go @@ -9,25 +9,132 @@ import ( "howett.net/plist" ) -func TestHandleMobileConfigDot(t *testing.T) { - var err error +func TestHandleMobileConfigDOH(t *testing.T) { + t.Run("success", func(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org", nil) + assert.Nil(t, err) - var r *http.Request - r, err = http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil) - assert.Nil(t, err) + w := httptest.NewRecorder() - w := httptest.NewRecorder() + handleMobileConfigDOH(w, r) + assert.Equal(t, http.StatusOK, w.Code) - handleMobileConfigDot(w, r) - assert.Equal(t, http.StatusOK, w.Code) + var mc MobileConfig + _, err = plist.Unmarshal(w.Body.Bytes(), &mc) + assert.Nil(t, err) - var mc MobileConfig - _, err = plist.Unmarshal(w.Body.Bytes(), &mc) - assert.Nil(t, err) + if assert.Equal(t, 1, len(mc.PayloadContent)) { + assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name) + assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName) + assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) + assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL) + } + }) - if assert.Equal(t, 1, len(mc.PayloadContent)) { - assert.Equal(t, "example.com DoT", mc.PayloadContent[0].Name) - assert.Equal(t, "example.com DoT", mc.PayloadContent[0].PayloadDisplayName) - assert.Equal(t, "example.com", mc.PayloadContent[0].DNSSettings.ServerName) - } + t.Run("success_no_host", func(t *testing.T) { + oldTLSConf := Context.tls + t.Cleanup(func() { Context.tls = oldTLSConf }) + + Context.tls = &TLSMod{ + conf: tlsConfigSettings{ServerName: "example.org"}, + } + + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOH(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + var mc MobileConfig + _, err = plist.Unmarshal(w.Body.Bytes(), &mc) + assert.Nil(t, err) + + if assert.Equal(t, 1, len(mc.PayloadContent)) { + assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name) + assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName) + assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) + assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL) + } + }) + + t.Run("error_no_host", func(t *testing.T) { + oldTLSConf := Context.tls + t.Cleanup(func() { Context.tls = oldTLSConf }) + + Context.tls = &TLSMod{conf: tlsConfigSettings{}} + + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOH(w, r) + assert.Equal(t, http.StatusInternalServerError, w.Code) + }) +} + +func TestHandleMobileConfigDOT(t *testing.T) { + t.Run("success", func(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOT(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + var mc MobileConfig + _, err = plist.Unmarshal(w.Body.Bytes(), &mc) + assert.Nil(t, err) + + if assert.Equal(t, 1, len(mc.PayloadContent)) { + assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name) + assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName) + assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) + } + }) + + t.Run("success_no_host", func(t *testing.T) { + oldTLSConf := Context.tls + t.Cleanup(func() { Context.tls = oldTLSConf }) + + Context.tls = &TLSMod{ + conf: tlsConfigSettings{ServerName: "example.org"}, + } + + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOT(w, r) + assert.Equal(t, http.StatusOK, w.Code) + + var mc MobileConfig + _, err = plist.Unmarshal(w.Body.Bytes(), &mc) + assert.Nil(t, err) + + if assert.Equal(t, 1, len(mc.PayloadContent)) { + assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name) + assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName) + assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName) + } + }) + + t.Run("error_no_host", func(t *testing.T) { + oldTLSConf := Context.tls + t.Cleanup(func() { Context.tls = oldTLSConf }) + + Context.tls = &TLSMod{conf: tlsConfigSettings{}} + + r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil) + assert.Nil(t, err) + + w := httptest.NewRecorder() + + handleMobileConfigDOT(w, r) + assert.Equal(t, http.StatusInternalServerError, w.Code) + }) } diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 05552218..d2b36cdf 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -959,27 +959,61 @@ 'application/json': 'schema': '$ref': '#/components/schemas/ProfileInfo' + '/apple/doh.mobileconfig': 'get': - 'tags': - - 'mobileconfig' - - 'global' 'operationId': 'mobileConfigDoH' - 'summary': 'Get DNS over HTTPS .mobileconfig' + 'parameters': + - 'description': > + Host for which the config is generated. If no host is provided, + `tls.server_name` from the configuration file is used. If + `tls.server_name` is not set, the API returns an error with a 500 + status. + 'example': 'example.org' + 'in': 'query' + 'name': 'host' + 'schema': + 'type': 'string' 'responses': '200': - 'description': 'DNS over HTTPS plist file' - - '/apple/dot.mobileconfig': - 'get': + 'description': 'DNS over HTTPS plist file.' + '500': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Server configuration error.' + 'summary': 'Get DNS over HTTPS .mobileconfig.' 'tags': - 'mobileconfig' - 'global' + '/apple/dot.mobileconfig': + 'get': 'operationId': 'mobileConfigDoT' - 'summary': 'Get TLS over TLS .mobileconfig' + 'parameters': + - 'description': > + Host for which the config is generated. If no host is provided, + `tls.server_name` from the configuration file is used. If + `tls.server_name` is not set, the API returns an error with a 500 + status. + 'example': 'example.org' + 'in': 'query' + 'name': 'host' + 'schema': + 'type': 'string' 'responses': '200': 'description': 'DNS over TLS plist file' + '500': + 'content': + 'application/json': + 'schema': + '$ref': '#/components/schemas/Error' + 'description': 'Server configuration error.' + 'summary': 'Get DNS over TLS .mobileconfig.' + 'tags': + - 'mobileconfig' + - 'global' 'components': 'requestBodies': From f9e4e7b024f87566223792c71578c097f35d4089 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 27 Nov 2020 12:33:25 +0300 Subject: [PATCH 17/54] Pull request: fix querylog bug Merge in DNS/adguard-home from 2345-querylog-bug-fix to master Closes #2345. Squashed commit of the following: commit 3ebd13e059242b041f3c4d77583a077f9e619b48 Author: Eugene Burkov Date: Fri Nov 27 12:14:49 2020 +0300 all: make changelog more humanly readable. commit 3c9bb1be6aec113ebebdb40c976dbdb821f75638 Author: Eugene Burkov Date: Thu Nov 26 14:43:14 2020 +0300 all: log changes commit 08c67da926aa085fabdec31c092285a351eb0e08 Merge: 650d2241e 96e83a133 Author: Eugene Burkov Date: Thu Nov 26 14:42:18 2020 +0300 Merge branch 'master' into 2345-querylog-bug-fix commit 650d2241e02cf54a7e1f7a611199e770fd119953 Author: Eugene Burkov Date: Thu Nov 26 14:02:57 2020 +0300 querylog: fix json parsing bug --- CHANGELOG.md | 2 ++ internal/querylog/decode.go | 47 +++++++++++++------------ internal/querylog/decode_test.go | 60 ++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0dfe8c8..1ec8e95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,9 +37,11 @@ and this project adheres to ### Fixed +- A JSON parsing error in query log [#2345]. - Incorrect detection of the IPv6 address of an interface as well as another infinite loop in the `/dhcp/find_active_dhcp` HTTP API [#2355]. +[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index cb798aa7..b385f3eb 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -3,7 +3,7 @@ package querylog import ( "encoding/base64" "encoding/json" - "strconv" + "io" "strings" "time" @@ -84,15 +84,11 @@ var logEntryHandlers = map[string](func(t json.Token, ent *logEntry) error){ return err }, "IsFiltered": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + v, ok := t.(bool) if !ok { return nil } - b, err := strconv.ParseBool(v) - if err != nil { - return err - } - ent.Result.IsFiltered = b + ent.Result.IsFiltered = v return nil }, "Rule": func(t json.Token, ent *logEntry) error { @@ -104,23 +100,23 @@ var logEntryHandlers = map[string](func(t json.Token, ent *logEntry) error){ return nil }, "FilterID": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + v, ok := t.(json.Number) if !ok { return nil } - i, err := strconv.Atoi(v) + i, err := v.Int64() if err != nil { return err } - ent.Result.FilterID = int64(i) + ent.Result.FilterID = i return nil }, "Reason": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + v, ok := t.(json.Number) if !ok { return nil } - i, err := strconv.Atoi(v) + i, err := v.Int64() if err != nil { return err } @@ -144,17 +140,20 @@ var logEntryHandlers = map[string](func(t json.Token, ent *logEntry) error){ return nil }, "Elapsed": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + v, ok := t.(json.Number) if !ok { return nil } - i, err := strconv.Atoi(v) + i, err := v.Int64() if err != nil { return err } ent.Elapsed = time.Duration(i) return nil }, + "Result": func(json.Token, *logEntry) error { + return nil + }, "Question": func(t json.Token, ent *logEntry) error { v, ok := t.(string) if !ok { @@ -192,10 +191,13 @@ var logEntryHandlers = map[string](func(t json.Token, ent *logEntry) error){ func decodeLogEntry(ent *logEntry, str string) { dec := json.NewDecoder(strings.NewReader(str)) - for dec.More() { + dec.UseNumber() + for { keyToken, err := dec.Token() if err != nil { - log.Debug("decodeLogEntry err: %s", err) + if err != io.EOF { + log.Debug("decodeLogEntry err: %s", err) + } return } if _, ok := keyToken.(json.Delim); ok { @@ -203,15 +205,16 @@ func decodeLogEntry(ent *logEntry, str string) { } key := keyToken.(string) handler, ok := logEntryHandlers[key] - value, err := dec.Token() + if !ok { + continue + } + val, err := dec.Token() if err != nil { return } - if ok { - if err := handler(value, ent); err != nil { - log.Debug("decodeLogEntry err: %s", err) - return - } + if err := handler(val, ent); err != nil { + log.Debug("decodeLogEntry err: %s", err) + return } } } diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index e9c7c9f7..02ee77df 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -32,6 +32,66 @@ func TestDecode_decodeQueryLog(t *testing.T) { name: "back_compatibility_bad_decoding", log: `{"Question":"LgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`, want: "decodeLogEntry err: illegal base64 data at input byte 48\n", + }, { + name: "modern_all_right", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_filter_id", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`, + want: "decodeLogEntry err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n", + }, { + name: "bad_is_filtered", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_elapsed", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`, + want: "default", + }, { + name: "bad_ip", + log: `{"IP":127001,"T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_time", + log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "decodeLogEntry err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n", + }, { + name: "bad_host", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_type", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":true,"QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_class", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":false,"CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_client_proto", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":8,"Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "very_bad_client_proto", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "decodeLogEntry err: invalid client proto: \"dog\"\n", + }, { + name: "bad_answer", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "very_bad_answer", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "decodeLogEntry err: illegal base64 data at input byte 61\n", + }, { + name: "bad_rule", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`, + want: "default", + }, { + name: "bad_reason", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":true,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + want: "default", }} for _, tc := range testCases { From a6e18c47000425dc28a2375b2480a16c84d85438 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 27 Nov 2020 14:39:43 +0300 Subject: [PATCH 18/54] Pull request: dhcpd: wait for interfaces' ip addresses to appear Merge in DNS/adguard-home from 2304-dncp-backoff to master Updates #2304. Squashed commit of the following: commit c9bff8b27c6b031d43a7dd98152adcde7f49fff1 Author: Ainar Garipov Date: Fri Nov 27 14:08:03 2020 +0300 dhcpd: try for 5s instead of 10s commit 983cf471832de0e7762b8b6e0a4ba9bb76ecadfc Author: Ainar Garipov Date: Wed Nov 25 19:58:41 2020 +0300 dhcpd: wait for interfaces' ip addresses to appear --- HACKING.md | 67 +++++----- internal/dhcpd/check_other_dhcp.go | 4 +- internal/dhcpd/v4.go | 46 +------ internal/dhcpd/v46.go | 123 +++++++++++++++++++ internal/dhcpd/v46_test.go | 189 +++++++++++++++++++++++++++++ internal/dhcpd/v6.go | 53 ++------ internal/dhcpd/v6_test.go | 55 --------- 7 files changed, 365 insertions(+), 172 deletions(-) create mode 100644 internal/dhcpd/v46.go create mode 100644 internal/dhcpd/v46_test.go diff --git a/HACKING.md b/HACKING.md index bf56c270..ae987af6 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,8 +1,9 @@ # *AdGuardHome* Developer Guidelines -As of **2020-11-20**, this document is still a work-in-progress. Some of the -rules aren't enforced, and others might change. Still, this is a good place to -find out about how we **want** our code to look like. +As of **2020-11-27**, this document is a work-in-progress, but should still be +followed. Some of the rules aren't enforced as thoroughly or remain broken in +old code, but this is still the place to find out about what we **want** our +code to look like. The rules are mostly sorted in the alphabetical order. @@ -31,27 +32,17 @@ The rules are mostly sorted in the alphabetical order. ## *Go* - * . + ### Code And Naming - * . - - * - - * Add an empty line before `break`, `continue`, and `return`, unless it's the - only statement in that block. + * Avoid `goto`. * Avoid `init` and use explicit initialization functions instead. * Avoid `new`, especially with structs. - * Document everything, including unexported top-level identifiers, to build - a habit of writing documentation. - * Constructors should validate their arguments and return meaningful errors. As a corollary, avoid lazy initialization. - * Don't put variable names into any kind of quotes. - * Don't use naked `return`s. * Don't use underscores in file and package names, unless they're build tags @@ -76,25 +67,34 @@ The rules are mostly sorted in the alphabetical order. * Name the deferred errors (e.g. when closing something) `cerr`. - * No `goto`. - * No shadowing, since it can often lead to subtle bugs, especially with errors. * Prefer constants to variables where possible. Reduce global variables. Use [constant errors] instead of `errors.New`. - * Put comments above the documented entity, **not** to the side, to improve - readability. - - * Use `gofumpt --extra -s`. - - **TODO(a.garipov):** Add to the linters. - * Use linters. * Use named returns to improve readability of function signatures. + * Write logs and error messages in lowercase only to make it easier to `grep` + logs and error messages without using the `-i` flag. + +[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors +[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation + + ### Commenting + + * See also the *Text, Including Comments* section below. + + * Document everything, including unexported top-level identifiers, to build + a habit of writing documentation. + + * Don't put identifiers into any kind of quotes. + + * Put comments above the documented entity, **not** to the side, to improve + readability. + * When a method implements an interface, start the doc comment with the standard template: @@ -105,8 +105,14 @@ The rules are mostly sorted in the alphabetical order. } ``` - * Write logs and error messages in lowercase only to make it easier to `grep` - logs and error messages without using the `-i` flag. + ### Formatting + + * Add an empty line before `break`, `continue`, `fallthrough`, and `return`, + unless it's the only statement in that block. + + * Use `gofumpt --extra -s`. + + **TODO(a.garipov):** Add to the linters. * Write slices of struct like this: @@ -123,8 +129,13 @@ The rules are mostly sorted in the alphabetical order. }} ``` -[constant errors]: https://dave.cheney.net/2016/04/07/constant-errors -[Linus said]: https://www.kernel.org/doc/html/v4.17/process/coding-style.html#indentation + ### Recommended Reading + + * . + + * . + + * ## *Markdown* diff --git a/internal/dhcpd/check_other_dhcp.go b/internal/dhcpd/check_other_dhcp.go index e77a7801..19512686 100644 --- a/internal/dhcpd/check_other_dhcp.go +++ b/internal/dhcpd/check_other_dhcp.go @@ -26,7 +26,7 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) { return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err) } - ifaceIPNet, err := ifaceIPv4Addrs(iface) + ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion4) if err != nil { return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err) } @@ -161,7 +161,7 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) { return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err) } - ifaceIPNet, err := ifaceIPv6Addrs(iface) + ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion6) if err != nil { return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err) } diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index d88272e4..686ee32f 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -16,7 +16,9 @@ import ( "github.com/insomniacslk/dhcp/dhcpv4/server4" ) -// v4Server - DHCPv4 server +// v4Server is a DHCPv4 server. +// +// TODO(a.garipov): Think about unifying this and v6Server. type v4Server struct { srv *server4.Server leasesLock sync.Mutex @@ -560,27 +562,6 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4 } } -// ifaceIPv4Addrs returns the interface's IPv4 addresses. -func ifaceIPv4Addrs(iface *net.Interface) (ips []net.IP, err error) { - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - for _, a := range addrs { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - - if ip := ipnet.IP.To4(); ip != nil { - ips = append(ips, ip) - } - } - - return ips, nil -} - // Start starts the IPv4 DHCP server. func (s *v4Server) Start() error { if !s.conf.Enabled { @@ -595,26 +576,9 @@ func (s *v4Server) Start() error { log.Debug("dhcpv4: starting...") - dnsIPAddrs, err := ifaceIPv4Addrs(iface) + dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff) if err != nil { - return fmt.Errorf("dhcpv4: getting ipv4 addrs for iface %s: %w", ifaceName, err) - } - - switch len(dnsIPAddrs) { - case 0: - log.Debug("dhcpv4: no ipv4 address for interface %s", iface.Name) - - return nil - case 1: - // Some Android devices use 8.8.8.8 if there is no secondary DNS - // server. Fix that by setting the secondary DNS address to our - // address as well. - // - // See https://github.com/AdguardTeam/AdGuardHome/issues/1708. - log.Debug("dhcpv4: setting secondary dns ip to iself for interface %s", iface.Name) - dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0]) - default: - // Go on. + return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err) } s.conf.dnsIPAddrs = dnsIPAddrs diff --git a/internal/dhcpd/v46.go b/internal/dhcpd/v46.go new file mode 100644 index 00000000..c8301875 --- /dev/null +++ b/internal/dhcpd/v46.go @@ -0,0 +1,123 @@ +package dhcpd + +import ( + "fmt" + "net" + "time" + + "github.com/AdguardTeam/golibs/log" +) + +// ipVersion is a documentational alias for int. Use it when the integer means +// IP version. +type ipVersion = int + +// IP version constants. +const ( + ipVersion4 ipVersion = 4 + ipVersion6 ipVersion = 6 +) + +// netIface is the interface for network interface methods. +type netIface interface { + Addrs() ([]net.Addr, error) +} + +// ifaceIPAddrs returns the interface's IP addresses. +func ifaceIPAddrs(iface netIface, ipv ipVersion) (ips []net.IP, err error) { + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + for _, a := range addrs { + var ip net.IP + switch a := a.(type) { + case *net.IPAddr: + ip = a.IP + case *net.IPNet: + ip = a.IP + default: + continue + } + + // Assume that net.(*Interface).Addrs can only return valid IPv4 + // and IPv6 addresses. Thus, if it isn't an IPv4 address, it + // must be an IPv6 one. + switch ipv { + case ipVersion4: + if ip4 := ip.To4(); ip4 != nil { + ips = append(ips, ip4) + } + case ipVersion6: + if ip6 := ip.To4(); ip6 == nil { + ips = append(ips, ip) + } + default: + return nil, fmt.Errorf("invalid ip version %d", ipv) + } + } + + return ips, nil +} + +// Currently used defaults for ifaceDNSAddrs. +const ( + defaultMaxAttempts int = 10 + + defaultBackoff time.Duration = 500 * time.Millisecond +) + +// ifaceDNSIPAddrs returns IP addresses of the interface suitable to send to +// clients as DNS addresses. If err is nil, addrs contains either no addresses +// or at least two. +// +// It makes up to maxAttempts attempts to get the addresses if there are none, +// each time using the provided backoff. Sometimes an interface needs a few +// seconds to really ititialize. +// +// See https://github.com/AdguardTeam/AdGuardHome/issues/2304. +func ifaceDNSIPAddrs( + iface netIface, + ipv ipVersion, + maxAttempts int, + backoff time.Duration, +) (addrs []net.IP, err error) { + var n int +waitForIP: + for n = 1; n <= maxAttempts; n++ { + addrs, err = ifaceIPAddrs(iface, ipv) + if err != nil { + return nil, fmt.Errorf("getting ip addrs: %w", err) + } + + switch len(addrs) { + case 0: + log.Debug("dhcpv%d: attempt %d: no ip addresses", ipv, n) + + time.Sleep(backoff) + case 1: + // Some Android devices use 8.8.8.8 if there is not + // a secondary DNS server. Fix that by setting the + // secondary DNS address to the same address. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/1708. + log.Debug("dhcpv%d: setting secondary dns ip to itself", ipv) + addrs = append(addrs, addrs[0]) + + fallthrough + default: + break waitForIP + } + } + + if len(addrs) == 0 { + // Don't return errors in case the users want to try and enable + // the DHCP server later. + log.Error("dhcpv%d: no ip address for interface after %d attempts and %s", ipv, n, time.Duration(n)*backoff) + } else { + log.Debug("dhcpv%d: got addresses %s after %d attempts", ipv, addrs, n) + } + + return addrs, nil +} diff --git a/internal/dhcpd/v46_test.go b/internal/dhcpd/v46_test.go new file mode 100644 index 00000000..6007205d --- /dev/null +++ b/internal/dhcpd/v46_test.go @@ -0,0 +1,189 @@ +package dhcpd + +import ( + "errors" + "net" + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/agherr" + "github.com/stretchr/testify/assert" +) + +type fakeIface struct { + addrs []net.Addr + err error +} + +// Addrs implements the netIface interface for *fakeIface. +func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) { + if iface.err != nil { + return nil, iface.err + } + + return iface.addrs, nil +} + +func TestIfaceIPAddrs(t *testing.T) { + const errTest agherr.Error = "test error" + + ip4 := net.IP{1, 2, 3, 4} + addr4 := &net.IPNet{IP: ip4} + + ip6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + addr6 := &net.IPNet{IP: ip6} + + testCases := []struct { + name string + iface netIface + ipv ipVersion + want []net.IP + wantErr error + }{{ + name: "ipv4_success", + iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil}, + ipv: ipVersion4, + want: []net.IP{ip4}, + wantErr: nil, + }, { + name: "ipv4_success_with_ipv6", + iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil}, + ipv: ipVersion4, + want: []net.IP{ip4}, + wantErr: nil, + }, { + name: "ipv4_error", + iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest}, + ipv: ipVersion4, + want: nil, + wantErr: errTest, + }, { + name: "ipv6_success", + iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil}, + ipv: ipVersion6, + want: []net.IP{ip6}, + wantErr: nil, + }, { + name: "ipv6_success_with_ipv4", + iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil}, + ipv: ipVersion6, + want: []net.IP{ip6}, + wantErr: nil, + }, { + name: "ipv6_error", + iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest}, + ipv: ipVersion6, + want: nil, + wantErr: errTest, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, gotErr := ifaceIPAddrs(tc.iface, tc.ipv) + assert.Equal(t, tc.want, got) + assert.True(t, errors.Is(gotErr, tc.wantErr)) + }) + } +} + +type waitingFakeIface struct { + addrs []net.Addr + err error + n int +} + +// Addrs implements the netIface interface for *waitingFakeIface. +func (iface *waitingFakeIface) Addrs() (addrs []net.Addr, err error) { + if iface.err != nil { + return nil, iface.err + } + + if iface.n == 0 { + return iface.addrs, nil + } + + iface.n-- + + return nil, nil +} + +func TestIfaceDNSIPAddrs(t *testing.T) { + const errTest agherr.Error = "test error" + + ip4 := net.IP{1, 2, 3, 4} + addr4 := &net.IPNet{IP: ip4} + + ip6 := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + addr6 := &net.IPNet{IP: ip6} + + testCases := []struct { + name string + iface netIface + ipv ipVersion + want []net.IP + wantErr error + }{{ + name: "ipv4_success", + iface: &fakeIface{addrs: []net.Addr{addr4}, err: nil}, + ipv: ipVersion4, + want: []net.IP{ip4, ip4}, + wantErr: nil, + }, { + name: "ipv4_success_with_ipv6", + iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil}, + ipv: ipVersion4, + want: []net.IP{ip4, ip4}, + wantErr: nil, + }, { + name: "ipv4_error", + iface: &fakeIface{addrs: []net.Addr{addr4}, err: errTest}, + ipv: ipVersion4, + want: nil, + wantErr: errTest, + }, { + name: "ipv4_wait", + iface: &waitingFakeIface{ + addrs: []net.Addr{addr4}, + err: nil, + n: 1, + }, + ipv: ipVersion4, + want: []net.IP{ip4, ip4}, + wantErr: nil, + }, { + name: "ipv6_success", + iface: &fakeIface{addrs: []net.Addr{addr6}, err: nil}, + ipv: ipVersion6, + want: []net.IP{ip6, ip6}, + wantErr: nil, + }, { + name: "ipv6_success_with_ipv4", + iface: &fakeIface{addrs: []net.Addr{addr6, addr4}, err: nil}, + ipv: ipVersion6, + want: []net.IP{ip6, ip6}, + wantErr: nil, + }, { + name: "ipv6_error", + iface: &fakeIface{addrs: []net.Addr{addr6}, err: errTest}, + ipv: ipVersion6, + want: nil, + wantErr: errTest, + }, { + name: "ipv6_wait", + iface: &waitingFakeIface{ + addrs: []net.Addr{addr6}, + err: nil, + n: 1, + }, + ipv: ipVersion6, + want: []net.IP{ip6, ip6}, + wantErr: nil, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got, gotErr := ifaceDNSIPAddrs(tc.iface, tc.ipv, 2, 0) + assert.Equal(t, tc.want, got) + assert.True(t, errors.Is(gotErr, tc.wantErr)) + }) + } +} diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index f1ec57b3..300788e6 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -17,7 +17,9 @@ import ( const valueIAID = "ADGH" // value for IANA.ID -// v6Server - DHCPv6 server +// v6Server is a DHCPv6 server. +// +// TODO(a.garipov): Think about unifying this and v4Server. type v6Server struct { srv *server6.Server leasesLock sync.Mutex @@ -537,34 +539,6 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6. } } -type netIface interface { - Addrs() ([]net.Addr, error) -} - -// ifaceIPv6Addrs returns the interface's IPv6 addresses. -func ifaceIPv6Addrs(iface netIface) (ips []net.IP, err error) { - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - - for _, a := range addrs { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - - if ip := ipnet.IP.To4(); ip == nil { - // Assume that net.(*Interface).Addrs can only return - // valid IPv4 and IPv6 addresses. Since this isn't an - // IPv4 address, it must be an IPv6 one. - ips = append(ips, ipnet.IP) - } - } - - return ips, nil -} - // initialize RA module func (s *v6Server) initRA(iface *net.Interface) error { // choose the source IP address - should be link-local-unicast @@ -598,24 +572,11 @@ func (s *v6Server) Start() error { return fmt.Errorf("dhcpv6: finding interface %s by name: %w", ifaceName, err) } - log.Debug("dhcpv4: starting...") + log.Debug("dhcpv6: starting...") - dnsIPAddrs, err := ifaceIPv6Addrs(iface) + dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion6, defaultMaxAttempts, defaultBackoff) if err != nil { - return fmt.Errorf("dhcpv6: getting ipv6 addrs for iface %s: %w", ifaceName, err) - } - - switch len(dnsIPAddrs) { - case 0: - log.Debug("dhcpv6: no ipv6 address for interface %s", iface.Name) - - return nil - case 1: - // See the comment in (*v4Server).Start. - log.Debug("dhcpv6: setting secondary dns ip to iself for interface %s", iface.Name) - dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0]) - default: - // Go on. + return fmt.Errorf("dhcpv6: interface %s: %w", ifaceName, err) } s.conf.dnsIPAddrs = dnsIPAddrs @@ -631,7 +592,7 @@ func (s *v6Server) Start() error { return nil } - log.Debug("DHCPv6: starting...") + log.Debug("dhcpv6: listening...") if len(iface.HardwareAddr) != 6 { return fmt.Errorf("dhcpv6: invalid MAC %s", iface.HardwareAddr) diff --git a/internal/dhcpd/v6_test.go b/internal/dhcpd/v6_test.go index d51c695c..7d7dd678 100644 --- a/internal/dhcpd/v6_test.go +++ b/internal/dhcpd/v6_test.go @@ -6,7 +6,6 @@ import ( "net" "testing" - "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/insomniacslk/dhcp/dhcpv6" "github.com/insomniacslk/dhcp/iana" "github.com/stretchr/testify/assert" @@ -224,57 +223,3 @@ func TestV6GetDynamicLease(t *testing.T) { assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::2"))) assert.True(t, ip6InRange(net.ParseIP("2001::2"), net.ParseIP("2001::3"))) } - -type fakeIface struct { - addrs []net.Addr - err error -} - -// Addrs implements the netIface interface for *fakeIface. -func (iface *fakeIface) Addrs() (addrs []net.Addr, err error) { - if iface.err != nil { - return nil, iface.err - } - - return iface.addrs, nil -} - -func TestIfaceIPv6Addrs(t *testing.T) { - ip := net.IP{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} - ip4 := net.IP{1, 2, 3, 4} - addr := &net.IPNet{IP: ip} - errTest := agherr.Error("test error") - - testCases := []struct { - name string - iface netIface - want []net.IP - wantErr error - }{{ - name: "success", - iface: &fakeIface{addrs: []net.Addr{addr}, err: nil}, - want: []net.IP{ip}, - wantErr: nil, - }, { - name: "success_with_ipv4", - iface: &fakeIface{ - addrs: []net.Addr{addr, &net.IPNet{IP: ip4}}, - err: nil, - }, - want: []net.IP{ip}, - wantErr: nil, - }, { - name: "error", - iface: &fakeIface{addrs: []net.Addr{addr}, err: errTest}, - want: nil, - wantErr: errTest, - }} - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - got, gotErr := ifaceIPv6Addrs(tc.iface) - assert.Equal(t, tc.want, got) - assert.Equal(t, tc.wantErr, gotErr) - }) - } -} From 60d72fb9c392c24711d3142efddde7c8fe85dfbe Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 27 Nov 2020 17:03:00 +0300 Subject: [PATCH 19/54] Pull request: all: update changelog Merge in DNS/adguard-home from update-docs to master Squashed commit of the following: commit 177ce523ecc31405837eaad46d894bdce4cbee00 Author: Ainar Garipov Date: Fri Nov 27 16:45:44 2020 +0300 all: update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ec8e95a..74355e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,12 @@ and this project adheres to ### Added +- A 5 second wait period until a DHCP server's network interface gets an IP + address [#2304]. - `$dnstype` modifier for filters [#2337]. - HTTP API request body size limit [#2305]. +[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 From 6e615c6eaacab342c2ed1bf949aa664d1fa1a2a7 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 30 Nov 2020 13:32:58 +0300 Subject: [PATCH 20/54] Pull request: querylog: resort buffers Merge in DNS/adguard-home from 2293-log-sort to master Updates #2293. Squashed commit of the following: commit f8961e5c52f82befe23ab1f7603a867243186498 Author: Ainar Garipov Date: Sat Nov 28 17:19:15 2020 +0300 all: document changes commit c92c53307f1ed4a1c3196bdc19d23a775876b106 Author: Ainar Garipov Date: Sat Nov 28 16:44:01 2020 +0300 querylog: resort buffers --- CHANGELOG.md | 3 + internal/querylog/{qlog_http.go => http.go} | 2 +- internal/querylog/json.go | 6 +- internal/querylog/qlog_test.go | 74 ++++++++++++++++++- internal/querylog/querylog_file.go | 2 +- .../{querylog_search.go => search.go} | 9 +++ internal/querylog/search_criteria.go | 2 +- 7 files changed, 91 insertions(+), 7 deletions(-) rename internal/querylog/{qlog_http.go => http.go} (99%) rename internal/querylog/{querylog_search.go => search.go} (94%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74355e71..cdd806a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,10 +40,13 @@ and this project adheres to ### Fixed +- A mitigation against records being shown in the wrong order on the query log + page [#2293]. - A JSON parsing error in query log [#2345]. - Incorrect detection of the IPv6 address of an interface as well as another infinite loop in the `/dhcp/find_active_dhcp` HTTP API [#2355]. +[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 diff --git a/internal/querylog/qlog_http.go b/internal/querylog/http.go similarity index 99% rename from internal/querylog/qlog_http.go rename to internal/querylog/http.go index 473df2a4..9bc63b7e 100644 --- a/internal/querylog/qlog_http.go +++ b/internal/querylog/http.go @@ -47,7 +47,7 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) { entries, oldest := l.search(params) // convert log entries to JSON - var data = l.entriesToJSON(entries, oldest) + data := l.entriesToJSON(entries, oldest) jsonVal, err := json.Marshal(data) if err != nil { diff --git a/internal/querylog/json.go b/internal/querylog/json.go index 46f3ea0b..4130ce9e 100644 --- a/internal/querylog/json.go +++ b/internal/querylog/json.go @@ -32,7 +32,7 @@ func (l *queryLog) getClientIP(clientIP string) string { // entriesToJSON - converts log entries to JSON func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[string]interface{} { // init the response object - var data = []map[string]interface{}{} + data := []map[string]interface{}{} // the elements order is already reversed (from newer to older) for i := 0; i < len(entries); i++ { @@ -41,7 +41,7 @@ func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[stri data = append(data, jsonEntry) } - var result = map[string]interface{}{} + result := map[string]interface{}{} result["oldest"] = "" if !oldest.IsZero() { result["oldest"] = oldest.Format(time.RFC3339Nano) @@ -123,7 +123,7 @@ func answerToMap(a *dns.Msg) []map[string]interface{} { return nil } - var answers = []map[string]interface{}{} + answers := []map[string]interface{}{} for _, k := range a.Answer { header := k.Header() answer := map[string]interface{}{ diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index 4dec5175..b6651158 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -1,9 +1,12 @@ package querylog import ( + "math/rand" "net" "os" + "sort" "testing" + "time" "github.com/AdguardTeam/dnsproxy/proxyutil" @@ -20,7 +23,7 @@ func TestMain(m *testing.M) { func prepareTestDir() string { const dir = "./agh-test" _ = os.RemoveAll(dir) - _ = os.MkdirAll(dir, 0755) + _ = os.MkdirAll(dir, 0o755) return dir } @@ -263,3 +266,72 @@ func assertLogEntry(t *testing.T, entry *logEntry, host, answer, client string) assert.Equal(t, answer, ip.String()) return true } + +func testEntries() (entries []*logEntry) { + rsrc := rand.NewSource(time.Now().UnixNano()) + rgen := rand.New(rsrc) + + entries = make([]*logEntry, 1000) + for i := range entries { + min := rgen.Intn(60) + sec := rgen.Intn(60) + entries[i] = &logEntry{ + Time: time.Date(2020, 1, 1, 0, min, sec, 0, time.UTC), + } + } + + return entries +} + +// logEntriesByTimeDesc is a wrapper over []*logEntry for sorting. +// +// NOTE(a.garipov): Weirdly enough, on my machine this gets consistently +// outperformed by sort.Slice, see the benchmark below. I'm leaving this +// implementation here, in tests, in case we want to make sure it outperforms on +// most machines, but for now this is unused in the actual code. +type logEntriesByTimeDesc []*logEntry + +// Len implements the sort.Interface interface for logEntriesByTimeDesc. +func (les logEntriesByTimeDesc) Len() (n int) { return len(les) } + +// Less implements the sort.Interface interface for logEntriesByTimeDesc. +func (les logEntriesByTimeDesc) Less(i, j int) (less bool) { + return les[i].Time.After(les[j].Time) +} + +// Swap implements the sort.Interface interface for logEntriesByTimeDesc. +func (les logEntriesByTimeDesc) Swap(i, j int) { les[i], les[j] = les[j], les[i] } + +func BenchmarkLogEntry_sort(b *testing.B) { + b.Run("methods", func(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + entries := testEntries() + b.StartTimer() + + sort.Stable(logEntriesByTimeDesc(entries)) + } + }) + + b.Run("reflect", func(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + entries := testEntries() + b.StartTimer() + + sort.SliceStable(entries, func(i, j int) (less bool) { + return entries[i].Time.After(entries[j].Time) + }) + } + }) +} + +func TestLogEntriesByTime_sort(t *testing.T) { + entries := testEntries() + sort.Sort(logEntriesByTimeDesc(entries)) + + for i := 1; i < len(entries); i++ { + assert.False(t, entries[i].Time.After(entries[i-1].Time), + "%s %s", entries[i].Time, entries[i-1].Time) + } +} diff --git a/internal/querylog/querylog_file.go b/internal/querylog/querylog_file.go index 42e1ddf9..c6d48235 100644 --- a/internal/querylog/querylog_file.go +++ b/internal/querylog/querylog_file.go @@ -65,7 +65,7 @@ func (l *queryLog) flushToFile(buffer []*logEntry) error { l.fileWriteLock.Lock() defer l.fileWriteLock.Unlock() - f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) if err != nil { log.Error("failed to create file \"%s\": %s", filename, err) return err diff --git a/internal/querylog/querylog_search.go b/internal/querylog/search.go similarity index 94% rename from internal/querylog/querylog_search.go rename to internal/querylog/search.go index 2e8ce333..0b9aa7d2 100644 --- a/internal/querylog/querylog_search.go +++ b/internal/querylog/search.go @@ -2,6 +2,7 @@ package querylog import ( "io" + "sort" "time" "github.com/AdguardTeam/AdGuardHome/internal/util" @@ -46,6 +47,14 @@ func (l *queryLog) search(params *searchParams) ([]*logEntry, time.Time) { entries = entries[:totalLimit] } + // Resort entries on start time to partially mitigate query log looking + // weird on the frontend. + // + // See https://github.com/AdguardTeam/AdGuardHome/issues/2293. + sort.SliceStable(entries, func(i, j int) (less bool) { + return entries[i].Time.After(entries[j].Time) + }) + if params.offset > 0 { if len(entries) > params.offset { entries = entries[params.offset:] diff --git a/internal/querylog/search_criteria.go b/internal/querylog/search_criteria.go index 73f2497e..bb573ea6 100644 --- a/internal/querylog/search_criteria.go +++ b/internal/querylog/search_criteria.go @@ -58,7 +58,7 @@ func (c *searchCriteria) quickMatch(line string) bool { } // quickMatchJSONValue - helper used by quickMatch -func (c *searchCriteria) quickMatchJSONValue(line string, propertyName string) bool { +func (c *searchCriteria) quickMatchJSONValue(line, propertyName string) bool { val := readJSONValue(line, propertyName) if len(val) == 0 { return false From 641db73a86a6d9d047f47c403bca70af320f23e9 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 30 Nov 2020 19:23:14 +0300 Subject: [PATCH 21/54] Pull request: 2231 autoupdate Merge in DNS/adguard-home from 2231-autoupdate to master Updates #2231. Squashed commit of the following: commit 4ee9148ee7a38f2759898302a2109aa982fb4ee9 Author: Eugene Burkov Date: Mon Nov 30 19:08:14 2020 +0300 sysutil: provide os-independent interface commit 778097c5fdeb1dec94f4cfc6443d08f92d9db0ba Author: Eugene Burkov Date: Mon Nov 30 16:40:33 2020 +0300 all: add sysutil package --- CHANGELOG.md | 4 ++- internal/home/control_update.go | 9 ++---- internal/home/home.go | 7 ++-- internal/home/service.go | 3 +- internal/sysutil/os_freebsd.go | 32 ++++++++++++++++++ internal/sysutil/os_linux.go | 34 ++++++++++++++++++++ internal/sysutil/os_unix.go | 32 ++++++++++++++++++ internal/{util => sysutil}/os_windows.go | 15 ++++++--- internal/{util => sysutil}/syslog_others.go | 7 ++-- internal/{util => sysutil}/syslog_windows.go | 8 +++-- internal/sysutil/sysutil.go | 31 ++++++++++++++++++ internal/util/os_freebsd.go | 32 ------------------ internal/util/os_unix.go | 32 ------------------ 13 files changed, 158 insertions(+), 88 deletions(-) create mode 100644 internal/sysutil/os_freebsd.go create mode 100644 internal/sysutil/os_linux.go create mode 100644 internal/sysutil/os_unix.go rename internal/{util => sysutil}/os_windows.go (69%) rename internal/{util => sysutil}/syslog_others.go (52%) rename internal/{util => sysutil}/syslog_windows.go (86%) create mode 100644 internal/sysutil/sysutil.go delete mode 100644 internal/util/os_freebsd.go delete mode 100644 internal/util/os_unix.go diff --git a/CHANGELOG.md b/CHANGELOG.md index cdd806a0..aa3afd28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,12 +26,14 @@ and this project adheres to ### Changed -- Make the mobileconfig HTTP API more robust and predictable, add parameters and +- Post-updating relaunch possibility is now determined OS-dependently ([#2231]). +- Made the mobileconfig HTTP API more robust and predictable, add parameters and improve error response ([#2358]). - Improved HTTP requests handling and timeouts. ([#2343]). - Our snap package now uses the `core20` image as its base [#2306]. - Various internal improvements ([#2271], [#2297]). +[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 diff --git a/internal/home/control_update.go b/internal/home/control_update.go index 2ab5d6a2..dcf428b9 100644 --- a/internal/home/control_update.go +++ b/internal/home/control_update.go @@ -10,8 +10,8 @@ import ( "strings" "syscall" + "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/AdGuardHome/internal/update" - "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/log" ) @@ -104,12 +104,7 @@ func getVersionResp(info update.VersionInfo) []byte { tlsConf.PortDNSOverQUIC < 1024)) || config.BindPort < 1024 || config.DNS.Port < 1024) { - // On UNIX, if we're running under a regular user, - // but with CAP_NET_BIND_SERVICE set on a binary file, - // and we're listening on ports <1024, - // we won't be able to restart after we replace the binary file, - // because we'll lose CAP_NET_BIND_SERVICE capability. - canUpdate, _ = util.HaveAdminRights() + canUpdate, _ = sysutil.CanBindPrivilegedPorts() } ret["can_autoupdate"] = canUpdate } diff --git a/internal/home/home.go b/internal/home/home.go index 82f5ccf4..a4be016f 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -26,6 +26,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/querylog" "github.com/AdguardTeam/AdGuardHome/internal/stats" + "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/AdGuardHome/internal/update" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/log" @@ -222,7 +223,7 @@ func setupConfig(args options) { if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && config.RlimitNoFile != 0 { - util.SetRlimit(config.RlimitNoFile) + sysutil.SetRlimit(config.RlimitNoFile) } // override bind host/port from the console @@ -376,7 +377,7 @@ func checkPermissions() { if runtime.GOOS == "windows" { // On Windows we need to have admin rights to run properly - admin, _ := util.HaveAdminRights() + admin, _ := sysutil.HaveAdminRights() if admin { return } @@ -493,7 +494,7 @@ func configureLogger(args options) { if ls.LogFile == configSyslog { // Use syslog where it is possible and eventlog on Windows - err := util.ConfigureSyslog(serviceName) + err := sysutil.ConfigureSyslog(serviceName) if err != nil { log.Fatalf("cannot initialize syslog: %s", err) } diff --git a/internal/home/service.go b/internal/home/service.go index fa86f943..aa243634 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -9,6 +9,7 @@ import ( "strings" "syscall" + "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/log" "github.com/kardianos/service" @@ -109,7 +110,7 @@ func sendSigReload() { log.Error("Can't read PID file %s: %s", pidfile, err) return } - err = util.SendProcessSignal(pid, syscall.SIGHUP) + err = sysutil.SendProcessSignal(pid, syscall.SIGHUP) if err != nil { log.Error("Can't send signal to PID %d: %s", pid, err) return diff --git a/internal/sysutil/os_freebsd.go b/internal/sysutil/os_freebsd.go new file mode 100644 index 00000000..48033f49 --- /dev/null +++ b/internal/sysutil/os_freebsd.go @@ -0,0 +1,32 @@ +//+build freebsd + +package sysutil + +import ( + "os" + "syscall" + + "github.com/AdguardTeam/golibs/log" +) + +func canBindPrivilegedPorts() (can bool, err error) { + return HaveAdminRights() +} + +func setRlimit(val uint) { + var rlim syscall.Rlimit + rlim.Max = int64(val) + rlim.Cur = int64(val) + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) + if err != nil { + log.Error("Setrlimit() failed: %v", err) + } +} + +func haveAdminRights() (bool, error) { + return os.Getuid() == 0, nil +} + +func sendProcessSignal(pid int, sig syscall.Signal) error { + return syscall.Kill(pid, sig) +} diff --git a/internal/sysutil/os_linux.go b/internal/sysutil/os_linux.go new file mode 100644 index 00000000..cfe8cf85 --- /dev/null +++ b/internal/sysutil/os_linux.go @@ -0,0 +1,34 @@ +//+build linux + +package sysutil + +import ( + "os" + "syscall" + + "github.com/AdguardTeam/golibs/log" + "golang.org/x/sys/unix" +) + +func canBindPrivilegedPorts() (can bool, err error) { + cnbs, err := unix.PrctlRetInt(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, unix.CAP_NET_BIND_SERVICE, 0, 0) + return cnbs == 1, err +} + +func setRlimit(val uint) { + var rlim syscall.Rlimit + rlim.Max = uint64(val) + rlim.Cur = uint64(val) + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) + if err != nil { + log.Error("Setrlimit() failed: %v", err) + } +} + +func haveAdminRights() (bool, error) { + return os.Getuid() == 0, nil +} + +func sendProcessSignal(pid int, sig syscall.Signal) error { + return syscall.Kill(pid, sig) +} diff --git a/internal/sysutil/os_unix.go b/internal/sysutil/os_unix.go new file mode 100644 index 00000000..0a182cdc --- /dev/null +++ b/internal/sysutil/os_unix.go @@ -0,0 +1,32 @@ +//+build aix darwin dragonfly netbsd openbsd solaris + +package sysutil + +import ( + "os" + "syscall" + + "github.com/AdguardTeam/golibs/log" +) + +func canBindPrivilegedPorts() (can bool, err error) { + return HaveAdminRights() +} + +func setRlimit(val uint) { + var rlim syscall.Rlimit + rlim.Max = uint64(val) + rlim.Cur = uint64(val) + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) + if err != nil { + log.Error("Setrlimit() failed: %v", err) + } +} + +func haveAdminRights() (bool, error) { + return os.Getuid() == 0, nil +} + +func sendProcessSignal(pid int, sig syscall.Signal) error { + return syscall.Kill(pid, sig) +} diff --git a/internal/util/os_windows.go b/internal/sysutil/os_windows.go similarity index 69% rename from internal/util/os_windows.go rename to internal/sysutil/os_windows.go index 2a3742fa..58b331b4 100644 --- a/internal/util/os_windows.go +++ b/internal/sysutil/os_windows.go @@ -1,4 +1,6 @@ -package util +//+build windows + +package sysutil import ( "fmt" @@ -7,11 +9,14 @@ import ( "golang.org/x/sys/windows" ) -// Set user-specified limit of how many fd's we can use -func SetRlimit(val uint) { +func canBindPrivilegedPorts() (can bool, err error) { + return HaveAdminRights() } -func HaveAdminRights() (bool, error) { +func setRlimit(val uint) { +} + +func haveAdminRights() (bool, error) { var token windows.Token h := windows.CurrentProcess() err := windows.OpenProcessToken(h, windows.TOKEN_QUERY, &token) @@ -32,6 +37,6 @@ func HaveAdminRights() (bool, error) { return true, nil } -func SendProcessSignal(pid int, sig syscall.Signal) error { +func sendProcessSignal(pid int, sig syscall.Signal) error { return fmt.Errorf("not supported on Windows") } diff --git a/internal/util/syslog_others.go b/internal/sysutil/syslog_others.go similarity index 52% rename from internal/util/syslog_others.go rename to internal/sysutil/syslog_others.go index f4ad9119..f0e15967 100644 --- a/internal/util/syslog_others.go +++ b/internal/sysutil/syslog_others.go @@ -1,14 +1,13 @@ -// +build !windows,!nacl,!plan9 +//+build !windows,!nacl,!plan9 -package util +package sysutil import ( "log" "log/syslog" ) -// ConfigureSyslog reroutes standard logger output to syslog -func ConfigureSyslog(serviceName string) error { +func configureSyslog(serviceName string) error { w, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName) if err != nil { return err diff --git a/internal/util/syslog_windows.go b/internal/sysutil/syslog_windows.go similarity index 86% rename from internal/util/syslog_windows.go rename to internal/sysutil/syslog_windows.go index 30ee7815..fbacf44e 100644 --- a/internal/util/syslog_windows.go +++ b/internal/sysutil/syslog_windows.go @@ -1,4 +1,6 @@ -package util +//+build windows nacl plan9 + +package sysutil import ( "log" @@ -12,12 +14,12 @@ type eventLogWriter struct { el *eventlog.Log } -// Write sends a log message to the Event Log. +// Write implements io.Writer interface for eventLogWriter. func (w *eventLogWriter) Write(b []byte) (int, error) { return len(b), w.el.Info(1, string(b)) } -func ConfigureSyslog(serviceName string) error { +func configureSyslog(serviceName string) error { // Note that the eventlog src is the same as the service name // Otherwise, we will get "the description for event id cannot be found" warning in every log record diff --git a/internal/sysutil/sysutil.go b/internal/sysutil/sysutil.go new file mode 100644 index 00000000..47f40600 --- /dev/null +++ b/internal/sysutil/sysutil.go @@ -0,0 +1,31 @@ +// Package sysutil contains utilities for functions requiring system calls. +package sysutil + +import "syscall" + +// CanBindPrivilegedPorts checks if current process can bind to privileged +// ports. +func CanBindPrivilegedPorts() (can bool, err error) { + return canBindPrivilegedPorts() +} + +// SetRlimit sets user-specified limit of how many fd's we can use +// https://github.com/AdguardTeam/AdGuardHome/internal/issues/659. +func SetRlimit(val uint) { + setRlimit(val) +} + +// HaveAdminRights checks if the current user has root (administrator) rights. +func HaveAdminRights() (bool, error) { + return haveAdminRights() +} + +// SendProcessSignal sends signal to a process. +func SendProcessSignal(pid int, sig syscall.Signal) error { + return sendProcessSignal(pid, sig) +} + +// ConfigureSyslog reroutes standard logger output to syslog. +func ConfigureSyslog(serviceName string) error { + return configureSyslog(serviceName) +} diff --git a/internal/util/os_freebsd.go b/internal/util/os_freebsd.go deleted file mode 100644 index 042e83fe..00000000 --- a/internal/util/os_freebsd.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build freebsd - -package util - -import ( - "os" - "syscall" - - "github.com/AdguardTeam/golibs/log" -) - -// Set user-specified limit of how many fd's we can use -// https://github.com/AdguardTeam/AdGuardHome/internal/issues/659 -func SetRlimit(val uint) { - var rlim syscall.Rlimit - rlim.Max = int64(val) - rlim.Cur = int64(val) - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) - if err != nil { - log.Error("Setrlimit() failed: %v", err) - } -} - -// Check if the current user has root (administrator) rights -func HaveAdminRights() (bool, error) { - return os.Getuid() == 0, nil -} - -// SendProcessSignal - send signal to a process -func SendProcessSignal(pid int, sig syscall.Signal) error { - return syscall.Kill(pid, sig) -} diff --git a/internal/util/os_unix.go b/internal/util/os_unix.go deleted file mode 100644 index 53557566..00000000 --- a/internal/util/os_unix.go +++ /dev/null @@ -1,32 +0,0 @@ -// +build aix darwin dragonfly linux netbsd openbsd solaris - -package util - -import ( - "os" - "syscall" - - "github.com/AdguardTeam/golibs/log" -) - -// Set user-specified limit of how many fd's we can use -// https://github.com/AdguardTeam/AdGuardHome/internal/issues/659 -func SetRlimit(val uint) { - var rlim syscall.Rlimit - rlim.Max = uint64(val) - rlim.Cur = uint64(val) - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlim) - if err != nil { - log.Error("Setrlimit() failed: %v", err) - } -} - -// Check if the current user has root (administrator) rights -func HaveAdminRights() (bool, error) { - return os.Getuid() == 0, nil -} - -// SendProcessSignal - send signal to a process -func SendProcessSignal(pid int, sig syscall.Signal) error { - return syscall.Kill(pid, sig) -} From 7d1d87d6ec1a500ba47a263d05f6abeb0872781e Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 1 Dec 2020 15:51:35 +0300 Subject: [PATCH 22/54] Pull request: + client: 2358 Make the mobileconfig API parameterized and more robust Merge in DNS/adguard-home from feature/2358 to master Updates #2358. Squashed commit of the following: commit b2b91ee3b7303d20b94265d43d785e77260b2210 Author: Artem Baskal Date: Tue Dec 1 14:54:35 2020 +0300 + client: 2358 Make the mobileconfig API parameterized and more robust --- client/src/components/ui/Guide.js | 218 ++++++++++++++++-------------- client/src/reducers/encryption.js | 4 + 2 files changed, 121 insertions(+), 101 deletions(-) diff --git a/client/src/components/ui/Guide.js b/client/src/components/ui/Guide.js index cef1c6f8..a06af83b 100644 --- a/client/src/components/ui/Guide.js +++ b/client/src/components/ui/Guide.js @@ -2,22 +2,25 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Trans, useTranslation } from 'react-i18next'; import i18next from 'i18next'; +import { useSelector } from 'react-redux'; import Tabs from './Tabs'; import Icons from './Icons'; +import { getPathWithQueryString } from '../../helpers/helpers'; const MOBILE_CONFIG_LINKS = { DOT: '/apple/dot.mobileconfig', DOH: '/apple/doh.mobileconfig', }; - -const renderMobileconfigInfo = ({ label, components }) =>
  • +const renderMobileconfigInfo = ({ label, components, server_name }) =>
  • {label}
  • ; @@ -38,37 +41,8 @@ const renderLi = ({ label, components }) =>
  • ; -const dnsPrivacyList = [{ - title: 'Android', - list: [ - { - label: 'setup_dns_privacy_android_1', - }, - { - label: 'setup_dns_privacy_android_2', - components: [ - { - key: 0, - href: 'https://adguard.com/adguard-android/overview.html', - }, - text, - ], - }, - { - label: 'setup_dns_privacy_android_3', - components: [ - { - key: 0, - href: 'https://getintra.org/', - }, - text, - ], - }, - ], -}, -{ - title: 'iOS', - list: [ +const getDnsPrivacyList = (server_name) => { + const iosList = [ { label: 'setup_dns_privacy_ios_2', components: [ @@ -79,13 +53,6 @@ const dnsPrivacyList = [{ text, ], }, - { - label: 'setup_dns_privacy_4', - components: { - highlight: , - }, - renderComponent: renderMobileconfigInfo, - }, { label: 'setup_dns_privacy_ios_1', components: [ @@ -93,68 +60,114 @@ const dnsPrivacyList = [{ key: 0, href: 'https://itunes.apple.com/app/id1452162351', }, - text, - { - key: 2, - href: 'https://dnscrypt.info/stamps', - }, + text, + { + key: 2, + href: 'https://dnscrypt.info/stamps', + }, ], - }, - ], -}, -{ - title: 'setup_dns_privacy_other_title', - list: [ - { - label: 'setup_dns_privacy_other_1', - }, - { - label: 'setup_dns_privacy_other_2', - components: [ - { - key: 0, - href: 'https://github.com/AdguardTeam/dnsproxy', - }, - ], - }, - { - href: 'https://github.com/jedisct1/dnscrypt-proxy', - label: 'setup_dns_privacy_other_3', - components: [ - { - key: 0, - href: 'https://github.com/jedisct1/dnscrypt-proxy', - }, + }]; + /* Insert second element if can generate .mobileconfig links */ + if (server_name) { + iosList.splice(1, 0, { + label: 'setup_dns_privacy_4', + components: { + highlight: , + }, + renderComponent: ({ label, components }) => renderMobileconfigInfo({ + label, + components, + server_name, + }), + }); + } + + return [{ + title: 'Android', + list: [ + { + label: 'setup_dns_privacy_android_1', + }, + { + label: 'setup_dns_privacy_android_2', + components: [ + { + key: 0, + href: 'https://adguard.com/adguard-android/overview.html', + }, text, - ], - }, - { - label: 'setup_dns_privacy_other_4', - components: [ - { - key: 0, - href: 'https://support.mozilla.org/kb/firefox-dns-over-https', - }, + ], + }, + { + label: 'setup_dns_privacy_android_3', + components: [ + { + key: 0, + href: 'https://getintra.org/', + }, text, - ], - }, - { - label: 'setup_dns_privacy_other_5', - components: [ - { - key: 0, - href: 'https://dnscrypt.info/implementations', - }, - { - key: 1, - href: 'https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients', - }, - ], - }, - ], -}, -]; + ], + }, + ], + }, + { + title: 'iOS', + list: iosList, + }, + { + title: 'setup_dns_privacy_other_title', + list: [ + { + label: 'setup_dns_privacy_other_1', + }, + { + label: 'setup_dns_privacy_other_2', + components: [ + { + key: 0, + href: 'https://github.com/AdguardTeam/dnsproxy', + }, + ], + }, + { + href: 'https://github.com/jedisct1/dnscrypt-proxy', + label: 'setup_dns_privacy_other_3', + components: [ + { + key: 0, + href: 'https://github.com/jedisct1/dnscrypt-proxy', + }, + text, + ], + }, + { + label: 'setup_dns_privacy_other_4', + components: [ + { + key: 0, + href: 'https://support.mozilla.org/kb/firefox-dns-over-https', + }, + text, + ], + }, + { + label: 'setup_dns_privacy_other_5', + components: [ + { + key: 0, + href: 'https://dnscrypt.info/implementations', + }, + { + key: 1, + href: 'https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients', + }, + ], + }, + ], + }, + ]; +}; const renderDnsPrivacyList = ({ title, list }) =>
    {title} @@ -172,6 +185,7 @@ const getTabs = ({ tlsAddress, httpsAddress, showDnsPrivacyNotice, + server_name, t, }) => ({ Router: { @@ -277,7 +291,7 @@ const getTabs = ({ setup_dns_privacy_3
    - {dnsPrivacyList.map(renderDnsPrivacyList)} + {getDnsPrivacyList(server_name).map(renderDnsPrivacyList)} }
    ; @@ -299,6 +313,7 @@ const renderContent = ({ title, list, getTitle }) =>
    { const { t } = useTranslation(); + const server_name = useSelector((state) => state.encryption.server_name); const tlsAddress = dnsAddresses?.filter((item) => item.includes('tls://')) ?? ''; const httpsAddress = dnsAddresses?.filter((item) => item.includes('https://')) ?? ''; const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1; @@ -309,6 +324,7 @@ const Guide = ({ dnsAddresses }) => { tlsAddress, httpsAddress, showDnsPrivacyNotice, + server_name, t, }); diff --git a/client/src/reducers/encryption.js b/client/src/reducers/encryption.js index a5cd7cbf..8fe9a2cb 100644 --- a/client/src/reducers/encryption.js +++ b/client/src/reducers/encryption.js @@ -9,6 +9,8 @@ const encryption = handleActions({ const newState = { ...state, ...payload, + /* TODO: handle property delete on api refactor */ + server_name: payload.server_name || '', processing: false, }; return newState; @@ -20,6 +22,7 @@ const encryption = handleActions({ const newState = { ...state, ...payload, + server_name: payload.server_name || '', processingConfig: false, }; return newState; @@ -49,6 +52,7 @@ const encryption = handleActions({ subject, warning_validation, dns_names, + server_name: payload.server_name || '', processingValidate: false, }; return newState; From ab8defdb084008457b39176018a4101ee91926c4 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Wed, 2 Dec 2020 14:42:59 +0300 Subject: [PATCH 23/54] Pull request: fix zero-length ip addresses list bug Merge in DNS/adguard-home from 2304-fix-panic to master Updates #2304. Squashed commit of the following: commit bd7742eb144b46e16c751f98f6a4a6f15fbfa60e Merge: 26313926e 7d1d87d6e Author: Eugene Burkov Date: Wed Dec 2 14:29:37 2020 +0300 Merge branch 'master' into 2304-fix-panic commit 26313926e827d1f5ceb4eec744b814ce7c32663d Author: Eugene Burkov Date: Wed Dec 2 14:09:16 2020 +0300 all: add gitignore rule commit 5a8521bd9b4014972107e8de352e20144f8187fb Author: Eugene Burkov Date: Wed Dec 2 14:03:26 2020 +0300 dhcpd: fix zero-length ip addresses list bug --- .gitignore | 1 + internal/dhcpd/v4.go | 5 +++++ internal/dhcpd/v6.go | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/.gitignore b/.gitignore index e7f55736..5b067dce 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ /querylog.json /querylog.json.1 coverage.txt +leases.db # Test output dnsfilter/tests/top-1m.csv diff --git a/internal/dhcpd/v4.go b/internal/dhcpd/v4.go index 686ee32f..81ba3a1d 100644 --- a/internal/dhcpd/v4.go +++ b/internal/dhcpd/v4.go @@ -581,6 +581,11 @@ func (s *v4Server) Start() error { return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err) } + if len(dnsIPAddrs) == 0 { + // No available IP addresses which may appear later. + return nil + } + s.conf.dnsIPAddrs = dnsIPAddrs laddr := &net.UDPAddr{ diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 300788e6..7b13dcfd 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -579,6 +579,11 @@ func (s *v6Server) Start() error { return fmt.Errorf("dhcpv6: interface %s: %w", ifaceName, err) } + if len(dnsIPAddrs) == 0 { + // No available IP addresses which may appear later. + return nil + } + s.conf.dnsIPAddrs = dnsIPAddrs err = s.initRA(iface) From 4134220c5426da8e467d5e892e5cf7562bc84eea Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 2 Dec 2020 20:34:03 +0300 Subject: [PATCH 24/54] Pull request: all: try using a different plug for snap Merge in DNS/adguard-home from 2228-different-plug to master Squashed commit of the following: commit 4b4da208e90fb00088a51c9abf599e6634c1ca1f Author: Ainar Garipov Date: Wed Dec 2 20:24:03 2020 +0300 all: try using a different plug for snap --- .goreleaser.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index ac9c57cf..7c132f87 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -93,10 +93,10 @@ 'plugs': # Add the "netrwork-bind" plug to bind to interfaces. - 'network-bind' - # Add the "netrwork-control" plug to be able to bind to ports below - # 1024 (cap_net_bind_service) and also to bind to a particular - # interface using SO_BINDTODEVICE (cap_net_raw). - - 'network-control' + # Add the "netrwork-observe" plug to be able to bind to ports below 1024 + # (cap_net_bind_service) and also to bind to a particular interface using + # SO_BINDTODEVICE (cap_net_raw). + - 'network-observe' 'daemon': 'simple' 'adguard-home-web': 'command': 'adguard-home-web.sh' From 8501a85292df93c5cf9a7d96ef193c8d25170482 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 4 Dec 2020 17:06:19 +0300 Subject: [PATCH 25/54] Pull request: fix binding capability defining Merge in DNS/adguard-home from 2391-updating-bug to master Updates #2391. Updates #2231. Squashed commit of the following: commit b321884e6ade04375dad3b981c2920500ff6f645 Author: Eugene Burkov Date: Fri Dec 4 16:54:20 2020 +0300 all: log changes commit 5aa0202a6f6d2abdfc37daee4b0d64f8cee8a62c Author: Eugene Burkov Date: Fri Dec 4 14:42:10 2020 +0300 sysutil: fix binding capability defining --- CHANGELOG.md | 7 ++++--- internal/sysutil/os_linux.go | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa3afd28..5b144fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,11 @@ and this project adheres to ### Changed -- Post-updating relaunch possibility is now determined OS-dependently ([#2231]). +- Post-updating relaunch possibility is now determined OS-dependently ([#2231], [#2391]). - Made the mobileconfig HTTP API more robust and predictable, add parameters and improve error response ([#2358]). -- Improved HTTP requests handling and timeouts. ([#2343]). -- Our snap package now uses the `core20` image as its base [#2306]. +- Improved HTTP requests handling and timeouts ([#2343]). +- Our snap package now uses the `core20` image as its base ([#2306]). - Various internal improvements ([#2271], [#2297]). [#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 @@ -39,6 +39,7 @@ and this project adheres to [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 [#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 [#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 +[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 ### Fixed diff --git a/internal/sysutil/os_linux.go b/internal/sysutil/os_linux.go index cfe8cf85..7f20e11e 100644 --- a/internal/sysutil/os_linux.go +++ b/internal/sysutil/os_linux.go @@ -12,7 +12,10 @@ import ( func canBindPrivilegedPorts() (can bool, err error) { cnbs, err := unix.PrctlRetInt(unix.PR_CAP_AMBIENT, unix.PR_CAP_AMBIENT_IS_SET, unix.CAP_NET_BIND_SERVICE, 0, 0) - return cnbs == 1, err + // Don't check the error because it's always nil on Linux. + adm, _ := haveAdminRights() + + return cnbs == 1 || adm, err } func setRlimit(val uint) { @@ -26,6 +29,8 @@ func setRlimit(val uint) { } func haveAdminRights() (bool, error) { + // The error is nil because the platform-independent function signature + // requires returning an error. return os.Getuid() == 0, nil } From a86172fda426ab24dc5e82c84b380fe110f169fc Mon Sep 17 00:00:00 2001 From: shopeonarope Date: Thu, 3 Dec 2020 19:49:45 -0700 Subject: [PATCH 26/54] Add security to openapi spec --- openapi/openapi.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d2b36cdf..d2329c31 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -12,6 +12,9 @@ 'servers': - 'url': '/control' +'security': +- 'basicAuth': [] + 'tags': - 'name': 'clients' 'description': 'Clients list operations' @@ -2084,3 +2087,7 @@ 'description': 'The error message, an opaque string.' 'type': 'string' 'type': 'object' + 'securitySchemes': + 'basicAuth': + 'type': 'http' + 'scheme': 'basic' From dfd28b45abcec855dad45a4aa4e07adf563aa205 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Fri, 4 Dec 2020 19:16:01 +0300 Subject: [PATCH 27/54] Pull request: all: improve changelog Merge in DNS/adguard-home from doc-fix to master Squashed commit of the following: commit 266940fce08a3694d771ab0a4643757ee1fa9c5f Author: Ainar Garipov Date: Tue Dec 1 16:29:15 2020 +0300 all: improve changelog --- CHANGELOG.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b144fb2..2e21e8d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,15 @@ and this project adheres to ## [Unreleased] ### Added - A 5 second wait period until a DHCP server's network interface gets an IP - address [#2304]. -- `$dnstype` modifier for filters [#2337]. -- HTTP API request body size limit [#2305]. + address ([#2304]). +- `$dnstype` modifier for filters ([#2337]). +- HTTP API request body size limit ([#2305]). [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 @@ -44,10 +44,10 @@ and this project adheres to ### Fixed - A mitigation against records being shown in the wrong order on the query log - page [#2293]. -- A JSON parsing error in query log [#2345]. + page ([#2293]). +- A JSON parsing error in query log ([#2345]). - Incorrect detection of the IPv6 address of an interface as well as another - infinite loop in the `/dhcp/find_active_dhcp` HTTP API [#2355]. + infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]). [#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 @@ -93,6 +93,10 @@ and this project adheres to + [Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.3...HEAD [v0.104.3]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...v0.104.3 [v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.1...v0.104.2 From a5728767758b8c6b98502f78702c134f90e0e20c Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 7 Dec 2020 14:32:06 +0300 Subject: [PATCH 28/54] Pull request: all: fix lint and naming issues Merge in DNS/adguard-home from 2276-fix-lint to master Updates #2276. Squashed commit of the following: commit 433f44cc7b674a20ed60a9d29466ba888b3ef66e Author: Ainar Garipov Date: Mon Dec 7 14:14:28 2020 +0300 querylog: improve code and documentation commit 851df97d2a87de5e7180a502055ee6f1a6defdca Author: Ainar Garipov Date: Fri Dec 4 20:36:32 2020 +0300 all: fix lint and naming issues --- internal/querylog/decode.go | 16 ++++++++++--- .../querylog/{qlog_file.go => qlogfile.go} | 4 ++-- .../{qlog_file_test.go => qlogfile_test.go} | 24 +++++++++---------- .../{qlog_reader.go => qlogreader.go} | 13 ++++------ ...qlog_reader_test.go => qlogreader_test.go} | 12 +++++----- .../{querylog_file.go => querylogfile.go} | 0 internal/querylog/search.go | 4 ++-- .../{search_criteria.go => searchcriteria.go} | 0 .../{search_params.go => searchparams.go} | 0 internal/stats/{stats_http.go => http.go} | 8 ++++++- internal/stats/stats.go | 4 ++-- internal/stats/{stats_unit.go => unit.go} | 0 internal/update/update_test.go | 20 ++++++++-------- internal/update/updater.go | 3 ++- 14 files changed, 61 insertions(+), 47 deletions(-) rename internal/querylog/{qlog_file.go => qlogfile.go} (98%) rename internal/querylog/{qlog_file_test.go => qlogfile_test.go} (95%) rename internal/querylog/{qlog_reader.go => qlogreader.go} (88%) rename internal/querylog/{qlog_reader_test.go => qlogreader_test.go} (97%) rename internal/querylog/{querylog_file.go => querylogfile.go} (100%) rename internal/querylog/{search_criteria.go => searchcriteria.go} (100%) rename internal/querylog/{search_params.go => searchparams.go} (100%) rename internal/stats/{stats_http.go => http.go} (94%) rename internal/stats/{stats_unit.go => unit.go} (100%) diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index b385f3eb..93a1d265 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -12,7 +12,9 @@ import ( "github.com/miekg/dns" ) -var logEntryHandlers = map[string](func(t json.Token, ent *logEntry) error){ +type logEntryHandler (func(t json.Token, ent *logEntry) error) + +var logEntryHandlers = map[string]logEntryHandler{ "IP": func(t json.Token, ent *logEntry) error { v, ok := t.(string) if !ok { @@ -203,16 +205,24 @@ func decodeLogEntry(ent *logEntry, str string) { if _, ok := keyToken.(json.Delim); ok { continue } - key := keyToken.(string) + + key, ok := keyToken.(string) + if !ok { + log.Debug("decodeLogEntry: keyToken is %T and not string", keyToken) + return + } + handler, ok := logEntryHandlers[key] if !ok { continue } + val, err := dec.Token() if err != nil { return } - if err := handler(val, ent); err != nil { + + if err = handler(val, ent); err != nil { log.Debug("decodeLogEntry err: %s", err) return } diff --git a/internal/querylog/qlog_file.go b/internal/querylog/qlogfile.go similarity index 98% rename from internal/querylog/qlog_file.go rename to internal/querylog/qlogfile.go index 13754427..69a42ed2 100644 --- a/internal/querylog/qlog_file.go +++ b/internal/querylog/qlogfile.go @@ -53,7 +53,7 @@ func NewQLogFile(path string) (*QLogFile, error) { }, nil } -// Seek performs binary search in the query log file looking for a record +// SeekTS performs binary search in the query log file looking for a record // with the specified timestamp. Once the record is found, it sets // "position" so that the next ReadNext call returned that record. // @@ -70,7 +70,7 @@ func NewQLogFile(path string) (*QLogFile, error) { // so that when we call "ReadNext" this line was returned. // * Depth of the search (how many times we compared timestamps). // * If we could not find it, it returns one of the errors described above. -func (q *QLogFile) Seek(timestamp int64) (int64, int, error) { +func (q *QLogFile) SeekTS(timestamp int64) (int64, int, error) { q.lock.Lock() defer q.lock.Unlock() diff --git a/internal/querylog/qlog_file_test.go b/internal/querylog/qlogfile_test.go similarity index 95% rename from internal/querylog/qlog_file_test.go rename to internal/querylog/qlogfile_test.go index b458e071..950eaaf3 100644 --- a/internal/querylog/qlog_file_test.go +++ b/internal/querylog/qlogfile_test.go @@ -61,7 +61,7 @@ func TestQLogFileLarge(t *testing.T) { line, err = q.ReadNext() if err == nil { assert.True(t, len(line) > 0) - read += 1 + read++ } } @@ -96,12 +96,12 @@ func TestQLogFileSeekLargeFile(t *testing.T) { testSeekLineQLogFile(t, q, count) // CASE 5: Seek non-existent (too low) - _, _, err = q.Seek(123) + _, _, err = q.SeekTS(123) assert.NotNil(t, err) // CASE 6: Seek non-existent (too high) ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") - _, _, err = q.Seek(ts.UnixNano()) + _, _, err = q.SeekTS(ts.UnixNano()) assert.NotNil(t, err) // CASE 7: "Almost" found @@ -110,7 +110,7 @@ func TestQLogFileSeekLargeFile(t *testing.T) { // ALMOST the record we need timestamp := readQLogTimestamp(line) - 1 assert.NotEqual(t, uint64(0), timestamp) - _, depth, err := q.Seek(timestamp) + _, depth, err := q.SeekTS(timestamp) assert.NotNil(t, err) assert.True(t, depth <= int(math.Log2(float64(count))+3)) } @@ -142,12 +142,12 @@ func TestQLogFileSeekSmallFile(t *testing.T) { testSeekLineQLogFile(t, q, count) // CASE 5: Seek non-existent (too low) - _, _, err = q.Seek(123) + _, _, err = q.SeekTS(123) assert.NotNil(t, err) // CASE 6: Seek non-existent (too high) ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") - _, _, err = q.Seek(ts.UnixNano()) + _, _, err = q.SeekTS(ts.UnixNano()) assert.NotNil(t, err) // CASE 7: "Almost" found @@ -156,7 +156,7 @@ func TestQLogFileSeekSmallFile(t *testing.T) { // ALMOST the record we need timestamp := readQLogTimestamp(line) - 1 assert.NotEqual(t, uint64(0), timestamp) - _, depth, err := q.Seek(timestamp) + _, depth, err := q.SeekTS(timestamp) assert.NotNil(t, err) assert.True(t, depth <= int(math.Log2(float64(count))+3)) } @@ -168,7 +168,7 @@ func testSeekLineQLogFile(t *testing.T, q *QLogFile, lineNumber int) { assert.NotEqual(t, uint64(0), ts) // try seeking to that line now - pos, _, err := q.Seek(ts) + pos, _, err := q.SeekTS(ts) assert.Nil(t, err) assert.NotEqual(t, int64(0), pos) @@ -249,7 +249,7 @@ func prepareTestFiles(dir string, filesCount, linesCount int) []string { files[filesCount-j-1] = f.Name() for i := 0; i < linesCount; i++ { - lineIP += 1 + lineIP++ lineTime = lineTime.Add(time.Second) ip := make(net.IP, 4) @@ -284,7 +284,7 @@ func TestQLogSeek(t *testing.T) { target, _ := time.Parse(time.RFC3339, "2020-08-31T18:44:25.376690873+03:00") - _, depth, err := q.Seek(target.UnixNano()) + _, depth, err := q.SeekTS(target.UnixNano()) assert.Nil(t, err) assert.Equal(t, 1, depth) } @@ -313,7 +313,7 @@ func TestQLogSeek_ErrTSTooLate(t *testing.T) { target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:25.382540454+03:00") assert.Nil(t, err) - _, depth, err := q.Seek(target.UnixNano() + int64(time.Second)) + _, depth, err := q.SeekTS(target.UnixNano() + int64(time.Second)) assert.Equal(t, ErrTSTooLate, err) assert.Equal(t, 2, depth) } @@ -342,7 +342,7 @@ func TestQLogSeek_ErrTSTooEarly(t *testing.T) { target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:23.911246629+03:00") assert.Nil(t, err) - _, depth, err := q.Seek(target.UnixNano() - int64(time.Second)) + _, depth, err := q.SeekTS(target.UnixNano() - int64(time.Second)) assert.Equal(t, ErrTSTooEarly, err) assert.Equal(t, 1, depth) } diff --git a/internal/querylog/qlog_reader.go b/internal/querylog/qlogreader.go similarity index 88% rename from internal/querylog/qlog_reader.go rename to internal/querylog/qlogreader.go index dbb058c6..19909110 100644 --- a/internal/querylog/qlog_reader.go +++ b/internal/querylog/qlogreader.go @@ -44,16 +44,13 @@ func NewQLogReader(files []string) (*QLogReader, error) { }, nil } -// Seek performs binary search of a query log record with the specified timestamp. -// If the record is found, it sets QLogReader's position to point to that line, -// so that the next ReadNext call returned this line. -// -// Returns nil if the record is successfully found. -// Returns an error if for some reason we could not find a record with the specified timestamp. -func (r *QLogReader) Seek(timestamp int64) (err error) { +// SeekTS performs binary search of a query log record with the specified +// timestamp. If the record is found, it sets QLogReader's position to point to +// that line, so that the next ReadNext call returned this line. +func (r *QLogReader) SeekTS(timestamp int64) (err error) { for i := len(r.qFiles) - 1; i >= 0; i-- { q := r.qFiles[i] - _, _, err = q.Seek(timestamp) + _, _, err = q.SeekTS(timestamp) if err == nil { // Search is finished, and the searched element have // been found. Update currentFile only, position is diff --git a/internal/querylog/qlog_reader_test.go b/internal/querylog/qlogreader_test.go similarity index 97% rename from internal/querylog/qlog_reader_test.go rename to internal/querylog/qlogreader_test.go index f1802a0a..d9dfb3ea 100644 --- a/internal/querylog/qlog_reader_test.go +++ b/internal/querylog/qlogreader_test.go @@ -50,7 +50,7 @@ func TestQLogReaderOneFile(t *testing.T) { line, err = r.ReadNext() if err == nil { assert.True(t, len(line) > 0) - read += 1 + read++ } } @@ -83,7 +83,7 @@ func TestQLogReaderMultipleFiles(t *testing.T) { line, err = r.ReadNext() if err == nil { assert.True(t, len(line) > 0) - read += 1 + read++ } } @@ -147,7 +147,7 @@ func TestQLogReader_Seek(t *testing.T) { timestamp, err := time.Parse(time.RFC3339Nano, tc.time) assert.Nil(t, err) - err = r.Seek(timestamp.UnixNano()) + err = r.SeekTS(timestamp.UnixNano()) assert.True(t, errors.Is(err, tc.want), err) }) } @@ -228,12 +228,12 @@ func TestQLogReaderSeek(t *testing.T) { testSeekLineQLogReader(t, r, count) // CASE 5: Seek non-existent (too low) - err = r.Seek(123) + err = r.SeekTS(123) assert.NotNil(t, err) // CASE 6: Seek non-existent (too high) ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00") - err = r.Seek(ts.UnixNano()) + err = r.SeekTS(ts.UnixNano()) assert.NotNil(t, err) } @@ -244,7 +244,7 @@ func testSeekLineQLogReader(t *testing.T, r *QLogReader, lineNumber int) { assert.NotEqual(t, uint64(0), ts) // try seeking to that line now - err = r.Seek(ts) + err = r.SeekTS(ts) assert.Nil(t, err) testLine, err := r.ReadNext() diff --git a/internal/querylog/querylog_file.go b/internal/querylog/querylogfile.go similarity index 100% rename from internal/querylog/querylog_file.go rename to internal/querylog/querylogfile.go diff --git a/internal/querylog/search.go b/internal/querylog/search.go index 0b9aa7d2..f23a42b6 100644 --- a/internal/querylog/search.go +++ b/internal/querylog/search.go @@ -97,7 +97,7 @@ func (l *queryLog) searchFiles(params *searchParams) ([]*logEntry, time.Time, in if params.olderThan.IsZero() { err = r.SeekStart() } else { - err = r.Seek(params.olderThan.UnixNano()) + err = r.SeekTS(params.olderThan.UnixNano()) if err == nil { // Read to the next record right away // The one that was specified in the "oldest" param is not needed, @@ -107,7 +107,7 @@ func (l *queryLog) searchFiles(params *searchParams) ([]*logEntry, time.Time, in } if err != nil { - log.Debug("Cannot Seek() to %v: %v", params.olderThan, err) + log.Debug("Cannot SeekTS() to %v: %v", params.olderThan, err) return entries, oldest, 0 } diff --git a/internal/querylog/search_criteria.go b/internal/querylog/searchcriteria.go similarity index 100% rename from internal/querylog/search_criteria.go rename to internal/querylog/searchcriteria.go diff --git a/internal/querylog/search_params.go b/internal/querylog/searchparams.go similarity index 100% rename from internal/querylog/search_params.go rename to internal/querylog/searchparams.go diff --git a/internal/stats/stats_http.go b/internal/stats/http.go similarity index 94% rename from internal/stats/stats_http.go rename to internal/stats/http.go index ab49a6ae..794bedf1 100644 --- a/internal/stats/stats_http.go +++ b/internal/stats/http.go @@ -37,7 +37,13 @@ func (s *statsCtx) handleStats(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - w.Write(data) + + _, err = w.Write(data) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "json encode: %s", err) + + return + } } type config struct { diff --git a/internal/stats/stats.go b/internal/stats/stats.go index 8f77425f..5cd5910d 100644 --- a/internal/stats/stats.go +++ b/internal/stats/stats.go @@ -1,5 +1,5 @@ -// Module for managing statistics for DNS filtering server - +// Package stats provides units for managing statistics of the filtering DNS +// server. package stats import ( diff --git a/internal/stats/stats_unit.go b/internal/stats/unit.go similarity index 100% rename from internal/stats/stats_unit.go rename to internal/stats/unit.go diff --git a/internal/update/update_test.go b/internal/update/update_test.go index 5616104a..ceca2b9d 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -88,16 +88,16 @@ func TestUpdateGetVersion(t *testing.T) { } func TestUpdate(t *testing.T) { - _ = os.Mkdir("aghtest", 0755) + _ = os.Mkdir("aghtest", 0o755) defer func() { _ = os.RemoveAll("aghtest") }() // create "current" files - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome", []byte("AdGuardHome"), 0755)) - assert.Nil(t, ioutil.WriteFile("aghtest/README.md", []byte("README.md"), 0644)) - assert.Nil(t, ioutil.WriteFile("aghtest/LICENSE.txt", []byte("LICENSE.txt"), 0644)) - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.yaml", []byte("AdGuardHome.yaml"), 0644)) + assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome", []byte("AdGuardHome"), 0o755)) + assert.Nil(t, ioutil.WriteFile("aghtest/README.md", []byte("README.md"), 0o644)) + assert.Nil(t, ioutil.WriteFile("aghtest/LICENSE.txt", []byte("LICENSE.txt"), 0o644)) + assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.yaml", []byte("AdGuardHome.yaml"), 0o644)) // start server for returning package file pkgData, err := ioutil.ReadFile("test/AdGuardHome.tar.gz") @@ -151,16 +151,16 @@ func TestUpdate(t *testing.T) { } func TestUpdateWindows(t *testing.T) { - _ = os.Mkdir("aghtest", 0755) + _ = os.Mkdir("aghtest", 0o755) defer func() { _ = os.RemoveAll("aghtest") }() // create "current" files - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.exe", []byte("AdGuardHome.exe"), 0755)) - assert.Nil(t, ioutil.WriteFile("aghtest/README.md", []byte("README.md"), 0644)) - assert.Nil(t, ioutil.WriteFile("aghtest/LICENSE.txt", []byte("LICENSE.txt"), 0644)) - assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.yaml", []byte("AdGuardHome.yaml"), 0644)) + assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.exe", []byte("AdGuardHome.exe"), 0o755)) + assert.Nil(t, ioutil.WriteFile("aghtest/README.md", []byte("README.md"), 0o644)) + assert.Nil(t, ioutil.WriteFile("aghtest/LICENSE.txt", []byte("LICENSE.txt"), 0o644)) + assert.Nil(t, ioutil.WriteFile("aghtest/AdGuardHome.yaml", []byte("AdGuardHome.yaml"), 0o644)) // start server for returning package file pkgData, err := ioutil.ReadFile("test/AdGuardHome.zip") diff --git a/internal/update/updater.go b/internal/update/updater.go index 34d66819..b6901889 100644 --- a/internal/update/updater.go +++ b/internal/update/updater.go @@ -1,3 +1,4 @@ +// Package update provides an updater for AdGuardHome. package update import ( @@ -224,7 +225,7 @@ func (u *Updater) clean() { const MaxPackageFileSize = 32 * 1024 * 1024 // Download package file and save it to disk -func (u *Updater) downloadPackageFile(url string, filename string) error { +func (u *Updater) downloadPackageFile(url, filename string) error { resp, err := u.Client.Get(url) if err != nil { return fmt.Errorf("http request failed: %w", err) From 7f29d4e5463c40fb682c968152f5e9c33c71c3cc Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 7 Dec 2020 15:38:05 +0300 Subject: [PATCH 29/54] Pull request: all: fix lint and naming issues vol. 2 Merge in DNS/adguard-home from 2276-fix-lint-2 to master Updates #2276. Squashed commit of the following: commit 24760b9586bb31be134ef9518dbece485560b1a0 Author: Ainar Garipov Date: Mon Dec 7 14:39:50 2020 +0300 all: fix lint and naming issues vol. 2 --- .../{check_other_dhcp.go => checkother.go} | 0 ..._dhcp_windows.go => checkother_windows.go} | 0 internal/dhcpd/dhcpd.go | 2 + internal/dhcpd/nclient4/client.go | 22 --- .../dhcpd/{network_utils.go => netutil.go} | 0 ...{network_utils_test.go => netutil_test.go} | 0 .../dhcpd/{router_adv.go => routeradv.go} | 15 +- .../{router_adv_test.go => routeradv_test.go} | 0 internal/dhcpd/v46_windows.go | 5 +- internal/dhcpd/v6.go | 8 +- .../{blocked_services.go => blocked.go} | 0 internal/dnsfilter/dnsfilter.go | 25 ++- internal/dnsfilter/safe_search.go | 148 ------------------ internal/dnsfilter/safesearch.go | 147 +++++++++++++++++ internal/dnsfilter/{sb_pc.go => sbpc.go} | 0 .../dnsfilter/{sb_pc_test.go => sbpc_test.go} | 0 internal/dnsforward/access_test.go | 3 +- internal/dnsforward/{handle_dns.go => dns.go} | 0 .../{dnsforward_http.go => http.go} | 0 .../{dnsforward_http_test.go => http_test.go} | 0 internal/dnsforward/msg.go | 2 +- 21 files changed, 188 insertions(+), 189 deletions(-) rename internal/dhcpd/{check_other_dhcp.go => checkother.go} (100%) rename internal/dhcpd/{check_other_dhcp_windows.go => checkother_windows.go} (100%) rename internal/dhcpd/{network_utils.go => netutil.go} (100%) rename internal/dhcpd/{network_utils_test.go => netutil_test.go} (100%) rename internal/dhcpd/{router_adv.go => routeradv.go} (96%) rename internal/dhcpd/{router_adv_test.go => routeradv_test.go} (100%) rename internal/dnsfilter/{blocked_services.go => blocked.go} (100%) delete mode 100644 internal/dnsfilter/safe_search.go rename internal/dnsfilter/{sb_pc.go => sbpc.go} (100%) rename internal/dnsfilter/{sb_pc_test.go => sbpc_test.go} (100%) rename internal/dnsforward/{handle_dns.go => dns.go} (100%) rename internal/dnsforward/{dnsforward_http.go => http.go} (100%) rename internal/dnsforward/{dnsforward_http_test.go => http_test.go} (100%) diff --git a/internal/dhcpd/check_other_dhcp.go b/internal/dhcpd/checkother.go similarity index 100% rename from internal/dhcpd/check_other_dhcp.go rename to internal/dhcpd/checkother.go diff --git a/internal/dhcpd/check_other_dhcp_windows.go b/internal/dhcpd/checkother_windows.go similarity index 100% rename from internal/dhcpd/check_other_dhcp_windows.go rename to internal/dhcpd/checkother_windows.go diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 999e9c7a..9371a29a 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -52,6 +52,7 @@ type ServerConfig struct { HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `yaml:"-"` } +// OnLeaseChangedT is a callback for lease changes. type OnLeaseChangedT func(flags int) // flags for onLeaseChanged() @@ -74,6 +75,7 @@ type Server struct { onLeaseChanged []OnLeaseChangedT } +// ServerInterface is an interface for servers. type ServerInterface interface { Leases(flags int) []Lease SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) diff --git a/internal/dhcpd/nclient4/client.go b/internal/dhcpd/nclient4/client.go index bb0473f1..b7f073c2 100644 --- a/internal/dhcpd/nclient4/client.go +++ b/internal/dhcpd/nclient4/client.go @@ -19,9 +19,7 @@ import ( "context" "errors" "fmt" - "log" "net" - "os" "sync" "sync/atomic" "time" @@ -317,26 +315,6 @@ func WithTimeout(d time.Duration) ClientOpt { } } -// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received. -func WithSummaryLogger() ClientOpt { - return func(c *Client) (err error) { - c.logger = ShortSummaryLogger{ - Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags), - } - return - } -} - -// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received. -func WithDebugLogger() ClientOpt { - return func(c *Client) (err error) { - c.logger = DebugLogger{ - Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags), - } - return - } -} - // WithLogger set the logger (see interface Logger). func WithLogger(newLogger Logger) ClientOpt { return func(c *Client) (err error) { diff --git a/internal/dhcpd/network_utils.go b/internal/dhcpd/netutil.go similarity index 100% rename from internal/dhcpd/network_utils.go rename to internal/dhcpd/netutil.go diff --git a/internal/dhcpd/network_utils_test.go b/internal/dhcpd/netutil_test.go similarity index 100% rename from internal/dhcpd/network_utils_test.go rename to internal/dhcpd/netutil_test.go diff --git a/internal/dhcpd/router_adv.go b/internal/dhcpd/routeradv.go similarity index 96% rename from internal/dhcpd/router_adv.go rename to internal/dhcpd/routeradv.go index e3d386f0..f1d63c7d 100644 --- a/internal/dhcpd/router_adv.go +++ b/internal/dhcpd/routeradv.go @@ -180,15 +180,18 @@ func (ra *raCtx) Init() error { data := createICMPv6RAPacket(params) var err error + success := false ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope) if err != nil { return fmt.Errorf("dhcpv6 ra: icmp.ListenPacket: %w", err) } - success := false defer func() { if !success { - ra.Close() + cerr := ra.Close() + if cerr != nil { + log.Error("closing context: %s", cerr) + } } }() @@ -227,13 +230,15 @@ func (ra *raCtx) Init() error { return nil } -// Close - close module -func (ra *raCtx) Close() { +// Close closes the module. +func (ra *raCtx) Close() (err error) { log.Debug("dhcpv6 ra: closing") ra.stop.Store(1) if ra.conn != nil { - ra.conn.Close() + return ra.conn.Close() } + + return nil } diff --git a/internal/dhcpd/router_adv_test.go b/internal/dhcpd/routeradv_test.go similarity index 100% rename from internal/dhcpd/router_adv_test.go rename to internal/dhcpd/routeradv_test.go diff --git a/internal/dhcpd/v46_windows.go b/internal/dhcpd/v46_windows.go index ebae25af..a02d9101 100644 --- a/internal/dhcpd/v46_windows.go +++ b/internal/dhcpd/v46_windows.go @@ -1,11 +1,12 @@ +// +build windows + package dhcpd // 'u-root/u-root' package, a dependency of 'insomniacslk/dhcp' package, doesn't build on Windows import "net" -type winServer struct { -} +type winServer struct{} func (s *winServer) ResetLeases(leases []*Lease) {} func (s *winServer) GetLeases(flags int) []Lease { return nil } diff --git a/internal/dhcpd/v6.go b/internal/dhcpd/v6.go index 7b13dcfd..2dd41b5c 100644 --- a/internal/dhcpd/v6.go +++ b/internal/dhcpd/v6.go @@ -628,7 +628,10 @@ func (s *v6Server) Start() error { // Stop - stop server func (s *v6Server) Stop() { - s.ra.Close() + err := s.ra.Close() + if err != nil { + log.Error("dhcpv6: s.ra.Close: %s", err) + } // DHCPv6 server may not be initialized if ra_slaac_only=true if s.srv == nil { @@ -636,10 +639,11 @@ func (s *v6Server) Stop() { } log.Debug("DHCPv6: stopping") - err := s.srv.Close() + err = s.srv.Close() if err != nil { log.Error("DHCPv6: srv.Close: %s", err) } + // now server.Serve() will return s.srv = nil } diff --git a/internal/dnsfilter/blocked_services.go b/internal/dnsfilter/blocked.go similarity index 100% rename from internal/dnsfilter/blocked_services.go rename to internal/dnsfilter/blocked.go diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 2b58b02e..855bbf60 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -263,11 +263,20 @@ func (d *Dnsfilter) Close() { } func (d *Dnsfilter) reset() { + var err error + if d.rulesStorage != nil { - _ = d.rulesStorage.Close() + err = d.rulesStorage.Close() + if err != nil { + log.Error("dnsfilter: rulesStorage.Close: %s", err) + } } + if d.rulesStorageWhite != nil { - d.rulesStorageWhite.Close() + err = d.rulesStorageWhite.Close() + if err != nil { + log.Error("dnsfilter: rulesStorageWhite.Close: %s", err) + } } } @@ -336,9 +345,9 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering // Now check the hosts file -- do we have any rules for it? // just like DNS rewrites, it has higher priority than filtering rules. if d.Config.AutoHosts != nil { - matched, err := d.checkAutoHosts(host, qtype, &result) + matched := d.checkAutoHosts(host, qtype, &result) if matched { - return result, err + return result, nil } } @@ -403,13 +412,13 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering return Result{}, nil } -func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool, err error) { +func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) { ips := d.Config.AutoHosts.Process(host, qtype) if ips != nil { result.Reason = RewriteEtcHosts result.IPList = ips - return true, nil + return true } revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype) @@ -422,10 +431,10 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m result.ReverseHosts[i] = revHosts[i] + "." } - return true, nil + return true } - return false, nil + return false } // Process rewrites table diff --git a/internal/dnsfilter/safe_search.go b/internal/dnsfilter/safe_search.go deleted file mode 100644 index 6bd6bc94..00000000 --- a/internal/dnsfilter/safe_search.go +++ /dev/null @@ -1,148 +0,0 @@ -package dnsfilter - -import ( - "bytes" - "encoding/binary" - "encoding/gob" - "encoding/json" - "fmt" - "net" - "net/http" - "time" - - "github.com/AdguardTeam/golibs/cache" - "github.com/AdguardTeam/golibs/log" -) - -/* -expire byte[4] -res Result -*/ -func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int { - var buf bytes.Buffer - - expire := uint(time.Now().Unix()) + d.Config.CacheTime*60 - exp := make([]byte, 4) - binary.BigEndian.PutUint32(exp, uint32(expire)) - _, _ = buf.Write(exp) - - enc := gob.NewEncoder(&buf) - err := enc.Encode(res) - if err != nil { - log.Error("gob.Encode(): %s", err) - return 0 - } - val := buf.Bytes() - _ = cache.Set([]byte(host), val) - return len(val) -} - -func getCachedResult(cache cache.Cache, host string) (Result, bool) { - data := cache.Get([]byte(host)) - if data == nil { - return Result{}, false - } - - exp := int(binary.BigEndian.Uint32(data[:4])) - if exp <= int(time.Now().Unix()) { - cache.Del([]byte(host)) - return Result{}, false - } - - var buf bytes.Buffer - buf.Write(data[4:]) - dec := gob.NewDecoder(&buf) - r := Result{} - err := dec.Decode(&r) - if err != nil { - log.Debug("gob.Decode(): %s", err) - return Result{}, false - } - - return r, true -} - -// SafeSearchDomain returns replacement address for search engine -func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) { - val, ok := safeSearchDomains[host] - return val, ok -} - -func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) { - if log.GetLevel() >= log.DEBUG { - timer := log.StartTimer() - defer timer.LogElapsed("SafeSearch: lookup for %s", host) - } - - // Check cache. Return cached result if it was found - cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host) - if isFound { - // atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1) - log.Tracef("SafeSearch: found in cache: %s", host) - return cachedValue, nil - } - - safeHost, ok := d.SafeSearchDomain(host) - if !ok { - return Result{}, nil - } - - res := Result{IsFiltered: true, Reason: FilteredSafeSearch} - if ip := net.ParseIP(safeHost); ip != nil { - res.IP = ip - valLen := d.setCacheResult(gctx.safeSearchCache, host, res) - log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) - return res, nil - } - - // TODO this address should be resolved with upstream that was configured in dnsforward - addrs, err := net.LookupIP(safeHost) - if err != nil { - log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err) - return Result{}, err - } - - for _, i := range addrs { - if ipv4 := i.To4(); ipv4 != nil { - res.IP = ipv4 - break - } - } - - if len(res.IP) == 0 { - return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost) - } - - // Cache result - valLen := d.setCacheResult(gctx.safeSearchCache, host, res) - log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) - return res, nil -} - -func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) { - d.Config.SafeSearchEnabled = true - d.Config.ConfigModified() -} - -func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { - d.Config.SafeSearchEnabled = false - d.Config.ConfigModified() -} - -func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { - data := map[string]interface{}{ - "enabled": d.Config.SafeSearchEnabled, - } - jsonVal, err := json.Marshal(data) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err) - return - } - - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonVal) - if err != nil { - httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err) - return - } -} diff --git a/internal/dnsfilter/safesearch.go b/internal/dnsfilter/safesearch.go index db6554d0..9485260b 100644 --- a/internal/dnsfilter/safesearch.go +++ b/internal/dnsfilter/safesearch.go @@ -1,5 +1,152 @@ package dnsfilter +import ( + "bytes" + "encoding/binary" + "encoding/gob" + "encoding/json" + "fmt" + "net" + "net/http" + "time" + + "github.com/AdguardTeam/golibs/cache" + "github.com/AdguardTeam/golibs/log" +) + +/* +expire byte[4] +res Result +*/ +func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int { + var buf bytes.Buffer + + expire := uint(time.Now().Unix()) + d.Config.CacheTime*60 + exp := make([]byte, 4) + binary.BigEndian.PutUint32(exp, uint32(expire)) + _, _ = buf.Write(exp) + + enc := gob.NewEncoder(&buf) + err := enc.Encode(res) + if err != nil { + log.Error("gob.Encode(): %s", err) + return 0 + } + val := buf.Bytes() + _ = cache.Set([]byte(host), val) + return len(val) +} + +func getCachedResult(cache cache.Cache, host string) (Result, bool) { + data := cache.Get([]byte(host)) + if data == nil { + return Result{}, false + } + + exp := int(binary.BigEndian.Uint32(data[:4])) + if exp <= int(time.Now().Unix()) { + cache.Del([]byte(host)) + return Result{}, false + } + + var buf bytes.Buffer + buf.Write(data[4:]) + dec := gob.NewDecoder(&buf) + r := Result{} + err := dec.Decode(&r) + if err != nil { + log.Debug("gob.Decode(): %s", err) + return Result{}, false + } + + return r, true +} + +// SafeSearchDomain returns replacement address for search engine +func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) { + val, ok := safeSearchDomains[host] + return val, ok +} + +func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) { + if log.GetLevel() >= log.DEBUG { + timer := log.StartTimer() + defer timer.LogElapsed("SafeSearch: lookup for %s", host) + } + + // Check cache. Return cached result if it was found + cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host) + if isFound { + // atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1) + log.Tracef("SafeSearch: found in cache: %s", host) + return cachedValue, nil + } + + safeHost, ok := d.SafeSearchDomain(host) + if !ok { + return Result{}, nil + } + + res := Result{IsFiltered: true, Reason: FilteredSafeSearch} + if ip := net.ParseIP(safeHost); ip != nil { + res.IP = ip + valLen := d.setCacheResult(gctx.safeSearchCache, host, res) + log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) + return res, nil + } + + // TODO this address should be resolved with upstream that was configured in dnsforward + addrs, err := net.LookupIP(safeHost) + if err != nil { + log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err) + return Result{}, err + } + + for _, i := range addrs { + if ipv4 := i.To4(); ipv4 != nil { + res.IP = ipv4 + break + } + } + + if len(res.IP) == 0 { + return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost) + } + + // Cache result + valLen := d.setCacheResult(gctx.safeSearchCache, host, res) + log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) + return res, nil +} + +func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) { + d.Config.SafeSearchEnabled = true + d.Config.ConfigModified() +} + +func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { + d.Config.SafeSearchEnabled = false + d.Config.ConfigModified() +} + +func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { + data := map[string]interface{}{ + "enabled": d.Config.SafeSearchEnabled, + } + jsonVal, err := json.Marshal(data) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err) + return + } + + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonVal) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err) + return + } +} + var safeSearchDomains = map[string]string{ "yandex.com": "213.180.193.56", "yandex.ru": "213.180.193.56", diff --git a/internal/dnsfilter/sb_pc.go b/internal/dnsfilter/sbpc.go similarity index 100% rename from internal/dnsfilter/sb_pc.go rename to internal/dnsfilter/sbpc.go diff --git a/internal/dnsfilter/sb_pc_test.go b/internal/dnsfilter/sbpc_test.go similarity index 100% rename from internal/dnsfilter/sb_pc_test.go rename to internal/dnsfilter/sbpc_test.go diff --git a/internal/dnsforward/access_test.go b/internal/dnsforward/access_test.go index b760d554..250b4931 100644 --- a/internal/dnsforward/access_test.go +++ b/internal/dnsforward/access_test.go @@ -50,7 +50,8 @@ func TestIsBlockedIPDisallowed(t *testing.T) { func TestIsBlockedIPBlockedDomain(t *testing.T) { a := &accessCtx{} - assert.True(t, a.Init(nil, nil, []string{"host1", + assert.True(t, a.Init(nil, nil, []string{ + "host1", "host2", "*.host.com", "||host3.com^", diff --git a/internal/dnsforward/handle_dns.go b/internal/dnsforward/dns.go similarity index 100% rename from internal/dnsforward/handle_dns.go rename to internal/dnsforward/dns.go diff --git a/internal/dnsforward/dnsforward_http.go b/internal/dnsforward/http.go similarity index 100% rename from internal/dnsforward/dnsforward_http.go rename to internal/dnsforward/http.go diff --git a/internal/dnsforward/dnsforward_http_test.go b/internal/dnsforward/http_test.go similarity index 100% rename from internal/dnsforward/dnsforward_http_test.go rename to internal/dnsforward/http_test.go diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index d0811df4..d4fd4937 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -1,12 +1,12 @@ package dnsforward import ( - "log" "net" "time" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/golibs/log" "github.com/miekg/dns" ) From 9b963fc777e3846325c7cd0d01a261bcae7c09e3 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 7 Dec 2020 16:04:53 +0300 Subject: [PATCH 30/54] Pull request: all: fix lint and naming issues vol. 3 Merge in DNS/adguard-home from 2276-fix-lint-3 to master Updates #2276. Squashed commit of the following: commit 6ee94cc6ed2a9762b70ef395b58b496434244b80 Author: Ainar Garipov Date: Mon Dec 7 15:55:45 2020 +0300 all: fix lint and naming issues vol. 3 --- go.mod | 2 +- go.sum | 2 + internal/home/auth_test.go | 6 +- .../home/{auth_glinet.go => authglinet.go} | 0 ...auth_glinet_test.go => authglinet_test.go} | 4 +- internal/home/clients.go | 46 ++-- internal/home/clients_test.go | 237 +++++++++--------- .../home/{clients_http.go => clientshttp.go} | 18 +- .../home/{clients_tags.go => clientstags.go} | 0 internal/home/control.go | 2 +- ...ntrol_filtering.go => controlfiltering.go} | 10 +- .../{control_install.go => controlinstall.go} | 2 +- .../{control_update.go => controlupdate.go} | 0 ...l_update_test.go => controlupdate_test.go} | 4 +- internal/home/dns.go | 6 +- internal/home/filter.go | 18 +- internal/home/home.go | 11 +- internal/home/home_test.go | 2 +- internal/home/mobileconfig.go | 18 +- internal/home/mobileconfig_test.go | 8 +- internal/home/upgrade.go | 25 +- internal/home/web.go | 26 +- main.go | 13 +- 23 files changed, 234 insertions(+), 226 deletions(-) rename internal/home/{auth_glinet.go => authglinet.go} (100%) rename internal/home/{auth_glinet_test.go => authglinet_test.go} (86%) rename internal/home/{clients_http.go => clientshttp.go} (95%) rename internal/home/{clients_tags.go => clientstags.go} (100%) rename internal/home/{control_filtering.go => controlfiltering.go} (98%) rename internal/home/{control_install.go => controlinstall.go} (99%) rename internal/home/{control_update.go => controlupdate.go} (100%) rename internal/home/{control_update_test.go => controlupdate_test.go} (98%) diff --git a/go.mod b/go.mod index c20a9da1..03dd3446 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/AdguardTeam/dnsproxy v0.33.2 - github.com/AdguardTeam/golibs v0.4.3 + github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect diff --git a/go.sum b/go.sum index 9a7f298f..7a7c1876 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjM github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.3 h1:nXTLLLlIyU4BSRF0An5azS0uimSK/YpIMOBAO0/v1RY= github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= +github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= +github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs= github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0= diff --git a/internal/home/auth_test.go b/internal/home/auth_test.go index b88035b9..25db2dd6 100644 --- a/internal/home/auth_test.go +++ b/internal/home/auth_test.go @@ -20,7 +20,7 @@ func TestMain(m *testing.M) { func prepareTestDir() string { const dir = "./agh-test" _ = os.RemoveAll(dir) - _ = os.MkdirAll(dir, 0755) + _ = os.MkdirAll(dir, 0o755) return dir } @@ -30,7 +30,7 @@ func TestAuth(t *testing.T) { fn := filepath.Join(dir, "sessions.db") users := []User{ - User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, + {Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, } a := InitAuth(fn, nil, 60) s := session{} @@ -106,7 +106,7 @@ func TestAuthHTTP(t *testing.T) { fn := filepath.Join(dir, "sessions.db") users := []User{ - User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, + {Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, } Context.auth = InitAuth(fn, users, 60) diff --git a/internal/home/auth_glinet.go b/internal/home/authglinet.go similarity index 100% rename from internal/home/auth_glinet.go rename to internal/home/authglinet.go diff --git a/internal/home/auth_glinet_test.go b/internal/home/authglinet_test.go similarity index 86% rename from internal/home/auth_glinet_test.go rename to internal/home/authglinet_test.go index 171bb84e..df5e3342 100644 --- a/internal/home/auth_glinet_test.go +++ b/internal/home/authglinet_test.go @@ -25,7 +25,7 @@ func TestAuthGL(t *testing.T) { } else { binary.BigEndian.PutUint32(data, tval) } - assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644)) + assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644)) assert.False(t, glCheckToken("test")) tval = uint32(time.Now().UTC().Unix() + 60) @@ -35,7 +35,7 @@ func TestAuthGL(t *testing.T) { } else { binary.BigEndian.PutUint32(data, tval) } - assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644)) + assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644)) r, _ := http.NewRequest("GET", "http://localhost/", nil) r.AddCookie(&http.Cookie{Name: glCookieName, Value: "test"}) assert.True(t, glProcessCookie(r)) diff --git a/internal/home/clients.go b/internal/home/clients.go index 538bfe7e..3c6bfa48 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -570,31 +570,35 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) { // so we overwrite existing entries with an equal or higher priority func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (bool, error) { clients.lock.Lock() - b, e := clients.addHost(ip, host, source) + b := clients.addHost(ip, host, source) clients.lock.Unlock() - return b, e + return b, nil } -func (clients *clientsContainer) addHost(ip, host string, source clientSource) (bool, error) { - // check auto-clients index +func (clients *clientsContainer) addHost(ip, host string, source clientSource) (addedNew bool) { ch, ok := clients.ipHost[ip] - if ok && ch.Source > source { - return false, nil - } else if ok { + if ok { + if ch.Source > source { + return false + } + ch.Source = source } else { ch = &ClientHost{ Host: host, Source: source, } + clients.ipHost[ip] = ch } - log.Debug("Clients: added %q -> %q [%d]", ip, host, len(clients.ipHost)) - return true, nil + + log.Debug("clients: added %q -> %q [%d]", ip, host, len(clients.ipHost)) + + return true } // Remove all entries that match the specified source -func (clients *clientsContainer) rmHosts(source clientSource) int { +func (clients *clientsContainer) rmHosts(source clientSource) { n := 0 for k, v := range clients.ipHost { if v.Source == source { @@ -602,8 +606,8 @@ func (clients *clientsContainer) rmHosts(source clientSource) int { n++ } } - log.Debug("Clients: removed %d client aliases", n) - return n + + log.Debug("clients: removed %d client aliases", n) } // addFromHostsFile fills the clients hosts list from the system's hosts files. @@ -613,15 +617,12 @@ func (clients *clientsContainer) addFromHostsFile() { clients.lock.Lock() defer clients.lock.Unlock() - _ = clients.rmHosts(ClientSourceHostsFile) + clients.rmHosts(ClientSourceHostsFile) n := 0 for ip, names := range hosts { for _, name := range names { - ok, err := clients.addHost(ip, name, ClientSourceHostsFile) - if err != nil { - log.Debug("Clients: %s", err) - } + ok := clients.addHost(ip, name, ClientSourceHostsFile) if ok { n++ } @@ -650,7 +651,7 @@ func (clients *clientsContainer) addFromSystemARP() { clients.lock.Lock() defer clients.lock.Unlock() - _ = clients.rmHosts(ClientSourceARP) + clients.rmHosts(ClientSourceARP) n := 0 lines := strings.Split(string(data), "\n") @@ -668,10 +669,7 @@ func (clients *clientsContainer) addFromSystemARP() { continue } - ok, e := clients.addHost(ip, host, ClientSourceARP) - if e != nil { - log.Tracef("%s", e) - } + ok := clients.addHost(ip, host, ClientSourceARP) if ok { n++ } @@ -689,7 +687,7 @@ func (clients *clientsContainer) addFromDHCP() { clients.lock.Lock() defer clients.lock.Unlock() - _ = clients.rmHosts(ClientSourceDHCP) + clients.rmHosts(ClientSourceDHCP) leases := clients.dhcpServer.Leases(dhcpd.LeasesAll) n := 0 @@ -697,7 +695,7 @@ func (clients *clientsContainer) addFromDHCP() { if len(l.Hostname) == 0 { continue } - ok, _ := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP) + ok := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP) if ok { n++ } diff --git a/internal/home/clients_test.go b/internal/home/clients_test.go index 5d5a3c81..9268c08f 100644 --- a/internal/home/clients_test.go +++ b/internal/home/clients_test.go @@ -12,144 +12,155 @@ import ( ) func TestClients(t *testing.T) { - var c Client - var e error - var b bool clients := clientsContainer{} clients.testing = true clients.Init(nil, nil, nil) - // add - c = Client{ - IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"}, - Name: "client1", - } - b, e = clients.Add(c) - if !b || e != nil { - t.Fatalf("Add #1") - } + t.Run("add_success", func(t *testing.T) { + c := Client{ + IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"}, + Name: "client1", + } - // add #2 - c = Client{ - IDs: []string{"2.2.2.2"}, - Name: "client2", - } - b, e = clients.Add(c) - if !b || e != nil { - t.Fatalf("Add #2") - } + b, err := clients.Add(c) + assert.True(t, b) + assert.Nil(t, err) - c, b = clients.Find("1.1.1.1") - assert.True(t, b && c.Name == "client1") + c = Client{ + IDs: []string{"2.2.2.2"}, + Name: "client2", + } - c, b = clients.Find("1:2:3::4") - assert.True(t, b && c.Name == "client1") + b, err = clients.Add(c) + assert.True(t, b) + assert.Nil(t, err) - c, b = clients.Find("2.2.2.2") - assert.True(t, b && c.Name == "client2") + c, b = clients.Find("1.1.1.1") + assert.True(t, b && c.Name == "client1") - // failed add - name in use - c = Client{ - IDs: []string{"1.2.3.5"}, - Name: "client1", - } - b, _ = clients.Add(c) - if b { - t.Fatalf("Add - name in use") - } + c, b = clients.Find("1:2:3::4") + assert.True(t, b && c.Name == "client1") - // failed add - ip in use - c = Client{ - IDs: []string{"2.2.2.2"}, - Name: "client3", - } - b, e = clients.Add(c) - if b || e == nil { - t.Fatalf("Add - ip in use") - } + c, b = clients.Find("2.2.2.2") + assert.True(t, b && c.Name == "client2") - // get - assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile)) - assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) - assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile)) + assert.True(t, !clients.Exists("1.2.3.4", ClientSourceHostsFile)) + assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile)) + }) - // failed update - no such name - c.IDs = []string{"1.2.3.0"} - c.Name = "client3" - if clients.Update("client3", c) == nil { - t.Fatalf("Update") - } + t.Run("add_fail_name", func(t *testing.T) { + c := Client{ + IDs: []string{"1.2.3.5"}, + Name: "client1", + } - // failed update - name in use - c.IDs = []string{"1.2.3.0"} - c.Name = "client2" - if clients.Update("client1", c) == nil { - t.Fatalf("Update - name in use") - } + b, err := clients.Add(c) + assert.False(t, b) + assert.Nil(t, err) + }) - // failed update - ip in use - c.IDs = []string{"2.2.2.2"} - c.Name = "client1" - if clients.Update("client1", c) == nil { - t.Fatalf("Update - ip in use") - } + t.Run("add_fail_ip", func(t *testing.T) { + c := Client{ + IDs: []string{"2.2.2.2"}, + Name: "client3", + } - // update - c.IDs = []string{"1.1.1.2"} - c.Name = "client1" - if clients.Update("client1", c) != nil { - t.Fatalf("Update") - } + b, err := clients.Add(c) + assert.False(t, b) + assert.NotNil(t, err) + }) - // get after update - assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile)) - assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) + t.Run("update_fail_name", func(t *testing.T) { + c := Client{ + IDs: []string{"1.2.3.0"}, + Name: "client3", + } - // update - rename - c.IDs = []string{"1.1.1.2"} - c.Name = "client1-renamed" - c.UseOwnSettings = true - assert.True(t, clients.Update("client1", c) == nil) - c = Client{} - c, b = clients.Find("1.1.1.2") - assert.True(t, b && c.Name == "client1-renamed" && c.IDs[0] == "1.1.1.2" && c.UseOwnSettings) - assert.True(t, clients.list["client1"] == nil) + err := clients.Update("client3", c) + assert.NotNil(t, err) - // failed remove - no such name - if clients.Del("client3") { - t.Fatalf("Del - no such name") - } + c = Client{ + IDs: []string{"1.2.3.0"}, + Name: "client2", + } - // remove - assert.True(t, !(!clients.Del("client1-renamed") || clients.Exists("1.1.1.2", ClientSourceHostsFile))) + err = clients.Update("client3", c) + assert.NotNil(t, err) + }) - // add host client - b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP) - if !b || e != nil { - t.Fatalf("clientAddHost") - } + t.Run("update_fail_ip", func(t *testing.T) { + c := Client{ + IDs: []string{"2.2.2.2"}, + Name: "client1", + } - // failed add - ip exists - b, e = clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS) - if b || e != nil { - t.Fatalf("clientAddHost - ip exists") - } + err := clients.Update("client1", c) + assert.NotNil(t, err) + }) - // overwrite with new data - b, e = clients.AddHost("1.1.1.1", "host2", ClientSourceARP) - if !b || e != nil { - t.Fatalf("clientAddHost - overwrite with new data") - } + t.Run("update_success", func(t *testing.T) { + c := Client{ + IDs: []string{"1.1.1.2"}, + Name: "client1", + } - // overwrite with new data (higher priority) - b, e = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile) - if !b || e != nil { - t.Fatalf("clientAddHost - overwrite with new data (higher priority)") - } + err := clients.Update("client1", c) + assert.Nil(t, err) - // get - assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile)) + assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) + + c = Client{ + IDs: []string{"1.1.1.2"}, + Name: "client1-renamed", + UseOwnSettings: true, + } + + err = clients.Update("client1", c) + assert.Nil(t, err) + + c, b := clients.Find("1.1.1.2") + assert.True(t, b) + assert.True(t, c.Name == "client1-renamed") + assert.True(t, c.IDs[0] == "1.1.1.2") + assert.True(t, c.UseOwnSettings) + assert.Nil(t, clients.list["client1"]) + }) + + t.Run("del_success", func(t *testing.T) { + b := clients.Del("client1-renamed") + assert.True(t, b) + assert.False(t, clients.Exists("1.1.1.2", ClientSourceHostsFile)) + }) + + t.Run("del_fail", func(t *testing.T) { + b := clients.Del("client3") + assert.False(t, b) + }) + + t.Run("addhost_success", func(t *testing.T) { + b, err := clients.AddHost("1.1.1.1", "host", ClientSourceARP) + assert.True(t, b) + assert.Nil(t, err) + + b, err = clients.AddHost("1.1.1.1", "host2", ClientSourceARP) + assert.True(t, b) + assert.Nil(t, err) + + b, err = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile) + assert.True(t, b) + assert.Nil(t, err) + + assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile)) + }) + + t.Run("addhost_fail", func(t *testing.T) { + b, err := clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS) + assert.False(t, b) + assert.Nil(t, err) + }) } func TestClientsWhois(t *testing.T) { diff --git a/internal/home/clients_http.go b/internal/home/clientshttp.go similarity index 95% rename from internal/home/clients_http.go rename to internal/home/clientshttp.go index 752b3c6f..d8cc3ee3 100644 --- a/internal/home/clients_http.go +++ b/internal/home/clientshttp.go @@ -94,8 +94,8 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http } // Convert JSON object to Client object -func jsonToClient(cj clientJSON) (*Client, error) { - c := Client{ +func jsonToClient(cj clientJSON) (c *Client) { + return &Client{ Name: cj.Name, IDs: cj.IDs, Tags: cj.Tags, @@ -110,7 +110,6 @@ func jsonToClient(cj clientJSON) (*Client, error) { Upstreams: cj.Upstreams, } - return &c, nil } // Convert Client object to JSON @@ -157,11 +156,7 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http. return } - c, err := jsonToClient(cj) - if err != nil { - httpError(w, http.StatusBadRequest, "%s", err) - return - } + c := jsonToClient(cj) ok, err := clients.Add(*c) if err != nil { httpError(w, http.StatusBadRequest, "%s", err) @@ -219,12 +214,7 @@ func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *ht return } - c, err := jsonToClient(dj.Data) - if err != nil { - httpError(w, http.StatusBadRequest, "%s", err) - return - } - + c := jsonToClient(dj.Data) err = clients.Update(dj.Name, *c) if err != nil { httpError(w, http.StatusBadRequest, "%s", err) diff --git a/internal/home/clients_tags.go b/internal/home/clientstags.go similarity index 100% rename from internal/home/clients_tags.go rename to internal/home/clientstags.go diff --git a/internal/home/control.go b/internal/home/control.go index 732fa919..3443515a 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -117,7 +117,7 @@ func registerControlHandlers() { RegisterAuthHandlers() } -func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) { +func httpRegister(method, url string, handler func(http.ResponseWriter, *http.Request)) { if len(method) == 0 { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method Context.mux.HandleFunc(url, postInstall(handler)) diff --git a/internal/home/control_filtering.go b/internal/home/controlfiltering.go similarity index 98% rename from internal/home/control_filtering.go rename to internal/home/controlfiltering.go index 1794cce7..e7fb80ba 100644 --- a/internal/home/control_filtering.go +++ b/internal/home/controlfiltering.go @@ -196,9 +196,9 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request } if (status&statusUpdateRequired) != 0 && fj.Data.Enabled { // download new filter and apply its rules - flags := FilterRefreshBlocklists + flags := filterRefreshBlocklists if fj.Whitelist { - flags = FilterRefreshAllowlists + flags = filterRefreshAllowlists } nUpdated, _ := f.refreshFilters(flags, true) // if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically @@ -244,11 +244,11 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques } Context.controlLock.Unlock() - flags := FilterRefreshBlocklists + flags := filterRefreshBlocklists if req.White { - flags = FilterRefreshAllowlists + flags = filterRefreshAllowlists } - resp.Updated, err = f.refreshFilters(flags|FilterRefreshForce, false) + resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false) Context.controlLock.Lock() if err != nil { httpError(w, http.StatusInternalServerError, "%s", err) diff --git a/internal/home/control_install.go b/internal/home/controlinstall.go similarity index 99% rename from internal/home/control_install.go rename to internal/home/controlinstall.go index 06f3bf43..3661d300 100644 --- a/internal/home/control_install.go +++ b/internal/home/controlinstall.go @@ -273,7 +273,7 @@ type applyConfigReq struct { } // Copy installation parameters between two configuration objects -func copyInstallSettings(dst *configuration, src *configuration) { +func copyInstallSettings(dst, src *configuration) { dst.BindHost = src.BindHost dst.BindPort = src.BindPort dst.DNS.BindHost = src.DNS.BindHost diff --git a/internal/home/control_update.go b/internal/home/controlupdate.go similarity index 100% rename from internal/home/control_update.go rename to internal/home/controlupdate.go diff --git a/internal/home/control_update_test.go b/internal/home/controlupdate_test.go similarity index 98% rename from internal/home/control_update_test.go rename to internal/home/controlupdate_test.go index 82101249..45112f50 100644 --- a/internal/home/control_update_test.go +++ b/internal/home/controlupdate_test.go @@ -81,7 +81,7 @@ func TestTargzFileUnpack(t *testing.T) { fn := "../dist/AdGuardHome_linux_amd64.tar.gz" outdir := "../test-unpack" defer os.RemoveAll(outdir) - _ = os.Mkdir(outdir, 0755) + _ = os.Mkdir(outdir, 0o755) files, e := targzFileUnpack(fn, outdir) if e != nil { t.Fatalf("FAILED: %s", e) @@ -92,7 +92,7 @@ func TestTargzFileUnpack(t *testing.T) { func TestZipFileUnpack(t *testing.T) { fn := "../dist/AdGuardHome_windows_amd64.zip" outdir := "../test-unpack" - _ = os.Mkdir(outdir, 0755) + _ = os.Mkdir(outdir, 0o755) files, e := zipFileUnpack(fn, outdir) if e != nil { t.Fatalf("FAILED: %s", e) diff --git a/internal/home/dns.go b/internal/home/dns.go index f4167f12..9c420e94 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -142,14 +142,14 @@ func generateServerConfig() dnsforward.ServerConfig { return newconfig } -type DNSEncryption struct { +type dnsEncryption struct { https string tls string quic string } -func getDNSEncryption() DNSEncryption { - dnsEncryption := DNSEncryption{} +func getDNSEncryption() dnsEncryption { + dnsEncryption := dnsEncryption{} tlsConf := tlsConfigSettings{} diff --git a/internal/home/filter.go b/internal/home/filter.go index 668af955..b85336d9 100644 --- a/internal/home/filter.go +++ b/internal/home/filter.go @@ -255,7 +255,7 @@ func (f *Filtering) periodicallyRefreshFilters() { isNetworkErr := false if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1) { f.refreshLock.Lock() - _, isNetworkErr = f.refreshFiltersIfNecessary(FilterRefreshBlocklists | FilterRefreshAllowlists) + _, isNetworkErr = f.refreshFiltersIfNecessary(filterRefreshBlocklists | filterRefreshAllowlists) f.refreshLock.Unlock() f.refreshStatus = 0 if !isNetworkErr { @@ -275,7 +275,7 @@ func (f *Filtering) periodicallyRefreshFilters() { } // Refresh filters -// flags: FilterRefresh* +// flags: filterRefresh* // important: // TRUE: ignore the fact that we're currently updating the filters func (f *Filtering) refreshFilters(flags int, important bool) (int, error) { @@ -368,14 +368,14 @@ func (f *Filtering) refreshFiltersArray(filters *[]filter, force bool) (int, []f } const ( - FilterRefreshForce = 1 // ignore last file modification date - FilterRefreshAllowlists = 2 // update allow-lists - FilterRefreshBlocklists = 4 // update block-lists + filterRefreshForce = 1 // ignore last file modification date + filterRefreshAllowlists = 2 // update allow-lists + filterRefreshBlocklists = 4 // update block-lists ) // Checks filters updates if necessary // If force is true, it ignores the filter.LastUpdated field value -// flags: FilterRefresh* +// flags: filterRefresh* // // Algorithm: // . Get the list of filters to be updated @@ -401,13 +401,13 @@ func (f *Filtering) refreshFiltersIfNecessary(flags int) (int, bool) { netError := false netErrorW := false force := false - if (flags & FilterRefreshForce) != 0 { + if (flags & filterRefreshForce) != 0 { force = true } - if (flags & FilterRefreshBlocklists) != 0 { + if (flags & filterRefreshBlocklists) != 0 { updateCount, updateFilters, updateFlags, netError = f.refreshFiltersArray(&config.Filters, force) } - if (flags & FilterRefreshAllowlists) != 0 { + if (flags & filterRefreshAllowlists) != 0 { updateCountW := 0 var updateFiltersW []filter var updateFlagsW []bool diff --git a/internal/home/home.go b/internal/home/home.go index a4be016f..acf6c3e0 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -44,6 +44,7 @@ var ( updateChannel = "none" versionCheckURL = "" ARMVersion = "" + MIPSVersion = "" ) // Global context @@ -98,11 +99,12 @@ func (c *homeContext) getDataDir() string { var Context homeContext // Main is the entry point -func Main(version, channel, armVer string) { +func Main(version, channel, armVer, mipsVer string) { // Init update-related global variables versionString = version updateChannel = channel ARMVersion = armVer + MIPSVersion = mipsVer versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json" // config can be specified, which reads options from there, but other command line flags have to override config values @@ -139,10 +141,15 @@ func Main(version, channel, armVer string) { // version - returns the current version string func version() string { + // TODO(a.garipov): I'm pretty sure we can extract some of this stuff + // from the build info. msg := "AdGuard Home, version %s, channel %s, arch %s %s" if ARMVersion != "" { msg = msg + " v" + ARMVersion + } else if MIPSVersion != "" { + msg = msg + " " + MIPSVersion } + return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH) } @@ -308,7 +315,7 @@ func run(args options) { log.Fatalf("Can't initialize TLS module") } - webConf := WebConfig{ + webConf := webConfig{ firstRun: Context.firstRun, BindHost: config.BindHost, BindPort: config.BindPort, diff --git a/internal/home/home_test.go b/internal/home/home_test.go index 3a4db397..1b16e357 100644 --- a/internal/home/home_test.go +++ b/internal/home/home_test.go @@ -119,7 +119,7 @@ func TestHome(t *testing.T) { fn := filepath.Join(dir, "AdGuardHome.yaml") // Prepare the test config - assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0644) == nil) + assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0o644) == nil) fn, _ = filepath.Abs(fn) config = configuration{} // the global variable is dirty because of the previous tests run diff --git a/internal/home/mobileconfig.go b/internal/home/mobileconfig.go index 58bdea88..2f06329e 100644 --- a/internal/home/mobileconfig.go +++ b/internal/home/mobileconfig.go @@ -10,13 +10,13 @@ import ( "howett.net/plist" ) -type DNSSettings struct { +type dnsSettings struct { DNSProtocol string ServerURL string `plist:",omitempty"` ServerName string `plist:",omitempty"` } -type PayloadContent struct { +type payloadContent struct { Name string PayloadDescription string PayloadDisplayName string @@ -24,11 +24,11 @@ type PayloadContent struct { PayloadType string PayloadUUID string PayloadVersion int - DNSSettings DNSSettings + DNSSettings dnsSettings } -type MobileConfig struct { - PayloadContent []PayloadContent +type mobileConfig struct { + PayloadContent []payloadContent PayloadDescription string PayloadDisplayName string PayloadIdentifier string @@ -47,7 +47,7 @@ const ( dnsProtoTLS = "TLS" ) -func getMobileConfig(d DNSSettings) ([]byte, error) { +func getMobileConfig(d dnsSettings) ([]byte, error) { var name string switch d.DNSProtocol { case dnsProtoHTTPS: @@ -59,8 +59,8 @@ func getMobileConfig(d DNSSettings) ([]byte, error) { return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol) } - data := MobileConfig{ - PayloadContent: []PayloadContent{{ + data := mobileConfig{ + PayloadContent: []payloadContent{{ Name: name, PayloadDescription: "Configures device to use AdGuard Home", PayloadDisplayName: name, @@ -102,7 +102,7 @@ func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) { return } - d := DNSSettings{ + d := dnsSettings{ DNSProtocol: dnsp, ServerName: host, } diff --git a/internal/home/mobileconfig_test.go b/internal/home/mobileconfig_test.go index 520d70e1..f5bf3f2b 100644 --- a/internal/home/mobileconfig_test.go +++ b/internal/home/mobileconfig_test.go @@ -19,7 +19,7 @@ func TestHandleMobileConfigDOH(t *testing.T) { handleMobileConfigDOH(w, r) assert.Equal(t, http.StatusOK, w.Code) - var mc MobileConfig + var mc mobileConfig _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) @@ -47,7 +47,7 @@ func TestHandleMobileConfigDOH(t *testing.T) { handleMobileConfigDOH(w, r) assert.Equal(t, http.StatusOK, w.Code) - var mc MobileConfig + var mc mobileConfig _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) @@ -85,7 +85,7 @@ func TestHandleMobileConfigDOT(t *testing.T) { handleMobileConfigDOT(w, r) assert.Equal(t, http.StatusOK, w.Code) - var mc MobileConfig + var mc mobileConfig _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) @@ -112,7 +112,7 @@ func TestHandleMobileConfigDOT(t *testing.T) { handleMobileConfigDOT(w, r) assert.Equal(t, http.StatusOK, w.Code) - var mc MobileConfig + var mc mobileConfig _, err = plist.Unmarshal(w.Body.Bytes(), &mc) assert.Nil(t, err) diff --git a/internal/home/upgrade.go b/internal/home/upgrade.go index 42fd83d6..335eb86c 100644 --- a/internal/home/upgrade.go +++ b/internal/home/upgrade.go @@ -3,9 +3,9 @@ package home import ( "fmt" "os" + "path" "path/filepath" - - "github.com/AdguardTeam/AdGuardHome/internal/util" + "runtime" "github.com/AdguardTeam/golibs/file" "github.com/AdguardTeam/golibs/log" @@ -122,7 +122,7 @@ func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) err // The first schema upgrade: // No more "dnsfilter.txt", filters are now kept in data/filters/ func upgradeSchema0to1(diskConfig *map[string]interface{}) error { - log.Printf("%s(): called", util.FuncName()) + log.Printf("%s(): called", funcName()) dnsFilterPath := filepath.Join(Context.workDir, "dnsfilter.txt") if _, err := os.Stat(dnsFilterPath); !os.IsNotExist(err) { @@ -143,7 +143,7 @@ func upgradeSchema0to1(diskConfig *map[string]interface{}) error { // coredns is now dns in config // delete 'Corefile', since we don't use that anymore func upgradeSchema1to2(diskConfig *map[string]interface{}) error { - log.Printf("%s(): called", util.FuncName()) + log.Printf("%s(): called", funcName()) coreFilePath := filepath.Join(Context.workDir, "Corefile") if _, err := os.Stat(coreFilePath); !os.IsNotExist(err) { @@ -167,7 +167,7 @@ func upgradeSchema1to2(diskConfig *map[string]interface{}) error { // Third schema upgrade: // Bootstrap DNS becomes an array func upgradeSchema2to3(diskConfig *map[string]interface{}) error { - log.Printf("%s(): called", util.FuncName()) + log.Printf("%s(): called", funcName()) // Let's read dns configuration from diskConfig dnsConfig, ok := (*diskConfig)["dns"] @@ -204,7 +204,7 @@ func upgradeSchema2to3(diskConfig *map[string]interface{}) error { // Add use_global_blocked_services=true setting for existing "clients" array func upgradeSchema3to4(diskConfig *map[string]interface{}) error { - log.Printf("%s(): called", util.FuncName()) + log.Printf("%s(): called", funcName()) (*diskConfig)["schema_version"] = 4 @@ -240,7 +240,7 @@ func upgradeSchema3to4(diskConfig *map[string]interface{}) error { // password: "..." // ... func upgradeSchema4to5(diskConfig *map[string]interface{}) error { - log.Printf("%s(): called", util.FuncName()) + log.Printf("%s(): called", funcName()) (*diskConfig)["schema_version"] = 5 @@ -295,7 +295,7 @@ func upgradeSchema4to5(diskConfig *map[string]interface{}) error { // - 127.0.0.1 // - ... func upgradeSchema5to6(diskConfig *map[string]interface{}) error { - log.Printf("%s(): called", util.FuncName()) + log.Printf("%s(): called", funcName()) (*diskConfig)["schema_version"] = 6 @@ -433,3 +433,12 @@ func upgradeSchema6to7(diskConfig *map[string]interface{}) error { return nil } + +// TODO(a.garipov): Replace with log.Output when we port it to our logging +// package. +func funcName() string { + pc := make([]uintptr, 10) // at least 1 entry needed + runtime.Callers(2, pc) + f := runtime.FuncForPC(pc[0]) + return path.Base(f.Name()) +} diff --git a/internal/home/web.go b/internal/home/web.go index 974016da..97f06979 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -3,7 +3,6 @@ package home import ( "context" "crypto/tls" - golog "log" "net" "net/http" "strconv" @@ -30,7 +29,7 @@ const ( WriteTimeout = 10 * time.Second ) -type WebConfig struct { +type webConfig struct { firstRun bool BindHost string BindPort int @@ -61,34 +60,20 @@ type HTTPSServer struct { // Web - module object type Web struct { - conf *WebConfig + conf *webConfig forceHTTPS bool portHTTPS int httpServer *http.Server // HTTP module httpsServer HTTPSServer // HTTPS module - errLogger *golog.Logger -} - -// Proxy between Go's "log" and "golibs/log" -type logWriter struct { -} - -// HTTP server calls this function to log an error -func (w *logWriter) Write(p []byte) (int, error) { - log.Debug("Web: %s", string(p)) - return 0, nil } // CreateWeb - create module -func CreateWeb(conf *WebConfig) *Web { +func CreateWeb(conf *webConfig) *Web { log.Info("Initialize web module") w := Web{} w.conf = conf - lw := logWriter{} - w.errLogger = golog.New(&lw, "", 0) - // Initialize and run the admin Web interface box := packr.NewBox("../../build/static") @@ -166,13 +151,14 @@ func (web *Web) Start() { // we need to have new instance, because after Shutdown() the Server is not usable address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort)) web.httpServer = &http.Server{ - ErrorLog: web.errLogger, + ErrorLog: log.StdLog("web: http", log.DEBUG), Addr: address, Handler: withMiddlewares(Context.mux, limitRequestBody), ReadTimeout: web.conf.ReadTimeout, ReadHeaderTimeout: web.conf.ReadHeaderTimeout, WriteTimeout: web.conf.WriteTimeout, } + err := web.httpServer.ListenAndServe() if err != http.ErrServerClosed { cleanupAlways() @@ -220,7 +206,7 @@ func (web *Web) tlsServerLoop() { // prepare HTTPS server address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.PortHTTPS)) web.httpsServer.server = &http.Server{ - ErrorLog: web.errLogger, + ErrorLog: log.StdLog("web: https", log.DEBUG), Addr: address, TLSConfig: &tls.Config{ Certificates: []tls.Certificate{web.httpsServer.cert}, diff --git a/main.go b/main.go index a040da7d..ecbb3d3b 100644 --- a/main.go +++ b/main.go @@ -7,15 +7,20 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/home" ) -// version will be set through ldflags, contains current version +// version is the release version. It is set by the linker. var version = "undefined" -// channel can be set via ldflags +// channel is the release channel. It is set by the linker. var channel = "release" -// GOARM value - set via ldflags +// goarm is the GOARM value. It is set by the linker. var goarm = "" +// gomips is the GOMIPS value. It is set by the linker. +// +// TODO(a.garipov): Implement. +var gomips = "" + func main() { - home.Main(version, channel, goarm) + home.Main(version, channel, goarm, gomips) } From 09b6eba7d9de4ac9522acad8585d9befe7e2cea1 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 7 Dec 2020 17:58:33 +0300 Subject: [PATCH 31/54] Pull request: all: add dnscrypt support Merge in DNS/adguard-home from 1361-dnscrypt to master Closes #1361. Squashed commit of the following: commit 31b780c16cc6b68336b95275f62381cee2e822a2 Merge: c2ce98aaf 9b963fc77 Author: Ainar Garipov Date: Mon Dec 7 17:48:41 2020 +0300 Merge branch 'master' into 1361-dnscrypt commit c2ce98aaf24bd5ed5b5cd7da86aae093866ab34e Merge: 3bf3d7b96 63e513e33 Author: Ainar Garipov Date: Fri Dec 4 19:32:40 2020 +0300 Merge branch 'master' into 1361-dnscrypt commit 3bf3d7b96530c86b54545462390562ebedc616b2 Merge: 5de451996 4134220c5 Author: Ainar Garipov Date: Thu Dec 3 17:31:59 2020 +0300 Merge branch 'master' into 1361-dnscrypt commit 5de451996d48ab3792ce78291068f72785303494 Merge: 60d7976f7 ab8defdb0 Author: Ainar Garipov Date: Wed Dec 2 19:07:56 2020 +0300 Merge branch 'master' into 1361-dnscrypt commit 60d7976f7c7ad0316751b92477a31f882c1e3134 Author: Ainar Garipov Date: Mon Nov 30 19:11:14 2020 +0300 all: add dnscrypt support --- CHANGELOG.md | 2 + go.mod | 1 + go.sum | 11 ----- internal/dnsforward/config.go | 18 +++++++ internal/home/config.go | 10 ++++ internal/home/dns.go | 90 ++++++++++++++++++++++++++++++----- 6 files changed, 109 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e21e8d8..60167c75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,11 +15,13 @@ and this project adheres to ### Added +- DNSCrypt protocol support [#1361]. - A 5 second wait period until a DHCP server's network interface gets an IP address ([#2304]). - `$dnstype` modifier for filters ([#2337]). - HTTP API request body size limit ([#2305]). +[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 diff --git a/go.mod b/go.mod index 03dd3446..8b232972 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 + github.com/ameshkov/dnscrypt/v2 v2.0.0 github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect github.com/fsnotify/fsnotify v1.4.9 github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 diff --git a/go.sum b/go.sum index 7a7c1876..af30b9bd 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,6 @@ github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs= -github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0= github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c= github.com/AdguardTeam/urlfilter v0.13.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -109,8 +107,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-ping/ping v0.0.0-20201022122018-3977ed72668a h1:O9xspHB2yrvKfMQ1m6OQhqe37i5yvg0dXAYMuAjugmM= -github.com/go-ping/ping v0.0.0-20201022122018-3977ed72668a/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 h1:jI2GiiRh+pPbey52EVmbU6kuLiXqwy4CXZ4gwUBj8Y0= github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -236,8 +232,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kardianos/service v1.1.0 h1:QV2SiEeWK42P0aEmGcsAgjApw/lRxkwopvT+Gu6t1/0= -github.com/kardianos/service v1.1.0/go.mod h1:RrJI2xn5vve/r32U5suTbeaSGoMU6GbNPoj36CVYcHc= github.com/kardianos/service v1.2.0 h1:bGuZ/epo3vrt8IPC7mnKQolqFeYJb7Cs8Rk4PSOBB/g= github.com/kardianos/service v1.2.0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU= @@ -259,8 +253,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= -github.com/lucas-clemente/quic-go v0.19.0 h1:IG5lB7DfHl6eZ7WTBVL8bnbDg0JGwDv906l6JffQbyg= -github.com/lucas-clemente/quic-go v0.19.0/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= github.com/lucas-clemente/quic-go v0.19.1 h1:J9TkQJGJVOR3UmGhd4zdVYwKSA0EoXbLRf15uQJ6gT4= github.com/lucas-clemente/quic-go v0.19.1/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -467,8 +459,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= -golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -554,7 +544,6 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/dnsforward/config.go b/internal/dnsforward/config.go index 73f5cb6d..881174d1 100644 --- a/internal/dnsforward/config.go +++ b/internal/dnsforward/config.go @@ -15,6 +15,7 @@ import ( "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt/v2" ) // FilteringConfig represents the DNS filtering configuration of AdGuard Home @@ -114,6 +115,15 @@ type TLSConfig struct { dnsNames []string } +// DNSCryptConfig is the DNSCrypt server configuration struct. +type DNSCryptConfig struct { + UDPListenAddr *net.UDPAddr + TCPListenAddr *net.TCPAddr + ProviderName string + ResolverCert *dnscrypt.Cert + Enabled bool +} + // ServerConfig represents server configuration. // The zero ServerConfig is empty and ready for use. type ServerConfig struct { @@ -124,6 +134,7 @@ type ServerConfig struct { FilteringConfig TLSConfig + DNSCryptConfig TLSAllowUnencryptedDOH bool TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2 @@ -189,6 +200,13 @@ func (s *Server) createProxyConfig() (proxy.Config, error) { return proxyConfig, err } + if s.conf.DNSCryptConfig.Enabled { + proxyConfig.DNSCryptUDPListenAddr = []*net.UDPAddr{s.conf.DNSCryptConfig.UDPListenAddr} + proxyConfig.DNSCryptTCPListenAddr = []*net.TCPAddr{s.conf.DNSCryptConfig.TCPListenAddr} + proxyConfig.DNSCryptProviderName = s.conf.DNSCryptConfig.ProviderName + proxyConfig.DNSCryptResolverCert = s.conf.DNSCryptConfig.ResolverCert + } + // Validate proxy config if proxyConfig.UpstreamConfig == nil || len(proxyConfig.UpstreamConfig.Upstreams) == 0 { return proxyConfig, errors.New("no default upstream servers configured") diff --git a/internal/home/config.go b/internal/home/config.go index 2f1e7da7..ed81c56e 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -99,6 +99,16 @@ type tlsConfigSettings struct { PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled PortDNSOverQUIC uint16 `yaml:"port_dns_over_quic" json:"port_dns_over_quic,omitempty"` // DNS-over-QUIC port. If 0, DoQ will be disabled + // PortDNSCrypt is the port for DNSCrypt requests. If it's zero, + // DNSCrypt is disabled. + PortDNSCrypt int `yaml:"port_dnscrypt" json:"port_dnscrypt"` + // DNSCryptConfigFile is the path to the DNSCrypt config file. Must be + // set if PortDNSCrypt is not zero. + // + // See https://github.com/AdguardTeam/dnsproxy and + // https://github.com/ameshkov/dnscrypt. + DNSCryptConfigFile string `yaml:"dnscrypt_config_file" json:"dnscrypt_config_file"` + // Allow DOH queries via unencrypted HTTP (e.g. for reverse proxying) AllowUnencryptedDOH bool `yaml:"allow_unencrypted_doh" json:"allow_unencrypted_doh"` diff --git a/internal/home/dns.go b/internal/home/dns.go index 9c420e94..1090d9be 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -3,8 +3,10 @@ package home import ( "fmt" "net" + "os" "path/filepath" + "github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/AdGuardHome/internal/dnsforward" "github.com/AdguardTeam/AdGuardHome/internal/querylog" @@ -12,6 +14,8 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/golibs/log" + "github.com/ameshkov/dnscrypt/v2" + yaml "gopkg.in/yaml.v2" ) // Called by other modules when configuration is changed @@ -70,7 +74,12 @@ func initDNSServer() error { } Context.dnsServer = dnsforward.NewServer(p) Context.clients.dnsServer = Context.dnsServer - dnsConfig := generateServerConfig() + dnsConfig, err := generateServerConfig() + if err != nil { + closeDNSServer() + return fmt.Errorf("generateServerConfig: %w", err) + } + err = Context.dnsServer.Prepare(&dnsConfig) if err != nil { closeDNSServer() @@ -104,10 +113,11 @@ func onDNSRequest(d *proxy.DNSContext) { } } -func generateServerConfig() dnsforward.ServerConfig { - newconfig := dnsforward.ServerConfig{ - UDPListenAddr: &net.UDPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port}, - TCPListenAddr: &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port}, +func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) { + bindHost := net.ParseIP(config.DNS.BindHost) + newconfig = dnsforward.ServerConfig{ + UDPListenAddr: &net.UDPAddr{IP: bindHost, Port: config.DNS.Port}, + TCPListenAddr: &net.TCPAddr{IP: bindHost, Port: config.DNS.Port}, FilteringConfig: config.DNS.FilteringConfig, ConfigModified: onConfigModified, HTTPRegister: httpRegister, @@ -121,25 +131,76 @@ func generateServerConfig() dnsforward.ServerConfig { if tlsConf.PortDNSOverTLS != 0 { newconfig.TLSListenAddr = &net.TCPAddr{ - IP: net.ParseIP(config.DNS.BindHost), + IP: bindHost, Port: tlsConf.PortDNSOverTLS, } } if tlsConf.PortDNSOverQUIC != 0 { newconfig.QUICListenAddr = &net.UDPAddr{ - IP: net.ParseIP(config.DNS.BindHost), + IP: bindHost, Port: int(tlsConf.PortDNSOverQUIC), } } + + if tlsConf.PortDNSCrypt != 0 { + newconfig.DNSCryptConfig, err = newDNSCrypt(bindHost, tlsConf) + if err != nil { + // Don't wrap the error, because it's already + // wrapped by newDNSCrypt. + return dnsforward.ServerConfig{}, err + } + } } + newconfig.TLSv12Roots = Context.tlsRoots newconfig.TLSCiphers = Context.tlsCiphers newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH newconfig.FilterHandler = applyAdditionalFiltering newconfig.GetCustomUpstreamByClient = Context.clients.FindUpstreams - return newconfig + + return newconfig, nil +} + +func newDNSCrypt(bindHost net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) { + if tlsConf.DNSCryptConfigFile == "" { + return dnscc, agherr.Error("no dnscrypt_config_file") + } + + f, err := os.Open(tlsConf.DNSCryptConfigFile) + if err != nil { + return dnscc, fmt.Errorf("opening dnscrypt config: %w", err) + } + defer f.Close() + + rc := &dnscrypt.ResolverConfig{} + err = yaml.NewDecoder(f).Decode(rc) + if err != nil { + return dnscc, fmt.Errorf("decoding dnscrypt config: %w", err) + } + + cert, err := rc.CreateCert() + if err != nil { + return dnscc, fmt.Errorf("creating dnscrypt cert: %w", err) + } + + udpAddr := &net.UDPAddr{ + IP: bindHost, + Port: tlsConf.PortDNSCrypt, + } + tcpAddr := &net.TCPAddr{ + IP: bindHost, + Port: tlsConf.PortDNSCrypt, + } + + return dnsforward.DNSCryptConfig{ + UDPListenAddr: udpAddr, + TCPListenAddr: tcpAddr, + ResolverCert: cert, + ProviderName: rc.ProviderName, + Enabled: true, + }, nil } type dnsEncryption struct { @@ -281,11 +342,16 @@ func startDNSServer() error { return nil } -func reconfigureDNSServer() error { - newconfig := generateServerConfig() - err := Context.dnsServer.Reconfigure(&newconfig) +func reconfigureDNSServer() (err error) { + var newconfig dnsforward.ServerConfig + newconfig, err = generateServerConfig() if err != nil { - return fmt.Errorf("couldn't start forwarding DNS server: %w", err) + return fmt.Errorf("generating forwarding dns server config: %w", err) + } + + err = Context.dnsServer.Reconfigure(&newconfig) + if err != nil { + return fmt.Errorf("starting forwarding dns server: %w", err) } return nil From e4383189a5cdb94b056504e3cce2dbcecd1a8d78 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 7 Dec 2020 19:00:52 +0300 Subject: [PATCH 32/54] Pull request: all: fix github action result reporting Merge in DNS/adguard-home from fix-gh-action-on-fail to master Squashed commit of the following: commit e8d48ee022772e0741de56dd955103efa27db0f6 Author: Ainar Garipov Date: Mon Dec 7 18:54:14 2020 +0300 all: remove tests commit ecdcea9c3a2ee3adda3aca57c761963678547cb2 Author: Ainar Garipov Date: Mon Dec 7 18:44:47 2020 +0300 all: fix github action result reporting --- .github/workflows/build.yml | 13 +++++++++++-- .github/workflows/lint.yml | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b59df4b1..b2851e53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,7 +121,16 @@ - 'docker' # Secrets are not passed to workflows that are triggered by a pull request # from a fork. - 'if': "${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}" + # + # Use always() to signal to the runner that this job must run even if the + # previous ones failed. + 'if': + ${{ always() && + ( + github.event_name == 'push' || + github.event.pull_request.head.repo.full_name == github.repository + ) + }} 'runs-on': 'ubuntu-latest' 'steps': - 'name': 'Conclusion' @@ -130,7 +139,7 @@ 'uses': '8398a7/action-slack@v3' 'with': 'status': '${{ env.WORKFLOW_CONCLUSION }}' - 'fields': 'repo, message, commit, author' + 'fields': 'repo, message, commit, author, job' 'env': 'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}' 'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b48b508b..6fdabaf8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,16 @@ - 'eslint' # Secrets are not passed to workflows that are triggered by a pull request # from a fork. - 'if': "${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}" + # + # Use always() to signal to the runner that this job must run even if the + # previous ones failed. + 'if': + ${{ always() && + ( + github.event_name == 'push' || + github.event.pull_request.head.repo.full_name == github.repository + ) + }} 'runs-on': 'ubuntu-latest' 'steps': - 'name': 'Conclusion' @@ -40,7 +49,7 @@ 'uses': '8398a7/action-slack@v3' 'with': 'status': '${{ env.WORKFLOW_CONCLUSION }}' - 'fields': 'repo, message, commit, author' + 'fields': 'repo, message, commit, author, job' 'env': 'GITHUB_TOKEN': '${{ secrets.GITHUB_TOKEN }}' 'SLACK_WEBHOOK_URL': '${{ secrets.SLACK_WEBHOOK_URL }}' From 2313eda12362050fa90e082c718839844a16918b Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 7 Dec 2020 19:48:24 +0300 Subject: [PATCH 33/54] Pull request: 2302 static ip Merge in DNS/adguard-home from 2302-static-ip to master Closes #2302. Squashed commit of the following: commit e62b7b033861f1c55f0d06811ca005b3934ddc5b Author: Eugene Burkov Date: Mon Dec 7 19:38:15 2020 +0300 all: format changelog commit 4127aa7630674ddcfe84f712e6c7c8d06b1cab9a Author: Eugene Burkov Date: Mon Dec 7 19:24:53 2020 +0300 all: fix changelog typo commit f8a432056d3682facae0cdec99d7d80a5c2bd9b5 Merge: b809a866e e4383189a Author: Eugene Burkov Date: Mon Dec 7 19:22:27 2020 +0300 Merge branch 'master' into 2302-static-ip commit b809a866e49147354f9c6952b2f958b6b56ad873 Author: Eugene Burkov Date: Mon Dec 7 19:20:05 2020 +0300 all: log changes commit 248c35ba411af731d6eae755a901cdbc77980628 Author: Eugene Burkov Date: Mon Dec 7 18:57:15 2020 +0300 sysutil: use bufio.Scanner commit 0dc19dd5c232fbe9552a2b0d846e048274d73a74 Author: Eugene Burkov Date: Mon Dec 7 17:26:18 2020 +0300 sysutil: fix linux tests commit 91202d6763595cff187040516dd1db10a20762b7 Author: Eugene Burkov Date: Mon Dec 7 17:15:29 2020 +0300 sysutil: fix linux files commit 40fbdbb95876322ebaeef1cbdaa8e3299b83ca7e Merge: d9a43ffb6 9b963fc77 Author: Eugene Burkov Date: Mon Dec 7 16:52:35 2020 +0300 Merge branch 'master' into 2302-static-ip commit d9a43ffb68a2ddbbcf185b69fc75aed139cd6919 Author: Eugene Burkov Date: Mon Dec 7 16:49:22 2020 +0300 sysutil: add main test commit bfef89186035ab0423d23246d46511584c26534c Author: Eugene Burkov Date: Mon Dec 7 16:21:59 2020 +0300 sysutil: improve code quality commit a5f57a373f736971fdeb0c03371da7c8138b3a82 Author: Eugene Burkov Date: Fri Dec 4 14:14:08 2020 +0300 all: move system functionality from dhcpd package to sysutil. commit 020b51864f85a39ca80e2b4e06faeb47cf318e11 Merge: 967e111a6 ab8defdb0 Author: Eugene Burkov Date: Wed Dec 2 14:53:43 2020 +0300 Merge branch 'master' into 2302-static-ip commit 967e111a663c18b5f4a87f3ae38d076f3ab6c200 Author: Eugene Burkov Date: Wed Dec 2 13:52:17 2020 +0300 all: improve code quality --- CHANGELOG.md | 3 + internal/dhcpd/dhcpd.go | 5 - internal/dhcpd/dhcphttp.go | 9 +- internal/dhcpd/netutil.go | 312 ------------------------- internal/dhcpd/netutil_test.go | 63 ----- internal/home/controlinstall.go | 6 +- internal/sysutil/net.go | 44 ++++ internal/sysutil/net_darwin.go | 161 +++++++++++++ internal/sysutil/net_linux.go | 168 +++++++++++++ internal/sysutil/net_linux_test.go | 109 +++++++++ internal/sysutil/net_others.go | 16 ++ internal/sysutil/{sysutil.go => os.go} | 5 - internal/sysutil/os_freebsd.go | 2 +- internal/sysutil/os_linux.go | 2 +- internal/sysutil/os_unix.go | 2 +- internal/sysutil/os_windows.go | 2 +- internal/sysutil/syslog.go | 6 + internal/sysutil/syslog_others.go | 2 +- internal/sysutil/syslog_windows.go | 2 +- internal/sysutil/sysutil_test.go | 11 + internal/util/network_utils.go | 5 +- 21 files changed, 533 insertions(+), 402 deletions(-) delete mode 100644 internal/dhcpd/netutil.go delete mode 100644 internal/dhcpd/netutil_test.go create mode 100644 internal/sysutil/net.go create mode 100644 internal/sysutil/net_darwin.go create mode 100644 internal/sysutil/net_linux.go create mode 100644 internal/sysutil/net_linux_test.go create mode 100644 internal/sysutil/net_others.go rename internal/sysutil/{sysutil.go => os.go} (83%) create mode 100644 internal/sysutil/syslog.go create mode 100644 internal/sysutil/sysutil_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 60167c75..b9b0f7ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to ### Added +- Detecting of network interface configurated to have static IP address via + `/etc/network/interfaces` ([#2302]). - DNSCrypt protocol support [#1361]. - A 5 second wait period until a DHCP server's network interface gets an IP address ([#2304]). @@ -22,6 +24,7 @@ and this project adheres to - HTTP API request body size limit ([#2305]). [#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 +[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 [#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337 diff --git a/internal/dhcpd/dhcpd.go b/internal/dhcpd/dhcpd.go index 9371a29a..ecb7d48d 100644 --- a/internal/dhcpd/dhcpd.go +++ b/internal/dhcpd/dhcpd.go @@ -81,11 +81,6 @@ type ServerInterface interface { SetOnLeaseChanged(onLeaseChanged OnLeaseChangedT) } -// CheckConfig checks the configuration -func (s *Server) CheckConfig(config ServerConfig) error { - return nil -} - // Create - create object func Create(config ServerConfig) *Server { s := &Server{} diff --git a/internal/dhcpd/dhcphttp.go b/internal/dhcpd/dhcphttp.go index c7584f8b..06a4cc79 100644 --- a/internal/dhcpd/dhcphttp.go +++ b/internal/dhcpd/dhcphttp.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/AdGuardHome/internal/util" "github.com/AdguardTeam/golibs/jsonutil" "github.com/AdguardTeam/golibs/log" @@ -205,9 +206,9 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { s.dbLoad() if s.conf.Enabled { - staticIP, err := HasStaticIP(newconfig.InterfaceName) + staticIP, err := sysutil.IfaceHasStaticIP(newconfig.InterfaceName) if !staticIP && err == nil { - err = SetStaticIP(newconfig.InterfaceName) + err = sysutil.IfaceSetStaticIP(newconfig.InterfaceName) if err != nil { httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err) return @@ -282,7 +283,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { } } if len(jsonIface.Addrs4)+len(jsonIface.Addrs6) != 0 { - jsonIface.GatewayIP = getGatewayIP(iface.Name) + jsonIface.GatewayIP = sysutil.GatewayIP(iface.Name) response[iface.Name] = jsonIface } } @@ -319,7 +320,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName) staticIP := map[string]interface{}{} - isStaticIP, err := HasStaticIP(interfaceName) + isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName) staticIPStatus := "yes" if err != nil { staticIPStatus = "error" diff --git a/internal/dhcpd/netutil.go b/internal/dhcpd/netutil.go deleted file mode 100644 index 41a1d7ec..00000000 --- a/internal/dhcpd/netutil.go +++ /dev/null @@ -1,312 +0,0 @@ -package dhcpd - -import ( - "errors" - "fmt" - "io/ioutil" - "net" - "os/exec" - "regexp" - "runtime" - "strings" - - "github.com/AdguardTeam/AdGuardHome/internal/util" - - "github.com/AdguardTeam/golibs/file" - - "github.com/AdguardTeam/golibs/log" -) - -// HasStaticIP check if the network interface has a static IP configured -// -// Supports: Raspbian. -func HasStaticIP(ifaceName string) (bool, error) { - if runtime.GOOS == "linux" { - body, err := ioutil.ReadFile("/etc/dhcpcd.conf") - if err != nil { - return false, err - } - - return hasStaticIPDhcpcdConf(string(body), ifaceName), nil - } - - if runtime.GOOS == "darwin" { - return hasStaticIPDarwin(ifaceName) - } - - return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS) -} - -// SetStaticIP sets a static IP for the network interface. -func SetStaticIP(ifaceName string) error { - if runtime.GOOS == "linux" { - return setStaticIPDhcpdConf(ifaceName) - } - - if runtime.GOOS == "darwin" { - return setStaticIPDarwin(ifaceName) - } - - return fmt.Errorf("cannot set static IP on %s", runtime.GOOS) -} - -// for dhcpcd.conf -func hasStaticIPDhcpcdConf(dhcpConf, ifaceName string) bool { - lines := strings.Split(dhcpConf, "\n") - nameLine := fmt.Sprintf("interface %s", ifaceName) - withinInterfaceCtx := false - - for _, line := range lines { - line = strings.TrimSpace(line) - - if withinInterfaceCtx && len(line) == 0 { - // an empty line resets our state - withinInterfaceCtx = false - } - - if len(line) == 0 || line[0] == '#' { - continue - } - line = strings.TrimSpace(line) - - if !withinInterfaceCtx { - if line == nameLine { - // we found our interface - withinInterfaceCtx = true - } - } else { - if strings.HasPrefix(line, "interface ") { - // we found another interface - reset our state - withinInterfaceCtx = false - continue - } - if strings.HasPrefix(line, "static ip_address=") { - return true - } - } - } - return false -} - -// Get gateway IP address -func getGatewayIP(ifaceName string) string { - cmd := exec.Command("ip", "route", "show", "dev", ifaceName) - log.Tracef("executing %s %v", cmd.Path, cmd.Args) - d, err := cmd.Output() - if err != nil || cmd.ProcessState.ExitCode() != 0 { - return "" - } - - fields := strings.Fields(string(d)) - if len(fields) < 3 || fields[0] != "default" { - return "" - } - - ip := net.ParseIP(fields[2]) - if ip == nil { - return "" - } - - return fields[2] -} - -// setStaticIPDhcpdConf - updates /etc/dhcpd.conf and sets the current IP address to be static -func setStaticIPDhcpdConf(ifaceName string) error { - ip := util.GetSubnet(ifaceName) - if len(ip) == 0 { - return errors.New("can't get IP address") - } - - ip4, _, err := net.ParseCIDR(ip) - if err != nil { - return err - } - gatewayIP := getGatewayIP(ifaceName) - add := updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, ip4.String()) - - body, err := ioutil.ReadFile("/etc/dhcpcd.conf") - if err != nil { - return err - } - - body = append(body, []byte(add)...) - err = file.SafeWrite("/etc/dhcpcd.conf", body) - if err != nil { - return err - } - - return nil -} - -// updates dhcpd.conf content -- sets static IP address there -// for dhcpcd.conf -func updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string { - var body []byte - - add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n", - ifaceName, ip) - body = append(body, []byte(add)...) - - if len(gatewayIP) != 0 { - add = fmt.Sprintf("static routers=%s\n", - gatewayIP) - body = append(body, []byte(add)...) - } - - add = fmt.Sprintf("static domain_name_servers=%s\n\n", - dnsIP) - body = append(body, []byte(add)...) - - return string(body) -} - -// Check if network interface has a static IP configured -// Supports: MacOS. -func hasStaticIPDarwin(ifaceName string) (bool, error) { - portInfo, err := getCurrentHardwarePortInfo(ifaceName) - if err != nil { - return false, err - } - - return portInfo.static, nil -} - -// setStaticIPDarwin - uses networksetup util to set the current IP address to be static -// Additionally it configures the current DNS servers as well -func setStaticIPDarwin(ifaceName string) error { - portInfo, err := getCurrentHardwarePortInfo(ifaceName) - if err != nil { - return err - } - - if portInfo.static { - return errors.New("IP address is already static") - } - - dnsAddrs, err := getEtcResolvConfServers() - if err != nil { - return err - } - - args := make([]string, 0) - args = append(args, "-setdnsservers", portInfo.name) - args = append(args, dnsAddrs...) - - // Setting DNS servers is necessary when configuring a static IP - code, _, err := util.RunCommand("networksetup", args...) - if err != nil { - return err - } - if code != 0 { - return fmt.Errorf("failed to set DNS servers, code=%d", code) - } - - // Actually configures hardware port to have static IP - code, _, err = util.RunCommand("networksetup", "-setmanual", - portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP) - if err != nil { - return err - } - if code != 0 { - return fmt.Errorf("failed to set DNS servers, code=%d", code) - } - - return nil -} - -// getCurrentHardwarePortInfo gets information the specified network interface -func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) { - // First of all we should find hardware port name - m := getNetworkSetupHardwareReports() - hardwarePort, ok := m[ifaceName] - if !ok { - return hardwarePortInfo{}, fmt.Errorf("could not find hardware port for %s", ifaceName) - } - - return getHardwarePortInfo(hardwarePort) -} - -// getNetworkSetupHardwareReports parses the output of the `networksetup -listallhardwareports` command -// it returns a map where the key is the interface name, and the value is the "hardware port" -// returns nil if it fails to parse the output -func getNetworkSetupHardwareReports() map[string]string { - _, out, err := util.RunCommand("networksetup", "-listallhardwareports") - if err != nil { - return nil - } - - re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n") - if err != nil { - return nil - } - - m := make(map[string]string) - - matches := re.FindAllStringSubmatch(out, -1) - for i := range matches { - port := matches[i][1] - device := matches[i][2] - m[device] = port - } - - return m -} - -// hardwarePortInfo - information obtained using MacOS networksetup -// about the current state of the internet connection -type hardwarePortInfo struct { - name string - ip string - subnet string - gatewayIP string - static bool -} - -func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) { - h := hardwarePortInfo{} - - _, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort) - if err != nil { - return h, err - } - - re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n") - - match := re.FindStringSubmatch(out) - if len(match) == 0 { - return h, errors.New("could not find hardware port info") - } - - h.name = hardwarePort - h.ip = match[1] - h.subnet = match[2] - h.gatewayIP = match[3] - - if strings.Index(out, "Manual Configuration") == 0 { - h.static = true - } - - return h, nil -} - -// Gets a list of nameservers currently configured in the /etc/resolv.conf -func getEtcResolvConfServers() ([]string, error) { - body, err := ioutil.ReadFile("/etc/resolv.conf") - if err != nil { - return nil, err - } - - re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)") - - matches := re.FindAllStringSubmatch(string(body), -1) - if len(matches) == 0 { - return nil, errors.New("found no DNS servers in /etc/resolv.conf") - } - - addrs := make([]string, 0) - for i := range matches { - addrs = append(addrs, matches[i][1]) - } - - return addrs, nil -} diff --git a/internal/dhcpd/netutil_test.go b/internal/dhcpd/netutil_test.go deleted file mode 100644 index fb6ef11e..00000000 --- a/internal/dhcpd/netutil_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris - -package dhcpd - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestHasStaticIPDhcpcdConf(t *testing.T) { - dhcpdConf := `#comment -# comment - -interface eth0 -static ip_address=192.168.0.1/24 - -# interface wlan0 -static ip_address=192.168.1.1/24 - -# comment -` - assert.True(t, !hasStaticIPDhcpcdConf(dhcpdConf, "wlan0")) - - dhcpdConf = `#comment -# comment - -interface eth0 -static ip_address=192.168.0.1/24 - -# interface wlan0 -static ip_address=192.168.1.1/24 - -# comment - -interface wlan0 -# comment -static ip_address=192.168.2.1/24 -` - assert.True(t, hasStaticIPDhcpcdConf(dhcpdConf, "wlan0")) -} - -func TestSetStaticIPDhcpcdConf(t *testing.T) { - dhcpcdConf := ` -interface wlan0 -static ip_address=192.168.0.2/24 -static routers=192.168.0.1 -static domain_name_servers=192.168.0.2 - -` - s := updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2") - assert.Equal(t, dhcpcdConf, s) - - // without gateway - dhcpcdConf = ` -interface wlan0 -static ip_address=192.168.0.2/24 -static domain_name_servers=192.168.0.2 - -` - s = updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2") - assert.Equal(t, dhcpcdConf, s) -} diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 3661d300..ed3cdd38 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -15,7 +15,7 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/util" - "github.com/AdguardTeam/AdGuardHome/internal/dhcpd" + "github.com/AdguardTeam/AdGuardHome/internal/sysutil" "github.com/AdguardTeam/golibs/log" ) @@ -167,7 +167,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON { if set { // Try to set static IP for the specified interface - err := dhcpd.SetStaticIP(interfaceName) + err := sysutil.IfaceSetStaticIP(interfaceName) if err != nil { resp.Static = "error" resp.Error = err.Error() @@ -177,7 +177,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON { // Fallthrough here even if we set static IP // Check if we have a static IP and return the details - isStaticIP, err := dhcpd.HasStaticIP(interfaceName) + isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName) if err != nil { resp.Static = "error" resp.Error = err.Error() diff --git a/internal/sysutil/net.go b/internal/sysutil/net.go new file mode 100644 index 00000000..557dd8d7 --- /dev/null +++ b/internal/sysutil/net.go @@ -0,0 +1,44 @@ +package sysutil + +import ( + "net" + "os/exec" + "strings" + + "github.com/AdguardTeam/golibs/log" +) + +// IfaceHasStaticIP checks if interface is configured to have static IP address. +func IfaceHasStaticIP(ifaceName string) (has bool, err error) { + return ifaceHasStaticIP(ifaceName) +} + +// IfaceSetStaticIP sets static IP address for network interface. +func IfaceSetStaticIP(ifaceName string) (err error) { + return ifaceSetStaticIP(ifaceName) +} + +// GatewayIP returns IP address of interface's gateway. +func GatewayIP(ifaceName string) string { + cmd := exec.Command("ip", "route", "show", "dev", ifaceName) + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + d, err := cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + return "" + } + + fields := strings.Fields(string(d)) + // The meaningful "ip route" command output should contain the word + // "default" at first field and default gateway IP address at third + // field. + if len(fields) < 3 || fields[0] != "default" { + return "" + } + + ip := net.ParseIP(fields[2]) + if ip == nil { + return "" + } + + return fields[2] +} diff --git a/internal/sysutil/net_darwin.go b/internal/sysutil/net_darwin.go new file mode 100644 index 00000000..1bc1ac61 --- /dev/null +++ b/internal/sysutil/net_darwin.go @@ -0,0 +1,161 @@ +// +build darwin + +package sysutil + +import ( + "errors" + "fmt" + "io/ioutil" + "regexp" + "strings" + + "github.com/AdguardTeam/AdGuardHome/internal/util" +) + +// hardwarePortInfo - information obtained using MacOS networksetup +// about the current state of the internet connection +type hardwarePortInfo struct { + name string + ip string + subnet string + gatewayIP string + static bool +} + +func ifaceHasStaticIP(ifaceName string) (bool, error) { + portInfo, err := getCurrentHardwarePortInfo(ifaceName) + if err != nil { + return false, err + } + + return portInfo.static, nil +} + +// getCurrentHardwarePortInfo gets information the specified network interface. +func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) { + // First of all we should find hardware port name + m := getNetworkSetupHardwareReports() + hardwarePort, ok := m[ifaceName] + if !ok { + return hardwarePortInfo{}, fmt.Errorf("could not find hardware port for %s", ifaceName) + } + + return getHardwarePortInfo(hardwarePort) +} + +// getNetworkSetupHardwareReports parses the output of the `networksetup -listallhardwareports` command +// it returns a map where the key is the interface name, and the value is the "hardware port" +// returns nil if it fails to parse the output +func getNetworkSetupHardwareReports() map[string]string { + _, out, err := util.RunCommand("networksetup", "-listallhardwareports") + if err != nil { + return nil + } + + re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n") + if err != nil { + return nil + } + + m := make(map[string]string) + + matches := re.FindAllStringSubmatch(out, -1) + for i := range matches { + port := matches[i][1] + device := matches[i][2] + m[device] = port + } + + return m +} + +func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) { + h := hardwarePortInfo{} + + _, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort) + if err != nil { + return h, err + } + + re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n") + + match := re.FindStringSubmatch(out) + if len(match) == 0 { + return h, errors.New("could not find hardware port info") + } + + h.name = hardwarePort + h.ip = match[1] + h.subnet = match[2] + h.gatewayIP = match[3] + + if strings.Index(out, "Manual Configuration") == 0 { + h.static = true + } + + return h, nil +} + +func ifaceSetStaticIP(ifaceName string) (err error) { + portInfo, err := getCurrentHardwarePortInfo(ifaceName) + if err != nil { + return err + } + + if portInfo.static { + return errors.New("IP address is already static") + } + + dnsAddrs, err := getEtcResolvConfServers() + if err != nil { + return err + } + + args := make([]string, 0) + args = append(args, "-setdnsservers", portInfo.name) + args = append(args, dnsAddrs...) + + // Setting DNS servers is necessary when configuring a static IP + code, _, err := util.RunCommand("networksetup", args...) + if err != nil { + return err + } + if code != 0 { + return fmt.Errorf("failed to set DNS servers, code=%d", code) + } + + // Actually configures hardware port to have static IP + code, _, err = util.RunCommand("networksetup", "-setmanual", + portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP) + if err != nil { + return err + } + if code != 0 { + return fmt.Errorf("failed to set DNS servers, code=%d", code) + } + + return nil +} + +// getEtcResolvConfServers returns a list of nameservers configured in +// /etc/resolv.conf. +func getEtcResolvConfServers() ([]string, error) { + body, err := ioutil.ReadFile("/etc/resolv.conf") + if err != nil { + return nil, err + } + + re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)") + + matches := re.FindAllStringSubmatch(string(body), -1) + if len(matches) == 0 { + return nil, errors.New("found no DNS servers in /etc/resolv.conf") + } + + addrs := make([]string, 0) + for i := range matches { + addrs = append(addrs, matches[i][1]) + } + + return addrs, nil +} diff --git a/internal/sysutil/net_linux.go b/internal/sysutil/net_linux.go new file mode 100644 index 00000000..5206f9fd --- /dev/null +++ b/internal/sysutil/net_linux.go @@ -0,0 +1,168 @@ +// +build linux + +package sysutil + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "strings" + + "github.com/AdguardTeam/AdGuardHome/internal/aghio" + "github.com/AdguardTeam/AdGuardHome/internal/util" + "github.com/AdguardTeam/golibs/file" +) + +// maxConfigFileSize is the maximum length of interfaces configuration file. +const maxConfigFileSize = 1024 * 1024 + +func ifaceHasStaticIP(ifaceName string) (has bool, err error) { + var f *os.File + for _, check := range []struct { + checker func(io.Reader, string) (bool, error) + filePath string + }{{ + checker: dhcpcdStaticConfig, + filePath: "/etc/dhcpcd.conf", + }, { + checker: ifacesStaticConfig, + filePath: "/etc/network/interfaces", + }} { + f, err = os.Open(check.filePath) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err != nil { + return false, err + } + defer f.Close() + + fileReadCloser, err := aghio.LimitReadCloser(f, maxConfigFileSize) + if err != nil { + return false, err + } + defer fileReadCloser.Close() + + has, err = check.checker(fileReadCloser, ifaceName) + if has || err != nil { + break + } + } + + return has, err +} + +// dhcpcdStaticConfig checks if interface is configured by /etc/dhcpcd.conf to +// have a static IP. +func dhcpcdStaticConfig(r io.Reader, ifaceName string) (has bool, err error) { + s := bufio.NewScanner(r) + var withinInterfaceCtx bool + + for s.Scan() { + line := strings.TrimSpace(s.Text()) + + if withinInterfaceCtx && len(line) == 0 { + // An empty line resets our state. + withinInterfaceCtx = false + } + + if len(line) == 0 || line[0] == '#' { + continue + } + + fields := strings.Fields(line) + + if withinInterfaceCtx { + if len(fields) >= 2 && fields[0] == "static" && strings.HasPrefix(fields[1], "ip_address=") { + return true, nil + } + if len(fields) > 0 && fields[0] == "interface" { + // Another interface found. + withinInterfaceCtx = false + } + continue + } + + if len(fields) == 2 && fields[0] == "interface" && fields[1] == ifaceName { + // The interface found. + withinInterfaceCtx = true + } + } + + return false, s.Err() +} + +// ifacesStaticConfig checks if interface is configured by +// /etc/network/interfaces to have a static IP. +func ifacesStaticConfig(r io.Reader, ifaceName string) (has bool, err error) { + s := bufio.NewScanner(r) + for s.Scan() { + line := strings.TrimSpace(s.Text()) + + if len(line) == 0 || line[0] == '#' { + continue + } + + fields := strings.Fields(line) + // Man page interfaces(5) declares that interface definition + // should consist of the key word "iface" followed by interface + // name, and method at fourth field. + if len(fields) >= 4 && fields[0] == "iface" && fields[1] == ifaceName && fields[3] == "static" { + return true, nil + } + } + return false, s.Err() +} + +func ifaceSetStaticIP(ifaceName string) (err error) { + ip := util.GetSubnet(ifaceName) + if len(ip) == 0 { + return errors.New("can't get IP address") + } + + ip4, _, err := net.ParseCIDR(ip) + if err != nil { + return err + } + gatewayIP := GatewayIP(ifaceName) + add := updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, ip4.String()) + + body, err := ioutil.ReadFile("/etc/dhcpcd.conf") + if err != nil { + return err + } + + body = append(body, []byte(add)...) + err = file.SafeWrite("/etc/dhcpcd.conf", body) + if err != nil { + return err + } + + return nil +} + +// updateStaticIPdhcpcdConf sets static IP address for the interface by writing +// into dhcpd.conf. +func updateStaticIPdhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string { + var body []byte + + add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n", + ifaceName, ip) + body = append(body, []byte(add)...) + + if len(gatewayIP) != 0 { + add = fmt.Sprintf("static routers=%s\n", + gatewayIP) + body = append(body, []byte(add)...) + } + + add = fmt.Sprintf("static domain_name_servers=%s\n\n", + dnsIP) + body = append(body, []byte(add)...) + + return string(body) +} diff --git a/internal/sysutil/net_linux_test.go b/internal/sysutil/net_linux_test.go new file mode 100644 index 00000000..8cadbbb7 --- /dev/null +++ b/internal/sysutil/net_linux_test.go @@ -0,0 +1,109 @@ +// +build linux + +package sysutil + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +const nl = "\n" + +func TestDHCPCDStaticConfig(t *testing.T) { + testCases := []struct { + name string + data []byte + want bool + }{{ + name: "has_not", + data: []byte(`#comment` + nl + + `# comment` + nl + + `interface eth0` + nl + + `static ip_address=192.168.0.1/24` + nl + + `# interface wlan0` + nl + + `static ip_address=192.168.1.1/24` + nl + + `# comment` + nl, + ), + want: false, + }, { + name: "has", + data: []byte(`#comment` + nl + + `# comment` + nl + + `interface eth0` + nl + + `static ip_address=192.168.0.1/24` + nl + + `# interface wlan0` + nl + + `static ip_address=192.168.1.1/24` + nl + + `# comment` + nl + + `interface wlan0` + nl + + `# comment` + nl + + `static ip_address=192.168.2.1/24` + nl, + ), + want: true, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := bytes.NewReader(tc.data) + has, err := dhcpcdStaticConfig(r, "wlan0") + assert.Nil(t, err) + assert.Equal(t, tc.want, has) + }) + } +} + +func TestIfacesStaticConfig(t *testing.T) { + testCases := []struct { + name string + data []byte + want bool + }{{ + name: "has_not", + data: []byte(`allow-hotplug enp0s3` + nl + + `#iface enp0s3 inet static` + nl + + `# address 192.168.0.200` + nl + + `# netmask 255.255.255.0` + nl + + `# gateway 192.168.0.1` + nl + + `iface enp0s3 inet dhcp` + nl, + ), + want: false, + }, { + name: "has", + data: []byte(`allow-hotplug enp0s3` + nl + + `iface enp0s3 inet static` + nl + + ` address 192.168.0.200` + nl + + ` netmask 255.255.255.0` + nl + + ` gateway 192.168.0.1` + nl + + `#iface enp0s3 inet dhcp` + nl, + ), + want: true, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + r := bytes.NewReader(tc.data) + has, err := ifacesStaticConfig(r, "enp0s3") + assert.Nil(t, err) + assert.Equal(t, tc.want, has) + }) + } +} + +func TestSetStaticIPdhcpcdConf(t *testing.T) { + dhcpcdConf := nl + `interface wlan0` + nl + + `static ip_address=192.168.0.2/24` + nl + + `static routers=192.168.0.1` + nl + + `static domain_name_servers=192.168.0.2` + nl + nl + + s := updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2") + assert.Equal(t, dhcpcdConf, s) + + // without gateway + dhcpcdConf = nl + `interface wlan0` + nl + + `static ip_address=192.168.0.2/24` + nl + + `static domain_name_servers=192.168.0.2` + nl + nl + + s = updateStaticIPdhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2") + assert.Equal(t, dhcpcdConf, s) +} diff --git a/internal/sysutil/net_others.go b/internal/sysutil/net_others.go new file mode 100644 index 00000000..fd28c1a1 --- /dev/null +++ b/internal/sysutil/net_others.go @@ -0,0 +1,16 @@ +// +build !linux,!darwin + +package sysutil + +import ( + "fmt" + "runtime" +) + +func ifaceHasStaticIP(string) (bool, error) { + return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS) +} + +func ifaceSetStaticIP(string) error { + return fmt.Errorf("cannot set static IP on %s", runtime.GOOS) +} diff --git a/internal/sysutil/sysutil.go b/internal/sysutil/os.go similarity index 83% rename from internal/sysutil/sysutil.go rename to internal/sysutil/os.go index 47f40600..63d2bf7e 100644 --- a/internal/sysutil/sysutil.go +++ b/internal/sysutil/os.go @@ -24,8 +24,3 @@ func HaveAdminRights() (bool, error) { func SendProcessSignal(pid int, sig syscall.Signal) error { return sendProcessSignal(pid, sig) } - -// ConfigureSyslog reroutes standard logger output to syslog. -func ConfigureSyslog(serviceName string) error { - return configureSyslog(serviceName) -} diff --git a/internal/sysutil/os_freebsd.go b/internal/sysutil/os_freebsd.go index 48033f49..12321aa7 100644 --- a/internal/sysutil/os_freebsd.go +++ b/internal/sysutil/os_freebsd.go @@ -1,4 +1,4 @@ -//+build freebsd +// +build freebsd package sysutil diff --git a/internal/sysutil/os_linux.go b/internal/sysutil/os_linux.go index 7f20e11e..2bab01d4 100644 --- a/internal/sysutil/os_linux.go +++ b/internal/sysutil/os_linux.go @@ -1,4 +1,4 @@ -//+build linux +// +build linux package sysutil diff --git a/internal/sysutil/os_unix.go b/internal/sysutil/os_unix.go index 0a182cdc..a77e9da1 100644 --- a/internal/sysutil/os_unix.go +++ b/internal/sysutil/os_unix.go @@ -1,4 +1,4 @@ -//+build aix darwin dragonfly netbsd openbsd solaris +// +build aix darwin dragonfly netbsd openbsd solaris package sysutil diff --git a/internal/sysutil/os_windows.go b/internal/sysutil/os_windows.go index 58b331b4..3d6745c7 100644 --- a/internal/sysutil/os_windows.go +++ b/internal/sysutil/os_windows.go @@ -1,4 +1,4 @@ -//+build windows +// +build windows package sysutil diff --git a/internal/sysutil/syslog.go b/internal/sysutil/syslog.go new file mode 100644 index 00000000..7bd505fc --- /dev/null +++ b/internal/sysutil/syslog.go @@ -0,0 +1,6 @@ +package sysutil + +// ConfigureSyslog reroutes standard logger output to syslog. +func ConfigureSyslog(serviceName string) error { + return configureSyslog(serviceName) +} diff --git a/internal/sysutil/syslog_others.go b/internal/sysutil/syslog_others.go index f0e15967..12c8a803 100644 --- a/internal/sysutil/syslog_others.go +++ b/internal/sysutil/syslog_others.go @@ -1,4 +1,4 @@ -//+build !windows,!nacl,!plan9 +// +build !windows,!nacl,!plan9 package sysutil diff --git a/internal/sysutil/syslog_windows.go b/internal/sysutil/syslog_windows.go index fbacf44e..f83eab80 100644 --- a/internal/sysutil/syslog_windows.go +++ b/internal/sysutil/syslog_windows.go @@ -1,4 +1,4 @@ -//+build windows nacl plan9 +// +build windows nacl plan9 package sysutil diff --git a/internal/sysutil/sysutil_test.go b/internal/sysutil/sysutil_test.go new file mode 100644 index 00000000..0cddbf42 --- /dev/null +++ b/internal/sysutil/sysutil_test.go @@ -0,0 +1,11 @@ +package sysutil + +import ( + "testing" + + "github.com/AdguardTeam/AdGuardHome/internal/testutil" +) + +func TestMain(m *testing.M) { + testutil.DiscardLogOutput(m) +} diff --git a/internal/util/network_utils.go b/internal/util/network_utils.go index 3aafecff..1731ed08 100644 --- a/internal/util/network_utils.go +++ b/internal/util/network_utils.go @@ -33,10 +33,7 @@ func GetValidNetInterfaces() ([]net.Interface, error) { netIfaces := []net.Interface{} - for i := range ifaces { - iface := ifaces[i] - netIfaces = append(netIfaces, iface) - } + netIfaces = append(netIfaces, ifaces...) return netIfaces, nil } From 88d44b4370ac94c7b465ab2e4c24f72899fdefee Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Tue, 8 Dec 2020 14:08:39 +0300 Subject: [PATCH 34/54] client: 2367 Show SSL Certificate Expire Banner in 5 Days Squashed commit of the following: commit 290b3fbc5e18a2cc8694fb2d5f777952d971dfd6 Merge: fe5c67e62 2313eda12 Author: Artem Baskal Date: Tue Dec 8 13:57:38 2020 +0300 Merge branch 'master' into fix/2367 commit fe5c67e624280d7fc08192ed3e953a09ca10a9ee Author: Artem Baskal Date: Mon Dec 7 16:44:41 2020 +0300 - client: 2367 Show SSL Certificate Expire Banner in 5 Days --- client/src/components/ui/EncryptionTopline.js | 71 ++++++++++++++----- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/client/src/components/ui/EncryptionTopline.js b/client/src/components/ui/EncryptionTopline.js index b013130c..6ff18136 100644 --- a/client/src/components/ui/EncryptionTopline.js +++ b/client/src/components/ui/EncryptionTopline.js @@ -6,6 +6,50 @@ import { useSelector } from 'react-redux'; import Topline from './Topline'; import { EMPTY_DATE } from '../../helpers/constants'; +const EXPIRATION_ENUM = { + VALID: 'VALID', + EXPIRED: 'EXPIRED', + EXPIRING: 'EXPIRING', +}; + +const EXPIRATION_STATE = { + [EXPIRATION_ENUM.EXPIRED]: { + toplineType: 'danger', + i18nKey: 'topline_expired_certificate', + }, + [EXPIRATION_ENUM.EXPIRING]: { + toplineType: 'warning', + i18nKey: 'topline_expiring_certificate', + }, +}; + +const getExpirationFlags = (not_after) => { + const DAYS_BEFORE_EXPIRATION = 5; + + const now = Date.now(); + const isExpiring = isAfter(addDays(now, DAYS_BEFORE_EXPIRATION), not_after); + const isExpired = isAfter(now, not_after); + + return { + isExpiring, + isExpired, + }; +}; + +const getExpirationEnumKey = (not_after) => { + const { isExpiring, isExpired } = getExpirationFlags(not_after); + + if (isExpired) { + return EXPIRATION_ENUM.EXPIRED; + } + + if (isExpiring) { + return EXPIRATION_ENUM.EXPIRING; + } + + return EXPIRATION_ENUM.VALID; +}; + const EncryptionTopline = () => { const not_after = useSelector((state) => state.encryption.not_after); @@ -13,30 +57,21 @@ const EncryptionTopline = () => { return null; } - const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after); - const isExpired = isAfter(Date.now(), not_after); + const expirationStateKey = getExpirationEnumKey(not_after); - if (isExpired) { - return ( - - link]}> - topline_expired_certificate - - - ); + if (expirationStateKey === EXPIRATION_ENUM.VALID) { + return null; } - if (isAboutExpire) { - return ( - + const { toplineType, i18nKey } = EXPIRATION_STATE[expirationStateKey]; + + return ( + link]}> - topline_expiring_certificate + {i18nKey} - ); - } - - return false; + ); }; export default EncryptionTopline; From 6aacb2105cd5673fa792f84597a607eb8e2cb542 Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Tue, 8 Dec 2020 14:26:44 +0300 Subject: [PATCH 35/54] client: 2368 Allow to enable DHCP even if there's another DHCP found on the network Squashed commit of the following: commit 2411b36b07b263c9a752f17f676bae93c15e430d Merge: 8b8740fd3 88d44b437 Author: Artem Baskal Date: Tue Dec 8 14:11:15 2020 +0300 Merge branch 'master' into fix/2368 commit 8b8740fd3f379ed1b17c3da27c748df9238efc77 Author: Artem Baskal Date: Mon Dec 7 16:51:45 2020 +0300 + client: 2368 Allow to enable DHCP even if there's another DHCP found on the network --- client/src/components/Settings/Dhcp/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js index 3be4efdb..167afc7f 100644 --- a/client/src/components/Settings/Dhcp/index.js +++ b/client/src/components/Settings/Dhcp/index.js @@ -113,9 +113,6 @@ const Dhcp = () => { const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName; const getToggleDhcpButton = () => { - const otherDhcpFound = check && (check.v4.other_server.found === STATUS_RESPONSE.YES - || check.v6.other_server.found === STATUS_RESPONSE.YES); - const filledConfig = interface_name && (Object.values(v4) .every(Boolean) || Object.values(v6) .every(Boolean)); @@ -141,7 +138,7 @@ const Dhcp = () => { className={className} onClick={enabled ? onClickDisable : onClickEnable} disabled={processingDhcp || processingConfig - || (!enabled && (!filledConfig || !check || otherDhcpFound))} + || (!enabled && (!filledConfig || !check))} > {enabled ? 'dhcp_disable' : 'dhcp_enable'} ; From b7bf7f78df6361e8be3effa2f6170e512944a73b Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Tue, 8 Dec 2020 15:31:20 +0300 Subject: [PATCH 36/54] client: 2353 sort ip in dns rewrites Close #2353 Squashed commit of the following: commit 1072b124c68ff09c6d718acb3aea625fd7b38c4f Merge: 77e9a6f10 6aacb2105 Author: Artem Baskal Date: Tue Dec 8 14:27:06 2020 +0300 Merge branch 'master' into 2353-fix-sort-ip commit 77e9a6f1013e200346b0dc332fd6b7e9e88c8ade Author: Artem Baskal Date: Mon Dec 7 17:38:24 2020 +0300 client: 2353 sort ip in dns rewrites --- client/src/__tests__/helpers.test.js | 8 ++++---- client/src/components/Filters/Rewrites/Table.js | 2 ++ client/src/helpers/helpers.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/__tests__/helpers.test.js b/client/src/__tests__/helpers.test.js index 309b40e6..a5ed121b 100644 --- a/client/src/__tests__/helpers.test.js +++ b/client/src/__tests__/helpers.test.js @@ -273,15 +273,15 @@ describe('sortIp', () => { }); }); describe('invalid input', () => { - const originalError = console.error; + const originalWarn = console.warn; beforeEach(() => { - console.error = jest.fn(); + console.warn = jest.fn(); }); afterEach(() => { - expect(console.error).toHaveBeenCalled(); - console.error = originalError; + expect(console.warn).toHaveBeenCalled(); + console.warn = originalWarn; }); test('invalid strings', () => { diff --git a/client/src/components/Filters/Rewrites/Table.js b/client/src/components/Filters/Rewrites/Table.js index 5bc1f678..45638ec0 100644 --- a/client/src/components/Filters/Rewrites/Table.js +++ b/client/src/components/Filters/Rewrites/Table.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import ReactTable from 'react-table'; import { withTranslation } from 'react-i18next'; +import { sortIp } from '../../../helpers/helpers'; class Table extends Component { cellWrap = ({ value }) => ( @@ -21,6 +22,7 @@ class Table extends Component { { Header: this.props.t('answer'), accessor: 'answer', + sortMethod: sortIp, Cell: this.cellWrap, }, { diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 0ace3f08..0cefcfdf 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -687,7 +687,7 @@ export const sortIp = (a, b) => { return 0; } catch (e) { - console.error(e); + console.warn(e); return 0; } }; From 73c30590e015f55e3de0a01f98a3c79761dce3b7 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 8 Dec 2020 16:01:13 +0300 Subject: [PATCH 37/54] Pull request: all: fix lint and naming issues vol. 4 Merge in DNS/adguard-home from 2276-fix-lint-4 to master Updates #2276. Squashed commit of the following: commit 15d49184cd8ce1f8701bf3221e69418ca1778b36 Author: Ainar Garipov Date: Tue Dec 8 15:51:34 2020 +0300 util: fix naming commit 3b9a86a0feb8c6e0b167e6e23105e8137b0dda76 Author: Ainar Garipov Date: Tue Dec 8 15:41:10 2020 +0300 all: fix lint and naming issues vol. 4 --- internal/agherr/agherr.go | 6 ++++++ internal/agherr/agherr_test.go | 2 ++ internal/dnsfilter/dnsfilter.go | 11 ----------- internal/sysutil/syslog_others.go | 3 ++- internal/sysutil/syslog_windows.go | 2 +- internal/util/{auto_hosts.go => autohosts.go} | 0 .../util/{auto_hosts_test.go => autohosts_test.go} | 0 internal/util/helpers.go | 8 -------- internal/util/{network_utils.go => network.go} | 0 .../util/{network_utils_test.go => network_test.go} | 3 +-- 10 files changed, 12 insertions(+), 23 deletions(-) rename internal/util/{auto_hosts.go => autohosts.go} (100%) rename internal/util/{auto_hosts_test.go => autohosts_test.go} (100%) rename internal/util/{network_utils.go => network.go} (100%) rename internal/util/{network_utils_test.go => network_test.go} (92%) diff --git a/internal/agherr/agherr.go b/internal/agherr/agherr.go index dee29466..aedf2a8b 100644 --- a/internal/agherr/agherr.go +++ b/internal/agherr/agherr.go @@ -65,3 +65,9 @@ func (e *manyError) Unwrap() error { return e.underlying[0] } + +// wrapper is a copy of the hidden errors.wrapper interface for tests, linting, +// etc. +type wrapper interface { + Unwrap() error +} diff --git a/internal/agherr/agherr_test.go b/internal/agherr/agherr_test.go index 8ef2f51f..123c45ef 100644 --- a/internal/agherr/agherr_test.go +++ b/internal/agherr/agherr_test.go @@ -41,6 +41,8 @@ func TestError_Error(t *testing.T) { } func TestError_Unwrap(t *testing.T) { + var _ wrapper = &manyError{} + const ( errSimple = iota errWrapped diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 855bbf60..340bfaaf 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -129,8 +129,6 @@ const ( NotFilteredNotFound Reason = iota // NotFilteredWhiteList - the host is explicitly whitelisted NotFilteredWhiteList - // NotFilteredError - there was a transitive error during check - NotFilteredError // reasons for filtering @@ -777,12 +775,3 @@ func (d *Dnsfilter) Start() { d.registerBlockedServicesHandlers() } } - -// -// stats -// - -// GetStats return dns filtering stats since startup. -func (d *Dnsfilter) GetStats() Stats { - return gctx.stats -} diff --git a/internal/sysutil/syslog_others.go b/internal/sysutil/syslog_others.go index 12c8a803..0e0e1c3f 100644 --- a/internal/sysutil/syslog_others.go +++ b/internal/sysutil/syslog_others.go @@ -3,8 +3,9 @@ package sysutil import ( - "log" "log/syslog" + + "github.com/AdguardTeam/golibs/log" ) func configureSyslog(serviceName string) error { diff --git a/internal/sysutil/syslog_windows.go b/internal/sysutil/syslog_windows.go index f83eab80..2160ea43 100644 --- a/internal/sysutil/syslog_windows.go +++ b/internal/sysutil/syslog_windows.go @@ -3,9 +3,9 @@ package sysutil import ( - "log" "strings" + "github.com/AdguardTeam/golibs/log" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc/eventlog" ) diff --git a/internal/util/auto_hosts.go b/internal/util/autohosts.go similarity index 100% rename from internal/util/auto_hosts.go rename to internal/util/autohosts.go diff --git a/internal/util/auto_hosts_test.go b/internal/util/autohosts_test.go similarity index 100% rename from internal/util/auto_hosts_test.go rename to internal/util/autohosts_test.go diff --git a/internal/util/helpers.go b/internal/util/helpers.go index e023da08..2770fa44 100644 --- a/internal/util/helpers.go +++ b/internal/util/helpers.go @@ -9,7 +9,6 @@ import ( "io/ioutil" "os" "os/exec" - "path" "runtime" "strings" ) @@ -41,13 +40,6 @@ func RunCommand(command string, arguments ...string) (int, string, error) { return cmd.ProcessState.ExitCode(), string(out), nil } -func FuncName() string { - pc := make([]uintptr, 10) // at least 1 entry needed - runtime.Callers(2, pc) - f := runtime.FuncForPC(pc[0]) - return path.Base(f.Name()) -} - // SplitNext - split string by a byte and return the first chunk // Skip empty chunks // Whitespace is trimmed diff --git a/internal/util/network_utils.go b/internal/util/network.go similarity index 100% rename from internal/util/network_utils.go rename to internal/util/network.go diff --git a/internal/util/network_utils_test.go b/internal/util/network_test.go similarity index 92% rename from internal/util/network_utils_test.go rename to internal/util/network_test.go index 7feac0f2..9b2a9554 100644 --- a/internal/util/network_utils_test.go +++ b/internal/util/network_test.go @@ -1,7 +1,6 @@ package util import ( - "log" "testing" ) @@ -19,6 +18,6 @@ func TestGetValidNetInterfacesForWeb(t *testing.T) { t.Fatalf("No addresses found for %s", iface.Name) } - log.Printf("%v", iface) + t.Logf("%v", iface) } } From 7f9a3a73b4cc8d6e5bac9a6b6be68bf4df866aa1 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Tue, 8 Dec 2020 18:23:35 +0300 Subject: [PATCH 38/54] Pull request: 2276 no golangci Merge in DNS/adguard-home from 2276-no-golangci to master Updates #2276. Squashed commit of the following: commit 81a5a62716b8c57e8575cf149938cd941660b6f5 Author: Ainar Garipov Date: Tue Dec 8 16:59:19 2020 +0300 all: fix Makefile commit a8f2546803a3986f1292b45921c27409366bc04a Author: Ainar Garipov Date: Tue Dec 8 16:11:09 2020 +0300 all: remove golangci-yaml, add new linters --- .github/workflows/lint.yml | 15 ++-- .gitignore | 51 +++++------ HACKING.md | 26 +++++- Makefile | 39 +++++---- README.md | 7 +- internal/tools/go.mod | 24 ++++++ internal/tools/go.sum | 164 ++++++++++++++++++++++++++++++++++++ internal/tools/tools.go | 22 +++++ scripts/go-install-tools.sh | 21 +++++ scripts/go-lint.sh | 95 +++++++++++++++++++++ 10 files changed, 398 insertions(+), 66 deletions(-) create mode 100644 internal/tools/go.mod create mode 100644 internal/tools/go.sum create mode 100644 internal/tools/tools.go create mode 100644 scripts/go-install-tools.sh create mode 100644 scripts/go-lint.sh diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6fdabaf8..d003dee9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -'name': 'golangci-lint' +'name': 'lint' 'on': 'push': 'tags': @@ -7,16 +7,13 @@ - '*' 'pull_request': 'jobs': - 'golangci': + 'go-lint': 'runs-on': 'ubuntu-latest' 'steps': - 'uses': 'actions/checkout@v2' - - 'name': 'golangci-lint' - 'uses': 'golangci/golangci-lint-action@v2.3.0' - 'with': - # This field is required. Don't set the patch version to always use - # the latest patch version. - 'version': 'v1.32' + - 'name': 'run-lint' + 'run': > + make go-install-tools go-lint 'eslint': 'runs-on': 'ubuntu-latest' 'steps': @@ -27,7 +24,7 @@ 'run': 'npm --prefix client run lint' 'notify': 'needs': - - 'golangci' + - 'go-lint' - 'eslint' # Secrets are not passed to workflows that are triggered by a pull request # from a fork. diff --git a/.gitignore b/.gitignore index 5b067dce..beba3ce1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,22 @@ -.DS_Store -/.vscode -.idea -/AdGuardHome -/AdGuardHome.exe -/AdGuardHome.yaml -/AdGuardHome.log -/data/ -/build/ -/dist/ -/client/node_modules/ -/querylog.json -/querylog.json.1 -coverage.txt -leases.db - -# Test output -dnsfilter/tests/top-1m.csv -dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof - -# Snapcraft build temporary files -*.snap -launchpad_credentials -snapcraft_login -snapcraft.yaml.bak - -# IntelliJ IDEA project files -*.iml - -# Packr +# Please, DO NOT put your text editors' temporary files here. The more are +# added, the harder it gets to maintain and manage projects' gitignores. Put +# them into your global gitignore file instead. +# +# See https://stackoverflow.com/a/7335487/1892060. +# +# Only build, run, and test outputs here. Sorted. *-packr.go +*.db +*.snap +/bin/ +/build/ +/data/ +/dist/ +/dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof +/dnsfilter/tests/top-1m.csv +/launchpad_credentials +/querylog.json* +/snapcraft_login +AdGuardHome* +coverage.txt +node_modules/ diff --git a/HACKING.md b/HACKING.md index ae987af6..77c47eab 100644 --- a/HACKING.md +++ b/HACKING.md @@ -1,9 +1,9 @@ # *AdGuardHome* Developer Guidelines -As of **2020-11-27**, this document is a work-in-progress, but should still be -followed. Some of the rules aren't enforced as thoroughly or remain broken in -old code, but this is still the place to find out about what we **want** our -code to look like. +As of **December 2020**, this document is partially a work-in-progress, but +should still be followed. Some of the rules aren't enforced as thoroughly or +remain broken in old code, but this is still the place to find out about what we +**want** our code to look like. The rules are mostly sorted in the alphabetical order. @@ -32,6 +32,11 @@ The rules are mostly sorted in the alphabetical order. ## *Go* +> Not Golang, not GO, not GOLANG, not GoLang. It is Go in natural language, +> golang for others. + +— [@rakyll](https://twitter.com/rakyll/status/1229850223184269312) + ### Code And Naming * Avoid `goto`. @@ -141,6 +146,19 @@ The rules are mostly sorted in the alphabetical order. * **TODO(a.garipov):** Define our *Markdown* conventions. +## Shell Scripting + + * Avoid bashisms, prefer *POSIX* features only. + + * Prefer `'raw strings'` to `"double quoted strings"` whenever possible. + + * Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`. + + * Use `set -e -f -u` and also `set -x` in verbose mode. + + * Use the `"$var"` form instead of the `$var` form, unless word splitting is + required. + ## Text, Including Comments * End sentences with appropriate punctuation. diff --git a/Makefile b/Makefile index f98d430f..683a92b1 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,8 @@ # * DOCKER_IMAGE_NAME - adguard/adguard-home # * DOCKER_OUTPUT - type=image,name=adguard/adguard-home,push=true -GOPATH := $(shell go env GOPATH) +GO := go +GOPATH := $(shell $(GO) env GOPATH) PWD := $(shell pwd) TARGET=AdGuardHome BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)" @@ -122,9 +123,9 @@ init: git config core.hooksPath .githooks build: client_with_deps - go mod download - PATH=$(GOPATH)/bin:$(PATH) go generate ./... - CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" + $(GO) mod download + PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./... + CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" PATH=$(GOPATH)/bin:$(PATH) packr clean client: @@ -151,38 +152,40 @@ docker: @echo Now you can run the docker image: @echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME) -lint: lint-js lint-go +lint: js-lint go-lint -lint-js: dependencies - @echo Running js linter +js-lint: dependencies npm --prefix client run lint -lint-go: - @echo Running go linter - golangci-lint run +go-install-tools: + env GO=$(GO) sh ./scripts/go-install-tools.sh -test: test-js test-go +go-lint: + env GO=$(GO) PATH="$$PWD/bin:$$PATH" sh ./scripts/go-lint.sh -test-js: +test: js-test go-test + +js-test: npm run test --prefix client -test-go: - go test $(TEST_FLAGS) --coverprofile coverage.txt ./... +go-test: + $(GO) test $(TEST_FLAGS) --coverprofile coverage.txt ./... ci: client_with_deps - go mod download + $(GO) mod download $(MAKE) test dependencies: npm --prefix client ci - go mod download + $(GO) mod download clean: rm -f ./AdGuardHome ./AdGuardHome.exe ./coverage.txt - rm -f -r ./build/ ./client/node_modules/ ./data/ $(DIST_DIR) + rm -f -r ./build/ ./client/node_modules/ ./data/ ./$(DIST_DIR)/ # Set the GOPATH explicitly in case make clean is called from under sudo # after a Docker build. env PATH="$(GOPATH)/bin:$$PATH" packr clean + rm -f -r ./bin/ docker-multi-arch: DOCKER_CLI_EXPERIMENTAL=enabled \ @@ -200,7 +203,7 @@ docker-multi-arch: @echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME) release: client_with_deps - go mod download + $(GO) mod download @echo Starting release build: version $(VERSION), channel $(CHANNEL) CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND) $(call write_version_file,$(VERSION)) diff --git a/README.md b/README.md index 5d774ee4..b38452d7 100644 --- a/README.md +++ b/README.md @@ -171,9 +171,6 @@ You will need this to build AdGuard Home: * [node.js](https://nodejs.org/en/download/) v10.16.2 or later. * [npm](https://www.npmjs.com/) v6.14 or later. -Optionally, for Go devs: - * [golangci-lint](https://github.com/golangci/golangci-lint) - ### Building Open Terminal and execute these commands: @@ -186,7 +183,7 @@ make Check the [`Makefile`](https://github.com/AdguardTeam/AdGuardHome/blob/master/Makefile) to learn about other commands. -**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Golang project. +**Building for a different platform.** You can build AdGuard for any OS/ARCH just like any other Go project. In order to do this, specify `GOOS` and `GOARCH` env variables before running make. For example: @@ -331,4 +328,4 @@ For a full list of all node.js packages in use, please take a look at [client/pa ## Privacy -Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html). \ No newline at end of file +Our main idea is that you are the one, who should be in control of your data. So it is only natural, that AdGuard Home does not collect any usage statistics, and does not use any web services unless you configure it to do so. Full policy with every bit that _could in theory be_ sent by AdGuard Home is available [here](https://adguard.com/en/privacy/home.html). diff --git a/internal/tools/go.mod b/internal/tools/go.mod new file mode 100644 index 00000000..73bb637b --- /dev/null +++ b/internal/tools/go.mod @@ -0,0 +1,24 @@ +module github.com/AdguardTeam/AdGuardHome/internal/tools + +go 1.15 + +require ( + dmitri.shuralyov.com/go/generated v0.0.0-20170818220700-b1254a446363 // indirect + github.com/client9/misspell v0.3.4 // indirect + github.com/fzipp/gocyclo v0.3.1 + github.com/golangci/misspell v0.3.5 + github.com/google/go-cmp v0.5.4 // indirect + github.com/gookit/color v1.3.3 // indirect + github.com/gordonklaus/ineffassign v0.0.0-20201107091007-3b93a8888063 + github.com/kisielk/errcheck v1.4.0 + github.com/kyoh86/looppointer v0.1.7 + github.com/kyoh86/nolint v0.0.1 // indirect + github.com/securego/gosec/v2 v2.5.0 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b + golang.org/x/mod v0.4.0 // indirect + golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7 + gopkg.in/yaml.v2 v2.4.0 // indirect + honnef.co/go/tools v0.0.1-2020.1.6 + mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 + mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 +) diff --git a/internal/tools/go.sum b/internal/tools/go.sum new file mode 100644 index 00000000..968ac8a4 --- /dev/null +++ b/internal/tools/go.sum @@ -0,0 +1,164 @@ +dmitri.shuralyov.com/go/generated v0.0.0-20170818220700-b1254a446363 h1:o4lAkfETerCnr1kF9/qwkwjICnU+YLHNDCM8h2xj7as= +dmitri.shuralyov.com/go/generated v0.0.0-20170818220700-b1254a446363/go.mod h1:WG7q7swWsS2f9PYpt5DoEP/EBYWx8We5UoRltn9vJl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc= +github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golangci/misspell v0.3.5 h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/gookit/color v1.3.1 h1:PPD/C7sf8u2L8XQPdPgsWRoAiLQGZEZOzU3cf5IYYUk= +github.com/gookit/color v1.3.1/go.mod h1:R3ogXq2B9rTbXoSHJ1HyUVAZ3poOJHpd9nQmyGZsfvQ= +github.com/gookit/color v1.3.3 h1:6IAwxWCdABGNc4gI+YFBMYw0Hz8g5+lpYeKRAaALQoQ= +github.com/gookit/color v1.3.3/go.mod h1:GqqLKF1le3EfrbHbYsYa5WdLqfc/PHMdMRbt6tMnqIc= +github.com/gordonklaus/ineffassign v0.0.0-20201107091007-3b93a8888063 h1:dKprcOvlsvqfWn/iGvz+oYuC2axESeSMuF8dDrWMNsE= +github.com/gordonklaus/ineffassign v0.0.0-20201107091007-3b93a8888063/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kisielk/errcheck v1.4.0 h1:ueN6QYA+c7eDQo7ebpNdYR8mUJZThiGz9PEoJEMGPzA= +github.com/kisielk/errcheck v1.4.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kyoh86/looppointer v0.1.7 h1:q5sZOhFvmvQ6ZoZxvPB/Mjj2croWX7L49BBuI4XQWCM= +github.com/kyoh86/looppointer v0.1.7/go.mod h1:l0cRF49N6xDPx8IuBGC/imZo8Yn1BBLJY0vzI+4fepc= +github.com/kyoh86/nolint v0.0.0-20200711045849-7a7b0d649b7a h1:WKwgzTn8xp2JuhOUsTbi+h+QygLZSfVGwaYZUejGMMw= +github.com/kyoh86/nolint v0.0.0-20200711045849-7a7b0d649b7a/go.mod h1:hPeUNhNOZ22wXzQKMzeYKXVFTBjJ9czxjeIDyI1ueVM= +github.com/kyoh86/nolint v0.0.1 h1:GjNxDEkVn2wAxKHtP7iNTrRxytRZ1wXxLV5j4XzGfRU= +github.com/kyoh86/nolint v0.0.1/go.mod h1:1ZiZZ7qqrZ9dZegU96phwVcdQOMKIqRzFJL3ewq9gtI= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/securego/gosec/v2 v2.5.0 h1:kjfXLeKdk98gBe2+eYRFMpC4+mxmQQtbidpiiOQ69Qc= +github.com/securego/gosec/v2 v2.5.0/go.mod h1:L/CDXVntIff5ypVHIkqPXbtRpJiNCh6c6Amn68jXDjo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7 h1:2OSu5vYyX4LVqZAtqZXnFEcN26SDKIJYlEVIRl1tj8U= +golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc= +honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= +mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8= +mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0= +mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY= +mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= diff --git a/internal/tools/tools.go b/internal/tools/tools.go new file mode 100644 index 00000000..b2f83783 --- /dev/null +++ b/internal/tools/tools.go @@ -0,0 +1,22 @@ +// +build tools + +// Package tools and its main module are a nested internal module containing our +// development tool dependencies. +// +// See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module. +package tools + +import ( + _ "github.com/fzipp/gocyclo/cmd/gocyclo" + _ "github.com/golangci/misspell/cmd/misspell" + _ "github.com/gordonklaus/ineffassign" + _ "github.com/kisielk/errcheck" + _ "github.com/kyoh86/looppointer" + _ "github.com/securego/gosec/v2/cmd/gosec" + _ "golang.org/x/lint/golint" + _ "golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness" + _ "golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow" + _ "honnef.co/go/tools/cmd/staticcheck" + _ "mvdan.cc/gofumpt/gofumports" + _ "mvdan.cc/unparam" +) diff --git a/scripts/go-install-tools.sh b/scripts/go-install-tools.sh new file mode 100644 index 00000000..e75a54e4 --- /dev/null +++ b/scripts/go-install-tools.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +test "$VERBOSE" = '1' && set -x +set -e -f -u + +# TODO(a.garipov): Add goconst? + +env GOBIN="${PWD}/bin" "$GO" install --modfile=./internal/tools/go.mod\ + github.com/fzipp/gocyclo/cmd/gocyclo\ + github.com/golangci/misspell/cmd/misspell\ + github.com/gordonklaus/ineffassign\ + github.com/kisielk/errcheck\ + github.com/kyoh86/looppointer/cmd/looppointer\ + github.com/securego/gosec/v2/cmd/gosec\ + golang.org/x/lint/golint\ + golang.org/x/tools/go/analysis/passes/nilness/cmd/nilness\ + golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow\ + honnef.co/go/tools/cmd/staticcheck\ + mvdan.cc/gofumpt\ + mvdan.cc/unparam\ + ; diff --git a/scripts/go-lint.sh b/scripts/go-lint.sh new file mode 100644 index 00000000..9f4502c6 --- /dev/null +++ b/scripts/go-lint.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +# Verbosity levels: +# 0 = Don't print anything except for errors. +# 1 = Print commands, but not nested commands. +# 2 = Print everything. +test "${VERBOSE:=0}" -gt '0' && set -x + +# Set $EXITONERROR to zero to see all errors. +test "${EXITONERROR:=1}" = '0' && set +e || set -e + +# We don't need glob expansions and we want to see errors about unset +# variables. +set -f -u + +# blocklistimports is a simple check against unwanted packages. +# Currently it only looks for package log which is replaced by our own +# package github.com/AdguardTeam/golibs/log. +blocklistimports () { + git grep -F -e '"log"' -- '*.go' || exit 0; +} + +# underscores is a simple check against Go filenames with underscores. +underscores () { + git ls-files '*_*.go' | { grep -F -e '_darwin.go' \ + -e '_freebsd.go' -e '_linux.go' -e '_others.go' \ + -e '_test.go' -e '_unix.go' -e '_windows.go' \ + -v || exit 0; } +} + +# exitonoutput exits with a nonzero exit code if there is anything in +# the command's combined output. +exitonoutput() { + test "$VERBOSE" -lt '2' && set +x + + cmd="$1" + shift + + exitcode='0' + output="$("$cmd" "$@" 2>&1)" + if [ "$output" != '' ] + then + if [ "$*" != '' ] + then + echo "combined output of '$cmd $@':" + else + echo "combined output of '$cmd':" + fi + + echo "$output" + + exitcode='1' + fi + + test "$VERBOSE" -gt '0' && set -x + + return "$exitcode" +} + +exitonoutput blocklistimports + +exitonoutput underscores + +exitonoutput gofumpt --extra -l -s . + +golint --set_exit_status ./... + +"$GO" vet ./... + +gocyclo --over 20 . + +gosec --quiet . + +ineffassign . + +unparam ./... + +misspell --error ./... + +looppointer ./... + +nilness ./... + +# TODO(a.garipov): Enable shadow after fixing all of the shadowing. +# shadow --strict ./... + +# TODO(a.garipov): Enable errcheck fully after handling all errors, +# including the deferred ones, properly. Also, perhaps, enable --blank. +# errcheck ./... +exitonoutput sh -c ' + errcheck --asserts ./... |\ + { grep -e "defer" -e "_test\.go:" -v || exit 0; } +' + +staticcheck --checks='all' ./... From 5c7b6bbf5c1d2da5a4f524b4a0305a337356794a Mon Sep 17 00:00:00 2001 From: Artem Baskal Date: Tue, 8 Dec 2020 18:47:47 +0300 Subject: [PATCH 39/54] Use external links Squashed commit of the following: commit 5617cde490beea6f09e1beef1ff8b8e151e26244 Merge: 0a6500e75 7f9a3a73b Author: Artem Baskal Date: Tue Dec 8 18:34:19 2020 +0300 Merge branch 'master' into use-external-links commit 0a6500e75fbaa354a938c818f02f0b2419bd0d8e Merge: 9d2ff3bb5 73c30590e Author: Artem Baskal Date: Tue Dec 8 18:15:57 2020 +0300 Merge branch 'master' into use-external-links commit 9d2ff3bb5b3f5d5f08f26f54552ac07dd1724de5 Author: Artem Baskal Date: Tue Dec 8 18:04:09 2020 +0300 client: open external links in new tab commit 57d0ed09a8d90282b9dcad1c4943dd6d15a13cc8 Author: Zhijie He Date: Tue Nov 24 09:53:37 2020 +0800 Encryption: use rel="noopener noreferrer" commit 0554590059a3e9d5a9c1b576af2b409cff8c77e8 Author: Zhijie He Date: Tue Nov 24 09:51:19 2020 +0800 Clients: use rel="noopener noreferrer" --- client/src/components/Settings/Clients/Form.js | 2 +- client/src/components/Settings/Encryption/Form.js | 2 +- client/src/components/Settings/FiltersConfig/Form.js | 4 ++-- client/src/helpers/constants.js | 4 ++-- client/src/install/Setup/AddressList.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index 28f8a59c..20f1f828 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -259,7 +259,7 @@ let Form = (props) => {
    link, ]}> tags_desc diff --git a/client/src/components/Settings/Encryption/Form.js b/client/src/components/Settings/Encryption/Form.js index 8d69f665..1619ea3e 100644 --- a/client/src/components/Settings/Encryption/Form.js +++ b/client/src/components/Settings/Encryption/Form.js @@ -232,7 +232,7 @@ let Form = (props) => { + link , ]} diff --git a/client/src/components/Settings/FiltersConfig/Form.js b/client/src/components/Settings/FiltersConfig/Form.js index 7d41fe8a..d76631ea 100644 --- a/client/src/components/Settings/FiltersConfig/Form.js +++ b/client/src/components/Settings/FiltersConfig/Form.js @@ -7,7 +7,7 @@ import flow from 'lodash/flow'; import { CheckboxField, toNumber } from '../../../helpers/form'; import { FILTERS_INTERVALS_HOURS, - FILTERS_LINK, + FILTERS_RELATIVE_LINK, FORM_NAME, } from '../../../helpers/constants'; @@ -45,7 +45,7 @@ const Form = (props) => { } = props; const components = { - a: , + a: , }; return ( diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 7bce8387..af10524f 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -53,10 +53,10 @@ export const REPOSITORY = { export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html'; export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse'; export const UPSTREAM_CONFIGURATION_WIKI_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams'; -export const FILTERS_LINK = '#filters'; - export const GETTING_STARTED_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'; +export const FILTERS_RELATIVE_LINK = '#filters'; + export const ADDRESS_IN_USE_TEXT = 'address already in use'; export const INSTALL_FIRST_STEP = 1; diff --git a/client/src/install/Setup/AddressList.js b/client/src/install/Setup/AddressList.js index 90bccfd2..15cf7113 100644 --- a/client/src/install/Setup/AddressList.js +++ b/client/src/install/Setup/AddressList.js @@ -12,7 +12,7 @@ const renderItem = ({ return
  • {isDns ? {dnsAddress} - : {webAddress} + : {webAddress} }
  • ; }; From ef178610d6440ca187f419d844b45d93bad257af Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 9 Dec 2020 12:39:20 +0300 Subject: [PATCH 40/54] Pull request: all: cleanup Merge in DNS/adguard-home from cleanup to master Squashed commit of the following: commit a62e28cd35fefe45e228d1762aa2c148204c3065 Author: Ainar Garipov Date: Wed Dec 9 12:24:34 2020 +0300 all: more cleanup commit 04dc2220483fa3216b138b7b848b818dcc2a393a Author: Ainar Garipov Date: Tue Dec 8 20:52:28 2020 +0300 all: cleanup --- .githooks/pre-commit | 2 +- .golangci.yml | 77 -------------------------------------------- CHANGELOG.md | 6 ++-- HACKING.md | 2 -- 4 files changed, 5 insertions(+), 82 deletions(-) delete mode 100644 .golangci.yml diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 9889db34..b4fb2ee1 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -11,7 +11,7 @@ fi found=0 git diff --cached --name-only | grep -q '.go$' && found=1 if [ $found == 1 ]; then - make lint-go || exit 1 + make go-lint || exit 1 go test ./... || exit 1 fi diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 19402bd9..00000000 --- a/.golangci.yml +++ /dev/null @@ -1,77 +0,0 @@ -# options for analysis running -'run': - # default concurrency is a available CPU number - 'concurrency': 4 - - # timeout for analysis, e.g. 30s, 5m, default is 1m - 'deadline': '2m' - - # which files to skip: they will be analyzed, but issues from them - # won't be reported. Default value is empty list, but there is - # no need to include all autogenerated files, we confidently recognize - # autogenerated files. If it's not please let us know. - 'skip-files': - - '.*generated.*' - - 'dnsfilter/rule_to_regexp.go' - - 'util/pprof.go' - - '.*_test.go' - - 'client/.*' - - 'build/.*' - - 'dist/.*' - -# all available settings of specific linters -'linters-settings': - 'errcheck': - # [deprecated] comma-separated list of pairs of the form pkg:regex - # the regex is used to ignore names within pkg. (default "fmt:.*"). - # see https://github.com/kisielk/errcheck#the-deprecated-method for details - 'ignore': 'fmt:.*,net:SetReadDeadline,net/http:^Write' - 'gocyclo': - 'min-complexity': 20 - 'lll': - 'line-length': 200 - -'linters': - 'enable': - - 'bodyclose' - - 'deadcode' - - 'depguard' - - 'dupl' - - 'errcheck' - - 'gocyclo' - - 'goimports' - - 'golint' - - 'gosec' - - 'govet' - - 'ineffassign' - - 'misspell' - - 'staticcheck' - - 'stylecheck' - - 'unconvert' - - 'unused' - - 'varcheck' - 'disable-all': true - 'fast': true - -'issues': - # List of regexps of issue texts to exclude, empty list by default. - # But independently from this option we use default exclude patterns, - # it can be disabled by `exclude-use-default: false`. To list all - # excluded by default patterns execute `golangci-lint run --help` - 'exclude': - # structcheck cannot detect usages while they're there - - '.parentalServer. is unused' - - '.safeBrowsingServer. is unused' - # errcheck - - 'Error return value of .s.closeConn. is not checked' - - 'Error return value of ..*.Shutdown.' - # goconst - - 'string .forcesafesearch.google.com. has 3 occurrences' - # gosec: Profiling endpoint is automatically exposed on /debug/pprof - - 'G108' - # gosec: Subprocess launched with function call as argument or cmd arguments - - 'G204' - # gosec: Potential DoS vulnerability via decompression bomb - - 'G110' - # gosec: Expect WriteFile permissions to be 0600 or less - - 'G306' diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b0f7ad..8bf8be89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,14 +31,16 @@ and this project adheres to ### Changed -- Post-updating relaunch possibility is now determined OS-dependently ([#2231], [#2391]). +- Post-updating relaunch possibility is now determined OS-dependently ([#2231], + [#2391]). - Made the mobileconfig HTTP API more robust and predictable, add parameters and improve error response ([#2358]). - Improved HTTP requests handling and timeouts ([#2343]). - Our snap package now uses the `core20` image as its base ([#2306]). -- Various internal improvements ([#2271], [#2297]). +- Various internal improvements ([#2267], [#2271], [#2297]). [#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231 +[#2267]: https://github.com/AdguardTeam/AdGuardHome/issues/2267 [#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271 [#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297 [#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306 diff --git a/HACKING.md b/HACKING.md index 77c47eab..4defedcd 100644 --- a/HACKING.md +++ b/HACKING.md @@ -117,8 +117,6 @@ The rules are mostly sorted in the alphabetical order. * Use `gofumpt --extra -s`. - **TODO(a.garipov):** Add to the linters. - * Write slices of struct like this: ```go From e02308dd422bf89244ca0958ef67653b1029342f Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 10 Dec 2020 14:35:07 +0300 Subject: [PATCH 41/54] Pull request: scripts: improve go-lint Merge in DNS/adguard-home from imp-lint-script to master Squashed commit of the following: commit 89a6e8343f9f0c7ea257899b5daac014bfb6b6df Author: Ainar Garipov Date: Thu Dec 10 13:36:38 2020 +0300 script: make go-lint more in line with HACKING.md commit dc4e1519d25877a074f667fec696578c80d7baf3 Author: Ainar Garipov Date: Thu Dec 10 13:35:03 2020 +0300 scripts: improve go-lint --- HACKING.md | 2 ++ scripts/go-lint.sh | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/HACKING.md b/HACKING.md index 4defedcd..fdc2e3a9 100644 --- a/HACKING.md +++ b/HACKING.md @@ -152,6 +152,8 @@ The rules are mostly sorted in the alphabetical order. * Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`. + * `snake_case`, not `camelCase`. + * Use `set -e -f -u` and also `set -x` in verbose mode. * Use the `"$var"` form instead of the `$var` form, unless word splitting is diff --git a/scripts/go-lint.sh b/scripts/go-lint.sh index 9f4502c6..e54cd53e 100644 --- a/scripts/go-lint.sh +++ b/scripts/go-lint.sh @@ -13,24 +13,44 @@ test "${EXITONERROR:=1}" = '0' && set +e || set -e # variables. set -f -u -# blocklistimports is a simple check against unwanted packages. +not_found_msg=' +looks like a binary not found error. +make sure you have installed the linter binaries using: + + $ make go-install-tools +' + +not_found() { + if [ "$?" = '127' ] + then + # Code 127 is the exit status a shell uses when + # a command or a file is not found, according to the + # Bash Hackers wiki. + # + # See https://wiki.bash-hackers.org/dict/terms/exit_status. + echo "$not_found_msg" 1>&2 + fi +} +trap not_found EXIT + +# blocklist_imports is a simple check against unwanted packages. # Currently it only looks for package log which is replaced by our own # package github.com/AdguardTeam/golibs/log. -blocklistimports () { +blocklist_imports() { git grep -F -e '"log"' -- '*.go' || exit 0; } # underscores is a simple check against Go filenames with underscores. -underscores () { +underscores() { git ls-files '*_*.go' | { grep -F -e '_darwin.go' \ -e '_freebsd.go' -e '_linux.go' -e '_others.go' \ -e '_test.go' -e '_unix.go' -e '_windows.go' \ -v || exit 0; } } -# exitonoutput exits with a nonzero exit code if there is anything in +# exit_on_output exits with a nonzero exit code if there is anything in # the command's combined output. -exitonoutput() { +exit_on_output() { test "$VERBOSE" -lt '2' && set +x cmd="$1" @@ -57,11 +77,11 @@ exitonoutput() { return "$exitcode" } -exitonoutput blocklistimports +exit_on_output blocklist_imports -exitonoutput underscores +exit_on_output underscores -exitonoutput gofumpt --extra -l -s . +exit_on_output gofumpt --extra -l -s . golint --set_exit_status ./... @@ -87,7 +107,7 @@ nilness ./... # TODO(a.garipov): Enable errcheck fully after handling all errors, # including the deferred ones, properly. Also, perhaps, enable --blank. # errcheck ./... -exitonoutput sh -c ' +exit_on_output sh -c ' errcheck --asserts ./... |\ { grep -e "defer" -e "_test\.go:" -v || exit 0; } ' From e2738fdf3f2cab3c4832bc3173c6cebd36379443 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 10 Dec 2020 14:49:11 +0300 Subject: [PATCH 42/54] Pull request: all: fix snapshot release version length Merge in DNS/adguard-home from 2410-fix-snapshot to master Updates #2410. Updates #2412. Squashed commit of the following: commit 6718e018533abbd02ccefdb5a0030655d5e8012a Merge: ba5fc4c58 e02308dd4 Author: Ainar Garipov Date: Thu Dec 10 14:36:19 2020 +0300 Merge branch 'master' into 2410-fix-snapshot commit ba5fc4c58b1f2be0b3e6fbbeea04f70b506633f2 Author: Ainar Garipov Date: Thu Dec 10 13:12:12 2020 +0300 all: fix snapshot release version length --- .goreleaser.yml | 8 ++++++++ Makefile | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index 7c132f87..c533712b 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -105,3 +105,11 @@ 'checksum': 'name_template': 'checksums.txt' + +'snapshot': + # TODO(a.garipov): A temporary solution to trim the prerelease versions. + # A real solution would consist of making a better versioning scheme that also + # doesn't break SemVer or Snapcraft. + # + # See https://github.com/AdguardTeam/AdGuardHome/issues/2412. + 'name_template': '{{ slice .Tag 0 8 }}-SNAPSHOT-{{ .ShortCommit }}' diff --git a/Makefile b/Makefile index 683a92b1..1848fe34 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,9 @@ endif # Version properties COMMIT=$(shell git rev-parse --short HEAD) -TAG_NAME=$(shell git describe --abbrev=0) +# TODO(a.garipov): The cut call is a temporary solution to trim +# prerelease versions. See the comment in .goreleaser.yml. +TAG_NAME=$(shell git describe --abbrev=0 | cut -c 1-8) RELEASE_VERSION=$(TAG_NAME) SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT) From 2c2a359d0ad26748b672c1b99aac92c5a4823039 Mon Sep 17 00:00:00 2001 From: Aaron Bieber Date: Fri, 11 Dec 2020 08:20:45 -0700 Subject: [PATCH 43/54] Update quic-go to v0.19.3 quic-go at version 0.19.1 has a bug that prevents it from building on systems that don't have IP_RECVTOS. Version 0.19.3 contains a fix: https://github.com/lucas-clemente/quic-go/pull/2886 --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 8b232972..bf3ae7e3 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/joomcode/errorx v1.0.3 // indirect github.com/kardianos/service v1.2.0 github.com/karrick/godirwalk v1.16.1 // indirect - github.com/lucas-clemente/quic-go v0.19.1 // indirect + github.com/lucas-clemente/quic-go v0.19.3 // indirect github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/miekg/dns v1.1.35 diff --git a/go.sum b/go.sum index af30b9bd..50893c45 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,6 @@ github.com/AdguardTeam/dnsproxy v0.33.2/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8u github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= -github.com/AdguardTeam/golibs v0.4.3 h1:nXTLLLlIyU4BSRF0An5azS0uimSK/YpIMOBAO0/v1RY= -github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= @@ -253,8 +251,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= -github.com/lucas-clemente/quic-go v0.19.1 h1:J9TkQJGJVOR3UmGhd4zdVYwKSA0EoXbLRf15uQJ6gT4= -github.com/lucas-clemente/quic-go v0.19.1/go.mod h1:ZUygOqIoai0ASXXLJ92LTnKdbqh9MHCLTX6Nr1jUrK0= +github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= +github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= From a2d39c810a13b3738af75c1db73568b15fcd3a87 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 14 Dec 2020 20:12:57 +0300 Subject: [PATCH 44/54] Pull request: dnsfilter: restore enum value Merge in DNS/adguard-home from fix-filter-names to master Squashed commit of the following: commit 620d80df12878a1b236abb26cd2a331df998a3d5 Author: Ainar Garipov Date: Mon Dec 14 20:02:41 2020 +0300 dnsfilter: restore enum value --- .githooks/pre-commit | 23 +++++++++-------------- internal/dnsfilter/dnsfilter.go | 3 +++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.githooks/pre-commit b/.githooks/pre-commit index b4fb2ee1..f2bbf1c9 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,18 +1,13 @@ -#!/bin/bash -set -e; +#!/bin/sh -found=0 -git diff --cached --name-only | grep -q '.js$' && found=1 -if [ $found == 1 ]; then - npm --prefix client run lint || exit 1 - npm run test --prefix client || exit 1 +set -e -f -u + +if [ "$(git diff --cached --name-only '*.js')" ] +then + make js-lint js-test fi -found=0 -git diff --cached --name-only | grep -q '.go$' && found=1 -if [ $found == 1 ]; then - make go-lint || exit 1 - go test ./... || exit 1 +if [ "$(git diff --cached --name-only '*.go')" ] +then + make go-lint go-test fi - -exit 0; diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 340bfaaf..2690507d 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -129,6 +129,9 @@ const ( NotFilteredNotFound Reason = iota // NotFilteredWhiteList - the host is explicitly whitelisted NotFilteredWhiteList + // NotFilteredError is return where there was an error during + // checking. Reserved, currently unused. + NotFilteredError // reasons for filtering From 2c56a685979d3746851f4074b6e51a216ab1c3b3 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Wed, 16 Dec 2020 16:35:57 +0300 Subject: [PATCH 45/54] Pull request: all: update dnsproxy Merge in DNS/adguard-home from 2394-update-dnsproxy to master Updates #2394. Squashed commit of the following: commit 57526d188055af7d63b8d2fa0cc339612ff4c098 Author: Ainar Garipov Date: Wed Dec 16 14:43:29 2020 +0300 all: document changes commit acdfd6c219b0570770faf291fadc69ec1855baf4 Author: Ainar Garipov Date: Wed Dec 16 14:28:23 2020 +0300 all: update dnsproxy --- CHANGELOG.md | 6 +++++- go.mod | 15 +++++---------- go.sum | 36 ++++++++++++++++-------------------- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf8be89..7ccb70e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to - Detecting of network interface configurated to have static IP address via `/etc/network/interfaces` ([#2302]). -- DNSCrypt protocol support [#1361]. +- DNSCrypt protocol support ([#1361]). - A 5 second wait period until a DHCP server's network interface gets an IP address ([#2304]). - `$dnstype` modifier for filters ([#2337]). @@ -31,6 +31,9 @@ and this project adheres to ### Changed +- When `dns.bogus_nxdomain` option is used, the server will now transform + responses if there is at least one bogus address instead of all of them + ([#2394]). The new behavior is the same as in `dnsmasq`. - Post-updating relaunch possibility is now determined OS-dependently ([#2231], [#2391]). - Made the mobileconfig HTTP API more robust and predictable, add parameters and @@ -47,6 +50,7 @@ and this project adheres to [#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343 [#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358 [#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391 +[#2394]: https://github.com/AdguardTeam/AdGuardHome/issues/2394 ### Fixed diff --git a/go.mod b/go.mod index bf3ae7e3..3e9c2640 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,11 @@ module github.com/AdguardTeam/AdGuardHome go 1.14 require ( - github.com/AdguardTeam/dnsproxy v0.33.2 + github.com/AdguardTeam/dnsproxy v0.33.5 github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 - github.com/ameshkov/dnscrypt/v2 v2.0.0 - github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect + github.com/ameshkov/dnscrypt/v2 v2.0.1 github.com/fsnotify/fsnotify v1.4.9 github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 github.com/gobuffalo/envy v1.9.0 // indirect @@ -16,10 +15,8 @@ require ( github.com/gobuffalo/packr/v2 v2.8.1 // indirect github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 - github.com/joomcode/errorx v1.0.3 // indirect github.com/kardianos/service v1.2.0 github.com/karrick/godirwalk v1.16.1 // indirect - github.com/lucas-clemente/quic-go v0.19.3 // indirect github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 github.com/miekg/dns v1.1.35 @@ -30,14 +27,12 @@ require ( github.com/stretchr/testify v1.6.1 github.com/u-root/u-root v7.0.0+incompatible go.etcd.io/bbolt v1.3.5 - golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 - golang.org/x/net v0.0.0-20201110031124-69a78807bb2b + golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 + golang.org/x/net v0.0.0-20201216054612-986b41b23924 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect - golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 - golang.org/x/text v0.3.4 // indirect + golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v2 v2.3.0 - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect howett.net/plist v0.0.0-20201026045517-117a925f2150 ) diff --git a/go.sum b/go.sum index 50893c45..579cd3a1 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk= -github.com/AdguardTeam/dnsproxy v0.33.2/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8uPWO4JQsU1EDJA= +github.com/AdguardTeam/dnsproxy v0.33.5 h1:YTfY16lFgFqBzC3pdKalQSaeb7hjm4VvGqdksO0AoVI= +github.com/AdguardTeam/dnsproxy v0.33.5/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= @@ -42,8 +42,8 @@ github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyY github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/ameshkov/dnscrypt/v2 v2.0.0 h1:i83G8MeGLrAFgUL8GSu98TVhtFDEifF7SIS7Qi/RZ3U= -github.com/ameshkov/dnscrypt/v2 v2.0.0/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI= +github.com/ameshkov/dnscrypt/v2 v2.0.1 h1:igNVNM6NLBOqYUzHXaDUxn8i+wJXOsosY0/xEBirixA= +github.com/ameshkov/dnscrypt/v2 v2.0.1/go.mod h1:nbZnxJt4edIPx2Haa8n2XtC2g5AWcsdQiSuXkNH8eDI= 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/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo= @@ -53,8 +53,6 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -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/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 h1:M57m0xQqZIhx7CEJgeLSvRFKEK1RjzRuIXiA3HfYU7g= github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -249,8 +247,6 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= -github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= github.com/lucas-clemente/quic-go v0.19.3 h1:eCDQqvGBB+kCTkA0XrAFtNe81FMa0/fn4QSoeAbmiF4= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= @@ -263,12 +259,9 @@ github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= -github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg= -github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-15 v0.1.1 h1:LIH6K34bPVttyXnUWixk0bzH6/N07VxbSabxn5A5gZQ= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -287,7 +280,6 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/miekg/dns v1.1.34/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -455,10 +447,8 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= +golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -514,9 +504,12 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= +golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -572,14 +565,19 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214095126-aec9a390925b h1:tv7/y4pd+sR8bcNb2D6o7BNU6zjWm0VjQLac+w7fNNM= +golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -694,8 +692,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= From 2e8352d31c8e4f85ed45f7cbc09e8106da046db3 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 17 Dec 2020 13:32:46 +0300 Subject: [PATCH 46/54] Pull request #886: all: allow multiple rules in dns filter results Merge in DNS/adguard-home from 2102-rules-result to master Updates #2102. Squashed commit of the following: commit 47b2aa94c56b37be492c3c01e8111054612d9722 Author: Ainar Garipov Date: Thu Dec 17 13:12:27 2020 +0300 querylog: remove pre-v0.99.3 compatibility code commit 2af0ee43c2444a7d842fcff057f2ba02f300244b Author: Ainar Garipov Date: Thu Dec 17 13:00:27 2020 +0300 all: improve documentation commit 3add300a42f0aa67bb315a448e294636c85d0b3b Author: Ainar Garipov Date: Wed Dec 16 18:30:01 2020 +0300 all: improve changelog commit e04ef701fc2de7f4453729e617641c47e0883679 Author: Ainar Garipov Date: Wed Dec 16 17:56:53 2020 +0300 all: improve code and documentation commit 4f04845ae275ae4291869e00c62e4ff81b01eaa3 Author: Ainar Garipov Date: Wed Dec 16 17:01:08 2020 +0300 all: document changes, improve api commit bc59b7656a402d0c65f13bd74a71d8dda6a8a65d Author: Ainar Garipov Date: Tue Dec 15 18:22:01 2020 +0300 all: allow multiple rules in dns filter results --- AGHTechDoc.md | 22 +- CHANGELOG.md | 7 +- HACKING.md | 19 +- internal/dnsfilter/blocked.go | 8 +- internal/dnsfilter/dnsfilter.go | 182 ++++++++++------ internal/dnsfilter/dnsfilter_test.go | 202 +++++++++--------- internal/dnsfilter/rewrites.go | 10 +- internal/dnsfilter/rewrites_test.go | 10 +- .../dnsfilter/{sbpc.go => safebrowsing.go} | 32 +-- .../{sbpc_test.go => safebrowsing_test.go} | 0 internal/dnsfilter/safesearch.go | 45 ++-- internal/dnsforward/dnsforward.go | 4 +- internal/dnsforward/dnsforward_test.go | 8 +- internal/dnsforward/filter.go | 6 +- internal/dnsforward/msg.go | 11 +- internal/home/controlfiltering.go | 31 ++- internal/home/home.go | 2 +- internal/querylog/decode.go | 153 ++++++++----- internal/querylog/decode_test.go | 26 +-- internal/querylog/json.go | 70 +++--- internal/querylog/qlog_test.go | 6 +- internal/querylog/searchcriteria.go | 4 +- internal/tools/go.mod | 2 +- internal/tools/go.sum | 3 + openapi/CHANGELOG.md | 32 +++ openapi/openapi.yaml | 55 ++++- scripts/go-lint.sh | 2 +- staticcheck.conf | 14 ++ 28 files changed, 610 insertions(+), 356 deletions(-) rename internal/dnsfilter/{sbpc.go => safebrowsing.go} (91%) rename internal/dnsfilter/{sbpc_test.go => safebrowsing_test.go} (100%) create mode 100644 staticcheck.conf diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 45dee068..b91b8586 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -1833,16 +1833,22 @@ Response: 200 OK { - "reason":"FilteredBlackList", - "filter_id":1, - "rule":"||doubleclick.net^", - "service_name": "...", // set if reason=FilteredBlockedService - - // if reason=ReasonRewrite: - "cname": "...", - "ip_addrs": ["1.2.3.4", ...], + "reason":"FilteredBlackList", + "rules":{ + "filter_list_id":42, + "text":"||doubleclick.net^", + }, + // If we have "reason":"FilteredBlockedService". + "service_name": "...", + // If we have "reason":"Rewrite". + "cname": "...", + "ip_addrs": ["1.2.3.4", ...] } +There are also deprecated properties `filter_id` and `rule` on the top level of +the response object. Their usaga should be replaced with +`rules[*].filter_list_id` and `rules[*].text` correspondingly. See the +_OpenAPI_ documentation and the `./openapi/CHANGELOG.md` file. ## Log-in page diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ccb70e4..fda4fe42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,9 @@ and this project adheres to ### Added -- Detecting of network interface configurated to have static IP address via +- The host checking API and the query logs API can now return multiple matched + rules ([#2102]). +- Detecting of network interface configured to have static IP address via `/etc/network/interfaces` ([#2302]). - DNSCrypt protocol support ([#1361]). - A 5 second wait period until a DHCP server's network interface gets an IP @@ -24,6 +26,7 @@ and this project adheres to - HTTP API request body size limit ([#2305]). [#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361 +[#2102]: https://github.com/AdguardTeam/AdGuardHome/issues/2102 [#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302 [#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304 [#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305 @@ -64,7 +67,9 @@ and this project adheres to [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 +### Removed +- Support for pre-v0.99.3 format of query logs ([#2102]). ## [v0.104.3] - 2020-11-19 diff --git a/HACKING.md b/HACKING.md index fdc2e3a9..ae14b784 100644 --- a/HACKING.md +++ b/HACKING.md @@ -78,6 +78,14 @@ The rules are mostly sorted in the alphabetical order. * Prefer constants to variables where possible. Reduce global variables. Use [constant errors] instead of `errors.New`. + * Unused arguments in anonymous functions must be called `_`: + + ```go + v.onSuccess = func(_ int, msg string) { + // … + } + ``` + * Use linters. * Use named returns to improve readability of function signatures. @@ -106,7 +114,16 @@ The rules are mostly sorted in the alphabetical order. ```go // Foo implements the Fooer interface for *foo. func (f *foo) Foo() { - // … + // … + } + ``` + + When the implemented interface is unexported: + + ```go + // Unwrap implements the hidden wrapper interface for *fooError. + func (err *fooError) Unwrap() (unwrapped error) { + // … } ``` diff --git a/internal/dnsfilter/blocked.go b/internal/dnsfilter/blocked.go index 08990e0a..48b02932 100644 --- a/internal/dnsfilter/blocked.go +++ b/internal/dnsfilter/blocked.go @@ -188,7 +188,7 @@ func BlockedSvcKnown(s string) bool { } // ApplyBlockedServices - set blocked services settings for this DNS request -func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) { +func (d *DNSFilter) ApplyBlockedServices(setts *RequestFilteringSettings, list []string, global bool) { setts.ServicesRules = []ServiceEntry{} if global { d.confLock.RLock() @@ -210,7 +210,7 @@ func (d *Dnsfilter) ApplyBlockedServices(setts *RequestFilteringSettings, list [ } } -func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) { d.confLock.RLock() list := d.Config.BlockedServices d.confLock.RUnlock() @@ -223,7 +223,7 @@ func (d *Dnsfilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Req } } -func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) { list := []string{} err := json.NewDecoder(r.Body).Decode(&list) if err != nil { @@ -241,7 +241,7 @@ func (d *Dnsfilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ } // registerBlockedServicesHandlers - register HTTP handlers -func (d *Dnsfilter) registerBlockedServicesHandlers() { +func (d *DNSFilter) registerBlockedServicesHandlers() { d.Config.HTTPRegister("GET", "/control/blocked_services/list", d.handleBlockedServicesList) d.Config.HTTPRegister("POST", "/control/blocked_services/set", d.handleBlockedServicesSet) } diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 2690507d..1735154d 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -91,8 +91,8 @@ type filtersInitializerParams struct { blockFilters []Filter } -// Dnsfilter holds added rules and performs hostname matches against the rules -type Dnsfilter struct { +// DNSFilter matches hostnames and DNS requests against filtering rules. +type DNSFilter struct { rulesStorage *filterlist.RuleStorage filteringEngine *urlfilter.DNSEngine rulesStorageWhite *filterlist.RuleStorage @@ -129,7 +129,7 @@ const ( NotFilteredNotFound Reason = iota // NotFilteredWhiteList - the host is explicitly whitelisted NotFilteredWhiteList - // NotFilteredError is return where there was an error during + // NotFilteredError is returned when there was an error during // checking. Reserved, currently unused. NotFilteredError @@ -148,27 +148,32 @@ const ( // FilteredBlockedService - the host is blocked by "blocked services" settings FilteredBlockedService - // ReasonRewrite - rewrite rule was applied + // ReasonRewrite is returned when there was a rewrite by + // a legacy DNS Rewrite rule. ReasonRewrite - // RewriteEtcHosts - rewrite by /etc/hosts rule - RewriteEtcHosts + // RewriteAutoHosts is returned when there was a rewrite by + // autohosts rules (/etc/hosts and so on). + RewriteAutoHosts ) +// TODO(a.garipov): Resync with actual code names or replace completely +// in HTTP API v1. var reasonNames = []string{ - "NotFilteredNotFound", - "NotFilteredWhiteList", - "NotFilteredError", + NotFilteredNotFound: "NotFilteredNotFound", + NotFilteredWhiteList: "NotFilteredWhiteList", + NotFilteredError: "NotFilteredError", - "FilteredBlackList", - "FilteredSafeBrowsing", - "FilteredParental", - "FilteredInvalid", - "FilteredSafeSearch", - "FilteredBlockedService", + FilteredBlackList: "FilteredBlackList", + FilteredSafeBrowsing: "FilteredSafeBrowsing", + FilteredParental: "FilteredParental", + FilteredInvalid: "FilteredInvalid", + FilteredSafeSearch: "FilteredSafeSearch", + FilteredBlockedService: "FilteredBlockedService", - "Rewrite", - "RewriteEtcHosts", + ReasonRewrite: "Rewrite", + + RewriteAutoHosts: "RewriteEtcHosts", } func (r Reason) String() string { @@ -189,7 +194,7 @@ func (r Reason) In(reasons ...Reason) bool { } // GetConfig - get configuration -func (d *Dnsfilter) GetConfig() RequestFilteringSettings { +func (d *DNSFilter) GetConfig() RequestFilteringSettings { c := RequestFilteringSettings{} // d.confLock.RLock() c.SafeSearchEnabled = d.Config.SafeSearchEnabled @@ -200,7 +205,7 @@ func (d *Dnsfilter) GetConfig() RequestFilteringSettings { } // WriteDiskConfig - write configuration -func (d *Dnsfilter) WriteDiskConfig(c *Config) { +func (d *DNSFilter) WriteDiskConfig(c *Config) { d.confLock.Lock() *c = d.Config c.Rewrites = rewriteArrayDup(d.Config.Rewrites) @@ -211,7 +216,7 @@ func (d *Dnsfilter) WriteDiskConfig(c *Config) { // SetFilters - set new filters (synchronously or asynchronously) // When filters are set asynchronously, the old filters continue working until the new filters are ready. // In this case the caller must ensure that the old filter files are intact. -func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error { +func (d *DNSFilter) SetFilters(blockFilters, allowFilters []Filter, async bool) error { if async { params := filtersInitializerParams{ allowFilters: allowFilters, @@ -245,7 +250,7 @@ func (d *Dnsfilter) SetFilters(blockFilters, allowFilters []Filter, async bool) } // Starts initializing new filters by signal from channel -func (d *Dnsfilter) filtersInitializer() { +func (d *DNSFilter) filtersInitializer() { for { params := <-d.filtersInitializerChan err := d.initFiltering(params.allowFilters, params.blockFilters) @@ -257,13 +262,13 @@ func (d *Dnsfilter) filtersInitializer() { } // Close - close the object -func (d *Dnsfilter) Close() { +func (d *DNSFilter) Close() { d.engineLock.Lock() defer d.engineLock.Unlock() d.reset() } -func (d *Dnsfilter) reset() { +func (d *DNSFilter) reset() { var err error if d.rulesStorage != nil { @@ -290,34 +295,60 @@ type dnsFilterContext struct { var gctx dnsFilterContext // global dnsfilter context -// Result holds state of hostname check -type Result struct { - IsFiltered bool `json:",omitempty"` // True if the host name is filtered - Reason Reason `json:",omitempty"` // Reason for blocking / unblocking - Rule string `json:",omitempty"` // Original rule text - IP net.IP `json:",omitempty"` // Not nil only in the case of a hosts file syntax - FilterID int64 `json:",omitempty"` // Filter ID the rule belongs to - - // for ReasonRewrite: - CanonName string `json:",omitempty"` // CNAME value - - // for RewriteEtcHosts: - ReverseHosts []string `json:",omitempty"` - - // for ReasonRewrite & RewriteEtcHosts: - IPList []net.IP `json:",omitempty"` // list of IP addresses - - // for FilteredBlockedService: - ServiceName string `json:",omitempty"` // Name of the blocked service +// ResultRule contains information about applied rules. +type ResultRule struct { + // FilterListID is the ID of the rule's filter list. + FilterListID int64 `json:",omitempty"` + // Text is the text of the rule. + Text string `json:",omitempty"` + // IP is the host IP. It is nil unless the rule uses the + // /etc/hosts syntax or the reason is FilteredSafeSearch. + IP net.IP `json:",omitempty"` } -// Matched can be used to see if any match at all was found, no matter filtered or not +// Result contains the result of a request check. +// +// All fields transitively have omitempty tags so that the query log +// doesn't become too large. +// +// TODO(a.garipov): Clarify relationships between fields. Perhaps +// replace with a sum type or an interface? +type Result struct { + // IsFiltered is true if the request is filtered. + IsFiltered bool `json:",omitempty"` + + // Reason is the reason for blocking or unblocking the request. + Reason Reason `json:",omitempty"` + + // Rules are applied rules. If Rules are not empty, each rule + // is not nil. + Rules []*ResultRule `json:",omitempty"` + + // ReverseHosts is the reverse lookup rewrite result. It is + // empty unless Reason is set to RewriteAutoHosts. + ReverseHosts []string `json:",omitempty"` + + // IPList is the lookup rewrite result. It is empty unless + // Reason is set to RewriteAutoHosts or ReasonRewrite. + IPList []net.IP `json:",omitempty"` + + // CanonName is the CNAME value from the lookup rewrite result. + // It is empty unless Reason is set to ReasonRewrite. + CanonName string `json:",omitempty"` + + // ServiceName is the name of the blocked service. It is empty + // unless Reason is set to FilteredBlockedService. + ServiceName string `json:",omitempty"` +} + +// Matched returns true if any match at all was found regardless of +// whether it was filtered or not. func (r Reason) Matched() bool { return r != NotFilteredNotFound } -// CheckHostRules tries to match the host against filtering rules only -func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) { +// CheckHostRules tries to match the host against filtering rules only. +func (d *DNSFilter) CheckHostRules(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) { if !setts.FilteringEnabled { return Result{}, nil } @@ -325,9 +356,9 @@ func (d *Dnsfilter) CheckHostRules(host string, qtype uint16, setts *RequestFilt return d.matchHost(host, qtype, *setts) } -// CheckHost tries to match the host against filtering rules, -// then safebrowsing and parental if they are enabled -func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) { +// CheckHost tries to match the host against filtering rules, then +// safebrowsing and parental control rules, if they are enabled. +func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFilteringSettings) (Result, error) { // sometimes DNS clients will try to resolve ".", which is a request to get root servers if host == "" { return Result{Reason: NotFilteredNotFound}, nil @@ -413,10 +444,10 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering return Result{}, nil } -func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) { +func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (matched bool) { ips := d.Config.AutoHosts.Process(host, qtype) if ips != nil { - result.Reason = RewriteEtcHosts + result.Reason = RewriteAutoHosts result.IPList = ips return true @@ -424,7 +455,7 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype) if len(revHosts) != 0 { - result.Reason = RewriteEtcHosts + result.Reason = RewriteAutoHosts // TODO(a.garipov): Optimize this with a buffer. result.ReverseHosts = make([]string, len(revHosts)) @@ -445,7 +476,7 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m // . repeat for the new domain name (Note: we return only the last CNAME) // . Find A or AAAA record for a domain name (exact match or by wildcard) // . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array -func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result { +func (d *DNSFilter) processRewrites(host string, qtype uint16) Result { var res Result d.confLock.RLock() @@ -504,9 +535,16 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result { res.Reason = FilteredBlockedService res.IsFiltered = true res.ServiceName = s.Name - res.Rule = rule.Text() - log.Debug("Blocked Services: matched rule: %s host: %s service: %s", - res.Rule, host, s.Name) + + ruleText := rule.Text() + res.Rules = []*ResultRule{{ + FilterListID: int64(rule.GetFilterListID()), + Text: ruleText, + }} + + log.Debug("blocked services: matched rule: %s host: %s service: %s", + ruleText, host, s.Name) + return res } } @@ -573,7 +611,7 @@ func createFilteringEngine(filters []Filter) (*filterlist.RuleStorage, *urlfilte } // Initialize urlfilter objects. -func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error { +func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error { rulesStorage, filteringEngine, err := createFilteringEngine(blockFilters) if err != nil { return err @@ -600,7 +638,7 @@ func (d *Dnsfilter) initFiltering(allowFilters, blockFilters []Filter) error { // matchHost is a low-level way to check only if hostname is filtered by rules, // skipping expensive safebrowsing and parental lookups. -func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) { +func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) { d.engineLock.RLock() // Keep in mind that this lock must be held no just when calling Match() // but also while using the rules returned by it. @@ -658,7 +696,8 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS log.Debug("Filtering: found rule for host %q: %q list_id: %d", host, rule.Text(), rule.GetFilterListID()) res := makeResult(rule, FilteredBlackList) - res.IP = rule.IP.To4() + res.Rules[0].IP = rule.IP.To4() + return res, nil } @@ -667,7 +706,8 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS log.Debug("Filtering: found rule for host %q: %q list_id: %d", host, rule.Text(), rule.GetFilterListID()) res := makeResult(rule, FilteredBlackList) - res.IP = rule.IP + res.Rules[0].IP = rule.IP + return res, nil } @@ -683,22 +723,28 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS log.Debug("Filtering: found rule for host %q: %q list_id: %d", host, rule.Text(), rule.GetFilterListID()) res := makeResult(rule, FilteredBlackList) - res.IP = net.IP{} + res.Rules[0].IP = net.IP{} + return res, nil } return Result{}, nil } -// Construct Result object +// makeResult returns a properly constructed Result. func makeResult(rule rules.Rule, reason Reason) Result { - res := Result{} - res.FilterID = int64(rule.GetFilterListID()) - res.Rule = rule.Text() - res.Reason = reason + res := Result{ + Reason: reason, + Rules: []*ResultRule{{ + FilterListID: int64(rule.GetFilterListID()), + Text: rule.Text(), + }}, + } + if reason == FilteredBlackList { res.IsFiltered = true } + return res } @@ -708,7 +754,7 @@ func InitModule() { } // New creates properly initialized DNS Filter that is ready to be used. -func New(c *Config, blockFilters []Filter) *Dnsfilter { +func New(c *Config, blockFilters []Filter) *DNSFilter { if c != nil { cacheConf := cache.Config{ EnableLRU: true, @@ -730,7 +776,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter { } } - d := new(Dnsfilter) + d := new(DNSFilter) err := d.initSecurityServices() if err != nil { @@ -768,7 +814,7 @@ func New(c *Config, blockFilters []Filter) *Dnsfilter { // Start - start the module: // . start async filtering initializer goroutine // . register web handlers -func (d *Dnsfilter) Start() { +func (d *DNSFilter) Start() { d.filtersInitializerChan = make(chan filtersInitializerParams, 1) go d.filtersInitializer() diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index 1eb7a31c..96376162 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -41,7 +41,7 @@ func purgeCaches() { } } -func NewForTest(c *Config, filters []Filter) *Dnsfilter { +func NewForTest(c *Config, filters []Filter) *DNSFilter { setts = RequestFilteringSettings{} setts.FilteringEnabled = true if c != nil { @@ -58,38 +58,48 @@ func NewForTest(c *Config, filters []Filter) *Dnsfilter { return d } -func (d *Dnsfilter) checkMatch(t *testing.T, hostname string) { +func (d *DNSFilter) checkMatch(t *testing.T, hostname string) { t.Helper() - ret, err := d.CheckHost(hostname, dns.TypeA, &setts) + res, err := d.CheckHost(hostname, dns.TypeA, &setts) if err != nil { t.Errorf("Error while matching host %s: %s", hostname, err) } - if !ret.IsFiltered { + if !res.IsFiltered { t.Errorf("Expected hostname %s to match", hostname) } } -func (d *Dnsfilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) { +func (d *DNSFilter) checkMatchIP(t *testing.T, hostname, ip string, qtype uint16) { t.Helper() - ret, err := d.CheckHost(hostname, qtype, &setts) + + res, err := d.CheckHost(hostname, qtype, &setts) if err != nil { t.Errorf("Error while matching host %s: %s", hostname, err) } - if !ret.IsFiltered { + + if !res.IsFiltered { t.Errorf("Expected hostname %s to match", hostname) } - if ret.IP == nil || ret.IP.String() != ip { - t.Errorf("Expected ip %s to match, actual: %v", ip, ret.IP) + + if len(res.Rules) == 0 { + t.Errorf("Expected result to have rules") + + return + } + + r := res.Rules[0] + if r.IP == nil || r.IP.String() != ip { + t.Errorf("Expected ip %s to match, actual: %v", ip, r.IP) } } -func (d *Dnsfilter) checkMatchEmpty(t *testing.T, hostname string) { +func (d *DNSFilter) checkMatchEmpty(t *testing.T, hostname string) { t.Helper() - ret, err := d.CheckHost(hostname, dns.TypeA, &setts) + res, err := d.CheckHost(hostname, dns.TypeA, &setts) if err != nil { t.Errorf("Error while matching host %s: %s", hostname, err) } - if ret.IsFiltered { + if res.IsFiltered { t.Errorf("Expected hostname %s to not match", hostname) } } @@ -120,26 +130,43 @@ func TestEtcHostsMatching(t *testing.T) { d.checkMatchIP(t, "block.com", "0.0.0.0", dns.TypeA) // ...but empty IPv6 - ret, err := d.CheckHost("block.com", dns.TypeAAAA, &setts) - assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0) - assert.True(t, ret.Rule == "0.0.0.0 block.com") + res, err := d.CheckHost("block.com", dns.TypeAAAA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + if assert.Len(t, res.Rules, 1) { + assert.Equal(t, "0.0.0.0 block.com", res.Rules[0].Text) + assert.Len(t, res.Rules[0].IP, 0) + } // IPv6 d.checkMatchIP(t, "ipv6.com", addr6, dns.TypeAAAA) // ...but empty IPv4 - ret, err = d.CheckHost("ipv6.com", dns.TypeA, &setts) - assert.True(t, err == nil && ret.IsFiltered && ret.IP != nil && len(ret.IP) == 0) + res, err = d.CheckHost("ipv6.com", dns.TypeA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + if assert.Len(t, res.Rules, 1) { + assert.Equal(t, "::1 ipv6.com", res.Rules[0].Text) + assert.Len(t, res.Rules[0].IP, 0) + } // 2 IPv4 (return only the first one) - ret, err = d.CheckHost("host2", dns.TypeA, &setts) - assert.True(t, err == nil && ret.IsFiltered) - assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("0.0.0.1"))) + res, err = d.CheckHost("host2", dns.TypeA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + if assert.Len(t, res.Rules, 1) { + loopback4 := net.IP{0, 0, 0, 1} + assert.Equal(t, res.Rules[0].IP, loopback4) + } // ...and 1 IPv6 address - ret, err = d.CheckHost("host2", dns.TypeAAAA, &setts) - assert.True(t, err == nil && ret.IsFiltered) - assert.True(t, ret.IP != nil && ret.IP.Equal(net.ParseIP("::1"))) + res, err = d.CheckHost("host2", dns.TypeAAAA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + if assert.Len(t, res.Rules, 1) { + loopback6 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + assert.Equal(t, res.Rules[0].IP, loopback6) + } } // SAFE BROWSING @@ -206,13 +233,11 @@ func TestCheckHostSafeSearchYandex(t *testing.T) { // Check host for each domain for _, host := range yandex { - result, err := d.CheckHost(host, dns.TypeA, &setts) - if err != nil { - t.Errorf("SafeSearch doesn't work for yandex domain `%s` cause %s", host, err) - } - - if result.IP.String() != "213.180.193.56" { - t.Errorf("SafeSearch doesn't work for yandex domain `%s`", host) + res, err := d.CheckHost(host, dns.TypeA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + if assert.Len(t, res.Rules, 1) { + assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56") } } } @@ -226,13 +251,11 @@ func TestCheckHostSafeSearchGoogle(t *testing.T) { // Check host for each domain for _, host := range googleDomains { - result, err := d.CheckHost(host, dns.TypeA, &setts) - if err != nil { - t.Errorf("SafeSearch doesn't work for %s cause %s", host, err) - } - - if result.IP == nil { - t.Errorf("SafeSearch doesn't work for %s", host) + res, err := d.CheckHost(host, dns.TypeA, &setts) + assert.Nil(t, err) + assert.True(t, res.IsFiltered) + if assert.Len(t, res.Rules, 1) { + assert.NotEqual(t, res.Rules[0].IP.String(), "0.0.0.0") } } } @@ -242,40 +265,30 @@ func TestSafeSearchCacheYandex(t *testing.T) { defer d.Close() domain := "yandex.ru" - var result Result - var err error - - // Check host with disabled safesearch - result, err = d.CheckHost(domain, dns.TypeA, &setts) - if err != nil { - t.Fatalf("Cannot check host due to %s", err) - } - if result.IP != nil { - t.Fatalf("SafeSearch is not enabled but there is an answer for `%s` !", domain) - } + // Check host with disabled safesearch. + res, err := d.CheckHost(domain, dns.TypeA, &setts) + assert.Nil(t, err) + assert.False(t, res.IsFiltered) + assert.Len(t, res.Rules, 0) d = NewForTest(&Config{SafeSearchEnabled: true}, nil) defer d.Close() - result, err = d.CheckHost(domain, dns.TypeA, &setts) + res, err = d.CheckHost(domain, dns.TypeA, &setts) if err != nil { t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err) } - // Fir yandex we already know valid ip - if result.IP.String() != "213.180.193.56" { - t.Fatalf("Wrong IP for %s safesearch: %s", domain, result.IP.String()) + // For yandex we already know valid ip. + if assert.Len(t, res.Rules, 1) { + assert.Equal(t, res.Rules[0].IP.String(), "213.180.193.56") } - // Check cache + // Check cache. cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain) - - if !isFound { - t.Fatalf("Safesearch cache doesn't work for %s!", domain) - } - - if cachedValue.IP.String() != "213.180.193.56" { - t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String()) + assert.True(t, isFound) + if assert.Len(t, cachedValue.Rules, 1) { + assert.Equal(t, cachedValue.Rules[0].IP.String(), "213.180.193.56") } } @@ -283,13 +296,10 @@ func TestSafeSearchCacheGoogle(t *testing.T) { d := NewForTest(nil, nil) defer d.Close() domain := "www.google.ru" - result, err := d.CheckHost(domain, dns.TypeA, &setts) - if err != nil { - t.Fatalf("Cannot check host due to %s", err) - } - if result.IP != nil { - t.Fatalf("SafeSearch is not enabled but there is an answer!") - } + res, err := d.CheckHost(domain, dns.TypeA, &setts) + assert.Nil(t, err) + assert.False(t, res.IsFiltered) + assert.Len(t, res.Rules, 0) d = NewForTest(&Config{SafeSearchEnabled: true}, nil) defer d.Close() @@ -313,25 +323,17 @@ func TestSafeSearchCacheGoogle(t *testing.T) { } } - result, err = d.CheckHost(domain, dns.TypeA, &setts) - if err != nil { - t.Fatalf("CheckHost for safesearh domain %s failed cause %s", domain, err) + res, err = d.CheckHost(domain, dns.TypeA, &setts) + assert.Nil(t, err) + if assert.Len(t, res.Rules, 1) { + assert.True(t, res.Rules[0].IP.Equal(ip)) } - if result.IP.String() != ip.String() { - t.Fatalf("Wrong IP for %s safesearch: %s. Should be: %s", - domain, result.IP.String(), ip) - } - - // Check cache + // Check cache. cachedValue, isFound := getCachedResult(gctx.safeSearchCache, domain) - - if !isFound { - t.Fatalf("Safesearch cache doesn't work for %s!", domain) - } - - if cachedValue.IP.String() != ip.String() { - t.Fatalf("Wrong IP in cache for %s safesearch: %s", domain, cachedValue.IP.String()) + assert.True(t, isFound) + if assert.Len(t, cachedValue.Rules, 1) { + assert.True(t, cachedValue.Rules[0].IP.Equal(ip)) } } @@ -433,15 +435,15 @@ func TestMatching(t *testing.T) { d := NewForTest(nil, filters) defer d.Close() - ret, err := d.CheckHost(test.hostname, test.dnsType, &setts) + res, err := d.CheckHost(test.hostname, test.dnsType, &setts) if err != nil { t.Errorf("Error while matching host %s: %s", test.hostname, err) } - if ret.IsFiltered != test.isFiltered { - t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, ret.IsFiltered, test.isFiltered) + if res.IsFiltered != test.isFiltered { + t.Errorf("Hostname %s has wrong result (%v must be %v)", test.hostname, res.IsFiltered, test.isFiltered) } - if ret.Reason != test.reason { - t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, ret.Reason.String(), test.reason.String()) + if res.Reason != test.reason { + t.Errorf("Hostname %s has wrong reason (%v must be %v)", test.hostname, res.Reason.String(), test.reason.String()) } }) } @@ -466,16 +468,20 @@ func TestWhitelist(t *testing.T) { defer d.Close() // matched by white filter - ret, err := d.CheckHost("host1", dns.TypeA, &setts) + res, err := d.CheckHost("host1", dns.TypeA, &setts) assert.True(t, err == nil) - assert.True(t, !ret.IsFiltered && ret.Reason == NotFilteredWhiteList) - assert.True(t, ret.Rule == "||host1^") + assert.True(t, !res.IsFiltered && res.Reason == NotFilteredWhiteList) + if assert.Len(t, res.Rules, 1) { + assert.True(t, res.Rules[0].Text == "||host1^") + } // not matched by white filter, but matched by block filter - ret, err = d.CheckHost("host2", dns.TypeA, &setts) + res, err = d.CheckHost("host2", dns.TypeA, &setts) assert.True(t, err == nil) - assert.True(t, ret.IsFiltered && ret.Reason == FilteredBlackList) - assert.True(t, ret.Rule == "||host2^") + assert.True(t, res.IsFiltered && res.Reason == FilteredBlackList) + if assert.Len(t, res.Rules, 1) { + assert.True(t, res.Rules[0].Text == "||host2^") + } } // CLIENT SETTINGS @@ -559,11 +565,11 @@ func BenchmarkSafeBrowsing(b *testing.B) { defer d.Close() for n := 0; n < b.N; n++ { hostname := "wmconvirus.narod.ru" - ret, err := d.CheckHost(hostname, dns.TypeA, &setts) + res, err := d.CheckHost(hostname, dns.TypeA, &setts) if err != nil { b.Errorf("Error while matching host %s: %s", hostname, err) } - if !ret.IsFiltered { + if !res.IsFiltered { b.Errorf("Expected hostname %s to match", hostname) } } @@ -575,11 +581,11 @@ func BenchmarkSafeBrowsingParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { hostname := "wmconvirus.narod.ru" - ret, err := d.CheckHost(hostname, dns.TypeA, &setts) + res, err := d.CheckHost(hostname, dns.TypeA, &setts) if err != nil { b.Errorf("Error while matching host %s: %s", hostname, err) } - if !ret.IsFiltered { + if !res.IsFiltered { b.Errorf("Expected hostname %s to match", hostname) } } diff --git a/internal/dnsfilter/rewrites.go b/internal/dnsfilter/rewrites.go index 0092344e..8db1fd0b 100644 --- a/internal/dnsfilter/rewrites.go +++ b/internal/dnsfilter/rewrites.go @@ -95,7 +95,7 @@ func (r *RewriteEntry) prepare() { } } -func (d *Dnsfilter) prepareRewrites() { +func (d *DNSFilter) prepareRewrites() { for i := range d.Rewrites { d.Rewrites[i].prepare() } @@ -148,7 +148,7 @@ type rewriteEntryJSON struct { Answer string `json:"answer"` } -func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { arr := []*rewriteEntryJSON{} d.confLock.Lock() @@ -169,7 +169,7 @@ func (d *Dnsfilter) handleRewriteList(w http.ResponseWriter, r *http.Request) { } } -func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { jsent := rewriteEntryJSON{} err := json.NewDecoder(r.Body).Decode(&jsent) if err != nil { @@ -191,7 +191,7 @@ func (d *Dnsfilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) { d.Config.ConfigModified() } -func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) { jsent := rewriteEntryJSON{} err := json.NewDecoder(r.Body).Decode(&jsent) if err != nil { @@ -218,7 +218,7 @@ func (d *Dnsfilter) handleRewriteDelete(w http.ResponseWriter, r *http.Request) d.Config.ConfigModified() } -func (d *Dnsfilter) registerRewritesHandlers() { +func (d *DNSFilter) registerRewritesHandlers() { d.Config.HTTPRegister("GET", "/control/rewrite/list", d.handleRewriteList) d.Config.HTTPRegister("POST", "/control/rewrite/add", d.handleRewriteAdd) d.Config.HTTPRegister("POST", "/control/rewrite/delete", d.handleRewriteDelete) diff --git a/internal/dnsfilter/rewrites_test.go b/internal/dnsfilter/rewrites_test.go index 31b5cc0d..4304b6de 100644 --- a/internal/dnsfilter/rewrites_test.go +++ b/internal/dnsfilter/rewrites_test.go @@ -9,7 +9,7 @@ import ( ) func TestRewrites(t *testing.T) { - d := Dnsfilter{} + d := DNSFilter{} // CNAME, A, AAAA d.Rewrites = []RewriteEntry{ {"somecname", "somehost.com", 0, nil}, @@ -104,7 +104,7 @@ func TestRewrites(t *testing.T) { } func TestRewritesLevels(t *testing.T) { - d := Dnsfilter{} + d := DNSFilter{} // exact host, wildcard L2, wildcard L3 d.Rewrites = []RewriteEntry{ {"host.com", "1.1.1.1", 0, nil}, @@ -133,7 +133,7 @@ func TestRewritesLevels(t *testing.T) { } func TestRewritesExceptionCNAME(t *testing.T) { - d := Dnsfilter{} + d := DNSFilter{} // wildcard; exception for a sub-domain d.Rewrites = []RewriteEntry{ {"*.host.com", "2.2.2.2", 0, nil}, @@ -153,7 +153,7 @@ func TestRewritesExceptionCNAME(t *testing.T) { } func TestRewritesExceptionWC(t *testing.T) { - d := Dnsfilter{} + d := DNSFilter{} // wildcard; exception for a sub-wildcard d.Rewrites = []RewriteEntry{ {"*.host.com", "2.2.2.2", 0, nil}, @@ -173,7 +173,7 @@ func TestRewritesExceptionWC(t *testing.T) { } func TestRewritesExceptionIP(t *testing.T) { - d := Dnsfilter{} + d := DNSFilter{} // exception for AAAA record d.Rewrites = []RewriteEntry{ {"host.com", "1.2.3.4", 0, nil}, diff --git a/internal/dnsfilter/sbpc.go b/internal/dnsfilter/safebrowsing.go similarity index 91% rename from internal/dnsfilter/sbpc.go rename to internal/dnsfilter/safebrowsing.go index 29a39fa2..f5aaca9f 100644 --- a/internal/dnsfilter/sbpc.go +++ b/internal/dnsfilter/safebrowsing.go @@ -1,5 +1,3 @@ -// Safe Browsing, Parental Control - package dnsfilter import ( @@ -22,6 +20,8 @@ import ( "golang.org/x/net/publicsuffix" ) +// Safe browsing and parental control methods. + const ( dnsTimeout = 3 * time.Second defaultSafebrowsingServer = `https://dns-family.adguard.com/dns-query` @@ -30,7 +30,7 @@ const ( pcTXTSuffix = `pc.dns.adguard.com.` ) -func (d *Dnsfilter) initSecurityServices() error { +func (d *DNSFilter) initSecurityServices() error { var err error d.safeBrowsingServer = defaultSafebrowsingServer d.parentalServer = defaultParentalServer @@ -287,7 +287,7 @@ func check(c *sbCtx, r Result, u upstream.Upstream) (Result, error) { return Result{}, nil } -func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) { +func (d *DNSFilter) checkSafeBrowsing(host string) (Result, error) { if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() defer timer.LogElapsed("SafeBrowsing lookup for %s", host) @@ -301,12 +301,14 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) { res := Result{ IsFiltered: true, Reason: FilteredSafeBrowsing, - Rule: "adguard-malware-shavar", + Rules: []*ResultRule{{ + Text: "adguard-malware-shavar", + }}, } return check(ctx, res, d.safeBrowsingUpstream) } -func (d *Dnsfilter) checkParental(host string) (Result, error) { +func (d *DNSFilter) checkParental(host string) (Result, error) { if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() defer timer.LogElapsed("Parental lookup for %s", host) @@ -320,7 +322,9 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) { res := Result{ IsFiltered: true, Reason: FilteredParental, - Rule: "parental CATEGORY_BLACKLISTED", + Rules: []*ResultRule{{ + Text: "parental CATEGORY_BLACKLISTED", + }}, } return check(ctx, res, d.parentalUpstream) } @@ -331,17 +335,17 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string, http.Error(w, text, code) } -func (d *Dnsfilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleSafeBrowsingEnable(w http.ResponseWriter, r *http.Request) { d.Config.SafeBrowsingEnabled = true d.Config.ConfigModified() } -func (d *Dnsfilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleSafeBrowsingDisable(w http.ResponseWriter, r *http.Request) { d.Config.SafeBrowsingEnabled = false d.Config.ConfigModified() } -func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{ "enabled": d.Config.SafeBrowsingEnabled, } @@ -358,17 +362,17 @@ func (d *Dnsfilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ } } -func (d *Dnsfilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleParentalEnable(w http.ResponseWriter, r *http.Request) { d.Config.ParentalEnabled = true d.Config.ConfigModified() } -func (d *Dnsfilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleParentalDisable(w http.ResponseWriter, r *http.Request) { d.Config.ParentalEnabled = false d.Config.ConfigModified() } -func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{ "enabled": d.Config.ParentalEnabled, } @@ -386,7 +390,7 @@ func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request) } } -func (d *Dnsfilter) registerSecurityHandlers() { +func (d *DNSFilter) registerSecurityHandlers() { d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable) d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable) d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus) diff --git a/internal/dnsfilter/sbpc_test.go b/internal/dnsfilter/safebrowsing_test.go similarity index 100% rename from internal/dnsfilter/sbpc_test.go rename to internal/dnsfilter/safebrowsing_test.go diff --git a/internal/dnsfilter/safesearch.go b/internal/dnsfilter/safesearch.go index 9485260b..4aefa5e1 100644 --- a/internal/dnsfilter/safesearch.go +++ b/internal/dnsfilter/safesearch.go @@ -18,7 +18,7 @@ import ( expire byte[4] res Result */ -func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int { +func (d *DNSFilter) setCacheResult(cache cache.Cache, host string, res Result) int { var buf bytes.Buffer expire := uint(time.Now().Unix()) + d.Config.CacheTime*60 @@ -63,12 +63,12 @@ func getCachedResult(cache cache.Cache, host string) (Result, bool) { } // SafeSearchDomain returns replacement address for search engine -func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) { +func (d *DNSFilter) SafeSearchDomain(host string) (string, bool) { val, ok := safeSearchDomains[host] return val, ok } -func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) { +func (d *DNSFilter) checkSafeSearch(host string) (Result, error) { if log.GetLevel() >= log.DEBUG { timer := log.StartTimer() defer timer.LogElapsed("SafeSearch: lookup for %s", host) @@ -87,49 +87,52 @@ func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) { return Result{}, nil } - res := Result{IsFiltered: true, Reason: FilteredSafeSearch} + res := Result{ + IsFiltered: true, + Reason: FilteredSafeSearch, + Rules: []*ResultRule{{}}, + } + if ip := net.ParseIP(safeHost); ip != nil { - res.IP = ip + res.Rules[0].IP = ip valLen := d.setCacheResult(gctx.safeSearchCache, host, res) log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) + return res, nil } // TODO this address should be resolved with upstream that was configured in dnsforward - addrs, err := net.LookupIP(safeHost) + ips, err := net.LookupIP(safeHost) if err != nil { log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err) return Result{}, err } - for _, i := range addrs { - if ipv4 := i.To4(); ipv4 != nil { - res.IP = ipv4 - break + for _, ip := range ips { + if ipv4 := ip.To4(); ipv4 != nil { + res.Rules[0].IP = ipv4 + + l := d.setCacheResult(gctx.safeSearchCache, host, res) + log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, l) + + return res, nil } } - if len(res.IP) == 0 { - return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost) - } - - // Cache result - valLen := d.setCacheResult(gctx.safeSearchCache, host, res) - log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen) - return res, nil + return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost) } -func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) { d.Config.SafeSearchEnabled = true d.Config.ConfigModified() } -func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) { d.Config.SafeSearchEnabled = false d.Config.ConfigModified() } -func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { +func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{ "enabled": d.Config.SafeSearchEnabled, } diff --git a/internal/dnsforward/dnsforward.go b/internal/dnsforward/dnsforward.go index 3640101c..f1b7e7d2 100644 --- a/internal/dnsforward/dnsforward.go +++ b/internal/dnsforward/dnsforward.go @@ -48,7 +48,7 @@ var webRegistered bool // The zero Server is empty and ready for use. type Server struct { dnsProxy *proxy.Proxy // DNS proxy instance - dnsFilter *dnsfilter.Dnsfilter // DNS filter instance + dnsFilter *dnsfilter.DNSFilter // DNS filter instance dhcpServer dhcpd.ServerInterface // DHCP server instance (optional) queryLog querylog.QueryLog // Query log instance stats stats.Stats @@ -74,7 +74,7 @@ type Server struct { // DNSCreateParams - parameters for NewServer() type DNSCreateParams struct { - DNSFilter *dnsfilter.Dnsfilter + DNSFilter *dnsfilter.DNSFilter Stats stats.Stats QueryLog querylog.QueryLog DHCPServer dhcpd.ServerInterface diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go index 327fbe82..fdee8648 100644 --- a/internal/dnsforward/dnsforward_test.go +++ b/internal/dnsforward/dnsforward_test.go @@ -296,7 +296,7 @@ func TestBlockedRequest(t *testing.T) { func TestServerCustomClientUpstream(t *testing.T) { s := createTestServer(t) - s.conf.GetCustomUpstreamByClient = func(clientAddr string) *proxy.UpstreamConfig { + s.conf.GetCustomUpstreamByClient = func(_ string) *proxy.UpstreamConfig { uc := &proxy.UpstreamConfig{} u := &testUpstream{} u.ipv4 = map[string][]net.IP{} @@ -473,7 +473,7 @@ func TestBlockCNAME(t *testing.T) { func TestClientRulesForCNAMEMatching(t *testing.T) { s := createTestServer(t) testUpstm := &testUpstream{testCNAMEs, testIPv4, nil} - s.conf.FilterHandler = func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) { + s.conf.FilterHandler = func(_ string, settings *dnsfilter.RequestFilteringSettings) { settings.FilteringEnabled = false } err := s.startWithUpstream(testUpstm) @@ -863,6 +863,8 @@ func sendTestMessages(t *testing.T, conn *dns.Conn) { } func exchangeAndAssertResponse(t *testing.T, client *dns.Client, addr net.Addr, host, ip string) { + t.Helper() + req := createTestMessage(host) reply, _, err := client.Exchange(req, addr.String()) if err != nil { @@ -900,6 +902,8 @@ func assertGoogleAResponse(t *testing.T, reply *dns.Msg) { } func assertResponse(t *testing.T, reply *dns.Msg, ip string) { + t.Helper() + if len(reply.Answer) != 1 { t.Fatalf("DNS server returned reply with wrong number of answers - %d", len(reply.Answer)) } diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index 11267adf..83effc60 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -52,13 +52,13 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { // Return immediately if there's an error return nil, fmt.Errorf("dnsfilter failed to check host %q: %w", host, err) } else if res.IsFiltered { - log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rule) + log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text) d.Res = s.genDNSFilterMessage(d, &res) } else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 { ctx.origQuestion = d.Req.Question[0] // resolve canonical name, not the original host name d.Req.Question[0].Name = dns.Fqdn(res.CanonName) - } else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHosts) != 0 { + } else if res.Reason == dnsfilter.RewriteAutoHosts && len(res.ReverseHosts) != 0 { resp := s.makeResponse(req) for _, h := range res.ReverseHosts { hdr := dns.RR_Header{ @@ -77,7 +77,7 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { } d.Res = resp - } else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts { + } else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteAutoHosts { resp := s.makeResponse(req) name := host diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index d4fd4937..3c381704 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -39,8 +39,11 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu // If the query was filtered by "Safe search", dnsfilter also must return // the IP address that must be used in response. // In this case regardless of the filtering method, we should return it - if result.Reason == dnsfilter.FilteredSafeSearch && result.IP != nil { - return s.genResponseWithIP(m, result.IP) + if result.Reason == dnsfilter.FilteredSafeSearch && + len(result.Rules) > 0 && + result.Rules[0].IP != nil { + + return s.genResponseWithIP(m, result.Rules[0].IP) } if s.conf.BlockingMode == "null_ip" { @@ -68,8 +71,8 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu // Default blocking mode // If there's an IP specified in the rule, return it // For host-type rules, return null IP - if result.IP != nil { - return s.genResponseWithIP(m, result.IP) + if len(result.Rules) > 0 && result.Rules[0].IP != nil { + return s.genResponseWithIP(m, result.Rules[0].IP) } return s.makeResponseNullIP(m) diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go index e7fb80ba..3fe07e7e 100644 --- a/internal/home/controlfiltering.go +++ b/internal/home/controlfiltering.go @@ -346,10 +346,22 @@ func (f *Filtering) handleFilteringConfig(w http.ResponseWriter, r *http.Request enableFilters(true) } +type checkHostRespRule struct { + FilterListID int64 `json:"filter_list_id"` + Text string `json:"text"` +} + type checkHostResp struct { - Reason string `json:"reason"` - FilterID int64 `json:"filter_id"` - Rule string `json:"rule"` + Reason string `json:"reason"` + + // FilterID is the ID of the rule's filter list. + // + // Deprecated: Use Rules[*].FilterListID. + FilterID int64 `json:"filter_id"` + + Rule string `json:"rule"` + + Rules []*checkHostRespRule `json:"rules"` // for FilteredBlockedService: SvcName string `json:"service_name"` @@ -374,11 +386,20 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) { resp := checkHostResp{} resp.Reason = result.Reason.String() - resp.FilterID = result.FilterID - resp.Rule = result.Rule + resp.FilterID = result.Rules[0].FilterListID + resp.Rule = result.Rules[0].Text resp.SvcName = result.ServiceName resp.CanonName = result.CanonName resp.IPList = result.IPList + + resp.Rules = make([]*checkHostRespRule, len(result.Rules)) + for i, r := range result.Rules { + resp.Rules[i] = &checkHostRespRule{ + FilterListID: r.FilterListID, + Text: r.Text, + } + } + js, err := json.Marshal(resp) if err != nil { httpError(w, http.StatusInternalServerError, "json encode: %s", err) diff --git a/internal/home/home.go b/internal/home/home.go index acf6c3e0..58c99ff6 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -58,7 +58,7 @@ type homeContext struct { dnsServer *dnsforward.Server // DNS module rdns *RDNS // rDNS module whois *Whois // WHOIS module - dnsFilter *dnsfilter.Dnsfilter // DNS filtering module + dnsFilter *dnsfilter.DNSFilter // DNS filtering module dhcpServer *dhcpd.Server // DHCP module auth *Auth // HTTP authentication module filters Filtering // DNS filtering module diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index 93a1d265..f5937781 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -9,7 +9,6 @@ import ( "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/golibs/log" - "github.com/miekg/dns" ) type logEntryHandler (func(t json.Token, ent *logEntry) error) @@ -85,6 +84,29 @@ var logEntryHandlers = map[string]logEntryHandler{ ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v) return err }, + "Upstream": func(t json.Token, ent *logEntry) error { + v, ok := t.(string) + if !ok { + return nil + } + ent.Upstream = v + return nil + }, + "Elapsed": func(t json.Token, ent *logEntry) error { + v, ok := t.(json.Number) + if !ok { + return nil + } + i, err := v.Int64() + if err != nil { + return err + } + ent.Elapsed = time.Duration(i) + return nil + }, +} + +var resultHandlers = map[string]logEntryHandler{ "IsFiltered": func(t json.Token, ent *logEntry) error { v, ok := t.(bool) if !ok { @@ -94,23 +116,40 @@ var logEntryHandlers = map[string]logEntryHandler{ return nil }, "Rule": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + s, ok := t.(string) if !ok { return nil } - ent.Result.Rule = v + + l := len(ent.Result.Rules) + if l == 0 { + ent.Result.Rules = []*dnsfilter.ResultRule{{}} + l++ + } + + ent.Result.Rules[l-1].Text = s + return nil }, "FilterID": func(t json.Token, ent *logEntry) error { - v, ok := t.(json.Number) + n, ok := t.(json.Number) if !ok { return nil } - i, err := v.Int64() + + i, err := n.Int64() if err != nil { return err } - ent.Result.FilterID = i + + l := len(ent.Result.Rules) + if l == 0 { + ent.Result.Rules = []*dnsfilter.ResultRule{{}} + l++ + } + + ent.Result.Rules[l-1].FilterListID = i + return nil }, "Reason": func(t json.Token, ent *logEntry) error { @@ -133,62 +172,50 @@ var logEntryHandlers = map[string]logEntryHandler{ ent.Result.ServiceName = v return nil }, - "Upstream": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) - if !ok { - return nil - } - ent.Upstream = v - return nil - }, - "Elapsed": func(t json.Token, ent *logEntry) error { - v, ok := t.(json.Number) - if !ok { - return nil - } - i, err := v.Int64() +} + +func decodeResult(dec *json.Decoder, ent *logEntry) { + for { + keyToken, err := dec.Token() if err != nil { - return err + if err != io.EOF { + log.Debug("decodeResult err: %s", err) + } + + return } - ent.Elapsed = time.Duration(i) - return nil - }, - "Result": func(json.Token, *logEntry) error { - return nil - }, - "Question": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + + if d, ok := keyToken.(json.Delim); ok { + if d == '}' { + return + } + + continue + } + + key, ok := keyToken.(string) if !ok { - return nil + log.Debug("decodeResult: keyToken is %T (%[1]v) and not string", keyToken) + + return } - var qstr []byte - qstr, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return err - } - q := new(dns.Msg) - err = q.Unpack(qstr) - if err != nil { - return err - } - ent.QHost = q.Question[0].Name - if len(ent.QHost) == 0 { - return nil // nil??? - } - ent.QHost = ent.QHost[:len(ent.QHost)-1] - ent.QType = dns.TypeToString[q.Question[0].Qtype] - ent.QClass = dns.ClassToString[q.Question[0].Qclass] - return nil - }, - "Time": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + + handler, ok := resultHandlers[key] if !ok { - return nil + continue } - var err error - ent.Time, err = time.Parse(time.RFC3339, v) - return err - }, + + val, err := dec.Token() + if err != nil { + return + } + + if err = handler(val, ent); err != nil { + log.Debug("decodeResult handler err: %s", err) + + return + } + } } func decodeLogEntry(ent *logEntry, str string) { @@ -200,18 +227,27 @@ func decodeLogEntry(ent *logEntry, str string) { if err != io.EOF { log.Debug("decodeLogEntry err: %s", err) } + return } + if _, ok := keyToken.(json.Delim); ok { continue } key, ok := keyToken.(string) if !ok { - log.Debug("decodeLogEntry: keyToken is %T and not string", keyToken) + log.Debug("decodeLogEntry: keyToken is %T (%[1]v) and not string", keyToken) + return } + if key == "Result" { + decodeResult(dec, ent) + + continue + } + handler, ok := logEntryHandlers[key] if !ok { continue @@ -223,7 +259,8 @@ func decodeLogEntry(ent *logEntry, str string) { } if err = handler(val, ent); err != nil { - log.Debug("decodeLogEntry err: %s", err) + log.Debug("decodeLogEntry handler err: %s", err) + return } } diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index 02ee77df..d9cbd600 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -21,29 +21,17 @@ func TestDecode_decodeQueryLog(t *testing.T) { log string want string }{{ - name: "back_compatibility_all_right", - log: `{"Question":"ULgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`, - want: "default", - }, { - name: "back_compatibility_bad_msg", - log: `{"Question":"","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`, - want: "decodeLogEntry err: dns: overflow unpacking uint16\n", - }, { - name: "back_compatibility_bad_decoding", - log: `{"Question":"LgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`, - want: "decodeLogEntry err: illegal base64 data at input byte 48\n", - }, { - name: "modern_all_right", + name: "all_right", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, want: "default", }, { name: "bad_filter_id", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`, - want: "decodeLogEntry err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n", + want: "decodeResult handler err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n", }, { name: "bad_is_filtered", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + want: "decodeLogEntry err: invalid character 'o' in literal true (expecting 'u')\n", }, { name: "bad_elapsed", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`, @@ -55,7 +43,7 @@ func TestDecode_decodeQueryLog(t *testing.T) { }, { name: "bad_time", log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "decodeLogEntry err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n", + want: "decodeLogEntry handler err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n", }, { name: "bad_host", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, @@ -75,7 +63,7 @@ func TestDecode_decodeQueryLog(t *testing.T) { }, { name: "very_bad_client_proto", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "decodeLogEntry err: invalid client proto: \"dog\"\n", + want: "decodeLogEntry handler err: invalid client proto: \"dog\"\n", }, { name: "bad_answer", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, @@ -83,7 +71,7 @@ func TestDecode_decodeQueryLog(t *testing.T) { }, { name: "very_bad_answer", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "decodeLogEntry err: illegal base64 data at input byte 61\n", + want: "decodeLogEntry handler err: illegal base64 data at input byte 61\n", }, { name: "bad_rule", log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`, @@ -102,7 +90,7 @@ func TestDecode_decodeQueryLog(t *testing.T) { l := &logEntry{} decodeLogEntry(l, tc.log) - assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), logOutput.String()) + assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), "%q\ndoes not end with\n%q", logOutput.String(), tc.want) logOutput.Reset() }) diff --git a/internal/querylog/json.go b/internal/querylog/json.go index 4130ce9e..3beeb0f1 100644 --- a/internal/querylog/json.go +++ b/internal/querylog/json.go @@ -6,10 +6,13 @@ import ( "strconv" "time" + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/golibs/log" "github.com/miekg/dns" ) +// TODO(a.garipov): Use a proper structured approach here. + // Get Client IP address func (l *queryLog) getClientIP(clientIP string) string { if l.conf.AnonymizeClientIP { @@ -29,10 +32,12 @@ func (l *queryLog) getClientIP(clientIP string) string { return clientIP } -// entriesToJSON - converts log entries to JSON -func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[string]interface{} { - // init the response object - data := []map[string]interface{}{} +// jobject is a JSON object alias. +type jobject = map[string]interface{} + +// entriesToJSON converts query log entries to JSON. +func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) (res jobject) { + data := []jobject{} // the elements order is already reversed (from newer to older) for i := 0; i < len(entries); i++ { @@ -41,17 +46,18 @@ func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[stri data = append(data, jsonEntry) } - result := map[string]interface{}{} - result["oldest"] = "" - if !oldest.IsZero() { - result["oldest"] = oldest.Format(time.RFC3339Nano) + res = jobject{ + "data": data, + "oldest": "", + } + if !oldest.IsZero() { + res["oldest"] = oldest.Format(time.RFC3339Nano) } - result["data"] = data - return result + return res } -func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} { +func (l *queryLog) logEntryToJSONEntry(entry *logEntry) (jsonEntry jobject) { var msg *dns.Msg if len(entry.Answer) > 0 { @@ -62,17 +68,18 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} { } } - jsonEntry := map[string]interface{}{ + jsonEntry = jobject{ "reason": entry.Result.Reason.String(), "elapsedMs": strconv.FormatFloat(entry.Elapsed.Seconds()*1000, 'f', -1, 64), "time": entry.Time.Format(time.RFC3339Nano), "client": l.getClientIP(entry.IP), "client_proto": entry.ClientProto, - } - jsonEntry["question"] = map[string]interface{}{ - "host": entry.QHost, - "type": entry.QType, - "class": entry.QClass, + "upstream": entry.Upstream, + "question": jobject{ + "host": entry.QHost, + "type": entry.QType, + "class": entry.QClass, + }, } if msg != nil { @@ -83,12 +90,15 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} { if opt != nil { dnssecOk = opt.Do() } + jsonEntry["answer_dnssec"] = dnssecOk } - if len(entry.Result.Rule) > 0 { - jsonEntry["rule"] = entry.Result.Rule - jsonEntry["filterId"] = entry.Result.FilterID + jsonEntry["rules"] = resultRulesToJSONRules(entry.Result.Rules) + + if len(entry.Result.Rules) > 0 && len(entry.Result.Rules[0].Text) > 0 { + jsonEntry["rule"] = entry.Result.Rules[0].Text + jsonEntry["filterId"] = entry.Result.Rules[0].FilterListID } if len(entry.Result.ServiceName) != 0 { @@ -113,20 +123,30 @@ func (l *queryLog) logEntryToJSONEntry(entry *logEntry) map[string]interface{} { } } - jsonEntry["upstream"] = entry.Upstream - return jsonEntry } -func answerToMap(a *dns.Msg) []map[string]interface{} { +func resultRulesToJSONRules(rules []*dnsfilter.ResultRule) (jsonRules []jobject) { + jsonRules = make([]jobject, len(rules)) + for i, r := range rules { + jsonRules[i] = jobject{ + "filter_list_id": r.FilterListID, + "text": r.Text, + } + } + + return jsonRules +} + +func answerToMap(a *dns.Msg) (answers []jobject) { if a == nil || len(a.Answer) == 0 { return nil } - answers := []map[string]interface{}{} + answers = []jobject{} for _, k := range a.Answer { header := k.Header() - answer := map[string]interface{}{ + answer := jobject{ "type": dns.TypeToString[header.Rrtype], "ttl": header.Ttl, } diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go index b6651158..fd37a1de 100644 --- a/internal/querylog/qlog_test.go +++ b/internal/querylog/qlog_test.go @@ -236,10 +236,12 @@ func addEntry(l *queryLog, host, answerStr, client string) { a.Answer = append(a.Answer, answer) res := dnsfilter.Result{ IsFiltered: true, - Rule: "SomeRule", Reason: dnsfilter.ReasonRewrite, ServiceName: "SomeService", - FilterID: 1, + Rules: []*dnsfilter.ResultRule{{ + FilterListID: 1, + Text: "SomeRule", + }}, } params := AddParams{ Question: &q, diff --git a/internal/querylog/searchcriteria.go b/internal/querylog/searchcriteria.go index bb573ea6..52b76459 100644 --- a/internal/querylog/searchcriteria.go +++ b/internal/querylog/searchcriteria.go @@ -117,7 +117,7 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { res.Reason.In( dnsfilter.NotFilteredWhiteList, dnsfilter.ReasonRewrite, - dnsfilter.RewriteEtcHosts, + dnsfilter.RewriteAutoHosts, ) case filteringStatusBlocked: @@ -137,7 +137,7 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { return res.Reason == dnsfilter.NotFilteredWhiteList case filteringStatusRewritten: - return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteEtcHosts) + return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts) case filteringStatusSafeSearch: return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeSearch diff --git a/internal/tools/go.mod b/internal/tools/go.mod index 73bb637b..0707d47f 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/mod v0.4.0 // indirect golang.org/x/tools v0.0.0-20201208062317-e652b2f42cc7 gopkg.in/yaml.v2 v2.4.0 // indirect - honnef.co/go/tools v0.0.1-2020.1.6 + honnef.co/go/tools v0.1.0 mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 ) diff --git a/internal/tools/go.sum b/internal/tools/go.sum index 968ac8a4..a8b20e9e 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -123,6 +123,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200710042808-f1c4188a97a1/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= @@ -158,6 +159,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.1-2020.1.6 h1:W18jzjh8mfPez+AwGLxmOImucz/IFjpNlrKVnaj2YVc= honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= +honnef.co/go/tools v0.1.0 h1:AWNL1W1i7f0wNZ8VwOKNJ0sliKvOF/adn0EHenfUh+c= +honnef.co/go/tools v0.1.0/go.mod h1:XtegFAyX/PfluP4921rXU5IkjkqBCDnUq4W8VCIoKvM= mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475 h1:5ZmJGYyuTlhdlIpRxSFhdJqkXQweXETFCEaLhRAX3e8= mvdan.cc/gofumpt v0.0.0-20201129102820-5c11c50e9475/go.mod h1:E4LOcu9JQEtnYXtB1Y51drqh2Qr2Ngk9J3YrRCwcbd0= mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY= diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index c4bbed51..f40dd490 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -1,5 +1,37 @@ # AdGuard Home API Change Log + + +## v0.105: API changes + +### Multiple matched rules in `GET /filtering/check_host` and `GET /querylog` + + + +* The properties `rule` and `filter_id` are now deprecated. API users should + inspect the newly-added `rules` object array instead. Currently, it's either + empty or contains one object, which contains the same things as the old two + properties did, but under more correct names: + + ```js + { + // … + + // Deprecated. + "rule": "||example.com^", + // Deprecated. + "filter_id": 42, + // Newly-added. + "rules": [{ + "text": "||example.com^", + "filter_list_id": 42 + }] + } + ``` + + The old fields will be removed in v0.106.0. + ## v0.103: API changes ### API: replace settings in GET /control/dns_info & POST /control/dns_config diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d2329c31..17e9f3f5 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -2,9 +2,9 @@ 'info': 'title': 'AdGuard Home' 'description': > - AdGuard Home REST API. Our admin web interface is built on top of this REST - API. - 'version': '0.104' + AdGuard Home REST-ish API. Our admin web interface is built on top of this + REST-ish API. + 'version': '0.105' 'contact': 'name': 'AdGuard Home' 'url': 'https://github.com/AdguardTeam/AdGuardHome' @@ -1259,11 +1259,26 @@ - 'FilteredBlockedService' - 'ReasonRewrite' 'filter_id': + 'deprecated': true + 'description': > + In case if there's a rule applied to this DNS request, this is ID of + the filter list that the rule belongs to. + + Deprecated: use `rules[*].filter_list_id` instead. 'type': 'integer' 'rule': + 'deprecated': true 'type': 'string' 'example': '||example.org^' - 'description': 'Filtering rule applied to the request (if any)' + 'description': > + Filtering rule applied to the request (if any). + + Deprecated: use `rules[*].text` instead. + 'rules': + 'description': 'Applied rules.' + 'type': 'array' + 'items': + '$ref': '#/components/schemas/ResultRule' 'service_name': 'type': 'string' 'description': 'Set if reason=FilteredBlockedService' @@ -1610,15 +1625,27 @@ 'question': '$ref': '#/components/schemas/DnsQuestion' 'filterId': + 'deprecated': true 'type': 'integer' 'example': 123123 'description': > In case if there's a rule applied to this DNS request, this is ID of - the filter that rule belongs to. + the filter list that the rule belongs to. + + Deprecated: use `rules[*].filter_list_id` instead. 'rule': + 'deprecated': true 'type': 'string' 'example': '||example.org^' - 'description': 'Filtering rule applied to the request (if any)' + 'description': > + Filtering rule applied to the request (if any). + + Deprecated: use `rules[*].text` instead. + 'rules': + 'description': 'Applied rules.' + 'type': 'array' + 'items': + '$ref': '#/components/schemas/ResultRule' 'reason': 'type': 'string' 'description': 'DNS filter status' @@ -1668,6 +1695,22 @@ 'anonymize_client_ip': 'type': 'boolean' 'description': "Anonymize clients' IP addresses" + 'ResultRule': + 'description': 'Applied rule.' + 'properties': + 'filter_list_id': + 'description': > + In case if there's a rule applied to this DNS request, this is ID of + the filter list that the rule belongs to. + 'example': 123123 + 'format': 'int64' + 'type': 'integer' + 'text': + 'description': > + The text of the filtering rule applied to the request (if any). + 'example': '||example.org^' + 'type': 'string' + 'type': 'object' 'TlsConfig': 'type': 'object' 'description': 'TLS configuration settings and status' diff --git a/scripts/go-lint.sh b/scripts/go-lint.sh index e54cd53e..c133379b 100644 --- a/scripts/go-lint.sh +++ b/scripts/go-lint.sh @@ -112,4 +112,4 @@ exit_on_output sh -c ' { grep -e "defer" -e "_test\.go:" -v || exit 0; } ' -staticcheck --checks='all' ./... +staticcheck ./... diff --git a/staticcheck.conf b/staticcheck.conf new file mode 100644 index 00000000..43639bf6 --- /dev/null +++ b/staticcheck.conf @@ -0,0 +1,14 @@ +checks = ["all"] +initialisms = [ + # See https://github.com/dominikh/go-tools/blob/master/config/config.go. + "inherit" +, "DHCP" +, "DOH" +, "DOQ" +, "DOT" +, "EDNS" +, "QUIC" +, "SDNS" +] +dot_import_whitelist = [] +http_status_code_whitelist = [] From fd7b061dd6583ff41607d21821854e09f089a218 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Thu, 17 Dec 2020 14:18:29 +0300 Subject: [PATCH 47/54] Pull request: 2225 daily freeze fix Merge in DNS/adguard-home from 2225-fix-freezes to master Updates #2225. Squashed commit of the following: commit 02a472120e9b4a0bc13129adb85eed5d9fd810a2 Author: Eugene Burkov Date: Thu Dec 17 14:10:20 2020 +0300 all: go mod tidy commit 6cfc23b780cf5da58719620ea5cd1fd3980c631e Author: Eugene Burkov Date: Thu Dec 17 14:06:28 2020 +0300 all: upd dnsproxy dependency --- go.mod | 4 ++-- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 3e9c2640..12da1505 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome go 1.14 require ( - github.com/AdguardTeam/dnsproxy v0.33.5 + github.com/AdguardTeam/dnsproxy v0.33.6 github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 @@ -27,7 +27,7 @@ require ( github.com/stretchr/testify v1.6.1 github.com/u-root/u-root v7.0.0+incompatible go.etcd.io/bbolt v1.3.5 - golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 + golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 golang.org/x/net v0.0.0-20201216054612-986b41b23924 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e diff --git a/go.sum b/go.sum index 579cd3a1..d359f04b 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdguardTeam/dnsproxy v0.33.5 h1:YTfY16lFgFqBzC3pdKalQSaeb7hjm4VvGqdksO0AoVI= -github.com/AdguardTeam/dnsproxy v0.33.5/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= +github.com/AdguardTeam/dnsproxy v0.33.6 h1:qoYQbyiVqqFL/O6EvIVnxnDC5vBCFoWz40vlYdfZ+lI= +github.com/AdguardTeam/dnsproxy v0.33.6/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= @@ -449,6 +449,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From 66d593bb0c3de6b4c582f793efcfdc2eb5f97843 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Thu, 17 Dec 2020 17:47:14 +0300 Subject: [PATCH 48/54] Pull request: fix docs Merge in DNS/adguard-home from fix-docs to master Squashed commit of the following: commit 81d3ada1917816752ee55f93d478d324a7c7e2f4 Author: Eugene Burkov Date: Thu Dec 17 17:35:23 2020 +0300 all: correct the wording commit a69a63be6218ef8d9211f753e13cb5e469d7a704 Author: Eugene Burkov Date: Thu Dec 17 17:30:49 2020 +0300 all: log changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fda4fe42..2e3a35c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,12 +57,14 @@ and this project adheres to ### Fixed +- Possible freezes on slower machines ([#2225]). - A mitigation against records being shown in the wrong order on the query log page ([#2293]). - A JSON parsing error in query log ([#2345]). - Incorrect detection of the IPv6 address of an interface as well as another infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]). +[#2225]: https://github.com/AdguardTeam/AdGuardHome/issues/2225 [#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 From 5f84cb1afef23e2e8c065b93d7e3ab88eca0d52c Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Thu, 17 Dec 2020 18:20:34 +0300 Subject: [PATCH 49/54] Pull request: all: update dnsproxy Merge in DNS/adguard-home from update-dnsproxy to master Squashed commit of the following: commit 2d6e80975eeef9e3851b705df592f651a9127c22 Author: Ainar Garipov Date: Thu Dec 17 18:09:20 2020 +0300 all: update dnsproxy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 12da1505..81d2e248 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/AdguardTeam/AdGuardHome go 1.14 require ( - github.com/AdguardTeam/dnsproxy v0.33.6 + github.com/AdguardTeam/dnsproxy v0.33.7 github.com/AdguardTeam/golibs v0.4.4 github.com/AdguardTeam/urlfilter v0.13.0 github.com/NYTimes/gziphandler v1.1.1 diff --git a/go.sum b/go.sum index d359f04b..37dd5867 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -github.com/AdguardTeam/dnsproxy v0.33.6 h1:qoYQbyiVqqFL/O6EvIVnxnDC5vBCFoWz40vlYdfZ+lI= -github.com/AdguardTeam/dnsproxy v0.33.6/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= +github.com/AdguardTeam/dnsproxy v0.33.7 h1:DXsLTJoBSUejB2ZqVHyMG0/kXD8PzuVPbLCsGKBdaDc= +github.com/AdguardTeam/dnsproxy v0.33.7/go.mod h1:dkI9VWh43XlOzF2XogDm1EmoVl7PANOR4isQV6X9LZs= github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o= github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= From 49c55e356f052e6bfa9b4ba51d806db4d267918e Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Fri, 18 Dec 2020 14:19:42 +0300 Subject: [PATCH 50/54] Pull request: install check Merge in DNS/adguard-home from 2453-install-check to master Closes #2453. Squashed commit of the following: commit b3123d7171ff5d1e00d8bcbb5cbe7fcf7a142a3c Author: Eugene Burkov Date: Fri Dec 18 14:00:26 2020 +0300 all: fix quotes commit 27e17f9543250d912dd559c9ba2e01e35636551f Author: Eugene Burkov Date: Fri Dec 18 13:34:00 2020 +0300 all: improve install script commit e9a927ffabc04dcd223bfc0b3b2541c7d5b96b61 Author: Eugene Burkov Date: Fri Dec 18 13:22:05 2020 +0300 all: add directory emptiness check --- HACKING.md | 23 ++++++++++++++++++++++- scripts/install.sh | 9 +++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/HACKING.md b/HACKING.md index ae14b784..a16d6496 100644 --- a/HACKING.md +++ b/HACKING.md @@ -163,12 +163,15 @@ The rules are mostly sorted in the alphabetical order. ## Shell Scripting - * Avoid bashisms, prefer *POSIX* features only. + * Avoid bashisms and GNUisms, prefer *POSIX* features only. * Prefer `'raw strings'` to `"double quoted strings"` whenever possible. * Put spaces within `$( cmd )`, `$(( expr ))`, and `{ cmd; }`. + * Put utility flags in the ASCII order and **don't** group them together. For + example, `ls -1 -A -q`. + * `snake_case`, not `camelCase`. * Use `set -e -f -u` and also `set -x` in verbose mode. @@ -176,6 +179,24 @@ The rules are mostly sorted in the alphabetical order. * Use the `"$var"` form instead of the `$var` form, unless word splitting is required. + * When concatenating, always use the form with curly braces to prevent + accidental bad variable names. That is, `"${var}_tmp.txt"` and **not** + `"$var_tmp.txt"`. The latter will try to lookup variable `var_tmp`. + + * When concatenating, surround the whole string with quotes. That is, use + this: + + ```sh + dir="${TOP_DIR}/sub" + ``` + + And **not** this: + + ```sh + # Bad! + dir="${TOP_DIR}"/sub + ``` + ## Text, Including Comments * End sentences with appropriate punctuation. diff --git a/scripts/install.sh b/scripts/install.sh index b983322c..6f1fa85f 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -190,6 +190,7 @@ main() { SCRIPT_URL="https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh" URL="https://static.adguard.com/adguardhome/${CHANNEL}/${PKG_NAME}" OUT_DIR=/opt + AGH_DIR="${OUT_DIR}/AdGuardHome" # Root check if [ "$(id -u)" -eq 0 ]; then @@ -208,22 +209,22 @@ main() { fi fi - log_info "AdGuard Home will be installed to ${OUT_DIR}/AdGuardHome" + log_info "AdGuard Home will be installed to ${AGH_DIR}" - [ -d "${OUT_DIR}/AdGuardHome" ] && error_exit "Directory ${OUT_DIR}/AdGuardHome already exists, abort installation" + [ -d "${AGH_DIR}" ] && [ -n "$(ls -1 -A -q ${AGH_DIR})" ] && error_exit "Directory ${AGH_DIR} is not empty, abort installation" download "${URL}" "${PKG_NAME}" || error_exit "Cannot download the package" unpack "${PKG_NAME}" "${OUT_DIR}" "${PKG_EXT}" || error_exit "Cannot unpack the package" # Install AdGuard Home service and run it - ${OUT_DIR}/AdGuardHome/AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" + ${AGH_DIR}/AdGuardHome -s install || error_exit "Cannot install AdGuardHome as a service" rm "${PKG_NAME}" log_info "AdGuard Home is now installed and running." log_info "You can control the service status with the following commands:" - log_info " sudo ${OUT_DIR}/AdGuardHome/AdGuardHome -s start|stop|restart|status|install|uninstall" + log_info " sudo ${AGH_DIR}/AdGuardHome -s start|stop|restart|status|install|uninstall" } main "$@" \ No newline at end of file From f165fd91c03c621fe9e7c0d59d3d7403399da7c9 Mon Sep 17 00:00:00 2001 From: Eugene Burkov Date: Mon, 21 Dec 2020 15:43:27 +0300 Subject: [PATCH 51/54] Pull request: fix dns cache ttl check Merge in DNS/adguard-home from 2459-dns-ttl to master Updates #2459. Squashed commit of the following: commit 27e74e30b202ab5163ebdbc2c00622099b11a1ff Author: Eugene Burkov Date: Mon Dec 21 15:00:46 2020 +0300 all: log changes commit e476fa5c4b8fd3896fa401f4dc546a5d937746eb Author: Eugene Burkov Date: Mon Dec 21 14:55:30 2020 +0300 dnsforward: fix dns cache ttl check --- CHANGELOG.md | 2 ++ internal/dnsforward/http.go | 3 ++- internal/dnsforward/msg.go | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e3a35c7..3fdf434a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ and this project adheres to ### Fixed +- Inability to set DNS cache TTL limits ([#2459]). - Possible freezes on slower machines ([#2225]). - A mitigation against records being shown in the wrong order on the query log page ([#2293]). @@ -68,6 +69,7 @@ and this project adheres to [#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293 [#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345 [#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355 +[#2459]: https://github.com/AdguardTeam/AdGuardHome/issues/2459 ### Removed diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go index c6c33c4e..e24ba89e 100644 --- a/internal/dnsforward/http.go +++ b/internal/dnsforward/http.go @@ -167,11 +167,12 @@ func (req *dnsConfig) checkCacheTTL() bool { if req.CacheMinTTL == nil && req.CacheMaxTTL == nil { return true } + var min, max uint32 if req.CacheMinTTL != nil { min = *req.CacheMinTTL } - if req.CacheMaxTTL == nil { + if req.CacheMaxTTL != nil { max = *req.CacheMaxTTL } diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index 3c381704..2e72c40c 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -42,7 +42,6 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu if result.Reason == dnsfilter.FilteredSafeSearch && len(result.Rules) > 0 && result.Rules[0].IP != nil { - return s.genResponseWithIP(m, result.Rules[0].IP) } From 2f265f249e5bc4d20a7a35ab10274c84baa9eba3 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 21 Dec 2020 13:51:08 +0100 Subject: [PATCH 52/54] Remove an unused function --- scripts/querylog/anonymize.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/querylog/anonymize.js b/scripts/querylog/anonymize.js index 3aed2877..ea2a3d39 100644 --- a/scripts/querylog/anonymize.js +++ b/scripts/querylog/anonymize.js @@ -2,11 +2,6 @@ const fs = require('fs'); const readline = require('readline'); const dnsPacket = require('dns-packet') -const decodeBase64 = (data) => { - let buff = new Buffer(data, 'base64'); - return buff.toString('ascii'); -} - const processLineByLine = async (source, callback) => { const fileStream = fs.createReadStream(source); From 61691fdedbe1002699f773892fff687ed7b31204 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 21 Dec 2020 13:53:19 +0100 Subject: [PATCH 53/54] Remove a superfluous condition `value` is always evaluated to true, due to the check on the previous line --- client/src/helpers/validators.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/helpers/validators.js b/client/src/helpers/validators.js index 9b853120..f2fee026 100644 --- a/client/src/helpers/validators.js +++ b/client/src/helpers/validators.js @@ -64,7 +64,7 @@ export const validateClientId = (value) => { if (!value) { return undefined; } - const formattedValue = value ? value.trim() : value; + const formattedValue = value.trim(); if (formattedValue && !( R_IPV4.test(formattedValue) || R_IPV6.test(formattedValue) From bdff46ec1dc8bfebaea236fef5e4ea64b4f21738 Mon Sep 17 00:00:00 2001 From: Ainar Garipov Date: Mon, 21 Dec 2020 17:48:07 +0300 Subject: [PATCH 54/54] Pull request: all: add $dnsrewrite handling Merge in DNS/adguard-home from 2102-dnsrewrite to master Updates #2102. Squashed commit of the following: commit 8490fc18179d38c4b162ff9b257fea1f8535afbd Merge: d9448ddca e7f7799b3 Author: Ainar Garipov Date: Mon Dec 21 16:44:00 2020 +0300 Merge branch 'master' into 2102-dnsrewrite commit d9448ddca6d4ef3635d767e3e496e44c35d3fc6e Author: Ainar Garipov Date: Mon Dec 21 15:44:54 2020 +0300 querylog: support dnsrewrite rules commit 40aa5d30acddf29fb90d249d8806941c6e1915a4 Author: Ainar Garipov Date: Fri Dec 18 19:27:40 2020 +0300 all: improve documentation commit f776a0cd63b1640ba1e5210d9301e2a2801fd824 Author: Ainar Garipov Date: Fri Dec 18 19:09:08 2020 +0300 dnsfilter: prevent panics, improve docs commit e14073b7500d9ed827a151c5b8fb863c980c10e8 Author: Ainar Garipov Date: Fri Dec 4 15:51:02 2020 +0300 all: add $dnsrewrite handling --- .githooks/pre-commit | 4 +- CHANGELOG.md | 1 + Makefile | 4 +- README.md | 4 +- go.mod | 2 +- go.sum | 4 +- internal/dnsfilter/dnsfilter.go | 150 +++++++------ internal/dnsfilter/dnsfilter_test.go | 61 +++--- internal/dnsfilter/dnsrewrite.go | 80 +++++++ internal/dnsfilter/dnsrewrite_test.go | 202 +++++++++++++++++ internal/dnsforward/dns.go | 8 +- internal/dnsforward/dnsrewrite.go | 79 +++++++ internal/dnsforward/filter.go | 16 +- internal/dnsforward/msg.go | 27 ++- internal/dnsforward/stats.go | 2 +- internal/home/controlfiltering.go | 10 +- internal/querylog/decode.go | 300 +++++++++++++++++++++++++- internal/querylog/decode_test.go | 150 ++++++++++--- internal/querylog/searchcriteria.go | 10 +- openapi/CHANGELOG.md | 42 +++- openapi/openapi.yaml | 18 +- scripts/go-lint.sh | 2 +- scripts/translations/README.md | 2 +- 23 files changed, 1009 insertions(+), 169 deletions(-) create mode 100644 internal/dnsfilter/dnsrewrite.go create mode 100644 internal/dnsfilter/dnsrewrite_test.go create mode 100644 internal/dnsforward/dnsrewrite.go diff --git a/.githooks/pre-commit b/.githooks/pre-commit index f2bbf1c9..278d7d3e 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -2,12 +2,12 @@ set -e -f -u -if [ "$(git diff --cached --name-only '*.js')" ] +if [ "$(git diff --cached --name-only -- '*.js')" ] then make js-lint js-test fi -if [ "$(git diff --cached --name-only '*.go')" ] +if [ "$(git diff --cached --name-only -- '*.go' 'go.mod')" ] then make go-lint go-test fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdf434a..e63c913c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to ### Added +- `$dnsrewrite` modifier for filters ([#2102]). - The host checking API and the query logs API can now return multiple matched rules ([#2102]). - Detecting of network interface configured to have static IP address via diff --git a/Makefile b/Makefile index 1848fe34..2bdee355 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ GPG_KEY := devteam@adguard.com GPG_KEY_PASSPHRASE := GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE) VERBOSE := -v +REBUILD_CLIENT = 1 # See release target DIST_DIR=dist @@ -124,7 +125,8 @@ all: build init: git config core.hooksPath .githooks -build: client_with_deps +build: + test '$(REBUILD_CLIENT)' = '1' && $(MAKE) client_with_deps || exit 0 $(GO) mod download PATH=$(GOPATH)/bin:$(PATH) $(GO) generate ./... CGO_ENABLED=0 $(GO) build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)" diff --git a/README.md b/README.md index b38452d7..eeb8f10e 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip * Beta channel builds * Linux: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz) - * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz) + * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_armv7.tar.gz) * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips64le_softfloat.tar.gz) * Windows: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_windows_386.zip) * MacOS: [64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_darwin_386.zip) @@ -264,7 +264,7 @@ curl -sSL https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scrip * Edge channel builds * Linux: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_amd64.tar.gz), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_386.tar.gz) - * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Rapsberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz) + * Linux ARM: [32-bit ARMv6](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv6.tar.gz) (recommended for Raspberry Pi), [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_arm64.tar.gz), [32-bit ARMv5](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv5.tar.gz), [32-bit ARMv7](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_armv7.tar.gz) * Linux MIPS: [32-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips_softfloat.tar.gz), [32-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mipsle_softfloat.tar.gz), [64-bit MIPS](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64_softfloat.tar.gz), [64-bit MIPSLE](https://static.adguard.com/adguardhome/edge/AdGuardHome_linux_mips64le_softfloat.tar.gz) * Windows: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_windows_386.zip) * MacOS: [64-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_amd64.zip), [32-bit](https://static.adguard.com/adguardhome/edge/AdGuardHome_darwin_386.zip) diff --git a/go.mod b/go.mod index 81d2e248..10866d88 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/AdguardTeam/dnsproxy v0.33.7 github.com/AdguardTeam/golibs v0.4.4 - github.com/AdguardTeam/urlfilter v0.13.0 + github.com/AdguardTeam/urlfilter v0.14.0 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.0.1 github.com/fsnotify/fsnotify v1.4.9 diff --git a/go.sum b/go.sum index 37dd5867..764a2fa0 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,8 @@ github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU github.com/AdguardTeam/golibs v0.4.4 h1:cM9UySQiYFW79zo5XRwnaIWVzfW4eNXmZktMrWbthpw= github.com/AdguardTeam/golibs v0.4.4/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4= github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU= -github.com/AdguardTeam/urlfilter v0.13.0 h1:MfO46K81JVTkhgP6gRu/buKl5wAOSfusjiDwjT1JN1c= -github.com/AdguardTeam/urlfilter v0.13.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= +github.com/AdguardTeam/urlfilter v0.14.0 h1:+aAhOvZDVGzl5gTERB4pOJCL1zxMyw7vLecJJ6TQTCw= +github.com/AdguardTeam/urlfilter v0.14.0/go.mod h1:klx4JbOfc4EaNb5lWLqOwfg+pVcyRukmoJRvO55lL5U= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= diff --git a/internal/dnsfilter/dnsfilter.go b/internal/dnsfilter/dnsfilter.go index 1735154d..4a1b255e 100644 --- a/internal/dnsfilter/dnsfilter.go +++ b/internal/dnsfilter/dnsfilter.go @@ -1,4 +1,4 @@ -// Package dnsfilter implements a DNS filter. +// Package dnsfilter implements a DNS request and response filter. package dnsfilter import ( @@ -95,8 +95,8 @@ type filtersInitializerParams struct { type DNSFilter struct { rulesStorage *filterlist.RuleStorage filteringEngine *urlfilter.DNSEngine - rulesStorageWhite *filterlist.RuleStorage - filteringEngineWhite *urlfilter.DNSEngine + rulesStorageAllow *filterlist.RuleStorage + filteringEngineAllow *urlfilter.DNSEngine engineLock sync.RWMutex parentalServer string // access via methods @@ -127,16 +127,16 @@ const ( // NotFilteredNotFound - host was not find in any checks, default value for result NotFilteredNotFound Reason = iota - // NotFilteredWhiteList - the host is explicitly whitelisted - NotFilteredWhiteList + // NotFilteredAllowList - the host is explicitly allowed + NotFilteredAllowList // NotFilteredError is returned when there was an error during // checking. Reserved, currently unused. NotFilteredError // reasons for filtering - // FilteredBlackList - the host was matched to be advertising host - FilteredBlackList + // FilteredBlockList - the host was matched to be advertising host + FilteredBlockList // FilteredSafeBrowsing - the host was matched to be malicious/phishing FilteredSafeBrowsing // FilteredParental - the host was matched to be outside of parental control settings @@ -155,16 +155,20 @@ const ( // RewriteAutoHosts is returned when there was a rewrite by // autohosts rules (/etc/hosts and so on). RewriteAutoHosts + + // DNSRewriteRule is returned when a $dnsrewrite filter rule was + // applied. + DNSRewriteRule ) // TODO(a.garipov): Resync with actual code names or replace completely // in HTTP API v1. var reasonNames = []string{ NotFilteredNotFound: "NotFilteredNotFound", - NotFilteredWhiteList: "NotFilteredWhiteList", + NotFilteredAllowList: "NotFilteredWhiteList", NotFilteredError: "NotFilteredError", - FilteredBlackList: "FilteredBlackList", + FilteredBlockList: "FilteredBlackList", FilteredSafeBrowsing: "FilteredSafeBrowsing", FilteredParental: "FilteredParental", FilteredInvalid: "FilteredInvalid", @@ -174,12 +178,15 @@ var reasonNames = []string{ ReasonRewrite: "Rewrite", RewriteAutoHosts: "RewriteEtcHosts", + + DNSRewriteRule: "DNSRewriteRule", } func (r Reason) String() string { - if uint(r) >= uint(len(reasonNames)) { + if r < 0 || int(r) >= len(reasonNames) { return "" } + return reasonNames[r] } @@ -278,16 +285,15 @@ func (d *DNSFilter) reset() { } } - if d.rulesStorageWhite != nil { - err = d.rulesStorageWhite.Close() + if d.rulesStorageAllow != nil { + err = d.rulesStorageAllow.Close() if err != nil { - log.Error("dnsfilter: rulesStorageWhite.Close: %s", err) + log.Error("dnsfilter: rulesStorageAllow.Close: %s", err) } } } type dnsFilterContext struct { - stats Stats safebrowsingCache cache.Cache parentalCache cache.Cache safeSearchCache cache.Cache @@ -339,6 +345,9 @@ type Result struct { // ServiceName is the name of the blocked service. It is empty // unless Reason is set to FilteredBlockedService. ServiceName string `json:",omitempty"` + + // DNSRewriteResult is the $dnsrewrite filter rule result. + DNSRewriteResult *DNSRewriteResult `json:",omitempty"` } // Matched returns true if any match at all was found regardless of @@ -383,9 +392,6 @@ func (d *DNSFilter) CheckHost(host string, qtype uint16, setts *RequestFiltering } } - // Then check the filter lists. - // if request is blocked -- it should be blocked. - // if it is whitelisted -- we should do nothing with it anymore. if setts.FilteringEnabled { result, err = d.matchHost(host, qtype, *setts) if err != nil { @@ -476,9 +482,7 @@ func (d *DNSFilter) checkAutoHosts(host string, qtype uint16, result *Result) (m // . repeat for the new domain name (Note: we return only the last CNAME) // . Find A or AAAA record for a domain name (exact match or by wildcard) // . if found, set IP addresses (IPv4 or IPv6 depending on qtype) in Result.IPList array -func (d *DNSFilter) processRewrites(host string, qtype uint16) Result { - var res Result - +func (d *DNSFilter) processRewrites(host string, qtype uint16) (res Result) { d.confLock.RLock() defer d.confLock.RUnlock() @@ -493,7 +497,8 @@ func (d *DNSFilter) processRewrites(host string, qtype uint16) Result { log.Debug("Rewrite: CNAME for %s is %s", host, rr[0].Answer) if host == rr[0].Answer { // "host == CNAME" is an exception - res.Reason = 0 + res.Reason = NotFilteredNotFound + return res } @@ -616,7 +621,7 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error { if err != nil { return err } - rulesStorageWhite, filteringEngineWhite, err := createFilteringEngine(allowFilters) + rulesStorageAllow, filteringEngineAllow, err := createFilteringEngine(allowFilters) if err != nil { return err } @@ -625,8 +630,8 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error { d.reset() d.rulesStorage = rulesStorage d.filteringEngine = filteringEngine - d.rulesStorageWhite = rulesStorageWhite - d.filteringEngineWhite = filteringEngineWhite + d.rulesStorageAllow = rulesStorageAllow + d.filteringEngineAllow = filteringEngineAllow d.engineLock.Unlock() // Make sure that the OS reclaims memory as soon as possible @@ -636,9 +641,31 @@ func (d *DNSFilter) initFiltering(allowFilters, blockFilters []Filter) error { return nil } +// matchHostProcessAllowList processes the allowlist logic of host +// matching. +func (d *DNSFilter) matchHostProcessAllowList(host string, dnsres urlfilter.DNSResult) (res Result, err error) { + var rule rules.Rule + if dnsres.NetworkRule != nil { + rule = dnsres.NetworkRule + } else if len(dnsres.HostRulesV4) > 0 { + rule = dnsres.HostRulesV4[0] + } else if len(dnsres.HostRulesV6) > 0 { + rule = dnsres.HostRulesV6[0] + } + + if rule == nil { + return Result{}, fmt.Errorf("invalid dns result: rules are empty") + } + + log.Debug("Filtering: found allowlist rule for host %q: %q list_id: %d", + host, rule.Text(), rule.GetFilterListID()) + + return makeResult(rule, NotFilteredAllowList), nil +} + // matchHost is a low-level way to check only if hostname is filtered by rules, // skipping expensive safebrowsing and parental lookups. -func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (Result, error) { +func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringSettings) (res Result, err error) { d.engineLock.RLock() // Keep in mind that this lock must be held no just when calling Match() // but also while using the rules returned by it. @@ -652,22 +679,10 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS DNSType: qtype, } - if d.filteringEngineWhite != nil { - rr, ok := d.filteringEngineWhite.MatchRequest(ureq) + if d.filteringEngineAllow != nil { + dnsres, ok := d.filteringEngineAllow.MatchRequest(ureq) if ok { - var rule rules.Rule - if rr.NetworkRule != nil { - rule = rr.NetworkRule - } else if rr.HostRulesV4 != nil { - rule = rr.HostRulesV4[0] - } else if rr.HostRulesV6 != nil { - rule = rr.HostRulesV6[0] - } - - log.Debug("Filtering: found whitelist rule for host %q: %q list_id: %d", - host, rule.Text(), rule.GetFilterListID()) - res := makeResult(rule, NotFilteredWhiteList) - return res, nil + return d.matchHostProcessAllowList(host, dnsres) } } @@ -675,54 +690,65 @@ func (d *DNSFilter) matchHost(host string, qtype uint16, setts RequestFilteringS return Result{}, nil } - rr, ok := d.filteringEngine.MatchRequest(ureq) - if !ok { + dnsres, ok := d.filteringEngine.MatchRequest(ureq) + + // Check DNS rewrites first, because the API there is a bit + // awkward. + if dnsr := dnsres.DNSRewrites(); len(dnsr) > 0 { + res = d.processDNSRewrites(dnsr) + if res.Reason == DNSRewriteRule && res.CanonName == host { + // A rewrite of a host to itself. Go on and + // try matching other things. + } else { + return res, nil + } + } else if !ok { return Result{}, nil } - if rr.NetworkRule != nil { + if dnsres.NetworkRule != nil { log.Debug("Filtering: found rule for host %q: %q list_id: %d", - host, rr.NetworkRule.Text(), rr.NetworkRule.GetFilterListID()) - reason := FilteredBlackList - if rr.NetworkRule.Whitelist { - reason = NotFilteredWhiteList + host, dnsres.NetworkRule.Text(), dnsres.NetworkRule.GetFilterListID()) + reason := FilteredBlockList + if dnsres.NetworkRule.Whitelist { + reason = NotFilteredAllowList } - res := makeResult(rr.NetworkRule, reason) - return res, nil + + return makeResult(dnsres.NetworkRule, reason), nil } - if qtype == dns.TypeA && rr.HostRulesV4 != nil { - rule := rr.HostRulesV4[0] // note that we process only 1 matched rule + if qtype == dns.TypeA && dnsres.HostRulesV4 != nil { + rule := dnsres.HostRulesV4[0] // note that we process only 1 matched rule log.Debug("Filtering: found rule for host %q: %q list_id: %d", host, rule.Text(), rule.GetFilterListID()) - res := makeResult(rule, FilteredBlackList) + res = makeResult(rule, FilteredBlockList) res.Rules[0].IP = rule.IP.To4() return res, nil } - if qtype == dns.TypeAAAA && rr.HostRulesV6 != nil { - rule := rr.HostRulesV6[0] // note that we process only 1 matched rule + if qtype == dns.TypeAAAA && dnsres.HostRulesV6 != nil { + rule := dnsres.HostRulesV6[0] // note that we process only 1 matched rule log.Debug("Filtering: found rule for host %q: %q list_id: %d", host, rule.Text(), rule.GetFilterListID()) - res := makeResult(rule, FilteredBlackList) + res = makeResult(rule, FilteredBlockList) res.Rules[0].IP = rule.IP return res, nil } - if rr.HostRulesV4 != nil || rr.HostRulesV6 != nil { + if dnsres.HostRulesV4 != nil || dnsres.HostRulesV6 != nil { // Question Type doesn't match the host rules // Return the first matched host rule, but without an IP address var rule rules.Rule - if rr.HostRulesV4 != nil { - rule = rr.HostRulesV4[0] - } else if rr.HostRulesV6 != nil { - rule = rr.HostRulesV6[0] + if dnsres.HostRulesV4 != nil { + rule = dnsres.HostRulesV4[0] + } else if dnsres.HostRulesV6 != nil { + rule = dnsres.HostRulesV6[0] } log.Debug("Filtering: found rule for host %q: %q list_id: %d", host, rule.Text(), rule.GetFilterListID()) - res := makeResult(rule, FilteredBlackList) + res = makeResult(rule, FilteredBlockList) res.Rules[0].IP = net.IP{} return res, nil @@ -741,7 +767,7 @@ func makeResult(rule rules.Rule, reason Reason) Result { }}, } - if reason == FilteredBlackList { + if reason == FilteredBlockList { res.IsFiltered = true } diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go index 96376162..2bae12de 100644 --- a/internal/dnsfilter/dnsfilter_test.go +++ b/internal/dnsfilter/dnsfilter_test.go @@ -178,7 +178,6 @@ func TestSafeBrowsing(t *testing.T) { d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil) defer d.Close() - gctx.stats.Safebrowsing.Requests = 0 d.checkMatch(t, "wmconvirus.narod.ru") assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru")) @@ -366,7 +365,7 @@ const nl = "\n" const ( blockingRules = `||example.org^` + nl - whitelistRules = `||example.org^` + nl + `@@||test.example.org` + nl + allowlistRules = `||example.org^` + nl + `@@||test.example.org` + nl importantRules = `@@||example.org^` + nl + `||test.example.org^$important` + nl regexRules = `/example\.org/` + nl + `@@||test.example.org^` + nl maskRules = `test*.example.org^` + nl + `exam*.com` + nl @@ -381,49 +380,49 @@ var tests = []struct { reason Reason dnsType uint16 }{ - {"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlackList, dns.TypeA}, + {"sanity", "||doubleclick.net^", "www.doubleclick.net", true, FilteredBlockList, dns.TypeA}, {"sanity", "||doubleclick.net^", "nodoubleclick.net", false, NotFilteredNotFound, dns.TypeA}, {"sanity", "||doubleclick.net^", "doubleclick.net.ru", false, NotFilteredNotFound, dns.TypeA}, {"sanity", "||doubleclick.net^", "wmconvirus.narod.ru", false, NotFilteredNotFound, dns.TypeA}, - {"blocking", blockingRules, "example.org", true, FilteredBlackList, dns.TypeA}, - {"blocking", blockingRules, "test.example.org", true, FilteredBlackList, dns.TypeA}, - {"blocking", blockingRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA}, + {"blocking", blockingRules, "example.org", true, FilteredBlockList, dns.TypeA}, + {"blocking", blockingRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, + {"blocking", blockingRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA}, {"blocking", blockingRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"blocking", blockingRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"whitelist", whitelistRules, "example.org", true, FilteredBlackList, dns.TypeA}, - {"whitelist", whitelistRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA}, - {"whitelist", whitelistRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA}, - {"whitelist", whitelistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"whitelist", whitelistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"allowlist", allowlistRules, "example.org", true, FilteredBlockList, dns.TypeA}, + {"allowlist", allowlistRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, + {"allowlist", allowlistRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA}, + {"allowlist", allowlistRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, + {"allowlist", allowlistRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"important", importantRules, "example.org", false, NotFilteredWhiteList, dns.TypeA}, - {"important", importantRules, "test.example.org", true, FilteredBlackList, dns.TypeA}, - {"important", importantRules, "test.test.example.org", true, FilteredBlackList, dns.TypeA}, + {"important", importantRules, "example.org", false, NotFilteredAllowList, dns.TypeA}, + {"important", importantRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, + {"important", importantRules, "test.test.example.org", true, FilteredBlockList, dns.TypeA}, {"important", importantRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"important", importantRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, - {"regex", regexRules, "example.org", true, FilteredBlackList, dns.TypeA}, - {"regex", regexRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA}, - {"regex", regexRules, "test.test.example.org", false, NotFilteredWhiteList, dns.TypeA}, - {"regex", regexRules, "testexample.org", true, FilteredBlackList, dns.TypeA}, - {"regex", regexRules, "onemoreexample.org", true, FilteredBlackList, dns.TypeA}, + {"regex", regexRules, "example.org", true, FilteredBlockList, dns.TypeA}, + {"regex", regexRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, + {"regex", regexRules, "test.test.example.org", false, NotFilteredAllowList, dns.TypeA}, + {"regex", regexRules, "testexample.org", true, FilteredBlockList, dns.TypeA}, + {"regex", regexRules, "onemoreexample.org", true, FilteredBlockList, dns.TypeA}, - {"mask", maskRules, "test.example.org", true, FilteredBlackList, dns.TypeA}, - {"mask", maskRules, "test2.example.org", true, FilteredBlackList, dns.TypeA}, - {"mask", maskRules, "example.com", true, FilteredBlackList, dns.TypeA}, - {"mask", maskRules, "exampleeee.com", true, FilteredBlackList, dns.TypeA}, - {"mask", maskRules, "onemoreexamsite.com", true, FilteredBlackList, dns.TypeA}, + {"mask", maskRules, "test.example.org", true, FilteredBlockList, dns.TypeA}, + {"mask", maskRules, "test2.example.org", true, FilteredBlockList, dns.TypeA}, + {"mask", maskRules, "example.com", true, FilteredBlockList, dns.TypeA}, + {"mask", maskRules, "exampleeee.com", true, FilteredBlockList, dns.TypeA}, + {"mask", maskRules, "onemoreexamsite.com", true, FilteredBlockList, dns.TypeA}, {"mask", maskRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, {"mask", maskRules, "testexample.org", false, NotFilteredNotFound, dns.TypeA}, {"mask", maskRules, "example.co.uk", false, NotFilteredNotFound, dns.TypeA}, {"dnstype", dnstypeRules, "onemoreexample.org", false, NotFilteredNotFound, dns.TypeA}, {"dnstype", dnstypeRules, "example.org", false, NotFilteredNotFound, dns.TypeA}, - {"dnstype", dnstypeRules, "example.org", true, FilteredBlackList, dns.TypeAAAA}, - {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeA}, - {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredWhiteList, dns.TypeAAAA}, + {"dnstype", dnstypeRules, "example.org", true, FilteredBlockList, dns.TypeAAAA}, + {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeA}, + {"dnstype", dnstypeRules, "test.example.org", false, NotFilteredAllowList, dns.TypeAAAA}, } func TestMatching(t *testing.T) { @@ -470,7 +469,7 @@ func TestWhitelist(t *testing.T) { // matched by white filter res, err := d.CheckHost("host1", dns.TypeA, &setts) assert.True(t, err == nil) - assert.True(t, !res.IsFiltered && res.Reason == NotFilteredWhiteList) + assert.True(t, !res.IsFiltered && res.Reason == NotFilteredAllowList) if assert.Len(t, res.Rules, 1) { assert.True(t, res.Rules[0].Text == "||host1^") } @@ -478,7 +477,7 @@ func TestWhitelist(t *testing.T) { // not matched by white filter, but matched by block filter res, err = d.CheckHost("host2", dns.TypeA, &setts) assert.True(t, err == nil) - assert.True(t, res.IsFiltered && res.Reason == FilteredBlackList) + assert.True(t, res.IsFiltered && res.Reason == FilteredBlockList) if assert.Len(t, res.Rules, 1) { assert.True(t, res.Rules[0].Text == "||host2^") } @@ -512,8 +511,8 @@ func TestClientSettings(t *testing.T) { // blocked by filters r, _ = d.CheckHost("example.org", dns.TypeA, &setts) - if !r.IsFiltered || r.Reason != FilteredBlackList { - t.Fatalf("CheckHost FilteredBlackList") + if !r.IsFiltered || r.Reason != FilteredBlockList { + t.Fatalf("CheckHost FilteredBlockList") } // blocked by parental diff --git a/internal/dnsfilter/dnsrewrite.go b/internal/dnsfilter/dnsrewrite.go new file mode 100644 index 00000000..1239fbad --- /dev/null +++ b/internal/dnsfilter/dnsrewrite.go @@ -0,0 +1,80 @@ +package dnsfilter + +import ( + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" +) + +// DNSRewriteResult is the result of application of $dnsrewrite rules. +type DNSRewriteResult struct { + RCode rules.RCode `json:",omitempty"` + Response DNSRewriteResultResponse `json:",omitempty"` +} + +// DNSRewriteResultResponse is the collection of DNS response records +// the server returns. +type DNSRewriteResultResponse map[rules.RRType][]rules.RRValue + +// processDNSRewrites processes DNS rewrite rules in dnsr. It returns +// an empty result if dnsr is empty. Otherwise, the result will have +// either CanonName or DNSRewriteResult set. +func (d *DNSFilter) processDNSRewrites(dnsr []*rules.NetworkRule) (res Result) { + if len(dnsr) == 0 { + return Result{} + } + + var rules []*ResultRule + dnsrr := &DNSRewriteResult{ + Response: DNSRewriteResultResponse{}, + } + + for _, nr := range dnsr { + dr := nr.DNSRewrite + if dr.NewCNAME != "" { + // NewCNAME rules have a higher priority than + // the other rules. + rules := []*ResultRule{{ + FilterListID: int64(nr.GetFilterListID()), + Text: nr.RuleText, + }} + + return Result{ + Reason: DNSRewriteRule, + Rules: rules, + CanonName: dr.NewCNAME, + } + } + + switch dr.RCode { + case dns.RcodeSuccess: + dnsrr.RCode = dr.RCode + dnsrr.Response[dr.RRType] = append(dnsrr.Response[dr.RRType], dr.Value) + rules = append(rules, &ResultRule{ + FilterListID: int64(nr.GetFilterListID()), + Text: nr.RuleText, + }) + default: + // RcodeRefused and other such codes have higher + // priority. Return immediately. + rules := []*ResultRule{{ + FilterListID: int64(nr.GetFilterListID()), + Text: nr.RuleText, + }} + dnsrr = &DNSRewriteResult{ + RCode: dr.RCode, + } + + return Result{ + Reason: DNSRewriteRule, + Rules: rules, + DNSRewriteResult: dnsrr, + } + } + } + + return Result{ + Reason: DNSRewriteRule, + Rules: rules, + DNSRewriteResult: dnsrr, + } +} diff --git a/internal/dnsfilter/dnsrewrite_test.go b/internal/dnsfilter/dnsrewrite_test.go new file mode 100644 index 00000000..4918ccc0 --- /dev/null +++ b/internal/dnsfilter/dnsrewrite_test.go @@ -0,0 +1,202 @@ +package dnsfilter + +import ( + "net" + "path" + "testing" + + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" +) + +func TestDNSFilter_CheckHostRules_dnsrewrite(t *testing.T) { + const text = ` +|cname^$dnsrewrite=new_cname + +|a_record^$dnsrewrite=127.0.0.1 + +|aaaa_record^$dnsrewrite=::1 + +|txt_record^$dnsrewrite=NOERROR;TXT;hello_world + +|refused^$dnsrewrite=REFUSED + +|a_records^$dnsrewrite=127.0.0.1 +|a_records^$dnsrewrite=127.0.0.2 + +|aaaa_records^$dnsrewrite=::1 +|aaaa_records^$dnsrewrite=::2 + +|disable_one^$dnsrewrite=127.0.0.1 +|disable_one^$dnsrewrite=127.0.0.2 +@@||disable_one^$dnsrewrite=127.0.0.1 + +|disable_cname^$dnsrewrite=127.0.0.1 +|disable_cname^$dnsrewrite=new_cname +@@||disable_cname^$dnsrewrite=new_cname + +|disable_cname_many^$dnsrewrite=127.0.0.1 +|disable_cname_many^$dnsrewrite=new_cname_1 +|disable_cname_many^$dnsrewrite=new_cname_2 +@@||disable_cname_many^$dnsrewrite=new_cname_1 + +|disable_all^$dnsrewrite=127.0.0.1 +|disable_all^$dnsrewrite=127.0.0.2 +@@||disable_all^$dnsrewrite +` + f := NewForTest(nil, []Filter{{ID: 0, Data: []byte(text)}}) + setts := &RequestFilteringSettings{ + FilteringEnabled: true, + } + + ipv4p1 := net.IPv4(127, 0, 0, 1) + ipv4p2 := net.IPv4(127, 0, 0, 2) + ipv6p1 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} + ipv6p2 := net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} + + t.Run("cname", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + assert.Equal(t, "new_cname", res.CanonName) + }) + + t.Run("a_record", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) { + assert.Equal(t, ipv4p1, ipVals[0]) + } + } + }) + + t.Run("aaaa_record", func(t *testing.T) { + dtyp := dns.TypeAAAA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) { + assert.Equal(t, ipv6p1, ipVals[0]) + } + } + }) + + t.Run("txt_record", func(t *testing.T) { + dtyp := dns.TypeTXT + host := path.Base(t.Name()) + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if strVals := dnsrr.Response[dtyp]; assert.Len(t, strVals, 1) { + assert.Equal(t, "hello_world", strVals[0]) + } + } + }) + + t.Run("refused", func(t *testing.T) { + host := path.Base(t.Name()) + res, err := f.CheckHostRules(host, dns.TypeA, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeRefused, dnsrr.RCode) + } + }) + + t.Run("a_records", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 2) { + assert.Equal(t, ipv4p1, ipVals[0]) + assert.Equal(t, ipv4p2, ipVals[1]) + } + } + }) + + t.Run("aaaa_records", func(t *testing.T) { + dtyp := dns.TypeAAAA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 2) { + assert.Equal(t, ipv6p1, ipVals[0]) + assert.Equal(t, ipv6p2, ipVals[1]) + } + } + }) + + t.Run("disable_one", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) { + assert.Equal(t, ipv4p2, ipVals[0]) + } + } + }) + + t.Run("disable_cname", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + assert.Equal(t, "", res.CanonName) + + if dnsrr := res.DNSRewriteResult; assert.NotNil(t, dnsrr) { + assert.Equal(t, dns.RcodeSuccess, dnsrr.RCode) + if ipVals := dnsrr.Response[dtyp]; assert.Len(t, ipVals, 1) { + assert.Equal(t, ipv4p1, ipVals[0]) + } + } + }) + + t.Run("disable_cname_many", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + assert.Equal(t, "new_cname_2", res.CanonName) + assert.Nil(t, res.DNSRewriteResult) + }) + + t.Run("disable_all", func(t *testing.T) { + dtyp := dns.TypeA + host := path.Base(t.Name()) + + res, err := f.CheckHostRules(host, dtyp, setts) + assert.Nil(t, err) + assert.Equal(t, "", res.CanonName) + assert.Len(t, res.Rules, 0) + }) +} diff --git a/internal/dnsforward/dns.go b/internal/dnsforward/dns.go index 0f9c764b..d7208d1c 100644 --- a/internal/dnsforward/dns.go +++ b/internal/dnsforward/dns.go @@ -366,7 +366,9 @@ func processFilteringAfterResponse(ctx *dnsContext) int { var err error switch res.Reason { - case dnsfilter.ReasonRewrite: + case dnsfilter.ReasonRewrite, + dnsfilter.DNSRewriteRule: + if len(ctx.origQuestion.Name) == 0 { // origQuestion is set in case we get only CNAME without IP from rewrites table break @@ -378,11 +380,11 @@ func processFilteringAfterResponse(ctx *dnsContext) int { if len(d.Res.Answer) != 0 { answer := []dns.RR{} answer = append(answer, s.genCNAMEAnswer(d.Req, res.CanonName)) - answer = append(answer, d.Res.Answer...) // host -> IP + answer = append(answer, d.Res.Answer...) d.Res.Answer = answer } - case dnsfilter.NotFilteredWhiteList: + case dnsfilter.NotFilteredAllowList: // nothing default: diff --git a/internal/dnsforward/dnsrewrite.go b/internal/dnsforward/dnsrewrite.go new file mode 100644 index 00000000..01895323 --- /dev/null +++ b/internal/dnsforward/dnsrewrite.go @@ -0,0 +1,79 @@ +package dnsforward + +import ( + "fmt" + "net" + + "github.com/AdguardTeam/AdGuardHome/internal/agherr" + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" + "github.com/AdguardTeam/dnsproxy/proxy" + "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" +) + +// filterDNSRewriteResponse handles a single DNS rewrite response entry. +// It returns the constructed answer resource record. +func (s *Server) filterDNSRewriteResponse(req *dns.Msg, rr rules.RRType, v rules.RRValue) (ans dns.RR, err error) { + switch rr { + case dns.TypeA, dns.TypeAAAA: + ip, ok := v.(net.IP) + if !ok { + return nil, fmt.Errorf("value has type %T, not net.IP", v) + } + + if rr == dns.TypeA { + return s.genAAnswer(req, ip.To4()), nil + } + + return s.genAAAAAnswer(req, ip), nil + case dns.TypeTXT: + str, ok := v.(string) + if !ok { + return nil, fmt.Errorf("value has type %T, not string", v) + } + + return s.genTXTAnswer(req, []string{str}), nil + default: + log.Debug("don't know how to handle dns rr type %d, skipping", rr) + + return nil, nil + } +} + +// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS +// response and sets it into d.Res. +func (s *Server) filterDNSRewrite(req *dns.Msg, res dnsfilter.Result, d *proxy.DNSContext) (err error) { + resp := s.makeResponse(req) + dnsrr := res.DNSRewriteResult + if dnsrr == nil { + return agherr.Error("no dns rewrite rule content") + } + + resp.Rcode = dnsrr.RCode + if resp.Rcode != dns.RcodeSuccess { + d.Res = resp + + return nil + } + + if dnsrr.Response == nil { + return agherr.Error("no dns rewrite rule responses") + } + + rr := req.Question[0].Qtype + values := dnsrr.Response[rr] + for i, v := range values { + var ans dns.RR + ans, err = s.filterDNSRewriteResponse(req, rr, v) + if err != nil { + return fmt.Errorf("dns rewrite response for %d[%d]: %w", rr, i, err) + } + + resp.Answer = append(resp.Answer, ans) + } + + d.Res = resp + + return nil +} diff --git a/internal/dnsforward/filter.go b/internal/dnsforward/filter.go index 83effc60..5cd0090a 100644 --- a/internal/dnsforward/filter.go +++ b/internal/dnsforward/filter.go @@ -42,7 +42,8 @@ func (s *Server) getClientRequestFilteringSettings(d *proxy.DNSContext) *dnsfilt return &setts } -// filterDNSRequest applies the dnsFilter and sets d.Res if the request was filtered +// filterDNSRequest applies the dnsFilter and sets d.Res if the request +// was filtered. func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { d := ctx.proxyCtx req := d.Req @@ -54,9 +55,13 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { } else if res.IsFiltered { log.Tracef("Host %s is filtered, reason - %q, matched rule: %q", host, res.Reason, res.Rules[0].Text) d.Res = s.genDNSFilterMessage(d, &res) - } else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 { + } else if res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.DNSRewriteRule) && + res.CanonName != "" && + len(res.IPList) == 0 { + // Resolve the new canonical name, not the original host + // name. The original question is readded in + // processFilteringAfterResponse. ctx.origQuestion = d.Req.Question[0] - // resolve canonical name, not the original host name d.Req.Question[0].Name = dns.Fqdn(res.CanonName) } else if res.Reason == dnsfilter.RewriteAutoHosts && len(res.ReverseHosts) != 0 { resp := s.makeResponse(req) @@ -99,6 +104,11 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) { } d.Res = resp + } else if res.Reason == dnsfilter.DNSRewriteRule { + err = s.filterDNSRewrite(req, res, d) + if err != nil { + return nil, err + } } return &res, err diff --git a/internal/dnsforward/msg.go b/internal/dnsforward/msg.go index 2e72c40c..f8200056 100644 --- a/internal/dnsforward/msg.go +++ b/internal/dnsforward/msg.go @@ -11,12 +11,17 @@ import ( ) // Create a DNS response by DNS request and set necessary flags -func (s *Server) makeResponse(req *dns.Msg) *dns.Msg { - resp := dns.Msg{} +func (s *Server) makeResponse(req *dns.Msg) (resp *dns.Msg) { + resp = &dns.Msg{ + MsgHdr: dns.MsgHdr{ + RecursionAvailable: true, + }, + Compress: true, + } + resp.SetReply(req) - resp.RecursionAvailable = true - resp.Compress = true - return &resp + + return resp } // genDNSFilterMessage generates a DNS message corresponding to the filtering result @@ -121,6 +126,18 @@ func (s *Server) genAAAAAnswer(req *dns.Msg, ip net.IP) *dns.AAAA { return answer } +func (s *Server) genTXTAnswer(req *dns.Msg, strs []string) (answer *dns.TXT) { + return &dns.TXT{ + Hdr: dns.RR_Header{ + Name: req.Question[0].Name, + Rrtype: dns.TypeTXT, + Ttl: s.conf.BlockedResponseTTL, + Class: dns.ClassINET, + }, + Txt: strs, + } +} + // generate DNS response message with an IP address func (s *Server) genResponseWithIP(req *dns.Msg, ip net.IP) *dns.Msg { if req.Question[0].Qtype == dns.TypeA && ip.To4() != nil { diff --git a/internal/dnsforward/stats.go b/internal/dnsforward/stats.go index c2b8921f..c447be05 100644 --- a/internal/dnsforward/stats.go +++ b/internal/dnsforward/stats.go @@ -91,7 +91,7 @@ func (s *Server) updateStats(d *proxy.DNSContext, elapsed time.Duration, res dns case dnsfilter.FilteredSafeSearch: e.Result = stats.RSafeSearch - case dnsfilter.FilteredBlackList: + case dnsfilter.FilteredBlockList: fallthrough case dnsfilter.FilteredInvalid: fallthrough diff --git a/internal/home/controlfiltering.go b/internal/home/controlfiltering.go index 3fe07e7e..1d0172e8 100644 --- a/internal/home/controlfiltering.go +++ b/internal/home/controlfiltering.go @@ -359,6 +359,9 @@ type checkHostResp struct { // Deprecated: Use Rules[*].FilterListID. FilterID int64 `json:"filter_id"` + // Rule is the text of the matched rule. + // + // Deprecated: Use Rules[*].Text. Rule string `json:"rule"` Rules []*checkHostRespRule `json:"rules"` @@ -386,12 +389,15 @@ func (f *Filtering) handleCheckHost(w http.ResponseWriter, r *http.Request) { resp := checkHostResp{} resp.Reason = result.Reason.String() - resp.FilterID = result.Rules[0].FilterListID - resp.Rule = result.Rules[0].Text resp.SvcName = result.ServiceName resp.CanonName = result.CanonName resp.IPList = result.IPList + if len(result.Rules) > 0 { + resp.FilterID = result.Rules[0].FilterListID + resp.Rule = result.Rules[0].Text + } + resp.Rules = make([]*checkHostRespRule, len(result.Rules)) for i, r := range result.Rules { resp.Rules[i] = &checkHostRespRule{ diff --git a/internal/querylog/decode.go b/internal/querylog/decode.go index f5937781..ed721489 100644 --- a/internal/querylog/decode.go +++ b/internal/querylog/decode.go @@ -4,11 +4,14 @@ import ( "encoding/base64" "encoding/json" "io" + "net" "strings" "time" "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" ) type logEntryHandler (func(t json.Token, ent *logEntry) error) @@ -165,13 +168,285 @@ var resultHandlers = map[string]logEntryHandler{ return nil }, "ServiceName": func(t json.Token, ent *logEntry) error { - v, ok := t.(string) + s, ok := t.(string) if !ok { return nil } - ent.Result.ServiceName = v + + ent.Result.ServiceName = s + return nil }, + "CanonName": func(t json.Token, ent *logEntry) error { + s, ok := t.(string) + if !ok { + return nil + } + + ent.Result.CanonName = s + + return nil + }, +} + +func decodeResultRuleKey(key string, i int, dec *json.Decoder, ent *logEntry) { + switch key { + case "FilterListID": + vToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultRuleKey %s err: %s", key, err) + } + + return + } + + if len(ent.Result.Rules) < i+1 { + ent.Result.Rules = append(ent.Result.Rules, &dnsfilter.ResultRule{}) + } + + if n, ok := vToken.(json.Number); ok { + ent.Result.Rules[i].FilterListID, _ = n.Int64() + } + case "IP": + vToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultRuleKey %s err: %s", key, err) + } + + return + } + + if len(ent.Result.Rules) < i+1 { + ent.Result.Rules = append(ent.Result.Rules, &dnsfilter.ResultRule{}) + } + + if ipStr, ok := vToken.(string); ok { + ent.Result.Rules[i].IP = net.ParseIP(ipStr) + } + case "Text": + vToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultRuleKey %s err: %s", key, err) + } + + return + } + + if len(ent.Result.Rules) < i+1 { + ent.Result.Rules = append(ent.Result.Rules, &dnsfilter.ResultRule{}) + } + + if s, ok := vToken.(string); ok { + ent.Result.Rules[i].Text = s + } + default: + // Go on. + } +} + +func decodeResultRules(dec *json.Decoder, ent *logEntry) { + for { + delimToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultRules err: %s", err) + } + + return + } + + if d, ok := delimToken.(json.Delim); ok { + if d != '[' { + log.Debug("decodeResultRules: unexpected delim %q", d) + } + } else { + return + } + + i := 0 + for { + keyToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultRules err: %s", err) + } + + return + } + + if d, ok := keyToken.(json.Delim); ok { + if d == '}' { + i++ + } else if d == ']' { + return + } + + continue + } + + key, ok := keyToken.(string) + if !ok { + log.Debug("decodeResultRules: keyToken is %T (%[1]v) and not string", keyToken) + + return + } + + decodeResultRuleKey(key, i, dec, ent) + } + } +} + +func decodeResultReverseHosts(dec *json.Decoder, ent *logEntry) { + for { + itemToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultReverseHosts err: %s", err) + } + + return + } + + switch v := itemToken.(type) { + case json.Delim: + if v == '[' { + continue + } else if v == ']' { + return + } + + log.Debug("decodeResultReverseHosts: unexpected delim %q", v) + + return + case string: + ent.Result.ReverseHosts = append(ent.Result.ReverseHosts, v) + default: + continue + } + } +} + +func decodeResultIPList(dec *json.Decoder, ent *logEntry) { + for { + itemToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultIPList err: %s", err) + } + + return + } + + switch v := itemToken.(type) { + case json.Delim: + if v == '[' { + continue + } else if v == ']' { + return + } + + log.Debug("decodeResultIPList: unexpected delim %q", v) + + return + case string: + ip := net.ParseIP(v) + if ip != nil { + ent.Result.IPList = append(ent.Result.IPList, ip) + } + default: + continue + } + } +} + +func decodeResultDNSRewriteResult(dec *json.Decoder, ent *logEntry) { + for { + keyToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultDNSRewriteResult err: %s", err) + } + + return + } + + if d, ok := keyToken.(json.Delim); ok { + if d == '}' { + return + } + + continue + } + + key, ok := keyToken.(string) + if !ok { + log.Debug("decodeResultDNSRewriteResult: keyToken is %T (%[1]v) and not string", keyToken) + + return + } + + // TODO(a.garipov): Refactor this into a separate + // function à la decodeResultRuleKey if we keep this + // code for a longer time than planned. + switch key { + case "RCode": + vToken, err := dec.Token() + if err != nil { + if err != io.EOF { + log.Debug("decodeResultDNSRewriteResult err: %s", err) + } + + return + } + + if ent.Result.DNSRewriteResult == nil { + ent.Result.DNSRewriteResult = &dnsfilter.DNSRewriteResult{} + } + + if n, ok := vToken.(json.Number); ok { + rcode64, _ := n.Int64() + ent.Result.DNSRewriteResult.RCode = rules.RCode(rcode64) + } + + continue + case "Response": + if ent.Result.DNSRewriteResult == nil { + ent.Result.DNSRewriteResult = &dnsfilter.DNSRewriteResult{} + } + + if ent.Result.DNSRewriteResult.Response == nil { + ent.Result.DNSRewriteResult.Response = dnsfilter.DNSRewriteResultResponse{} + } + + // TODO(a.garipov): I give up. This whole file + // is a mess. Luckily, we can assume that this + // field is relatively rare and just use the + // normal decoding and correct the values. + err = dec.Decode(&ent.Result.DNSRewriteResult.Response) + if err != nil { + log.Debug("decodeResultDNSRewriteResult response err: %s", err) + } + + for rrType, rrValues := range ent.Result.DNSRewriteResult.Response { + switch rrType { + case dns.TypeA, dns.TypeAAAA: + for i, v := range rrValues { + s, _ := v.(string) + rrValues[i] = net.ParseIP(s) + } + default: + // Go on. + } + } + + continue + default: + // Go on. + } + } } func decodeResult(dec *json.Decoder, ent *logEntry) { @@ -200,6 +475,27 @@ func decodeResult(dec *json.Decoder, ent *logEntry) { return } + switch key { + case "ReverseHosts": + decodeResultReverseHosts(dec, ent) + + continue + case "IPList": + decodeResultIPList(dec, ent) + + continue + case "Rules": + decodeResultRules(dec, ent) + + continue + case "DNSRewriteResult": + decodeResultDNSRewriteResult(dec, ent) + + continue + default: + // Go on. + } + handler, ok := resultHandlers[key] if !ok { continue diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go index d9cbd600..ffcf94dc 100644 --- a/internal/querylog/decode_test.go +++ b/internal/querylog/decode_test.go @@ -2,95 +2,181 @@ package querylog import ( "bytes" + "encoding/base64" + "net" "strings" "testing" + "time" + "github.com/AdguardTeam/AdGuardHome/internal/dnsfilter" "github.com/AdguardTeam/AdGuardHome/internal/testutil" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/urlfilter/rules" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" ) -func TestDecode_decodeQueryLog(t *testing.T) { +func TestDecodeLogEntry(t *testing.T) { logOutput := &bytes.Buffer{} testutil.ReplaceLogWriter(t, logOutput) testutil.ReplaceLogLevel(t, log.DEBUG) + t.Run("success", func(t *testing.T) { + const ansStr = `Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==` + const data = `{"IP":"127.0.0.1",` + + `"T":"2020-11-25T18:55:56.519796+03:00",` + + `"QH":"an.yandex.ru",` + + `"QT":"A",` + + `"QC":"IN",` + + `"CP":"",` + + `"Answer":"` + ansStr + `",` + + `"Result":{` + + `"IsFiltered":true,` + + `"Reason":3,` + + `"ReverseHosts":["example.net"],` + + `"IPList":["127.0.0.2"],` + + `"Rules":[{"FilterListID":42,"Text":"||an.yandex.ru","IP":"127.0.0.2"},` + + `{"FilterListID":43,"Text":"||an2.yandex.ru","IP":"127.0.0.3"}],` + + `"CanonName":"example.com",` + + `"ServiceName":"example.org",` + + `"DNSRewriteResult":{"RCode":0,"Response":{"1":["127.0.0.2"]}}},` + + `"Elapsed":837429}` + + ans, err := base64.StdEncoding.DecodeString(ansStr) + assert.Nil(t, err) + + want := &logEntry{ + IP: "127.0.0.1", + Time: time.Date(2020, 11, 25, 15, 55, 56, 519796000, time.UTC), + QHost: "an.yandex.ru", + QType: "A", + QClass: "IN", + ClientProto: "", + Answer: ans, + Result: dnsfilter.Result{ + IsFiltered: true, + Reason: dnsfilter.FilteredBlockList, + ReverseHosts: []string{"example.net"}, + IPList: []net.IP{net.IPv4(127, 0, 0, 2)}, + Rules: []*dnsfilter.ResultRule{{ + FilterListID: 42, + Text: "||an.yandex.ru", + IP: net.IPv4(127, 0, 0, 2), + }, { + FilterListID: 43, + Text: "||an2.yandex.ru", + IP: net.IPv4(127, 0, 0, 3), + }}, + CanonName: "example.com", + ServiceName: "example.org", + DNSRewriteResult: &dnsfilter.DNSRewriteResult{ + RCode: dns.RcodeSuccess, + Response: dnsfilter.DNSRewriteResultResponse{ + dns.TypeA: []rules.RRValue{net.IPv4(127, 0, 0, 2)}, + }, + }, + }, + Elapsed: 837429, + } + + got := &logEntry{} + decodeLogEntry(got, data) + + s := logOutput.String() + assert.Equal(t, "", s) + + // Correct for time zones. + got.Time = got.Time.UTC() + assert.Equal(t, want, got) + }) + testCases := []struct { name string log string want string }{{ - name: "all_right", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + name: "all_right_old_rule", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1,"ReverseHosts":["example.com"],"IPList":["127.0.0.1"]},"Elapsed":837429}`, + want: "", }, { - name: "bad_filter_id", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`, + name: "bad_filter_id_old_rule", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"FilterID":1.5},"Elapsed":837429}`, want: "decodeResult handler err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n", }, { name: "bad_is_filtered", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3},"Elapsed":837429}`, want: "decodeLogEntry err: invalid character 'o' in literal true (expecting 'u')\n", }, { name: "bad_elapsed", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":-1}`, + want: "", }, { name: "bad_ip", - log: `{"IP":127001,"T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":127001,"T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, + want: "", }, { name: "bad_time", - log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, want: "decodeLogEntry handler err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n", }, { name: "bad_host", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, + want: "", }, { name: "bad_type", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":true,"QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":true,"QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, + want: "", }, { name: "bad_class", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":false,"CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":false,"CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, + want: "", }, { name: "bad_client_proto", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":8,"Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":8,"Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, + want: "", }, { name: "very_bad_client_proto", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, want: "decodeLogEntry handler err: invalid client proto: \"dog\"\n", }, { name: "bad_answer", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, + want: "", }, { name: "very_bad_answer", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3},"Elapsed":837429}`, want: "decodeLogEntry handler err: illegal base64 data at input byte 61\n", }, { name: "bad_rule", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false},"Elapsed":837429}`, + want: "", }, { name: "bad_reason", - log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":true,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`, - want: "default", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":true},"Elapsed":837429}`, + want: "", + }, { + name: "bad_reverse_hosts", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"ReverseHosts":[{}]},"Elapsed":837429}`, + want: "decodeResultReverseHosts: unexpected delim \"{\"\n", + }, { + name: "bad_ip_list", + log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"ReverseHosts":["example.net"],"IPList":[{}]},"Elapsed":837429}`, + want: "decodeResultIPList: unexpected delim \"{\"\n", }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - _, err := logOutput.Write([]byte("default")) - assert.Nil(t, err) - l := &logEntry{} decodeLogEntry(l, tc.log) - assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), "%q\ndoes not end with\n%q", logOutput.String(), tc.want) + s := logOutput.String() + if tc.want == "" { + assert.Equal(t, "", s) + } else { + assert.True(t, strings.HasSuffix(s, tc.want), + "got %q", s) + } logOutput.Reset() }) diff --git a/internal/querylog/searchcriteria.go b/internal/querylog/searchcriteria.go index 52b76459..b98e0838 100644 --- a/internal/querylog/searchcriteria.go +++ b/internal/querylog/searchcriteria.go @@ -115,14 +115,14 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { case filteringStatusFiltered: return res.IsFiltered || res.Reason.In( - dnsfilter.NotFilteredWhiteList, + dnsfilter.NotFilteredAllowList, dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts, ) case filteringStatusBlocked: return res.IsFiltered && - res.Reason.In(dnsfilter.FilteredBlackList, dnsfilter.FilteredBlockedService) + res.Reason.In(dnsfilter.FilteredBlockList, dnsfilter.FilteredBlockedService) case filteringStatusBlockedService: return res.IsFiltered && res.Reason == dnsfilter.FilteredBlockedService @@ -134,7 +134,7 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { return res.IsFiltered && res.Reason == dnsfilter.FilteredSafeBrowsing case filteringStatusWhitelisted: - return res.Reason == dnsfilter.NotFilteredWhiteList + return res.Reason == dnsfilter.NotFilteredAllowList case filteringStatusRewritten: return res.Reason.In(dnsfilter.ReasonRewrite, dnsfilter.RewriteAutoHosts) @@ -144,9 +144,9 @@ func (c *searchCriteria) ctFilteringStatusCase(res dnsfilter.Result) bool { case filteringStatusProcessed: return !res.Reason.In( - dnsfilter.FilteredBlackList, + dnsfilter.FilteredBlockList, dnsfilter.FilteredBlockedService, - dnsfilter.NotFilteredWhiteList, + dnsfilter.NotFilteredAllowList, ) default: diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md index f40dd490..076d9896 100644 --- a/openapi/CHANGELOG.md +++ b/openapi/CHANGELOG.md @@ -4,15 +4,21 @@ ## v0.105: API changes +### New `"reason"` in `GET /filtering/check_host` and `GET /querylog` + +* The new `DNSRewriteRule` reason is added to `GET /filtering/check_host` and + `GET /querylog`. + +* Also, the reason which was incorrectly documented as `"ReasonRewrite"` is now + correctly documented as `"Rewrite"`, and the previously undocumented + `"RewriteEtcHosts"` is now documented as well. + ### Multiple matched rules in `GET /filtering/check_host` and `GET /querylog` - - * The properties `rule` and `filter_id` are now deprecated. API users should - inspect the newly-added `rules` object array instead. Currently, it's either - empty or contains one object, which contains the same things as the old two - properties did, but under more correct names: + inspect the newly-added `rules` object array instead. For most rules, it's + either empty or contains one object, which contains the same things as the old + two properties did, but under more correct names: ```js { @@ -30,6 +36,30 @@ checked in. --> } ``` + For `$dnsrewrite` rules, they contain all rules that contributed to the + result. For example, if you have the following filtering rules: + + ``` + ||example.com^$dnsrewrite=127.0.0.1 + ||example.com^$dnsrewrite=127.0.0.2 + ``` + + The `"rules"` will be something like: + + ```js + { + // … + + "rules": [{ + "text": "||example.com^$dnsrewrite=127.0.0.1", + "filter_list_id": 0 + }, { + "text": "||example.com^$dnsrewrite=127.0.0.2", + "filter_list_id": 0 + }] + } + ``` + The old fields will be removed in v0.106.0. ## v0.103: API changes diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 17e9f3f5..61db836e 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -523,7 +523,7 @@ 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 + you like, it wont create unnecessary 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 @@ -1246,7 +1246,7 @@ 'properties': 'reason': 'type': 'string' - 'description': 'DNS filter status' + 'description': 'Request filtering status.' 'enum': - 'NotFilteredNotFound' - 'NotFilteredWhiteList' @@ -1257,7 +1257,9 @@ - 'FilteredInvalid' - 'FilteredSafeSearch' - 'FilteredBlockedService' - - 'ReasonRewrite' + - 'Rewrite' + - 'RewriteEtcHosts' + - 'DNSRewriteRule' 'filter_id': 'deprecated': true 'description': > @@ -1284,12 +1286,12 @@ 'description': 'Set if reason=FilteredBlockedService' 'cname': 'type': 'string' - 'description': 'Set if reason=ReasonRewrite' + 'description': 'Set if reason=Rewrite' 'ip_addrs': 'type': 'array' 'items': 'type': 'string' - 'description': 'Set if reason=ReasonRewrite' + 'description': 'Set if reason=Rewrite' 'FilterRefreshResponse': 'type': 'object' 'description': '/filtering/refresh response data' @@ -1648,7 +1650,7 @@ '$ref': '#/components/schemas/ResultRule' 'reason': 'type': 'string' - 'description': 'DNS filter status' + 'description': 'Request filtering status.' 'enum': - 'NotFilteredNotFound' - 'NotFilteredWhiteList' @@ -1659,7 +1661,9 @@ - 'FilteredInvalid' - 'FilteredSafeSearch' - 'FilteredBlockedService' - - 'ReasonRewrite' + - 'Rewrite' + - 'RewriteEtcHosts' + - 'DNSRewriteRule' 'service_name': 'type': 'string' 'description': 'Set if reason=FilteredBlockedService' diff --git a/scripts/go-lint.sh b/scripts/go-lint.sh index c133379b..ca30e1e7 100644 --- a/scripts/go-lint.sh +++ b/scripts/go-lint.sh @@ -95,7 +95,7 @@ ineffassign . unparam ./... -misspell --error ./... +git ls-files -- '*.go' '*.md' '*.yaml' '*.yml' | xargs misspell --error looppointer ./... diff --git a/scripts/translations/README.md b/scripts/translations/README.md index 2c8e5636..3a5e336c 100644 --- a/scripts/translations/README.md +++ b/scripts/translations/README.md @@ -1,4 +1,4 @@ -## Twosky intergration script +## Twosky integration script ### Usage