Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69713f34b2 | ||
|
|
55801597c6 | ||
|
|
ff3cc98d46 | ||
|
|
79489796ae | ||
|
|
8e495494fd | ||
|
|
1abb716bb6 | ||
|
|
3f012dd7a3 | ||
|
|
bf6bab7963 | ||
|
|
9db10f56ef | ||
|
|
3b91e351b7 | ||
|
|
657937d272 | ||
|
|
d294fbab15 | ||
|
|
cfbf5624e1 | ||
|
|
c833e9a1a8 | ||
|
|
f1b261163b | ||
|
|
4553240601 | ||
|
|
007a4536c7 | ||
|
|
31cf5d4a5a | ||
|
|
3e3bd05c79 | ||
|
|
20deaf2950 | ||
|
|
680aef62ee | ||
|
|
f5eb4887a7 | ||
|
|
dc3452c5b7 | ||
|
|
a67efd1ad1 |
@@ -1,7 +1,6 @@
|
|||||||
.devcontainer
|
.devcontainer
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.vscode
|
|
||||||
cmd
|
cmd
|
||||||
!cmd/gluetun
|
!cmd/gluetun
|
||||||
doc
|
doc
|
||||||
|
|||||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -7,7 +7,7 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
|
|||||||
1. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
|
1. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
|
||||||
1. Create a new branch `git checkout -b my-branch-name`
|
1. Create a new branch `git checkout -b my-branch-name`
|
||||||
1. Modify the code
|
1. Modify the code
|
||||||
1. Ensure the docker build succeeds `docker build .`
|
1. Ensure the docker build succeeds `docker build .` (you might need `export DOCKER_BUILDKIT=1`)
|
||||||
1. Commit your modifications
|
1. Commit your modifications
|
||||||
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)
|
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)
|
||||||
|
|
||||||
|
|||||||
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Maintain dependencies for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
52
.github/workflows/branch.yml
vendored
Normal file
52
.github/workflows/branch.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: branch
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
- "!master"
|
||||||
|
paths:
|
||||||
|
- .github/workflows/branch.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target test .
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test, lint]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Dockerhub login
|
||||||
|
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||||
|
- name: Docker build
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
|
||||||
|
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||||
|
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||||
|
--build-arg VERSION="branch-${GITHUB_REF##*/}" \
|
||||||
|
-t qmcgaw/gluetun:branch-${GITHUB_REF##*/} \
|
||||||
|
--push \
|
||||||
|
.
|
||||||
21
.github/workflows/build.yml
vendored
21
.github/workflows/build.yml
vendored
@@ -1,21 +0,0 @@
|
|||||||
name: Docker build
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
paths:
|
|
||||||
- .github/workflows/build.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Build image
|
|
||||||
run: docker build .
|
|
||||||
39
.github/workflows/buildx-branch.yml
vendored
39
.github/workflows/buildx-branch.yml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Buildx branch
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "*"
|
|
||||||
- "*/*"
|
|
||||||
- "!master"
|
|
||||||
paths:
|
|
||||||
- .github/workflows/buildx-branch.yml
|
|
||||||
- cmd/**
|
|
||||||
- internal/**
|
|
||||||
- .dockerignore
|
|
||||||
- .golangci.yml
|
|
||||||
- Dockerfile
|
|
||||||
- go.mod
|
|
||||||
- go.sum
|
|
||||||
jobs:
|
|
||||||
buildx:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Buildx setup
|
|
||||||
uses: crazy-max/ghaction-docker-buildx@v3
|
|
||||||
- name: Dockerhub login
|
|
||||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
|
||||||
- name: Run Buildx
|
|
||||||
run: |
|
|
||||||
docker buildx build \
|
|
||||||
--progress plain \
|
|
||||||
--platform=linux/amd64 \
|
|
||||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
|
||||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
|
||||||
--build-arg VERSION=${GITHUB_REF##*/} \
|
|
||||||
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
|
|
||||||
-t qmcgaw/gluetun:${GITHUB_REF##*/} \
|
|
||||||
--push \
|
|
||||||
.
|
|
||||||
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I=
|
|
||||||
continue-on-error: true
|
|
||||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Labeler
|
- name: Labeler
|
||||||
if: success()
|
if: success()
|
||||||
uses: crazy-max/ghaction-github-labeler@v1
|
uses: crazy-max/ghaction-github-labeler@v3.1.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
name: Buildx latest
|
name: latest
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/buildx-latest.yml
|
- .github/workflows/latest.yml
|
||||||
- cmd/**
|
- cmd/**
|
||||||
- internal/**
|
- internal/**
|
||||||
- .dockerignore
|
- .dockerignore
|
||||||
@@ -11,20 +11,37 @@ on:
|
|||||||
- Dockerfile
|
- Dockerfile
|
||||||
- go.mod
|
- go.mod
|
||||||
- go.sum
|
- go.sum
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
buildx:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Buildx setup
|
- env:
|
||||||
uses: crazy-max/ghaction-docker-buildx@v3
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target test .
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test, lint]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
- name: Dockerhub login
|
- name: Dockerhub login
|
||||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||||
- name: Run Buildx
|
- name: Docker buildx
|
||||||
run: |
|
run: |
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--progress plain \
|
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
|
||||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 \
|
|
||||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||||
--build-arg VERSION=latest \
|
--build-arg VERSION=latest \
|
||||||
40
.github/workflows/pr.yml
vendored
Normal file
40
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
name: pull request
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/pr.yml
|
||||||
|
- cmd/**
|
||||||
|
- internal/**
|
||||||
|
- .dockerignore
|
||||||
|
- .golangci.yml
|
||||||
|
- Dockerfile
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target test .
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test, lint]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Docker build
|
||||||
|
env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build .
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
name: Buildx release
|
name: release
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/buildx-release.yml
|
- .github/workflows/release.yml
|
||||||
- cmd/**
|
- cmd/**
|
||||||
- internal/**
|
- internal/**
|
||||||
- .dockerignore
|
- .dockerignore
|
||||||
@@ -11,20 +11,37 @@ on:
|
|||||||
- Dockerfile
|
- Dockerfile
|
||||||
- go.mod
|
- go.mod
|
||||||
- go.sum
|
- go.sum
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
buildx:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Buildx setup
|
- env:
|
||||||
uses: crazy-max/ghaction-docker-buildx@v3
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target test .
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- env:
|
||||||
|
DOCKER_BUILDKIT: "1"
|
||||||
|
run: docker build --target lint .
|
||||||
|
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [test, lint]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: docker/setup-qemu-action@v1
|
||||||
|
- uses: docker/setup-buildx-action@v1
|
||||||
- name: Dockerhub login
|
- name: Dockerhub login
|
||||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||||
- name: Run Buildx
|
- name: Docker buildx
|
||||||
run: |
|
run: |
|
||||||
docker buildx build \
|
docker buildx build \
|
||||||
--progress plain \
|
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
|
||||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 \
|
|
||||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||||
--build-arg VERSION=${GITHUB_REF##*/} \
|
--build-arg VERSION=${GITHUB_REF##*/} \
|
||||||
@@ -32,5 +49,3 @@ jobs:
|
|||||||
-t qmcgaw/gluetun:${GITHUB_REF##*/} \
|
-t qmcgaw/gluetun:${GITHUB_REF##*/} \
|
||||||
--push \
|
--push \
|
||||||
.
|
.
|
||||||
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I=
|
|
||||||
continue-on-error: true
|
|
||||||
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"shardulm94.trailing-spaces",
|
|
||||||
"ms-azuretools.vscode-docker",
|
|
||||||
"davidanson.vscode-markdownlint",
|
|
||||||
"IBM.output-colorizer",
|
|
||||||
"golang.go"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
91
.vscode/settings.json
vendored
91
.vscode/settings.json
vendored
@@ -1,91 +0,0 @@
|
|||||||
{
|
|
||||||
// General settings
|
|
||||||
"files.eol": "\n",
|
|
||||||
// Docker
|
|
||||||
"remote.extensionKind": {
|
|
||||||
"ms-azuretools.vscode-docker": "workspace"
|
|
||||||
},
|
|
||||||
// Golang general settings
|
|
||||||
"go.useLanguageServer": true,
|
|
||||||
"go.autocompleteUnimportedPackages": true,
|
|
||||||
"go.gotoSymbol.includeImports": true,
|
|
||||||
"go.gotoSymbol.includeGoroot": true,
|
|
||||||
"gopls": {
|
|
||||||
"completeUnimported": true,
|
|
||||||
"deepCompletion": true,
|
|
||||||
"usePlaceholders": false
|
|
||||||
},
|
|
||||||
"go.lintTool": "golangci-lint",
|
|
||||||
"go.lintFlags": [
|
|
||||||
"--fast",
|
|
||||||
"--enable",
|
|
||||||
"rowserrcheck",
|
|
||||||
"--enable",
|
|
||||||
"bodyclose",
|
|
||||||
"--enable",
|
|
||||||
"dogsled",
|
|
||||||
"--enable",
|
|
||||||
"dupl",
|
|
||||||
"--enable",
|
|
||||||
"gochecknoglobals",
|
|
||||||
"--enable",
|
|
||||||
"gochecknoinits",
|
|
||||||
"--enable",
|
|
||||||
"gocognit",
|
|
||||||
"--enable",
|
|
||||||
"goconst",
|
|
||||||
"--enable",
|
|
||||||
"gocritic",
|
|
||||||
"--enable",
|
|
||||||
"gocyclo",
|
|
||||||
"--enable",
|
|
||||||
"goimports",
|
|
||||||
"--enable",
|
|
||||||
"golint",
|
|
||||||
"--enable",
|
|
||||||
"gosec",
|
|
||||||
"--enable",
|
|
||||||
"interfacer",
|
|
||||||
"--enable",
|
|
||||||
"maligned",
|
|
||||||
"--enable",
|
|
||||||
"misspell",
|
|
||||||
"--enable",
|
|
||||||
"nakedret",
|
|
||||||
"--enable",
|
|
||||||
"prealloc",
|
|
||||||
"--enable",
|
|
||||||
"scopelint",
|
|
||||||
"--enable",
|
|
||||||
"unconvert",
|
|
||||||
"--enable",
|
|
||||||
"unparam",
|
|
||||||
"--enable",
|
|
||||||
"whitespace"
|
|
||||||
],
|
|
||||||
// Golang on save
|
|
||||||
"go.buildOnSave": "workspace",
|
|
||||||
"go.lintOnSave": "workspace",
|
|
||||||
"go.vetOnSave": "workspace",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"[go]": {
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Golang testing
|
|
||||||
"go.toolsEnvVars": {
|
|
||||||
"GOFLAGS": "-tags="
|
|
||||||
},
|
|
||||||
"gopls.env": {
|
|
||||||
"GOFLAGS": "-tags="
|
|
||||||
},
|
|
||||||
"go.testEnvVars": {},
|
|
||||||
"go.testFlags": [
|
|
||||||
"-v",
|
|
||||||
// "-race"
|
|
||||||
],
|
|
||||||
"go.testTimeout": "600s",
|
|
||||||
"go.coverOnSingleTestFile": true,
|
|
||||||
"go.coverOnSingleTest": true
|
|
||||||
}
|
|
||||||
35
Dockerfile
35
Dockerfile
@@ -1,27 +1,42 @@
|
|||||||
ARG ALPINE_VERSION=3.12
|
ARG ALPINE_VERSION=3.12
|
||||||
ARG GO_VERSION=1.15
|
ARG GO_VERSION=1.15
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||||
RUN apk --update add git
|
RUN apk --update add git
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.34.1
|
|
||||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s ${GOLANGCI_LINT_VERSION}
|
|
||||||
WORKDIR /tmp/gobuild
|
WORKDIR /tmp/gobuild
|
||||||
COPY .golangci.yml .
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
COPY cmd/ ./cmd/
|
||||||
|
COPY internal/ ./internal/
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM base AS test
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
RUN apk --update add g++
|
||||||
|
RUN go test -race ./...
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM base AS lint
|
||||||
|
ARG GOLANGCI_LINT_VERSION=v1.34.1
|
||||||
|
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
|
||||||
|
sh -s -- -b /usr/local/bin ${GOLANGCI_LINT_VERSION}
|
||||||
|
COPY .golangci.yml ./
|
||||||
|
RUN golangci-lint run --timeout=10m
|
||||||
|
|
||||||
|
FROM --platform=$BUILDPLATFORM base AS build
|
||||||
|
COPY --from=qmcgaw/xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
|
ARG TARGETPLATFORM
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
ARG BUILD_DATE="an unknown date"
|
ARG BUILD_DATE="an unknown date"
|
||||||
ARG COMMIT=unknown
|
ARG COMMIT=unknown
|
||||||
COPY cmd/gluetun/main.go .
|
COPY cmd/ ./cmd/
|
||||||
COPY internal/ ./internal/
|
COPY internal/ ./internal/
|
||||||
RUN go test ./...
|
RUN GOARCH="$(echo ${TARGETPLATFORM} | xcputranslate -field arch)" \
|
||||||
RUN golangci-lint run --timeout=10m
|
GOARM="$(echo ${TARGETPLATFORM} | xcputranslate -field arm)" \
|
||||||
RUN go build -trimpath -ldflags="-s -w \
|
go build -trimpath -ldflags="-s -w \
|
||||||
-X 'main.version=$VERSION' \
|
-X 'main.version=$VERSION' \
|
||||||
-X 'main.buildDate=$BUILD_DATE' \
|
-X 'main.buildDate=$BUILD_DATE' \
|
||||||
-X 'main.commit=$COMMIT' \
|
-X 'main.commit=$COMMIT' \
|
||||||
" -o entrypoint main.go
|
" -o entrypoint cmd/gluetun/main.go
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION}
|
FROM alpine:${ALPINE_VERSION}
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
@@ -126,4 +141,4 @@ RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables i
|
|||||||
mkdir /gluetun
|
mkdir /gluetun
|
||||||
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
||||||
ENV GODEBUG=x509ignoreCN=0
|
ENV GODEBUG=x509ignoreCN=0
|
||||||
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint
|
COPY --from=build /tmp/gobuild/entrypoint /entrypoint
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
|||||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||||
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7 🎆
|
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even s390x as well as ppc64le 🎆
|
||||||
- VPN server side port forwarding for Private Internet Access and Vyprvpn
|
- VPN server side port forwarding for Private Internet Access and Vyprvpn
|
||||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||||
- Subprograms all drop root privileges once launched
|
- Subprograms all drop root privileges once launched
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
@@ -36,6 +37,7 @@ import (
|
|||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
"github.com/qdm12/golibs/os"
|
"github.com/qdm12/golibs/os"
|
||||||
"github.com/qdm12/golibs/os/user"
|
"github.com/qdm12/golibs/os/user"
|
||||||
|
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
@@ -51,49 +53,92 @@ func main() {
|
|||||||
Commit: commit,
|
Commit: commit,
|
||||||
BuildDate: buildDate,
|
BuildDate: buildDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
nativeos.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
args := nativeos.Args
|
args := nativeos.Args
|
||||||
os := os.New()
|
os := os.New()
|
||||||
osUser := user.New()
|
osUser := user.New()
|
||||||
unix := unix.New()
|
unix := unix.New()
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
nativeos.Exit(_main(ctx, buildInfo, args, os, osUser, unix, cli))
|
|
||||||
|
errorCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
|
||||||
|
}()
|
||||||
|
|
||||||
|
signalsCh := make(chan nativeos.Signal, 1)
|
||||||
|
signal.Notify(signalsCh,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
nativeos.Interrupt,
|
||||||
|
)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case signal := <-signalsCh:
|
||||||
|
logger.Warn("Caught OS signal %s, shutting down", signal)
|
||||||
|
case err := <-errorCh:
|
||||||
|
close(errorCh)
|
||||||
|
if err == nil { // expected exit such as healthcheck
|
||||||
|
nativeos.Exit(0)
|
||||||
|
}
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
const shutdownGracePeriod = 5 * time.Second
|
||||||
|
timer := time.NewTimer(shutdownGracePeriod)
|
||||||
|
select {
|
||||||
|
case <-errorCh:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
logger.Info("Shutdown successful")
|
||||||
|
case <-timer.C:
|
||||||
|
logger.Warn("Shutdown timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeos.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gocognit,gocyclo
|
//nolint:gocognit,gocyclo
|
||||||
func _main(background context.Context, buildInfo models.BuildInformation,
|
func _main(background context.Context, buildInfo models.BuildInformation,
|
||||||
args []string, os os.OS, osUser user.OSUser, unix unix.Unix,
|
args []string, logger logging.Logger, os os.OS, osUser user.OSUser, unix unix.Unix,
|
||||||
cli cli.CLI) int {
|
cli cli.CLI) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
var err error
|
|
||||||
switch args[1] {
|
switch args[1] {
|
||||||
case "healthcheck":
|
case "healthcheck":
|
||||||
err = cli.HealthCheck(background)
|
return cli.HealthCheck(background)
|
||||||
case "clientkey":
|
case "clientkey":
|
||||||
err = cli.ClientKey(args[2:], os.OpenFile)
|
return cli.ClientKey(args[2:], os.OpenFile)
|
||||||
case "openvpnconfig":
|
case "openvpnconfig":
|
||||||
err = cli.OpenvpnConfig(os)
|
return cli.OpenvpnConfig(os)
|
||||||
case "update":
|
case "update":
|
||||||
err = cli.Update(args[2:], os)
|
return cli.Update(args[2:], os)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("command %q is unknown", args[1])
|
return fmt.Errorf("command %q is unknown", args[1])
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(background)
|
ctx, cancel := context.WithCancel(background)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
logger := createLogger()
|
|
||||||
|
|
||||||
const clientTimeout = 15 * time.Second
|
const clientTimeout = 15 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
// Create configurators
|
// Create configurators
|
||||||
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
|
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
|
||||||
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
|
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
|
||||||
dnsConf := dns.NewConfigurator(logger, httpClient, os.OpenFile)
|
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||||
|
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||||
|
dnsConf := unbound.NewConfigurator(logger, os.OpenFile, dnsCrypto,
|
||||||
|
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
||||||
routingConf := routing.NewRouting(logger)
|
routingConf := routing.NewRouting(logger)
|
||||||
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
|
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
|
||||||
streamMerger := command.NewStreamMerger()
|
streamMerger := command.NewStreamMerger()
|
||||||
@@ -109,26 +154,22 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
allSettings, err := settings.GetAllSettings(paramsReader)
|
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
logger.Info(allSettings.String())
|
logger.Info(allSettings.String())
|
||||||
|
|
||||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll("/gluetun", 0644); err != nil {
|
if err := os.MkdirAll("/gluetun", 0644); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
storage := storage.New(logger, os, constants.ServersData)
|
storage := storage.New(logger, os, constants.ServersData)
|
||||||
allServers, err := storage.SyncServers(constants.GetAllServers())
|
allServers, err := storage.SyncServers(constants.GetAllServers())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should never change
|
// Should never change
|
||||||
@@ -137,16 +178,14 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
const defaultUsername = "nonrootuser"
|
const defaultUsername = "nonrootuser"
|
||||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
if nonRootUsername != defaultUsername {
|
if nonRootUsername != defaultUsername {
|
||||||
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
|
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if allSettings.Firewall.Debug {
|
if allSettings.Firewall.Debug {
|
||||||
@@ -156,27 +195,23 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localSubnet, err := routingConf.LocalSubnet()
|
localSubnet, err := routingConf.LocalSubnet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultIP, err := routingConf.DefaultIP()
|
defaultIP, err := routingConf.DefaultIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
routingConf.SetVerbose(false)
|
routingConf.SetVerbose(false)
|
||||||
@@ -186,56 +221,50 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
|
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
|
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ovpnConf.CheckTUN(); err != nil {
|
if err := ovpnConf.CheckTUN(); err != nil {
|
||||||
logger.Warn(err)
|
logger.Warn(err)
|
||||||
err = ovpnConf.CreateTUN()
|
err = ovpnConf.CreateTUN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelReadyCh, dnsReadyCh := make(chan struct{}), make(chan struct{})
|
tunnelReadyCh := make(chan struct{})
|
||||||
signalTunnelReady := func() { tunnelReadyCh <- struct{}{} }
|
dnsReadyCh := make(chan struct{})
|
||||||
signalDNSReady := func() { dnsReadyCh <- struct{}{} }
|
|
||||||
defer close(tunnelReadyCh)
|
defer close(tunnelReadyCh)
|
||||||
defer close(dnsReadyCh)
|
defer close(dnsReadyCh)
|
||||||
|
|
||||||
if allSettings.Firewall.Enabled {
|
if allSettings.Firewall.Enabled {
|
||||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
||||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range allSettings.Firewall.InputPorts {
|
for _, port := range allSettings.Firewall.InputPorts {
|
||||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
} // TODO move inside firewall?
|
} // TODO move inside firewall?
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
|
wg.Add(1)
|
||||||
|
go collectStreamLines(ctx, wg, streamMerger, logger, tunnelReadyCh)
|
||||||
|
|
||||||
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
|
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
|
||||||
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, streamMerger, cancel)
|
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, streamMerger, cancel)
|
||||||
@@ -249,10 +278,11 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||||
go updaterLooper.Run(ctx, wg)
|
go updaterLooper.Run(ctx, wg)
|
||||||
|
|
||||||
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, logger, streamMerger, nonRootUsername, puid, pgid)
|
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, httpClient,
|
||||||
|
logger, streamMerger, nonRootUsername, puid, pgid)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
||||||
go unboundLooper.Run(ctx, wg, signalDNSReady)
|
go unboundLooper.Run(ctx, wg, dnsReadyCh)
|
||||||
|
|
||||||
publicIPLooper := publicip.NewLooper(
|
publicIPLooper := publicip.NewLooper(
|
||||||
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
|
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
|
||||||
@@ -290,55 +320,18 @@ func _main(background context.Context, buildInfo models.BuildInformation,
|
|||||||
// until openvpn is launched
|
// until openvpn is launched
|
||||||
_, _ = openvpnLooper.SetStatus(constants.Running) // TODO option to disable with variable
|
_, _ = openvpnLooper.SetStatus(constants.Running) // TODO option to disable with variable
|
||||||
|
|
||||||
signalsCh := make(chan nativeos.Signal, 1)
|
<-ctx.Done()
|
||||||
signal.Notify(signalsCh,
|
|
||||||
syscall.SIGINT,
|
|
||||||
syscall.SIGTERM,
|
|
||||||
nativeos.Interrupt,
|
|
||||||
)
|
|
||||||
shutdownErrorsCount := 0
|
|
||||||
select {
|
|
||||||
case signal := <-signalsCh:
|
|
||||||
logger.Warn("Caught OS signal %s, shutting down", signal)
|
|
||||||
cancel()
|
|
||||||
case <-ctx.Done():
|
|
||||||
logger.Warn("context canceled, shutting down")
|
|
||||||
}
|
|
||||||
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
||||||
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
||||||
if err := os.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
|
if err := os.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
shutdownErrorsCount++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const shutdownGracePeriod = 5 * time.Second
|
|
||||||
waiting, waited := context.WithTimeout(context.Background(), shutdownGracePeriod)
|
|
||||||
go func() {
|
|
||||||
defer waited()
|
|
||||||
wg.Wait()
|
|
||||||
}()
|
|
||||||
<-waiting.Done()
|
|
||||||
if waiting.Err() == context.DeadlineExceeded {
|
|
||||||
if shutdownErrorsCount > 0 {
|
|
||||||
logger.Warn("Shutdown had %d errors", shutdownErrorsCount)
|
|
||||||
}
|
|
||||||
logger.Warn("Shutdown timed out")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if shutdownErrorsCount > 0 {
|
|
||||||
logger.Warn("Shutdown had %d errors")
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
logger.Info("Shutdown successful")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func createLogger() logging.Logger {
|
wg.Wait()
|
||||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
|
||||||
if err != nil {
|
return nil
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVersions(ctx context.Context, logger logging.Logger,
|
func printVersions(ctx context.Context, logger logging.Logger,
|
||||||
@@ -356,9 +349,10 @@ func printVersions(ctx context.Context, logger logging.Logger,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:lll
|
func collectStreamLines(ctx context.Context, wg *sync.WaitGroup,
|
||||||
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
streamMerger command.StreamMerger,
|
||||||
logger logging.Logger, signalTunnelReady func()) {
|
logger logging.Logger, tunnelReadyCh chan<- struct{}) {
|
||||||
|
defer wg.Done()
|
||||||
// Blocking line merging paramsReader for openvpn and unbound
|
// Blocking line merging paramsReader for openvpn and unbound
|
||||||
logger.Info("Launching standard output merger")
|
logger.Info("Launching standard output merger")
|
||||||
streamMerger.CollectLines(ctx, func(line string) {
|
streamMerger.CollectLines(ctx, func(line string) {
|
||||||
@@ -378,10 +372,10 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(line, "Initialization Sequence Completed"):
|
case strings.Contains(line, "Initialization Sequence Completed"):
|
||||||
signalTunnelReady()
|
tunnelReadyCh <- struct{}{}
|
||||||
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"):
|
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"): //nolint:lll
|
||||||
logger.Warn("This means that either...")
|
logger.Warn("This means that either...")
|
||||||
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information")
|
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information") //nolint:lll
|
||||||
logger.Warn("2. The VPN server crashed, try changing region")
|
logger.Warn("2. The VPN server crashed, try changing region")
|
||||||
logger.Warn("3. Your Internet connection is not working, ensure it works")
|
logger.Warn("3. Your Internet connection is not working, ensure it works")
|
||||||
logger.Warn("Feel free to create an issue at https://github.com/qdm12/gluetun/issues/new/choose")
|
logger.Warn("Feel free to create an issue at https://github.com/qdm12/gluetun/issues/new/choose")
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -6,8 +6,10 @@ require (
|
|||||||
github.com/fatih/color v1.10.0
|
github.com/fatih/color v1.10.0
|
||||||
github.com/golang/mock v1.4.4
|
github.com/golang/mock v1.4.4
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||||
github.com/qdm12/golibs v0.0.0-20210102015428-6e1d159e61a3
|
github.com/qdm12/dns v1.4.0-rc3
|
||||||
|
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973
|
||||||
github.com/qdm12/ss-server v0.1.0
|
github.com/qdm12/ss-server v0.1.0
|
||||||
|
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/vishvananda/netlink v1.1.0
|
github.com/vishvananda/netlink v1.1.0
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
|
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
|
||||||
|
|||||||
43
go.sum
43
go.sum
@@ -4,16 +4,23 @@ github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVk
|
|||||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||||
@@ -44,14 +51,19 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE=
|
github.com/gotify/go-api-client/v2 v2.0.4 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE=
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
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.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
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/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
|
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
|
||||||
@@ -67,26 +79,36 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe
|
|||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
|
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
|
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
|
||||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/qdm12/golibs v0.0.0-20210102015428-6e1d159e61a3 h1:tnkjZkYZuAFNga7Wd/j3z3gPJLkv0OXow4q/YTkRdmE=
|
github.com/qdm12/dns v1.4.0-rc3 h1:pbzeygQtX1ElaAYPj0Dn9XictYZgNyJc1xS7bDMyQ6Y=
|
||||||
github.com/qdm12/golibs v0.0.0-20210102015428-6e1d159e61a3/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
github.com/qdm12/dns v1.4.0-rc3/go.mod h1:JhUKBhuDRYBUQ2XwW/jbeWx/qS0sSJjIFjGTCFGP5I8=
|
||||||
|
github.com/qdm12/golibs v0.0.0-20201227203847-2fd99ffdfdba/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||||
|
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973 h1:5YeJALmDjvg2wSi6XB8MpQQekbT/eBnwGahJrh01HHQ=
|
||||||
|
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||||
github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
|
github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
|
||||||
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
|
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
|
||||||
|
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a h1:gkyP+gMEeBgMgyRYGrVNcoy6cL1065IvXsyfB6xboIc=
|
||||||
|
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a/go.mod h1:bbJGxEYCnsA8WU4vBcXYU6mOoHyzdP458FIKP4mfLJM=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@@ -96,6 +118,8 @@ github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJ
|
|||||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||||
|
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||||
|
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||||
@@ -104,11 +128,15 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa
|
|||||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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-20190308221718-c2843e01d9a2/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||||
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
@@ -116,21 +144,28 @@ golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
||||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/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-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -139,6 +174,10 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||||
|
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||||
|
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Cloudflare is a DNS over TLS provider.
|
|
||||||
Cloudflare models.DNSProvider = "cloudflare"
|
|
||||||
// Google is a DNS over TLS provider.
|
|
||||||
Google models.DNSProvider = "google"
|
|
||||||
// Quad9 is a DNS over TLS provider.
|
|
||||||
Quad9 models.DNSProvider = "quad9"
|
|
||||||
// Quadrant is a DNS over TLS provider.
|
|
||||||
Quadrant models.DNSProvider = "quadrant"
|
|
||||||
// CleanBrowsing is a DNS over TLS provider.
|
|
||||||
CleanBrowsing models.DNSProvider = "cleanbrowsing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNSProviderMapping returns a constant mapping of dns provider name
|
|
||||||
// to their data such as IP addresses or TLS host name.
|
|
||||||
func DNSProviderMapping() map[models.DNSProvider]models.DNSProviderData {
|
|
||||||
return map[models.DNSProvider]models.DNSProviderData{
|
|
||||||
Cloudflare: {
|
|
||||||
IPs: []net.IP{
|
|
||||||
{1, 1, 1, 1},
|
|
||||||
{1, 0, 0, 1},
|
|
||||||
{0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11},
|
|
||||||
{0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x01},
|
|
||||||
},
|
|
||||||
SupportsTLS: true,
|
|
||||||
SupportsIPv6: true,
|
|
||||||
Host: models.DNSHost("cloudflare-dns.com"),
|
|
||||||
},
|
|
||||||
Google: {
|
|
||||||
IPs: []net.IP{
|
|
||||||
{8, 8, 8, 8},
|
|
||||||
{8, 8, 4, 4},
|
|
||||||
{0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x88},
|
|
||||||
{0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x44},
|
|
||||||
},
|
|
||||||
SupportsTLS: true,
|
|
||||||
SupportsIPv6: true,
|
|
||||||
Host: models.DNSHost("dns.google"),
|
|
||||||
},
|
|
||||||
Quad9: {
|
|
||||||
IPs: []net.IP{
|
|
||||||
{9, 9, 9, 9},
|
|
||||||
{149, 112, 112, 112},
|
|
||||||
{0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe},
|
|
||||||
{0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9},
|
|
||||||
},
|
|
||||||
SupportsTLS: true,
|
|
||||||
SupportsIPv6: true,
|
|
||||||
Host: models.DNSHost("dns.quad9.net"),
|
|
||||||
},
|
|
||||||
Quadrant: {
|
|
||||||
IPs: []net.IP{
|
|
||||||
{12, 159, 2, 159},
|
|
||||||
{0x20, 0x1, 0x18, 0x90, 0x14, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x59},
|
|
||||||
},
|
|
||||||
SupportsTLS: true,
|
|
||||||
SupportsIPv6: true,
|
|
||||||
Host: models.DNSHost("dns-tls.qis.io"),
|
|
||||||
},
|
|
||||||
CleanBrowsing: {
|
|
||||||
IPs: []net.IP{
|
|
||||||
{185, 228, 168, 9},
|
|
||||||
{185, 228, 169, 9},
|
|
||||||
{0x2a, 0xd, 0x2a, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
|
|
||||||
{0x2a, 0xd, 0x2a, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
|
|
||||||
},
|
|
||||||
SupportsTLS: true,
|
|
||||||
SupportsIPv6: true,
|
|
||||||
Host: models.DNSHost("security-filter-dns.cleanbrowsing.org"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block lists URLs.
|
|
||||||
//nolint:lll
|
|
||||||
const (
|
|
||||||
AdsBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated"
|
|
||||||
AdsBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated"
|
|
||||||
MaliciousBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-hostnames.updated"
|
|
||||||
MaliciousBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-ips.updated"
|
|
||||||
SurveillanceBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated"
|
|
||||||
SurveillanceBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNS certificates to fetch.
|
|
||||||
// TODO obtain from source directly, see qdm12/updated).
|
|
||||||
const (
|
|
||||||
NamedRootURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/named.root.updated"
|
|
||||||
RootKeyURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/root.key.updated"
|
|
||||||
)
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *configurator) Start(ctx context.Context, verbosityDetailsLevel uint8) (
|
|
||||||
stdout io.ReadCloser, waitFn func() error, err error) {
|
|
||||||
c.logger.Info("starting unbound")
|
|
||||||
args := []string{"-d", "-c", string(constants.UnboundConf)}
|
|
||||||
if verbosityDetailsLevel > 0 {
|
|
||||||
args = append(args, "-"+strings.Repeat("v", int(verbosityDetailsLevel)))
|
|
||||||
}
|
|
||||||
// Only logs to stderr
|
|
||||||
_, stdout, waitFn, err = c.commander.Start(ctx, "unbound", args...)
|
|
||||||
return stdout, waitFn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *configurator) Version(ctx context.Context) (version string, err error) {
|
|
||||||
output, err := c.commander.Run(ctx, "unbound", "-V")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unbound version: %w", err)
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(output, "\n") {
|
|
||||||
if strings.Contains(line, "Version ") {
|
|
||||||
words := strings.Fields(line)
|
|
||||||
const minWords = 2
|
|
||||||
if len(words) < minWords {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
version = words[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if version == "" {
|
|
||||||
return "", fmt.Errorf("unbound version was not found in %q", output)
|
|
||||||
}
|
|
||||||
return version, nil
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/command/mock_command"
|
|
||||||
"github.com/qdm12/golibs/logging/mock_logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Start(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
|
||||||
logger.EXPECT().Info("starting unbound")
|
|
||||||
commander := mock_command.NewMockCommander(mockCtrl)
|
|
||||||
commander.EXPECT().Start(context.Background(), "unbound", "-d", "-c", string(constants.UnboundConf), "-vv").
|
|
||||||
Return(nil, nil, nil, nil)
|
|
||||||
c := &configurator{commander: commander, logger: logger}
|
|
||||||
stdout, waitFn, err := c.Start(context.Background(), 2)
|
|
||||||
assert.Nil(t, stdout)
|
|
||||||
assert.Nil(t, waitFn)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Version(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tests := map[string]struct {
|
|
||||||
runOutput string
|
|
||||||
runErr error
|
|
||||||
version string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"no data": {
|
|
||||||
err: fmt.Errorf(`unbound version was not found in ""`),
|
|
||||||
},
|
|
||||||
"2 lines with version": {
|
|
||||||
runOutput: "Version \nVersion 1.0-a hello\n",
|
|
||||||
version: "1.0-a",
|
|
||||||
},
|
|
||||||
"run error": {
|
|
||||||
runErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("unbound version: error"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
commander := mock_command.NewMockCommander(mockCtrl)
|
|
||||||
commander.EXPECT().Run(context.Background(), "unbound", "-V").
|
|
||||||
Return(tc.runOutput, tc.runErr)
|
|
||||||
c := &configurator{commander: commander}
|
|
||||||
version, err := c.Version(context.Background())
|
|
||||||
if tc.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, tc.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
assert.Equal(t, tc.version, version)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *configurator) MakeUnboundConf(ctx context.Context, settings settings.DNS,
|
|
||||||
username string, puid, pgid int) (err error) {
|
|
||||||
c.logger.Info("generating Unbound configuration")
|
|
||||||
lines, warnings := generateUnboundConf(ctx, settings, username, c.client, c.logger)
|
|
||||||
for _, warning := range warnings {
|
|
||||||
c.logger.Warn(warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
const filepath = string(constants.UnboundConf)
|
|
||||||
file, err := c.openFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0400)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.WriteString(strings.Join(lines, "\n"))
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := file.Chown(puid, pgid); err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := file.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeUnboundConf generates an Unbound configuration from the user provided settings.
|
|
||||||
func generateUnboundConf(ctx context.Context, settings settings.DNS, username string,
|
|
||||||
client *http.Client, logger logging.Logger) (
|
|
||||||
lines []string, warnings []error) {
|
|
||||||
doIPv6 := "no"
|
|
||||||
if settings.IPv6 {
|
|
||||||
doIPv6 = "yes"
|
|
||||||
}
|
|
||||||
serverSection := map[string]string{
|
|
||||||
// Logging
|
|
||||||
"verbosity": fmt.Sprintf("%d", settings.VerbosityLevel),
|
|
||||||
"val-log-level": fmt.Sprintf("%d", settings.ValidationLogLevel),
|
|
||||||
"use-syslog": "no",
|
|
||||||
// Performance
|
|
||||||
"num-threads": "1",
|
|
||||||
"prefetch": "yes",
|
|
||||||
"prefetch-key": "yes",
|
|
||||||
"key-cache-size": "16m",
|
|
||||||
"key-cache-slabs": "4",
|
|
||||||
"msg-cache-size": "4m",
|
|
||||||
"msg-cache-slabs": "4",
|
|
||||||
"rrset-cache-size": "4m",
|
|
||||||
"rrset-cache-slabs": "4",
|
|
||||||
"cache-min-ttl": "3600",
|
|
||||||
"cache-max-ttl": "9000",
|
|
||||||
// Privacy
|
|
||||||
"rrset-roundrobin": "yes",
|
|
||||||
"hide-identity": "yes",
|
|
||||||
"hide-version": "yes",
|
|
||||||
// Security
|
|
||||||
"tls-cert-bundle": fmt.Sprintf("%q", constants.CACertificates),
|
|
||||||
"root-hints": fmt.Sprintf("%q", constants.RootHints),
|
|
||||||
"trust-anchor-file": fmt.Sprintf("%q", constants.RootKey),
|
|
||||||
"harden-below-nxdomain": "yes",
|
|
||||||
"harden-referral-path": "yes",
|
|
||||||
"harden-algo-downgrade": "yes",
|
|
||||||
// Network
|
|
||||||
"do-ip4": "yes",
|
|
||||||
"do-ip6": doIPv6,
|
|
||||||
"interface": "0.0.0.0",
|
|
||||||
"port": "53",
|
|
||||||
// Other
|
|
||||||
"username": fmt.Sprintf("%q", username),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block lists
|
|
||||||
hostnamesLines, ipsLines, warnings := buildBlocked(ctx, client,
|
|
||||||
settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance,
|
|
||||||
settings.AllowedHostnames, settings.PrivateAddresses,
|
|
||||||
)
|
|
||||||
logger.Info("%d hostnames blocked overall", len(hostnamesLines))
|
|
||||||
logger.Info("%d IP addresses blocked overall", len(ipsLines))
|
|
||||||
sort.Slice(hostnamesLines, func(i, j int) bool { // for unit tests really
|
|
||||||
return hostnamesLines[i] < hostnamesLines[j]
|
|
||||||
})
|
|
||||||
sort.Slice(ipsLines, func(i, j int) bool { // for unit tests really
|
|
||||||
return ipsLines[i] < ipsLines[j]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Server
|
|
||||||
lines = append(lines, "server:")
|
|
||||||
serverLines := make([]string, len(serverSection))
|
|
||||||
i := 0
|
|
||||||
for k, v := range serverSection {
|
|
||||||
serverLines[i] = " " + k + ": " + v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Slice(serverLines, func(i, j int) bool {
|
|
||||||
return serverLines[i] < serverLines[j]
|
|
||||||
})
|
|
||||||
lines = append(lines, serverLines...)
|
|
||||||
lines = append(lines, hostnamesLines...)
|
|
||||||
lines = append(lines, ipsLines...)
|
|
||||||
|
|
||||||
// Forward zone
|
|
||||||
lines = append(lines, "forward-zone:")
|
|
||||||
forwardZoneSection := map[string]string{
|
|
||||||
"name": "\".\"",
|
|
||||||
"forward-tls-upstream": "yes",
|
|
||||||
}
|
|
||||||
if settings.Caching {
|
|
||||||
forwardZoneSection["forward-no-cache"] = "no"
|
|
||||||
} else {
|
|
||||||
forwardZoneSection["forward-no-cache"] = "yes"
|
|
||||||
}
|
|
||||||
forwardZoneLines := make([]string, len(forwardZoneSection))
|
|
||||||
i = 0
|
|
||||||
for k, v := range forwardZoneSection {
|
|
||||||
forwardZoneLines[i] = " " + k + ": " + v
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Slice(forwardZoneLines, func(i, j int) bool {
|
|
||||||
return forwardZoneLines[i] < forwardZoneLines[j]
|
|
||||||
})
|
|
||||||
for _, provider := range settings.Providers {
|
|
||||||
providerData := constants.DNSProviderMapping()[provider]
|
|
||||||
for _, IP := range providerData.IPs {
|
|
||||||
forwardZoneLines = append(forwardZoneLines,
|
|
||||||
fmt.Sprintf(" forward-addr: %s@853#%s", IP, providerData.Host))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines = append(lines, forwardZoneLines...)
|
|
||||||
return lines, warnings
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBlocked(ctx context.Context, client *http.Client, blockMalicious, blockAds, blockSurveillance bool,
|
|
||||||
allowedHostnames, privateAddresses []string) (hostnamesLines, ipsLines []string, errs []error) {
|
|
||||||
chHostnames := make(chan []string)
|
|
||||||
chIPs := make(chan []string)
|
|
||||||
chErrors := make(chan []error)
|
|
||||||
go func() {
|
|
||||||
lines, errs := buildBlockedHostnames(ctx, client, blockMalicious, blockAds, blockSurveillance, allowedHostnames)
|
|
||||||
chHostnames <- lines
|
|
||||||
chErrors <- errs
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
lines, errs := buildBlockedIPs(ctx, client, blockMalicious, blockAds, blockSurveillance, privateAddresses)
|
|
||||||
chIPs <- lines
|
|
||||||
chErrors <- errs
|
|
||||||
}()
|
|
||||||
n := 2
|
|
||||||
for n > 0 {
|
|
||||||
select {
|
|
||||||
case lines := <-chHostnames:
|
|
||||||
hostnamesLines = append(hostnamesLines, lines...)
|
|
||||||
case lines := <-chIPs:
|
|
||||||
ipsLines = append(ipsLines, lines...)
|
|
||||||
case routineErrs := <-chErrors:
|
|
||||||
errs = append(errs, routineErrs...)
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hostnamesLines, ipsLines, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func getList(ctx context.Context, client *http.Client, url string) (results []string, err error) {
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("%w from %s: %s", ErrBadStatusCode, url, response.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := ioutil.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %s", ErrCannotReadBody, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
results = strings.Split(string(content), "\n")
|
|
||||||
|
|
||||||
// remove empty lines
|
|
||||||
last := len(results) - 1
|
|
||||||
for i := range results {
|
|
||||||
if len(results[i]) == 0 {
|
|
||||||
results[i] = results[last]
|
|
||||||
last--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results = results[:last+1]
|
|
||||||
|
|
||||||
if len(results) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBlockedHostnames(ctx context.Context, client *http.Client, blockMalicious, blockAds, blockSurveillance bool,
|
|
||||||
allowedHostnames []string) (lines []string, errs []error) {
|
|
||||||
chResults := make(chan []string)
|
|
||||||
chError := make(chan error)
|
|
||||||
listsLeftToFetch := 0
|
|
||||||
if blockMalicious {
|
|
||||||
listsLeftToFetch++
|
|
||||||
go func() {
|
|
||||||
results, err := getList(ctx, client, string(constants.MaliciousBlockListHostnamesURL))
|
|
||||||
chResults <- results
|
|
||||||
chError <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if blockAds {
|
|
||||||
listsLeftToFetch++
|
|
||||||
go func() {
|
|
||||||
results, err := getList(ctx, client, string(constants.AdsBlockListHostnamesURL))
|
|
||||||
chResults <- results
|
|
||||||
chError <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if blockSurveillance {
|
|
||||||
listsLeftToFetch++
|
|
||||||
go func() {
|
|
||||||
results, err := getList(ctx, client, string(constants.SurveillanceBlockListHostnamesURL))
|
|
||||||
chResults <- results
|
|
||||||
chError <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
uniqueResults := make(map[string]struct{})
|
|
||||||
for listsLeftToFetch > 0 {
|
|
||||||
select {
|
|
||||||
case results := <-chResults:
|
|
||||||
for _, result := range results {
|
|
||||||
uniqueResults[result] = struct{}{}
|
|
||||||
}
|
|
||||||
case err := <-chError:
|
|
||||||
listsLeftToFetch--
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, allowedHostname := range allowedHostnames {
|
|
||||||
delete(uniqueResults, allowedHostname)
|
|
||||||
}
|
|
||||||
for result := range uniqueResults {
|
|
||||||
lines = append(lines, " local-zone: \""+result+"\" static")
|
|
||||||
}
|
|
||||||
return lines, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBlockedIPs(ctx context.Context, client *http.Client, blockMalicious, blockAds, blockSurveillance bool,
|
|
||||||
privateAddresses []string) (lines []string, errs []error) {
|
|
||||||
chResults := make(chan []string)
|
|
||||||
chError := make(chan error)
|
|
||||||
listsLeftToFetch := 0
|
|
||||||
if blockMalicious {
|
|
||||||
listsLeftToFetch++
|
|
||||||
go func() {
|
|
||||||
results, err := getList(ctx, client, string(constants.MaliciousBlockListIPsURL))
|
|
||||||
chResults <- results
|
|
||||||
chError <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if blockAds {
|
|
||||||
listsLeftToFetch++
|
|
||||||
go func() {
|
|
||||||
results, err := getList(ctx, client, string(constants.AdsBlockListIPsURL))
|
|
||||||
chResults <- results
|
|
||||||
chError <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if blockSurveillance {
|
|
||||||
listsLeftToFetch++
|
|
||||||
go func() {
|
|
||||||
results, err := getList(ctx, client, string(constants.SurveillanceBlockListIPsURL))
|
|
||||||
chResults <- results
|
|
||||||
chError <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
uniqueResults := make(map[string]struct{})
|
|
||||||
for listsLeftToFetch > 0 {
|
|
||||||
select {
|
|
||||||
case results := <-chResults:
|
|
||||||
for _, result := range results {
|
|
||||||
uniqueResults[result] = struct{}{}
|
|
||||||
}
|
|
||||||
case err := <-chError:
|
|
||||||
listsLeftToFetch--
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, privateAddress := range privateAddresses {
|
|
||||||
uniqueResults[privateAddress] = struct{}{}
|
|
||||||
}
|
|
||||||
for result := range uniqueResults {
|
|
||||||
lines = append(lines, " private-address: "+result)
|
|
||||||
}
|
|
||||||
return lines, errs
|
|
||||||
}
|
|
||||||
@@ -1,702 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
|
||||||
"github.com/qdm12/golibs/logging/mock_logging"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_generateUnboundConf(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
settings := settings.DNS{
|
|
||||||
Providers: []models.DNSProvider{constants.Cloudflare, constants.Quad9},
|
|
||||||
AllowedHostnames: []string{"a"},
|
|
||||||
PrivateAddresses: []string{"9.9.9.9"},
|
|
||||||
BlockMalicious: true,
|
|
||||||
BlockSurveillance: false,
|
|
||||||
BlockAds: false,
|
|
||||||
VerbosityLevel: 2,
|
|
||||||
ValidationLogLevel: 3,
|
|
||||||
Caching: true,
|
|
||||||
IPv6: true,
|
|
||||||
}
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
clientCalls := map[models.URL]int{
|
|
||||||
constants.MaliciousBlockListIPsURL: 0,
|
|
||||||
constants.MaliciousBlockListHostnamesURL: 0,
|
|
||||||
}
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
url := models.URL(r.URL.String())
|
|
||||||
if _, ok := clientCalls[url]; !ok {
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
clientCalls[url]++
|
|
||||||
var body string
|
|
||||||
switch url {
|
|
||||||
case constants.MaliciousBlockListIPsURL:
|
|
||||||
body = "c\nd"
|
|
||||||
case constants.MaliciousBlockListHostnamesURL:
|
|
||||||
body = "b\na\nc"
|
|
||||||
default:
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Body: ioutil.NopCloser(strings.NewReader(body)),
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
|
||||||
logger.EXPECT().Info("%d hostnames blocked overall", 2)
|
|
||||||
logger.EXPECT().Info("%d IP addresses blocked overall", 3)
|
|
||||||
lines, warnings := generateUnboundConf(ctx, settings, "nonrootuser", client, logger)
|
|
||||||
require.Len(t, warnings, 0)
|
|
||||||
for url, count := range clientCalls {
|
|
||||||
assert.Equalf(t, 1, count, "for url %q", url)
|
|
||||||
}
|
|
||||||
const expected = `
|
|
||||||
server:
|
|
||||||
cache-max-ttl: 9000
|
|
||||||
cache-min-ttl: 3600
|
|
||||||
do-ip4: yes
|
|
||||||
do-ip6: yes
|
|
||||||
harden-algo-downgrade: yes
|
|
||||||
harden-below-nxdomain: yes
|
|
||||||
harden-referral-path: yes
|
|
||||||
hide-identity: yes
|
|
||||||
hide-version: yes
|
|
||||||
interface: 0.0.0.0
|
|
||||||
key-cache-size: 16m
|
|
||||||
key-cache-slabs: 4
|
|
||||||
msg-cache-size: 4m
|
|
||||||
msg-cache-slabs: 4
|
|
||||||
num-threads: 1
|
|
||||||
port: 53
|
|
||||||
prefetch-key: yes
|
|
||||||
prefetch: yes
|
|
||||||
root-hints: "/etc/unbound/root.hints"
|
|
||||||
rrset-cache-size: 4m
|
|
||||||
rrset-cache-slabs: 4
|
|
||||||
rrset-roundrobin: yes
|
|
||||||
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
|
|
||||||
trust-anchor-file: "/etc/unbound/root.key"
|
|
||||||
use-syslog: no
|
|
||||||
username: "nonrootuser"
|
|
||||||
val-log-level: 3
|
|
||||||
verbosity: 2
|
|
||||||
local-zone: "b" static
|
|
||||||
local-zone: "c" static
|
|
||||||
private-address: 9.9.9.9
|
|
||||||
private-address: c
|
|
||||||
private-address: d
|
|
||||||
forward-zone:
|
|
||||||
forward-no-cache: no
|
|
||||||
forward-tls-upstream: yes
|
|
||||||
name: "."
|
|
||||||
forward-addr: 1.1.1.1@853#cloudflare-dns.com
|
|
||||||
forward-addr: 1.0.0.1@853#cloudflare-dns.com
|
|
||||||
forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com
|
|
||||||
forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com
|
|
||||||
forward-addr: 9.9.9.9@853#dns.quad9.net
|
|
||||||
forward-addr: 149.112.112.112@853#dns.quad9.net
|
|
||||||
forward-addr: 2620:fe::fe@853#dns.quad9.net
|
|
||||||
forward-addr: 2620:fe::9@853#dns.quad9.net`
|
|
||||||
assert.Equal(t, expected, "\n"+strings.Join(lines, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_buildBlocked(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
type blockParams struct {
|
|
||||||
blocked bool
|
|
||||||
content []byte
|
|
||||||
clientErr error
|
|
||||||
}
|
|
||||||
tests := map[string]struct {
|
|
||||||
malicious blockParams
|
|
||||||
ads blockParams
|
|
||||||
surveillance blockParams
|
|
||||||
allowedHostnames []string
|
|
||||||
privateAddresses []string
|
|
||||||
hostnamesLines []string
|
|
||||||
ipsLines []string
|
|
||||||
errsString []string
|
|
||||||
}{
|
|
||||||
"none blocked": {},
|
|
||||||
"all blocked without lists": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"all blocked with lists": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("malicious"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("ads"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("surveillance"),
|
|
||||||
},
|
|
||||||
hostnamesLines: []string{
|
|
||||||
" local-zone: \"ads\" static",
|
|
||||||
" local-zone: \"malicious\" static",
|
|
||||||
" local-zone: \"surveillance\" static"},
|
|
||||||
ipsLines: []string{
|
|
||||||
" private-address: ads",
|
|
||||||
" private-address: malicious",
|
|
||||||
" private-address: surveillance"},
|
|
||||||
},
|
|
||||||
"all blocked with allowed hostnames": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("malicious"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("ads"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("surveillance"),
|
|
||||||
},
|
|
||||||
allowedHostnames: []string{"ads"},
|
|
||||||
hostnamesLines: []string{
|
|
||||||
" local-zone: \"malicious\" static",
|
|
||||||
" local-zone: \"surveillance\" static"},
|
|
||||||
ipsLines: []string{
|
|
||||||
" private-address: ads",
|
|
||||||
" private-address: malicious",
|
|
||||||
" private-address: surveillance"},
|
|
||||||
},
|
|
||||||
"all blocked with private addresses": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("malicious"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("ads"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("surveillance"),
|
|
||||||
},
|
|
||||||
privateAddresses: []string{"ads", "192.100.1.5"},
|
|
||||||
hostnamesLines: []string{
|
|
||||||
" local-zone: \"ads\" static",
|
|
||||||
" local-zone: \"malicious\" static",
|
|
||||||
" local-zone: \"surveillance\" static"},
|
|
||||||
ipsLines: []string{
|
|
||||||
" private-address: 192.100.1.5",
|
|
||||||
" private-address: ads",
|
|
||||||
" private-address: malicious",
|
|
||||||
" private-address: surveillance"},
|
|
||||||
},
|
|
||||||
"all blocked with lists and one error": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("malicious"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("ads"),
|
|
||||||
clientErr: fmt.Errorf("ads error"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("surveillance"),
|
|
||||||
},
|
|
||||||
hostnamesLines: []string{
|
|
||||||
" local-zone: \"malicious\" static",
|
|
||||||
" local-zone: \"surveillance\" static"},
|
|
||||||
ipsLines: []string{
|
|
||||||
" private-address: malicious",
|
|
||||||
" private-address: surveillance"},
|
|
||||||
errsString: []string{
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated": ads error`,
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated": ads error`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"all blocked with errors": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
clientErr: fmt.Errorf("malicious"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
clientErr: fmt.Errorf("ads"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
clientErr: fmt.Errorf("surveillance"),
|
|
||||||
},
|
|
||||||
errsString: []string{
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/malicious-ips.updated": malicious`,
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/malicious-hostnames.updated": malicious`,
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated": ads`,
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated": ads`,
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated": surveillance`,
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated": surveillance`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
clientCalls := map[models.URL]int{}
|
|
||||||
if tc.malicious.blocked {
|
|
||||||
clientCalls[constants.MaliciousBlockListIPsURL] = 0
|
|
||||||
clientCalls[constants.MaliciousBlockListHostnamesURL] = 0
|
|
||||||
}
|
|
||||||
if tc.ads.blocked {
|
|
||||||
clientCalls[constants.AdsBlockListIPsURL] = 0
|
|
||||||
clientCalls[constants.AdsBlockListHostnamesURL] = 0
|
|
||||||
}
|
|
||||||
if tc.surveillance.blocked {
|
|
||||||
clientCalls[constants.SurveillanceBlockListIPsURL] = 0
|
|
||||||
clientCalls[constants.SurveillanceBlockListHostnamesURL] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
url := models.URL(r.URL.String())
|
|
||||||
if _, ok := clientCalls[url]; !ok {
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
clientCalls[url]++
|
|
||||||
var body []byte
|
|
||||||
var err error
|
|
||||||
switch url {
|
|
||||||
case constants.MaliciousBlockListIPsURL, constants.MaliciousBlockListHostnamesURL:
|
|
||||||
body = tc.malicious.content
|
|
||||||
err = tc.malicious.clientErr
|
|
||||||
case constants.AdsBlockListIPsURL, constants.AdsBlockListHostnamesURL:
|
|
||||||
body = tc.ads.content
|
|
||||||
err = tc.ads.clientErr
|
|
||||||
case constants.SurveillanceBlockListIPsURL, constants.SurveillanceBlockListHostnamesURL:
|
|
||||||
body = tc.surveillance.content
|
|
||||||
err = tc.surveillance.clientErr
|
|
||||||
default: // just in case if the test is badly written
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
hostnamesLines, ipsLines, errs := buildBlocked(ctx, client,
|
|
||||||
tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked,
|
|
||||||
tc.allowedHostnames, tc.privateAddresses)
|
|
||||||
|
|
||||||
var errsString []string
|
|
||||||
for _, err := range errs {
|
|
||||||
errsString = append(errsString, err.Error())
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, tc.errsString, errsString)
|
|
||||||
assert.ElementsMatch(t, tc.hostnamesLines, hostnamesLines)
|
|
||||||
assert.ElementsMatch(t, tc.ipsLines, ipsLines)
|
|
||||||
|
|
||||||
for url, count := range clientCalls {
|
|
||||||
assert.Equalf(t, 1, count, "for url %q", url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_getList(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tests := map[string]struct {
|
|
||||||
content []byte
|
|
||||||
status int
|
|
||||||
clientErr error
|
|
||||||
results []string
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"no result": {
|
|
||||||
status: http.StatusOK,
|
|
||||||
},
|
|
||||||
"bad status": {
|
|
||||||
status: http.StatusInternalServerError,
|
|
||||||
err: fmt.Errorf("bad HTTP status from irrelevant_url: Internal Server Error"),
|
|
||||||
},
|
|
||||||
"network error": {
|
|
||||||
status: http.StatusOK,
|
|
||||||
clientErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf(`Get "irrelevant_url": error`),
|
|
||||||
},
|
|
||||||
"results": {
|
|
||||||
content: []byte("a\nb\nc\n"),
|
|
||||||
status: http.StatusOK,
|
|
||||||
results: []string{"a", "b", "c"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
assert.Equal(t, "irrelevant_url", r.URL.String())
|
|
||||||
if tc.clientErr != nil {
|
|
||||||
return nil, tc.clientErr
|
|
||||||
}
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: tc.status,
|
|
||||||
Status: http.StatusText(tc.status),
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(tc.content)),
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
results, err := getList(ctx, client, "irrelevant_url")
|
|
||||||
if tc.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, tc.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
assert.Equal(t, tc.results, results)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_buildBlockedHostnames(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
type blockParams struct {
|
|
||||||
blocked bool
|
|
||||||
content []byte
|
|
||||||
clientErr error
|
|
||||||
}
|
|
||||||
tests := map[string]struct {
|
|
||||||
malicious blockParams
|
|
||||||
ads blockParams
|
|
||||||
surveillance blockParams
|
|
||||||
allowedHostnames []string
|
|
||||||
lines []string
|
|
||||||
errsString []string
|
|
||||||
}{
|
|
||||||
"nothing blocked": {},
|
|
||||||
"only malicious blocked": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
clientErr: nil,
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
" local-zone: \"site_a\" static",
|
|
||||||
" local-zone: \"site_b\" static"},
|
|
||||||
},
|
|
||||||
"all blocked with some duplicates": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_c"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_c\nsite_a"),
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
" local-zone: \"site_a\" static",
|
|
||||||
" local-zone: \"site_b\" static",
|
|
||||||
" local-zone: \"site_c\" static"},
|
|
||||||
},
|
|
||||||
"all blocked with one errored": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_c"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
clientErr: fmt.Errorf("surveillance error"),
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
" local-zone: \"site_a\" static",
|
|
||||||
" local-zone: \"site_b\" static",
|
|
||||||
" local-zone: \"site_c\" static"},
|
|
||||||
errsString: []string{
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated": surveillance error`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"blocked with allowed hostnames": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_c\nsite_d"),
|
|
||||||
},
|
|
||||||
allowedHostnames: []string{"site_b", "site_c"},
|
|
||||||
lines: []string{
|
|
||||||
" local-zone: \"site_a\" static",
|
|
||||||
" local-zone: \"site_d\" static"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
clientCalls := map[models.URL]int{}
|
|
||||||
if tc.malicious.blocked {
|
|
||||||
clientCalls[constants.MaliciousBlockListHostnamesURL] = 0
|
|
||||||
}
|
|
||||||
if tc.ads.blocked {
|
|
||||||
clientCalls[constants.AdsBlockListHostnamesURL] = 0
|
|
||||||
}
|
|
||||||
if tc.surveillance.blocked {
|
|
||||||
clientCalls[constants.SurveillanceBlockListHostnamesURL] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
url := models.URL(r.URL.String())
|
|
||||||
if _, ok := clientCalls[url]; !ok {
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
clientCalls[url]++
|
|
||||||
var body []byte
|
|
||||||
var err error
|
|
||||||
switch url {
|
|
||||||
case constants.MaliciousBlockListHostnamesURL:
|
|
||||||
body = tc.malicious.content
|
|
||||||
err = tc.malicious.clientErr
|
|
||||||
case constants.AdsBlockListHostnamesURL:
|
|
||||||
body = tc.ads.content
|
|
||||||
err = tc.ads.clientErr
|
|
||||||
case constants.SurveillanceBlockListHostnamesURL:
|
|
||||||
body = tc.surveillance.content
|
|
||||||
err = tc.surveillance.clientErr
|
|
||||||
default: // just in case if the test is badly written
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
lines, errs := buildBlockedHostnames(ctx, client,
|
|
||||||
tc.malicious.blocked, tc.ads.blocked,
|
|
||||||
tc.surveillance.blocked, tc.allowedHostnames)
|
|
||||||
|
|
||||||
var errsString []string
|
|
||||||
for _, err := range errs {
|
|
||||||
errsString = append(errsString, err.Error())
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, tc.errsString, errsString)
|
|
||||||
assert.ElementsMatch(t, tc.lines, lines)
|
|
||||||
|
|
||||||
for url, count := range clientCalls {
|
|
||||||
assert.Equalf(t, 1, count, "for url %q", url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_buildBlockedIPs(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
type blockParams struct {
|
|
||||||
blocked bool
|
|
||||||
content []byte
|
|
||||||
clientErr error
|
|
||||||
}
|
|
||||||
tests := map[string]struct {
|
|
||||||
malicious blockParams
|
|
||||||
ads blockParams
|
|
||||||
surveillance blockParams
|
|
||||||
privateAddresses []string
|
|
||||||
lines []string
|
|
||||||
errsString []string
|
|
||||||
}{
|
|
||||||
"nothing blocked": {},
|
|
||||||
"only malicious blocked": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
clientErr: nil,
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
" private-address: site_a",
|
|
||||||
" private-address: site_b"},
|
|
||||||
},
|
|
||||||
"all blocked with some duplicates": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_c"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_c\nsite_a"),
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
" private-address: site_a",
|
|
||||||
" private-address: site_b",
|
|
||||||
" private-address: site_c"},
|
|
||||||
},
|
|
||||||
"all blocked with one errored": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_c"),
|
|
||||||
},
|
|
||||||
surveillance: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
clientErr: fmt.Errorf("surveillance error"),
|
|
||||||
},
|
|
||||||
lines: []string{
|
|
||||||
" private-address: site_a",
|
|
||||||
" private-address: site_b",
|
|
||||||
" private-address: site_c"},
|
|
||||||
errsString: []string{
|
|
||||||
`Get "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated": surveillance error`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"blocked with private addresses": {
|
|
||||||
malicious: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_a\nsite_b"),
|
|
||||||
},
|
|
||||||
ads: blockParams{
|
|
||||||
blocked: true,
|
|
||||||
content: []byte("site_c"),
|
|
||||||
},
|
|
||||||
privateAddresses: []string{"site_c", "site_d"},
|
|
||||||
lines: []string{
|
|
||||||
" private-address: site_a",
|
|
||||||
" private-address: site_b",
|
|
||||||
" private-address: site_c",
|
|
||||||
" private-address: site_d"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
clientCalls := map[models.URL]int{}
|
|
||||||
if tc.malicious.blocked {
|
|
||||||
clientCalls[constants.MaliciousBlockListIPsURL] = 0
|
|
||||||
}
|
|
||||||
if tc.ads.blocked {
|
|
||||||
clientCalls[constants.AdsBlockListIPsURL] = 0
|
|
||||||
}
|
|
||||||
if tc.surveillance.blocked {
|
|
||||||
clientCalls[constants.SurveillanceBlockListIPsURL] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
url := models.URL(r.URL.String())
|
|
||||||
if _, ok := clientCalls[url]; !ok {
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
clientCalls[url]++
|
|
||||||
var body []byte
|
|
||||||
var err error
|
|
||||||
switch url {
|
|
||||||
case constants.MaliciousBlockListIPsURL:
|
|
||||||
body = tc.malicious.content
|
|
||||||
err = tc.malicious.clientErr
|
|
||||||
case constants.AdsBlockListIPsURL:
|
|
||||||
body = tc.ads.content
|
|
||||||
err = tc.ads.clientErr
|
|
||||||
case constants.SurveillanceBlockListIPsURL:
|
|
||||||
body = tc.surveillance.content
|
|
||||||
err = tc.surveillance.clientErr
|
|
||||||
default: // just in case if the test is badly written
|
|
||||||
t.Errorf("unknown URL %q", url)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(body)),
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
lines, errs := buildBlockedIPs(ctx, client,
|
|
||||||
tc.malicious.blocked, tc.ads.blocked,
|
|
||||||
tc.surveillance.blocked, tc.privateAddresses)
|
|
||||||
|
|
||||||
var errsString []string
|
|
||||||
for _, err := range errs {
|
|
||||||
errsString = append(errsString, err.Error())
|
|
||||||
}
|
|
||||||
assert.ElementsMatch(t, tc.errsString, errsString)
|
|
||||||
assert.ElementsMatch(t, tc.lines, lines)
|
|
||||||
|
|
||||||
for url, count := range clientCalls {
|
|
||||||
assert.Equalf(t, 1, count, "for url %q", url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
|
||||||
"github.com/qdm12/golibs/command"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Configurator interface {
|
|
||||||
DownloadRootHints(ctx context.Context, puid, pgid int) error
|
|
||||||
DownloadRootKey(ctx context.Context, puid, pgid int) error
|
|
||||||
MakeUnboundConf(ctx context.Context, settings settings.DNS, username string, puid, pgid int) (err error)
|
|
||||||
UseDNSInternally(IP net.IP)
|
|
||||||
UseDNSSystemWide(ip net.IP, keepNameserver bool) error
|
|
||||||
Start(ctx context.Context, logLevel uint8) (stdout io.ReadCloser, waitFn func() error, err error)
|
|
||||||
WaitForUnbound() (err error)
|
|
||||||
Version(ctx context.Context) (version string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type configurator struct {
|
|
||||||
logger logging.Logger
|
|
||||||
client *http.Client
|
|
||||||
openFile os.OpenFileFunc
|
|
||||||
commander command.Commander
|
|
||||||
lookupIP func(host string) ([]net.IP, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigurator(logger logging.Logger, httpClient *http.Client,
|
|
||||||
openFile os.OpenFileFunc) Configurator {
|
|
||||||
return &configurator{
|
|
||||||
logger: logger.WithPrefix("dns configurator: "),
|
|
||||||
client: httpClient,
|
|
||||||
openFile: openFile,
|
|
||||||
commander: command.NewCommander(),
|
|
||||||
lookupIP: net.LookupIP,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrBadStatusCode = errors.New("bad HTTP status")
|
|
||||||
ErrCannotReadBody = errors.New("cannot read response body")
|
|
||||||
)
|
|
||||||
@@ -4,9 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
@@ -15,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Looper interface {
|
type Looper interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady func())
|
Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{})
|
||||||
RunRestartTicker(ctx context.Context, wg *sync.WaitGroup)
|
RunRestartTicker(ctx context.Context, wg *sync.WaitGroup)
|
||||||
GetStatus() (status models.LoopStatus)
|
GetStatus() (status models.LoopStatus)
|
||||||
SetStatus(status models.LoopStatus) (outcome string, err error)
|
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||||
@@ -25,7 +27,8 @@ type Looper interface {
|
|||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
state state
|
state state
|
||||||
conf Configurator
|
conf unbound.Configurator
|
||||||
|
client *http.Client
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
username string
|
username string
|
||||||
@@ -44,14 +47,16 @@ type looper struct {
|
|||||||
|
|
||||||
const defaultBackoffTime = 10 * time.Second
|
const defaultBackoffTime = 10 * time.Second
|
||||||
|
|
||||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
func NewLooper(conf unbound.Configurator, settings settings.DNS, client *http.Client,
|
||||||
streamMerger command.StreamMerger, username string, puid, pgid int) Looper {
|
logger logging.Logger, streamMerger command.StreamMerger,
|
||||||
|
username string, puid, pgid int) Looper {
|
||||||
return &looper{
|
return &looper{
|
||||||
state: state{
|
state: state{
|
||||||
status: constants.Stopped,
|
status: constants.Stopped,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
},
|
},
|
||||||
conf: conf,
|
conf: conf,
|
||||||
|
client: client,
|
||||||
logger: logger.WithPrefix("dns over tls: "),
|
logger: logger.WithPrefix("dns over tls: "),
|
||||||
username: username,
|
username: username,
|
||||||
puid: puid,
|
puid: puid,
|
||||||
@@ -82,7 +87,7 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady func()) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{}) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
const fallback = false
|
const fallback = false
|
||||||
@@ -126,7 +131,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady fun
|
|||||||
l.useUnencryptedDNS(fallback)
|
l.useUnencryptedDNS(fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
signalDNSReady()
|
dnsReadyCh <- struct{}{}
|
||||||
|
|
||||||
stayHere := true
|
stayHere := true
|
||||||
for stayHere {
|
for stayHere {
|
||||||
@@ -177,7 +182,7 @@ func (l *looper) setupUnbound(ctx context.Context,
|
|||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
|
|
||||||
unboundCtx, cancel := context.WithCancel(context.Background())
|
unboundCtx, cancel := context.WithCancel(context.Background())
|
||||||
stream, waitFn, err := l.conf.Start(unboundCtx, settings.VerbosityDetailsLevel)
|
stream, waitFn, err := l.conf.Start(unboundCtx, settings.Unbound.VerbosityDetailsLevel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
if !previousCrashed {
|
if !previousCrashed {
|
||||||
@@ -194,7 +199,7 @@ func (l *looper) setupUnbound(ctx context.Context,
|
|||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.conf.WaitForUnbound(); err != nil {
|
if err := l.conf.WaitForUnbound(ctx); err != nil {
|
||||||
if !previousCrashed {
|
if !previousCrashed {
|
||||||
l.running <- constants.Crashed
|
l.running <- constants.Crashed
|
||||||
}
|
}
|
||||||
@@ -236,8 +241,8 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try with any IPv4 address from the providers chosen
|
// Try with any IPv4 address from the providers chosen
|
||||||
for _, provider := range settings.Providers {
|
for _, provider := range settings.Unbound.Providers {
|
||||||
data := constants.DNSProviderMapping()[provider]
|
data, _ := unbound.GetProviderData(provider)
|
||||||
for _, targetIP = range data.IPs {
|
for _, targetIP = range data.IPs {
|
||||||
if targetIP.To4() != nil {
|
if targetIP.To4() != nil {
|
||||||
if fallback {
|
if fallback {
|
||||||
@@ -255,7 +260,7 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No IPv4 address found
|
// No IPv4 address found
|
||||||
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Providers)
|
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Unbound.Providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
@@ -317,14 +322,24 @@ func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) updateFiles(ctx context.Context) (err error) {
|
func (l *looper) updateFiles(ctx context.Context) (err error) {
|
||||||
if err := l.conf.DownloadRootHints(ctx, l.puid, l.pgid); err != nil {
|
l.logger.Info("downloading DNS over TLS cryptographic files")
|
||||||
return err
|
if err := l.conf.SetupFiles(ctx); err != nil {
|
||||||
}
|
|
||||||
if err := l.conf.DownloadRootKey(ctx, l.puid, l.pgid); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
if err := l.conf.MakeUnboundConf(ctx, settings, l.username, l.puid, l.pgid); err != nil {
|
|
||||||
|
l.logger.Info("downloading hostnames and IP block lists")
|
||||||
|
hostnameLines, ipLines, errs := l.conf.BuildBlocked(ctx, l.client,
|
||||||
|
settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance,
|
||||||
|
settings.Unbound.BlockedHostnames, settings.Unbound.BlockedIPs,
|
||||||
|
settings.Unbound.AllowedHostnames)
|
||||||
|
for _, err := range errs {
|
||||||
|
l.logger.Warn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.conf.MakeUnboundConf(
|
||||||
|
settings.Unbound, hostnameLines, ipLines,
|
||||||
|
l.username, l.puid, l.pgid); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UseDNSInternally is to change the Go program DNS only.
|
|
||||||
func (c *configurator) UseDNSInternally(ip net.IP) {
|
|
||||||
c.logger.Info("using DNS address %s internally", ip.String())
|
|
||||||
net.DefaultResolver = &net.Resolver{
|
|
||||||
PreferGo: true,
|
|
||||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
d := net.Dialer{}
|
|
||||||
return d.DialContext(ctx, "udp", net.JoinHostPort(ip.String(), "53"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseDNSSystemWide changes the nameserver to use for DNS system wide.
|
|
||||||
func (c *configurator) UseDNSSystemWide(ip net.IP, keepNameserver bool) error {
|
|
||||||
c.logger.Info("using DNS address %s system wide", ip.String())
|
|
||||||
const filepath = string(constants.ResolvConf)
|
|
||||||
file, err := c.openFile(filepath, os.O_RDWR|os.O_TRUNC, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := strings.TrimSuffix(string(data), "\n")
|
|
||||||
|
|
||||||
lines := []string{
|
|
||||||
"nameserver " + ip.String(),
|
|
||||||
}
|
|
||||||
for _, line := range strings.Split(s, "\n") {
|
|
||||||
if line == "" ||
|
|
||||||
(!keepNameserver && strings.HasPrefix(line, "nameserver ")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
s = strings.Join(lines, "\n") + "\n"
|
|
||||||
_, err = file.WriteString(s)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return file.Close()
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/logging/mock_logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
"github.com/qdm12/golibs/os/mock_os"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_UseDNSSystemWide(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tests := map[string]struct {
|
|
||||||
ip net.IP
|
|
||||||
keepNameserver bool
|
|
||||||
data []byte
|
|
||||||
writtenData string
|
|
||||||
openErr error
|
|
||||||
readErr error
|
|
||||||
writeErr error
|
|
||||||
closeErr error
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"no data": {
|
|
||||||
ip: net.IP{127, 0, 0, 1},
|
|
||||||
writtenData: "nameserver 127.0.0.1\n",
|
|
||||||
},
|
|
||||||
"open error": {
|
|
||||||
ip: net.IP{127, 0, 0, 1},
|
|
||||||
openErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
"read error": {
|
|
||||||
readErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
"write error": {
|
|
||||||
ip: net.IP{127, 0, 0, 1},
|
|
||||||
writtenData: "nameserver 127.0.0.1\n",
|
|
||||||
writeErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
"lines without nameserver": {
|
|
||||||
ip: net.IP{127, 0, 0, 1},
|
|
||||||
data: []byte("abc\ndef\n"),
|
|
||||||
writtenData: "nameserver 127.0.0.1\nabc\ndef\n",
|
|
||||||
},
|
|
||||||
"lines with nameserver": {
|
|
||||||
ip: net.IP{127, 0, 0, 1},
|
|
||||||
data: []byte("abc\nnameserver abc def\ndef\n"),
|
|
||||||
writtenData: "nameserver 127.0.0.1\nabc\ndef\n",
|
|
||||||
},
|
|
||||||
"keep nameserver": {
|
|
||||||
ip: net.IP{127, 0, 0, 1},
|
|
||||||
keepNameserver: true,
|
|
||||||
data: []byte("abc\nnameserver abc def\ndef\n"),
|
|
||||||
writtenData: "nameserver 127.0.0.1\nabc\nnameserver abc def\ndef\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
file := mock_os.NewMockFile(mockCtrl)
|
|
||||||
if tc.openErr == nil {
|
|
||||||
firstReadCall := file.EXPECT().
|
|
||||||
Read(gomock.AssignableToTypeOf([]byte{})).
|
|
||||||
DoAndReturn(func(b []byte) (int, error) {
|
|
||||||
copy(b, tc.data)
|
|
||||||
return len(tc.data), nil
|
|
||||||
})
|
|
||||||
readErr := tc.readErr
|
|
||||||
if readErr == nil {
|
|
||||||
readErr = io.EOF
|
|
||||||
}
|
|
||||||
finalReadCall := file.EXPECT().
|
|
||||||
Read(gomock.AssignableToTypeOf([]byte{})).
|
|
||||||
Return(0, readErr).After(firstReadCall)
|
|
||||||
if tc.readErr == nil {
|
|
||||||
writeCall := file.EXPECT().WriteString(tc.writtenData).
|
|
||||||
Return(0, tc.writeErr).After(finalReadCall)
|
|
||||||
file.EXPECT().Close().Return(tc.closeErr).After(writeCall)
|
|
||||||
} else {
|
|
||||||
file.EXPECT().Close().Return(tc.closeErr).After(finalReadCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openFile := func(name string, flag int, perm os.FileMode) (os.File, error) {
|
|
||||||
assert.Equal(t, string(constants.ResolvConf), name)
|
|
||||||
assert.Equal(t, os.O_RDWR|os.O_TRUNC, flag)
|
|
||||||
assert.Equal(t, os.FileMode(0644), perm)
|
|
||||||
return file, tc.openErr
|
|
||||||
}
|
|
||||||
|
|
||||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
|
||||||
logger.EXPECT().Info("using DNS address %s system wide", tc.ip.String())
|
|
||||||
c := &configurator{
|
|
||||||
openFile: openFile,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
err := c.UseDNSSystemWide(tc.ip, tc.keepNameserver)
|
|
||||||
if tc.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, tc.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *configurator) DownloadRootHints(ctx context.Context, puid, pgid int) error {
|
|
||||||
return c.downloadAndSave(ctx, "root hints",
|
|
||||||
string(constants.NamedRootURL), string(constants.RootHints), puid, pgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *configurator) DownloadRootKey(ctx context.Context, puid, pgid int) error {
|
|
||||||
return c.downloadAndSave(ctx, "root key",
|
|
||||||
string(constants.RootKeyURL), string(constants.RootKey), puid, pgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *configurator) downloadAndSave(ctx context.Context, logName, url, filepath string, puid, pgid int) error {
|
|
||||||
c.logger.Info("downloading %s from %s", logName, url)
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := c.client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
if response.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("%w from %s: %s", ErrBadStatusCode, url, response.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := c.openFile(filepath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0400)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(file, response.Body)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Chown(puid, pgid)
|
|
||||||
if err != nil {
|
|
||||||
_ = file.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.Close()
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/golibs/logging/mock_logging"
|
|
||||||
"github.com/qdm12/golibs/os"
|
|
||||||
"github.com/qdm12/golibs/os/mock_os"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_downloadAndSave(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const defaultURL = "https://test.com"
|
|
||||||
tests := map[string]struct {
|
|
||||||
url string // to trigger a new request error
|
|
||||||
content []byte
|
|
||||||
status int
|
|
||||||
clientErr error
|
|
||||||
openErr error
|
|
||||||
writeErr error
|
|
||||||
chownErr error
|
|
||||||
closeErr error
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"no data": {
|
|
||||||
url: defaultURL,
|
|
||||||
status: http.StatusOK,
|
|
||||||
},
|
|
||||||
"bad status": {
|
|
||||||
url: defaultURL,
|
|
||||||
status: http.StatusBadRequest,
|
|
||||||
err: fmt.Errorf("bad HTTP status from %s: Bad Request", defaultURL),
|
|
||||||
},
|
|
||||||
"client error": {
|
|
||||||
url: defaultURL,
|
|
||||||
clientErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("Get %q: error", defaultURL),
|
|
||||||
},
|
|
||||||
"open error": {
|
|
||||||
url: defaultURL,
|
|
||||||
status: http.StatusOK,
|
|
||||||
openErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
"chown error": {
|
|
||||||
url: defaultURL,
|
|
||||||
status: http.StatusOK,
|
|
||||||
chownErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
"close error": {
|
|
||||||
url: defaultURL,
|
|
||||||
status: http.StatusOK,
|
|
||||||
closeErr: fmt.Errorf("error"),
|
|
||||||
err: fmt.Errorf("error"),
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
url: defaultURL,
|
|
||||||
content: []byte("content"),
|
|
||||||
status: http.StatusOK,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
|
||||||
logger.EXPECT().Info("downloading %s from %s", "root hints", tc.url)
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
assert.Equal(t, tc.url, r.URL.String())
|
|
||||||
if tc.clientErr != nil {
|
|
||||||
return nil, tc.clientErr
|
|
||||||
}
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: tc.status,
|
|
||||||
Status: http.StatusText(tc.status),
|
|
||||||
Body: ioutil.NopCloser(bytes.NewReader(tc.content)),
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
openFile := func(name string, flag int, perm os.FileMode) (os.File, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const filepath = "/test"
|
|
||||||
|
|
||||||
if tc.clientErr == nil && tc.status == http.StatusOK {
|
|
||||||
file := mock_os.NewMockFile(mockCtrl)
|
|
||||||
if tc.openErr == nil {
|
|
||||||
if len(tc.content) > 0 {
|
|
||||||
file.EXPECT().
|
|
||||||
Write(tc.content).
|
|
||||||
Return(len(tc.content), tc.writeErr)
|
|
||||||
}
|
|
||||||
file.EXPECT().
|
|
||||||
Close().
|
|
||||||
Return(tc.closeErr)
|
|
||||||
file.EXPECT().
|
|
||||||
Chown(1000, 1000).
|
|
||||||
Return(tc.chownErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
openFile = func(name string, flag int, perm os.FileMode) (os.File, error) {
|
|
||||||
assert.Equal(t, filepath, name)
|
|
||||||
assert.Equal(t, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, flag)
|
|
||||||
assert.Equal(t, os.FileMode(0400), perm)
|
|
||||||
return file, tc.openErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &configurator{
|
|
||||||
logger: logger,
|
|
||||||
client: client,
|
|
||||||
openFile: openFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.downloadAndSave(ctx, "root hints",
|
|
||||||
tc.url, filepath,
|
|
||||||
1000, 1000)
|
|
||||||
|
|
||||||
if tc.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, tc.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DownloadRootHints(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
|
||||||
logger.EXPECT().Info("downloading %s from %s", "root hints", string(constants.NamedRootURL))
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
assert.Equal(t, string(constants.NamedRootURL), r.URL.String())
|
|
||||||
return nil, errors.New("test")
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &configurator{
|
|
||||||
logger: logger,
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.DownloadRootHints(ctx, 1000, 1000)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, `Get "https://raw.githubusercontent.com/qdm12/files/master/named.root.updated": test`, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_DownloadRootKey(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
|
||||||
logger.EXPECT().Info("downloading %s from %s", "root key", string(constants.RootKeyURL))
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
||||||
assert.Equal(t, string(constants.RootKeyURL), r.URL.String())
|
|
||||||
return nil, errors.New("test")
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &configurator{
|
|
||||||
logger: logger,
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := c.DownloadRootKey(ctx, 1000, 1000)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, `Get "https://raw.githubusercontent.com/qdm12/files/master/root.key.updated": test`, err.Error())
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type roundTripFunc func(r *http.Request) (*http.Response, error)
|
|
||||||
|
|
||||||
func (s roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
return s(r)
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *configurator) WaitForUnbound() (err error) {
|
|
||||||
const hostToResolve = "github.com"
|
|
||||||
waitDurations := [...]time.Duration{
|
|
||||||
300 * time.Millisecond,
|
|
||||||
100 * time.Millisecond,
|
|
||||||
300 * time.Millisecond,
|
|
||||||
500 * time.Millisecond,
|
|
||||||
time.Second,
|
|
||||||
2 * time.Second,
|
|
||||||
}
|
|
||||||
maxTries := len(waitDurations)
|
|
||||||
for i, waitDuration := range waitDurations {
|
|
||||||
time.Sleep(waitDuration)
|
|
||||||
_, err := c.lookupIP(hostToResolve)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.logger.Warn("could not resolve %s (try %d of %d): %s", hostToResolve, i+1, maxTries, err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Unbound does not seem to be working after %d tries", maxTries)
|
|
||||||
}
|
|
||||||
@@ -12,8 +12,15 @@ import (
|
|||||||
func (s *server) runHealthcheckLoop(ctx context.Context, wg *sync.WaitGroup) {
|
func (s *server) runHealthcheckLoop(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for {
|
for {
|
||||||
|
previousErr := s.handler.getErr()
|
||||||
|
|
||||||
err := healthCheck(ctx, s.resolver)
|
err := healthCheck(ctx, s.resolver)
|
||||||
s.handler.setErr(err)
|
s.handler.setErr(err)
|
||||||
|
|
||||||
|
if previousErr != nil && err == nil {
|
||||||
|
s.logger.Info("passed")
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil { // try again after 1 second
|
if err != nil { // try again after 1 second
|
||||||
timer := time.NewTimer(time.Second)
|
timer := time.NewTimer(time.Second)
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
var regularExpressions = struct { //nolint:gochecknoglobals
|
var regularExpressions = struct { //nolint:gochecknoglobals
|
||||||
unboundPrefix *regexp.Regexp
|
unboundPrefix *regexp.Regexp
|
||||||
}{
|
}{
|
||||||
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
|
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:[0|1]\] `),
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
type (
|
type (
|
||||||
// VPNDevice is the device name used to tunnel using Openvpn.
|
// VPNDevice is the device name used to tunnel using Openvpn.
|
||||||
VPNDevice string
|
VPNDevice string
|
||||||
// DNSProvider is a DNS over TLS server provider name.
|
|
||||||
DNSProvider string
|
|
||||||
// DNSHost is the DNS host to use for TLS validation.
|
// DNSHost is the DNS host to use for TLS validation.
|
||||||
DNSHost string
|
DNSHost string
|
||||||
// URL is an HTTP(s) URL address.
|
// URL is an HTTP(s) URL address.
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ type DNSProviderData struct {
|
|||||||
IPs []net.IP
|
IPs []net.IP
|
||||||
SupportsTLS bool
|
SupportsTLS bool
|
||||||
SupportsIPv6 bool
|
SupportsIPv6 bool
|
||||||
|
SupportsDNSSec bool
|
||||||
Host DNSHost
|
Host DNSHost
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
dns "github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
libparams "github.com/qdm12/golibs/params"
|
libparams "github.com/qdm12/golibs/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,20 +18,17 @@ func (r *reader) GetDNSOverTLS() (DNSOverTLS bool, err error) { //nolint:gocriti
|
|||||||
|
|
||||||
// GetDNSOverTLSProviders obtains the DNS over TLS providers to use
|
// GetDNSOverTLSProviders obtains the DNS over TLS providers to use
|
||||||
// from the environment variable DOT_PROVIDERS.
|
// from the environment variable DOT_PROVIDERS.
|
||||||
func (r *reader) GetDNSOverTLSProviders() (providers []models.DNSProvider, err error) {
|
func (r *reader) GetDNSOverTLSProviders() (providers []string, err error) {
|
||||||
s, err := r.envParams.GetEnv("DOT_PROVIDERS", libparams.Default("cloudflare"))
|
s, err := r.envParams.GetEnv("DOT_PROVIDERS", libparams.Default("cloudflare"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, word := range strings.Split(s, ",") {
|
for _, provider := range strings.Split(s, ",") {
|
||||||
provider := models.DNSProvider(word)
|
_, ok := dns.GetProviderData(provider)
|
||||||
switch provider {
|
if !ok {
|
||||||
case constants.Cloudflare, constants.Google, constants.Quad9,
|
|
||||||
constants.Quadrant, constants.CleanBrowsing:
|
|
||||||
providers = append(providers, provider)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider)
|
return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider)
|
||||||
}
|
}
|
||||||
|
providers = append(providers, provider)
|
||||||
}
|
}
|
||||||
return providers, nil
|
return providers, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ type Reader interface {
|
|||||||
|
|
||||||
// DNS over TLS getters
|
// DNS over TLS getters
|
||||||
GetDNSOverTLS() (DNSOverTLS bool, err error)
|
GetDNSOverTLS() (DNSOverTLS bool, err error)
|
||||||
GetDNSOverTLSProviders() (providers []models.DNSProvider, err error)
|
GetDNSOverTLSProviders() (providers []string, err error)
|
||||||
GetDNSOverTLSCaching() (caching bool, err error)
|
GetDNSOverTLSCaching() (caching bool, err error)
|
||||||
GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error)
|
GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error)
|
||||||
GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error)
|
GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error)
|
||||||
|
|||||||
@@ -6,79 +6,77 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
unboundmodels "github.com/qdm12/dns/pkg/models"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
unbound "github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/params"
|
"github.com/qdm12/gluetun/internal/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNS contains settings to configure Unbound for DNS over TLS operation.
|
// DNS contains settings to configure Unbound for DNS over TLS operation.
|
||||||
type DNS struct {
|
type DNS struct { //nolint:maligned
|
||||||
Enabled bool
|
Enabled bool
|
||||||
KeepNameserver bool
|
|
||||||
Providers []models.DNSProvider
|
|
||||||
PlaintextAddress net.IP
|
PlaintextAddress net.IP
|
||||||
AllowedHostnames []string
|
KeepNameserver bool
|
||||||
PrivateAddresses []string
|
|
||||||
Caching bool
|
|
||||||
BlockMalicious bool
|
BlockMalicious bool
|
||||||
BlockSurveillance bool
|
|
||||||
BlockAds bool
|
BlockAds bool
|
||||||
VerbosityLevel uint8
|
BlockSurveillance bool
|
||||||
VerbosityDetailsLevel uint8
|
|
||||||
ValidationLogLevel uint8
|
|
||||||
IPv6 bool
|
|
||||||
UpdatePeriod time.Duration
|
UpdatePeriod time.Duration
|
||||||
|
Unbound unboundmodels.Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) String() string {
|
func (d *DNS) String() string {
|
||||||
|
return strings.Join(d.lines(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNS) lines() (lines []string) {
|
||||||
if !d.Enabled {
|
if !d.Enabled {
|
||||||
return fmt.Sprintf("DNS over TLS disabled, using plaintext DNS %s", d.PlaintextAddress)
|
return []string{"DNS over TLS disabled, using plaintext DNS " + d.PlaintextAddress.String()}
|
||||||
}
|
}
|
||||||
caching, blockMalicious, blockSurveillance, blockAds, ipv6 := disabled, disabled, disabled, disabled, disabled
|
|
||||||
if d.Caching {
|
const prefix = " |--"
|
||||||
caching = enabled
|
|
||||||
|
lines = append(lines, "DNS settings:")
|
||||||
|
|
||||||
|
lines = append(lines, prefix+"Unbound:")
|
||||||
|
for _, line := range d.Unbound.Lines() {
|
||||||
|
indent := " " + prefix
|
||||||
|
if strings.HasPrefix(line, prefix) {
|
||||||
|
indent = " "
|
||||||
}
|
}
|
||||||
|
lines = append(lines, indent+line)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockMalicious := disabled
|
||||||
if d.BlockMalicious {
|
if d.BlockMalicious {
|
||||||
blockMalicious = enabled
|
blockMalicious = enabled
|
||||||
}
|
}
|
||||||
if d.BlockSurveillance {
|
lines = append(lines, prefix+"Block malicious: "+blockMalicious)
|
||||||
blockSurveillance = enabled
|
|
||||||
}
|
blockAds := disabled
|
||||||
if d.BlockAds {
|
if d.BlockAds {
|
||||||
blockAds = enabled
|
blockAds = enabled
|
||||||
}
|
}
|
||||||
if d.IPv6 {
|
lines = append(lines, prefix+"Block ads: "+blockAds)
|
||||||
ipv6 = enabled
|
|
||||||
}
|
blockSurveillance := disabled
|
||||||
providersStr := make([]string, len(d.Providers))
|
if d.BlockSurveillance {
|
||||||
for i := range d.Providers {
|
blockSurveillance = enabled
|
||||||
providersStr[i] = string(d.Providers[i])
|
|
||||||
}
|
}
|
||||||
|
lines = append(lines, prefix+"Block surveillance: "+blockSurveillance)
|
||||||
|
|
||||||
update := "deactivated"
|
update := "deactivated"
|
||||||
if d.UpdatePeriod > 0 {
|
if d.UpdatePeriod > 0 {
|
||||||
update = fmt.Sprintf("every %s", d.UpdatePeriod)
|
update = "every " + d.UpdatePeriod.String()
|
||||||
}
|
}
|
||||||
|
lines = append(lines, prefix+"Update: "+update)
|
||||||
|
|
||||||
keepNameserver := "no"
|
keepNameserver := "no"
|
||||||
if d.KeepNameserver {
|
if d.KeepNameserver {
|
||||||
keepNameserver = "yes"
|
keepNameserver = "yes"
|
||||||
}
|
}
|
||||||
settingsList := []string{
|
lines = append(lines,
|
||||||
"DNS over TLS settings:",
|
prefix+"Keep nameserver (disabled blocking): "+keepNameserver)
|
||||||
"DNS over TLS provider:\n |--" + strings.Join(providersStr, "\n |--"),
|
|
||||||
"Caching: " + caching,
|
return lines
|
||||||
"Block malicious: " + blockMalicious,
|
|
||||||
"Block surveillance: " + blockSurveillance,
|
|
||||||
"Block ads: " + blockAds,
|
|
||||||
"Allowed hostnames:\n |--" + strings.Join(d.AllowedHostnames, "\n |--"),
|
|
||||||
"Private addresses:\n |--" + strings.Join(d.PrivateAddresses, "\n |--"),
|
|
||||||
"Verbosity level: " + fmt.Sprintf("%d/5", d.VerbosityLevel),
|
|
||||||
"Verbosity details level: " + fmt.Sprintf("%d/4", d.VerbosityDetailsLevel),
|
|
||||||
"Validation log level: " + fmt.Sprintf("%d/2", d.ValidationLogLevel),
|
|
||||||
"IPv6 resolution: " + ipv6,
|
|
||||||
"Update: " + update,
|
|
||||||
"Keep nameserver (disabled blocking): " + keepNameserver,
|
|
||||||
}
|
|
||||||
return strings.Join(settingsList, "\n |--")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDNSSettings obtains DNS over TLS settings from environment variables using the params package.
|
// GetDNSSettings obtains DNS over TLS settings from environment variables using the params package.
|
||||||
@@ -87,22 +85,18 @@ func GetDNSSettings(paramsReader params.Reader) (settings DNS, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
if !settings.Enabled {
|
|
||||||
|
// Plain DNS settings
|
||||||
settings.PlaintextAddress, err = paramsReader.GetDNSPlaintext()
|
settings.PlaintextAddress, err = paramsReader.GetDNSPlaintext()
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.Providers, err = paramsReader.GetDNSOverTLSProviders()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
settings.AllowedHostnames, err = paramsReader.GetDNSUnblockedHostnames()
|
settings.KeepNameserver, err = paramsReader.GetDNSKeepNameserver()
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.Caching, err = paramsReader.GetDNSOverTLSCaching()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DNS over TLS external settings
|
||||||
settings.BlockMalicious, err = paramsReader.GetDNSMaliciousBlocking()
|
settings.BlockMalicious, err = paramsReader.GetDNSMaliciousBlocking()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
@@ -115,39 +109,21 @@ func GetDNSSettings(paramsReader params.Reader) (settings DNS, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
settings.VerbosityLevel, err = paramsReader.GetDNSOverTLSVerbosity()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.VerbosityDetailsLevel, err = paramsReader.GetDNSOverTLSVerbosityDetails()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.ValidationLogLevel, err = paramsReader.GetDNSOverTLSValidationLogLevel()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.PrivateAddresses, err = paramsReader.GetDNSOverTLSPrivateAddresses()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.IPv6, err = paramsReader.GetDNSOverTLSIPv6()
|
|
||||||
if err != nil {
|
|
||||||
return settings, err
|
|
||||||
}
|
|
||||||
settings.UpdatePeriod, err = paramsReader.GetDNSUpdatePeriod()
|
settings.UpdatePeriod, err = paramsReader.GetDNSUpdatePeriod()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
settings.KeepNameserver, err = paramsReader.GetDNSKeepNameserver()
|
|
||||||
|
// Unbound specific settings
|
||||||
|
settings.Unbound, err = getUnboundSettings(paramsReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consistency check
|
// Consistency check
|
||||||
IPv6Support := false
|
IPv6Support := false
|
||||||
for _, provider := range settings.Providers {
|
for _, provider := range settings.Unbound.Providers {
|
||||||
providerData, ok := constants.DNSProviderMapping()[provider]
|
providerData, ok := unbound.GetProviderData(provider)
|
||||||
switch {
|
switch {
|
||||||
case !ok:
|
case !ok:
|
||||||
return settings, fmt.Errorf("DNS provider %q does not have associated data", provider)
|
return settings, fmt.Errorf("DNS provider %q does not have associated data", provider)
|
||||||
@@ -157,8 +133,57 @@ func GetDNSSettings(paramsReader params.Reader) (settings DNS, err error) {
|
|||||||
IPv6Support = true
|
IPv6Support = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if settings.IPv6 && !IPv6Support {
|
if settings.Unbound.IPv6 && !IPv6Support {
|
||||||
return settings, fmt.Errorf("None of the DNS over TLS provider(s) set support IPv6")
|
return settings, fmt.Errorf("None of the DNS over TLS provider(s) set support IPv6")
|
||||||
}
|
}
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUnboundSettings(reader params.Reader) (settings unboundmodels.Settings, err error) {
|
||||||
|
settings.Providers, err = reader.GetDNSOverTLSProviders()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.ListeningPort = 53
|
||||||
|
settings.Caching, err = reader.GetDNSOverTLSCaching()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.IPv4 = true
|
||||||
|
settings.IPv6, err = reader.GetDNSOverTLSIPv6()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.VerbosityLevel, err = reader.GetDNSOverTLSVerbosity()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.VerbosityDetailsLevel, err = reader.GetDNSOverTLSVerbosityDetails()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.ValidationLogLevel, err = reader.GetDNSOverTLSValidationLogLevel()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.BlockedHostnames = []string{}
|
||||||
|
settings.BlockedIPs, err = reader.GetDNSOverTLSPrivateAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.AllowedHostnames, err = reader.GetDNSUnblockedHostnames()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
|
settings.AccessControl.Allowed = []net.IPNet{
|
||||||
|
{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Mask: net.IPv4Mask(0, 0, 0, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IP: net.IPv6zero,
|
||||||
|
Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|||||||
60
internal/settings/dns_test.go
Normal file
60
internal/settings/dns_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_DNS_Lines(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
settings DNS
|
||||||
|
lines []string
|
||||||
|
}{
|
||||||
|
"disabled DOT": {
|
||||||
|
settings: DNS{
|
||||||
|
PlaintextAddress: net.IP{1, 1, 1, 1},
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"DNS over TLS disabled, using plaintext DNS 1.1.1.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"enabled DOT": {
|
||||||
|
settings: DNS{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
lines: []string{
|
||||||
|
"DNS settings:",
|
||||||
|
" |--Unbound:",
|
||||||
|
" |--DNS over TLS provider:",
|
||||||
|
" |--Listening port: 0",
|
||||||
|
" |--Access control:",
|
||||||
|
" |--Allowed:",
|
||||||
|
" |--Caching: disabled",
|
||||||
|
" |--IPv4 resolution: disabled",
|
||||||
|
" |--IPv6 resolution: disabled",
|
||||||
|
" |--Verbosity level: 0/5",
|
||||||
|
" |--Verbosity details level: 0/4",
|
||||||
|
" |--Validation log level: 0/2",
|
||||||
|
" |--Blocked hostnames:",
|
||||||
|
" |--Blocked IP addresses:",
|
||||||
|
" |--Allowed hostnames:",
|
||||||
|
" |--Block malicious: disabled",
|
||||||
|
" |--Block ads: disabled",
|
||||||
|
" |--Block surveillance: disabled",
|
||||||
|
" |--Update: deactivated",
|
||||||
|
" |--Keep nameserver (disabled blocking): no",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
lines := testCase.settings.lines()
|
||||||
|
assert.Equal(t, testCase.lines, lines)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -53,11 +54,14 @@ func Test_resolveRepeat(t *testing.T) {
|
|||||||
}
|
}
|
||||||
const host = "blabla"
|
const host = "blabla"
|
||||||
i := 0
|
i := 0
|
||||||
|
mutex := &sync.Mutex{}
|
||||||
lookupIP := func(ctx context.Context, argHost string) (
|
lookupIP := func(ctx context.Context, argHost string) (
|
||||||
ips []net.IP, err error) {
|
ips []net.IP, err error) {
|
||||||
assert.Equal(t, host, argHost)
|
assert.Equal(t, host, argHost)
|
||||||
|
mutex.Lock()
|
||||||
result := testCase.lookupIPResult[i]
|
result := testCase.lookupIPResult[i]
|
||||||
i++
|
i++
|
||||||
|
mutex.Unlock()
|
||||||
return result, testCase.err
|
return result, testCase.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user