You've already forked bird-lg-go
mirror of
https://github.com/xddxdd/bird-lg-go
synced 2025-10-17 22:42:12 +02:00
Compare commits
140 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4a8c752157 | ||
![]() |
5ad6a4d35c | ||
![]() |
5422c8fd8c | ||
![]() |
5042980d79 | ||
![]() |
7884531a24 | ||
![]() |
cc804e81b6 | ||
![]() |
c9bab2ae2b | ||
![]() |
b9a4f95978 | ||
![]() |
7cd69746df | ||
![]() |
e719859d68 | ||
![]() |
5d06affefc | ||
![]() |
060fe9bf8e | ||
![]() |
f23d36f357 | ||
![]() |
1dbc0fccd2 | ||
![]() |
b2d64d19e3 | ||
![]() |
4dee4b0806 | ||
![]() |
cb279e0459 | ||
![]() |
31ba36beaf | ||
![]() |
5ab3b95d64 | ||
![]() |
7bf654f35f | ||
![]() |
5b33629a9d | ||
![]() |
0868c5d42c | ||
![]() |
bc61579e6a | ||
![]() |
e9750a8278 | ||
![]() |
d40dd3a4d3 | ||
![]() |
ffdeeac06e | ||
![]() |
7eb4d75bbf | ||
![]() |
6e5e190d32 | ||
![]() |
1b2573d87c | ||
![]() |
0d5337508b | ||
![]() |
b9094d3d6c | ||
![]() |
ec7f348418 | ||
![]() |
a632739443 | ||
![]() |
a9e278357a | ||
![]() |
e4c00c897f | ||
![]() |
4df3918b35 | ||
![]() |
45dc24470d | ||
![]() |
55ea5c3b28 | ||
![]() |
7eb44c3828 | ||
![]() |
124fdedbda | ||
![]() |
e6a98358b5 | ||
![]() |
761eb2160a | ||
![]() |
cc2a146a88 | ||
![]() |
3db9454350 | ||
![]() |
c30bed112c | ||
![]() |
af5ab3c78f | ||
![]() |
0fdde8afc7 | ||
![]() |
39a129db9d | ||
![]() |
0dd1c07b66 | ||
![]() |
f0f072c4a6 | ||
![]() |
657565857b | ||
![]() |
7ac2158e70 | ||
![]() |
5c433bc27a | ||
![]() |
1b0b923da9 | ||
![]() |
01438edaef | ||
![]() |
90f36610dc | ||
![]() |
6174208d07 | ||
![]() |
76174cdc08 | ||
![]() |
088bb6fe5a | ||
![]() |
3951eed011 | ||
![]() |
91c0a8962b | ||
![]() |
5f7850a903 | ||
![]() |
6a78cf2e80 | ||
![]() |
5b5a44bcb6 | ||
![]() |
ac31862237 | ||
![]() |
86129190ab | ||
![]() |
ff55064a20 | ||
![]() |
dbb02c04ed | ||
![]() |
c2b7de2e17 | ||
![]() |
c1b578e8db | ||
![]() |
7b0e5689d4 | ||
![]() |
3c46bda49d | ||
![]() |
32e00d2ce3 | ||
![]() |
a19750cdef | ||
![]() |
7f1cdaa4ee | ||
![]() |
2d2193041e | ||
![]() |
aad8ee98d7 | ||
![]() |
00b5c12787 | ||
![]() |
55a1eb54fd | ||
![]() |
0594edc69d | ||
![]() |
38bf6aba09 | ||
![]() |
d261c22235 | ||
![]() |
19aa8c77c5 | ||
![]() |
fe07ebb5a5 | ||
![]() |
66547ebfa9 | ||
![]() |
d253e4311b | ||
![]() |
026498ba2f | ||
![]() |
27c348a864 | ||
![]() |
43b4ad93dd | ||
![]() |
6176c45006 | ||
![]() |
47113184f4 | ||
![]() |
3c9a3e4339 | ||
![]() |
8457b18d46 | ||
![]() |
f8f64b03a6 | ||
![]() |
cc818c1cc0 | ||
![]() |
6224b43808 | ||
![]() |
17e0b14243 | ||
![]() |
b4c1bed9ba | ||
![]() |
abb32abff3 | ||
![]() |
b368c75aa3 | ||
![]() |
09405cdb38 | ||
![]() |
f999d47d9f | ||
![]() |
005dfb1435 | ||
![]() |
4bd7a6bb95 | ||
![]() |
462d76a2d0 | ||
![]() |
58f217578c | ||
![]() |
0e95727de1 | ||
![]() |
a48f1c8040 | ||
![]() |
81acde3a37 | ||
![]() |
7c0fe0d512 | ||
![]() |
a5f4452d02 | ||
![]() |
b237185ef7 | ||
![]() |
e949646790 | ||
![]() |
bb479d22ae | ||
![]() |
d40f41b4d5 | ||
![]() |
cdc34704b5 | ||
![]() |
db58bd3354 | ||
![]() |
a0246ccee2 | ||
![]() |
ccd14af0c8 | ||
![]() |
594ca80f50 | ||
![]() |
5625058e71 | ||
![]() |
7efa3237a9 | ||
![]() |
7b0c8c0556 | ||
![]() |
ffd9165062 | ||
![]() |
24fd5203e8 | ||
![]() |
49a05767c1 | ||
![]() |
e7010f75f8 | ||
![]() |
dba2af7634 | ||
![]() |
049775319b | ||
![]() |
47c66b125c | ||
![]() |
9e17b116f1 | ||
![]() |
335ad40634 | ||
![]() |
6ec0f2e7a6 | ||
![]() |
4b73cf0fcb | ||
![]() |
3b1d001543 | ||
![]() |
675cb26ed1 | ||
![]() |
556d3e50d3 | ||
![]() |
06796f546e | ||
![]() |
d029d6684c | ||
![]() |
5ce0f55f35 |
@@ -1,217 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
workflows:
|
||||
docker:
|
||||
jobs:
|
||||
- build
|
||||
- docker-frontend:
|
||||
context:
|
||||
- docker
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
- docker-frontend-deploy:
|
||||
context:
|
||||
- docker
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- docker-proxy:
|
||||
context:
|
||||
- docker
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
- docker-proxy-deploy:
|
||||
context:
|
||||
- docker
|
||||
requires:
|
||||
- build
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cimg/go:1.17
|
||||
working_directory: /home/circleci/go/src/github.com/xddxdd/bird-lg-go
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Test frontend
|
||||
command: |
|
||||
export GO111MODULE=on
|
||||
cd frontend
|
||||
go get -v -t -d ./...
|
||||
go test -v ./...
|
||||
- run:
|
||||
name: Test proxy
|
||||
command: |
|
||||
export GO111MODULE=on
|
||||
cd proxy
|
||||
go get -v -t -d ./...
|
||||
go test -v ./...
|
||||
|
||||
docker-frontend:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
environment:
|
||||
BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install buildx
|
||||
command: |
|
||||
BUILDX_BINARY_URL="https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-amd64"
|
||||
|
||||
curl --output docker-buildx \
|
||||
--silent --show-error --location --fail --retry 3 \
|
||||
"$BUILDX_BINARY_URL"
|
||||
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
||||
mv docker-buildx ~/.docker/cli-plugins/
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
docker buildx install
|
||||
# Run binfmt
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- run:
|
||||
name: Build Docker image
|
||||
environment:
|
||||
BUILD_ID: << pipeline.number >>
|
||||
command: |
|
||||
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
|
||||
docker buildx create --name mybuilder --use
|
||||
docker buildx build \
|
||||
--platform $BUILDX_PLATFORMS \
|
||||
-t $DOCKER_USERNAME/bird-lg-go:circleci-build$BUILD_ID \
|
||||
--progress plain \
|
||||
frontend
|
||||
|
||||
docker-frontend-deploy:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
environment:
|
||||
BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install buildx
|
||||
command: |
|
||||
BUILDX_BINARY_URL="https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-amd64"
|
||||
|
||||
curl --output docker-buildx \
|
||||
--silent --show-error --location --fail --retry 3 \
|
||||
"$BUILDX_BINARY_URL"
|
||||
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
||||
mv docker-buildx ~/.docker/cli-plugins/
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
docker buildx install
|
||||
# Run binfmt
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- run:
|
||||
name: Build Docker image
|
||||
environment:
|
||||
BUILD_ID: << pipeline.number >>
|
||||
command: |
|
||||
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
|
||||
docker buildx create --name mybuilder --use
|
||||
docker buildx build \
|
||||
--platform $BUILDX_PLATFORMS \
|
||||
-t $DOCKER_USERNAME/bird-lg-go:circleci-build$BUILD_ID \
|
||||
--progress plain \
|
||||
--push frontend
|
||||
docker buildx build \
|
||||
--platform $BUILDX_PLATFORMS \
|
||||
-t $DOCKER_USERNAME/bird-lg-go:latest \
|
||||
--progress plain \
|
||||
--push frontend
|
||||
|
||||
docker-proxy:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
environment:
|
||||
BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install buildx
|
||||
command: |
|
||||
BUILDX_BINARY_URL="https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-amd64"
|
||||
|
||||
curl --output docker-buildx \
|
||||
--silent --show-error --location --fail --retry 3 \
|
||||
"$BUILDX_BINARY_URL"
|
||||
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
||||
mv docker-buildx ~/.docker/cli-plugins/
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
docker buildx install
|
||||
# Run binfmt
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- run:
|
||||
name: Build Docker image
|
||||
environment:
|
||||
BUILD_ID: << pipeline.number >>
|
||||
command: |
|
||||
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
|
||||
docker buildx create --name mybuilder --use
|
||||
docker buildx build \
|
||||
--platform $BUILDX_PLATFORMS \
|
||||
-t $DOCKER_USERNAME/bird-lgproxy-go:circleci-build$BUILD_ID \
|
||||
--progress plain \
|
||||
proxy
|
||||
|
||||
docker-proxy-deploy:
|
||||
machine:
|
||||
image: ubuntu-2004:202111-02
|
||||
environment:
|
||||
BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Install buildx
|
||||
command: |
|
||||
BUILDX_BINARY_URL="https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-amd64"
|
||||
|
||||
curl --output docker-buildx \
|
||||
--silent --show-error --location --fail --retry 3 \
|
||||
"$BUILDX_BINARY_URL"
|
||||
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
|
||||
mv docker-buildx ~/.docker/cli-plugins/
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
|
||||
docker buildx install
|
||||
# Run binfmt
|
||||
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- run:
|
||||
name: Build Docker image
|
||||
environment:
|
||||
BUILD_ID: << pipeline.number >>
|
||||
command: |
|
||||
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
|
||||
docker buildx create --name mybuilder --use
|
||||
docker buildx build \
|
||||
--platform $BUILDX_PLATFORMS \
|
||||
-t $DOCKER_USERNAME/bird-lgproxy-go:circleci-build$BUILD_ID \
|
||||
--push proxy
|
||||
docker buildx build \
|
||||
--platform $BUILDX_PLATFORMS \
|
||||
-t $DOCKER_USERNAME/bird-lgproxy-go:latest \
|
||||
--progress plain \
|
||||
--push proxy
|
16
.github/workflows/auto-merge.yml
vendored
Normal file
16
.github/workflows/auto-merge.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
name: auto-merge
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
name: Dependabot Auto Merge
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
target: minor
|
||||
github-token: ${{ secrets.AUTOMERGE_TOKEN }}
|
127
.github/workflows/develop.yaml
vendored
Normal file
127
.github/workflows/develop.yaml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
go-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v4
|
||||
|
||||
- name: Run frontend unit test
|
||||
run: |
|
||||
export GO111MODULE=on
|
||||
cd frontend
|
||||
go get -v -t -d ./...
|
||||
go test -v ./...
|
||||
cd ..
|
||||
|
||||
- name: Run proxy unit test
|
||||
run: |
|
||||
export GO111MODULE=on
|
||||
cd proxy
|
||||
go get -v -t -d ./...
|
||||
go test -v ./...
|
||||
cd ..
|
||||
|
||||
docker-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Test whois binary in frontend image
|
||||
run: |
|
||||
docker build -t local/frontend frontend/
|
||||
docker run --rm --net host --entrypoint whois local/frontend -I github.com || exit 1
|
||||
docker run --rm --net host --entrypoint whois local/frontend -h whois.ripe.net github.com || exit 1
|
||||
docker run --rm --net host --entrypoint whois local/frontend -h whois.ripe.net:43 github.com || exit 1
|
||||
|
||||
- name: Test traceroute binary in proxy image
|
||||
run: |
|
||||
docker build -t local/proxy proxy/
|
||||
docker run --rm --net host --entrypoint traceroute local/proxy 127.0.0.1 || exit 1
|
||||
docker run --rm --net host --entrypoint traceroute local/proxy ::1 || exit 1
|
||||
|
||||
- name: Test mtr binary in proxy image
|
||||
run: |
|
||||
docker build -t local/proxy:mtr -f proxy/Dockerfile.mtr proxy/
|
||||
docker run --rm --net host --entrypoint mtr local/proxy:mtr -w -c1 -Z1 -G1 -b 127.0.0.1 || exit 1
|
||||
docker run --rm --net host --entrypoint mtr local/proxy:mtr -w -c1 -Z1 -G1 -b ::1 || exit 1
|
||||
|
||||
docker-develop:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- go-test
|
||||
- docker-test
|
||||
if: github.event_name != 'pull_request'
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build frontend docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: '{{defaultContext}}:frontend'
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
xddxdd/bird-lg-go:develop
|
||||
xddxdd/bird-lg-go:develop-${{ github.sha }}
|
||||
ghcr.io/xddxdd/bird-lg-go:frontend-develop
|
||||
ghcr.io/xddxdd/bird-lg-go:frontend-develop-${{ github.sha }}
|
||||
|
||||
- name: Build proxy docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: '{{defaultContext}}:proxy'
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
xddxdd/bird-lgproxy-go:develop
|
||||
xddxdd/bird-lgproxy-go:develop-${{ github.sha }}
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-develop
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-develop-${{ github.sha }}
|
||||
|
||||
- name: Build proxy docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: '{{defaultContext}}:proxy'
|
||||
file: 'Dockerfile.mtr'
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
xddxdd/bird-lgproxy-go:develop-mtr
|
||||
xddxdd/bird-lgproxy-go:develop-${{ github.sha }}-mtr
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-develop-mtr
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-develop-${{ github.sha }}-mtr
|
73
.github/workflows/release.yaml
vendored
73
.github/workflows/release.yaml
vendored
@@ -3,10 +3,11 @@ on:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
releases-matrix:
|
||||
go-release:
|
||||
name: Release Go Binary
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, "arm", arm64]
|
||||
@@ -18,18 +19,82 @@ jobs:
|
||||
- goarch: "arm"
|
||||
goos: windows
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: wangyoucao577/go-release-action@v1.30
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Release frontend
|
||||
uses: wangyoucao577/go-release-action@v1.40
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
project_path: "./frontend"
|
||||
binary_name: "bird-lg-go"
|
||||
- uses: wangyoucao577/go-release-action@v1.30
|
||||
|
||||
- name: Release proxy
|
||||
uses: wangyoucao577/go-release-action@v1.40
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
project_path: "./proxy"
|
||||
binary_name: "bird-lgproxy-go"
|
||||
|
||||
docker-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build frontend docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: '{{defaultContext}}:frontend'
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
xddxdd/bird-lg-go:latest
|
||||
xddxdd/bird-lg-go:${{ github.event.release.tag_name }}
|
||||
ghcr.io/xddxdd/bird-lg-go:frontend
|
||||
ghcr.io/xddxdd/bird-lg-go:frontend-${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Build proxy docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: '{{defaultContext}}:proxy'
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
xddxdd/bird-lgproxy-go:latest
|
||||
xddxdd/bird-lgproxy-go:${{ github.event.release.tag_name }}
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-${{ github.event.release.tag_name }}
|
||||
|
||||
- name: Build proxy docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: '{{defaultContext}}:proxy'
|
||||
file: 'Dockerfile.mtr'
|
||||
platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
xddxdd/bird-lgproxy-go:latest-mtr
|
||||
xddxdd/bird-lgproxy-go:${{ github.event.release.tag_name }}-mtr
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-mtr
|
||||
ghcr.io/xddxdd/bird-lg-go:proxy-${{ github.event.release.tag_name }}-mtr
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,7 +20,3 @@ proxy/proxy
|
||||
|
||||
# don't include generated bindata file
|
||||
frontend/bindata.go
|
||||
|
||||
# don't include generated Dockerfiles
|
||||
frontend/Dockerfile.*
|
||||
proxy/Dockerfile.*
|
37
README.md
37
README.md
@@ -58,15 +58,13 @@ Configuration can be set in:
|
||||
|
||||
Configuration is handled by [viper](https://github.com/spf13/viper), any config format supported by it can be used.
|
||||
|
||||
> Note: the config system is replaced with viper only recently (2022-07-08). If some config items do not work, please open an issue, and use commit [892a7bee22a1bb02d3b4da6d270c65b6e4e1321a](https://github.com/xddxdd/bird-lg-go/tree/892a7bee22a1bb02d3b4da6d270c65b6e4e1321a) (last version before config system replace) for the time being.
|
||||
|
||||
| Config Key | Parameter | Environment Variable | Description |
|
||||
| ---------- | --------- | -------------------- | ----------- |
|
||||
| servers | --servers | BIRDLG_SERVERS | server name prefixes, separated by comma |
|
||||
| domain | --domain | BIRDLG_DOMAIN | server name domain suffixes |
|
||||
| listen | --listen | BIRDLG_LISTEN | address bird-lg is listening on (default "5000") |
|
||||
| proxy_port | --proxy-port | BIRDLG_PROXY_PORT | port bird-lgproxy is running on (default 8000) |
|
||||
| whois | --whois | BIRDLG_WHOIS | whois server for queries (default "whois.verisign-grs.com") |
|
||||
| whois | --whois | BIRDLG_WHOIS | whois server for queries (default "whois.verisign-grs.com"). Start with "/" to spacify local whois binary("/usr/local/whois"). |
|
||||
| dns_interface | --dns-interface | BIRDLG_DNS_INTERFACE | dns zone to query ASN information (default "asn.cymru.com") |
|
||||
| bgpmap_info | --bgpmap-info | BIRDLG_BGPMAP_INFO | the infos displayed in bgpmap, separated by comma, start with `:` means allow multiline (default "asn,as-name,ASName,descr") |
|
||||
| title_brand | --title-brand | BIRDLG_TITLE_BRAND | prefix of page titles in browser tabs (default "Bird-lg Go") |
|
||||
@@ -79,6 +77,8 @@ Configuration is handled by [viper](https://github.com/spf13/viper), any config
|
||||
| name_filter | --name-filter | BIRDLG_NAME_FILTER | protocol names to hide in summary tables (RE2 syntax); defaults to none if not set |
|
||||
| timeout | --time-out | BIRDLG_TIMEOUT | time before request timed out, in seconds; defaults to 120 if not set |
|
||||
|
||||
### Examples
|
||||
|
||||
Example: the following command starts the frontend with 2 BIRD nodes, with domain name "gigsgigscloud.dn42.lantian.pub" and "hostdare.dn42.lantian.pub", and proxies are running on port 8000 on both nodes.
|
||||
|
||||
```bash
|
||||
@@ -90,7 +90,8 @@ Example: the following docker-compose.yml entry does the same as above, but by s
|
||||
```yaml
|
||||
services:
|
||||
bird-lg:
|
||||
image: xddxdd/bird-lg-go
|
||||
# Use xddxdd/bird-lg-go:develop for the latest build from master branch
|
||||
image: xddxdd/bird-lg-go:latest
|
||||
container_name: bird-lg
|
||||
restart: always
|
||||
environment:
|
||||
@@ -122,16 +123,32 @@ Configuration can be set in:
|
||||
|
||||
Configuration is handled by [viper](https://github.com/spf13/viper), any config format supported by it can be used.
|
||||
|
||||
> Note: the config system is replaced with viper only recently (2022-07-08). If some config items do not work, please open an issue, and use commit [892a7bee22a1bb02d3b4da6d270c65b6e4e1321a](https://github.com/xddxdd/bird-lg-go/tree/892a7bee22a1bb02d3b4da6d270c65b6e4e1321a) (last version before config system replace) for the time being.
|
||||
|
||||
| Config Key | Parameter | Environment Variable | Description |
|
||||
| ---------- | --------- | -------------------- | ----------- |
|
||||
| allowed_ips | --allowed | ALLOWED_IPS | IPs allowed to access this proxy, separated by commas. Don't set to allow all IPs. (default "") |
|
||||
| allowed_ips | --allowed | ALLOWED_IPS | IPs or networks allowed to access this proxy, separated by commas. Don't set to allow all IPs. (default "") |
|
||||
| bird_socket | --bird | BIRD_SOCKET | socket file for bird, set either in parameter or environment variable BIRD_SOCKET (default "/var/run/bird/bird.ctl") |
|
||||
| listen | --listen | BIRDLG_PROXY_PORT | listen address, set either in parameter or environment variable BIRDLG_PROXY_PORT(default "8000") |
|
||||
| traceroute_bin | --traceroute_bin | BIRDLG_TRACEROUTE_BIN | traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN(default "traceroute") |
|
||||
| traceroute_bin | --traceroute_bin | BIRDLG_TRACEROUTE_BIN | traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN |
|
||||
| traceroute_flags | --traceroute_flags | BIRDLG_TRACEROUTE_FLAGS | traceroute flags, supports multiple flags separated with space. |
|
||||
| traceroute_raw | --traceroute_raw | BIRDLG_TRACEROUTE_RAW | whether to display traceroute outputs raw (default false) |
|
||||
|
||||
### Traceroute Binary Autodetection
|
||||
|
||||
If `traceroute_bin` or `traceroute_flags` is not set, then on startup, the proxy will try to `traceroute 127.0.0.1` with different traceroute binaries and arguments, in order to use the most optimized setting available, while maintaining compatibility with multiple variants of traceroute binaries.
|
||||
|
||||
Traceroute binaries will be autodetected in the following order:
|
||||
|
||||
1. If `traceroute_bin` is set:
|
||||
1. `[traceroute_bin] -q1 -N32 -w1 127.0.0.1` (Corresponds to Traceroute on Debian)
|
||||
2. `[traceroute_bin] -q1 -w1 127.0.0.1` (Corresponds to Traceroute on FreeBSD)
|
||||
3. `[traceroute_bin] 127.0.0.1` (Corresponds to Busybox Traceroute)
|
||||
2. `mtr -w -c1 -Z1 -G1 -b 127.0.0.1` (MTR)
|
||||
3. `traceroute -q1 -N32 -w1 127.0.0.1` (Corresponds to Traceroute on Debian)
|
||||
4. `traceroute -q1 -w1 127.0.0.1` (Corresponds to Traceroute on FreeBSD)
|
||||
5. `traceroute 127.0.0.1` (Corresponds to Busybox Traceroute)
|
||||
|
||||
### Examples
|
||||
|
||||
Example: start proxy with default configuration, should work "out of the box" on Debian 9 with BIRDv1:
|
||||
|
||||
```bash
|
||||
@@ -149,7 +166,9 @@ Example: the following docker-compose.yml entry does the same as above, but by s
|
||||
```yaml
|
||||
services:
|
||||
bird-lgproxy:
|
||||
image: xddxdd/bird-lgproxy-go
|
||||
# Use xddxdd/bird-lgproxy-go:develop for the latest build from master branch
|
||||
# Use xddxdd/bird-lgproxy-go:latest-mtr to use MTR instead of Traceroute
|
||||
image: xddxdd/bird-lgproxy-go:latest
|
||||
container_name: bird-lgproxy
|
||||
restart: always
|
||||
volumes:
|
||||
|
16
docs/API.md
16
docs/API.md
@@ -153,9 +153,11 @@ Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": [],
|
||||
"type": "server_list",
|
||||
"args": ""
|
||||
"servers": [
|
||||
"alpha"
|
||||
],
|
||||
"type": "bird",
|
||||
"args": "show status"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -179,11 +181,9 @@ Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": [
|
||||
"alpha"
|
||||
],
|
||||
"type": "bird",
|
||||
"args": "show status"
|
||||
"servers": [],
|
||||
"type": "server_list",
|
||||
"args": ""
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
FROM golang:buster AS step_0
|
||||
FROM golang AS step_0
|
||||
ENV CGO_ENABLED=0 GO111MODULE=on
|
||||
WORKDIR /root
|
||||
COPY . .
|
||||
@@ -6,6 +6,28 @@ RUN go build -ldflags "-w -s" -o /frontend
|
||||
|
||||
################################################################################
|
||||
|
||||
FROM scratch AS step_1
|
||||
FROM alpine:edge AS step_1
|
||||
|
||||
WORKDIR /root
|
||||
RUN apk add --no-cache build-base pkgconf perl gettext \
|
||||
libidn2-dev libidn2-static libunistring-dev libunistring-static gnu-libiconv-dev
|
||||
|
||||
RUN wget https://github.com/rfc1036/whois/archive/refs/tags/v5.5.18.tar.gz \
|
||||
-O whois-5.5.18.tar.gz
|
||||
|
||||
RUN tar xvf whois-5.5.18.tar.gz \
|
||||
&& cd whois-5.5.18 \
|
||||
&& sed -i "s/#if defined _POSIX_C_SOURCE && _POSIX_C_SOURCE >= 200112L/#if 1/g" config.h \
|
||||
&& make whois -j4 \
|
||||
LDFLAGS="-static" CONFIG_FILE="/etc/whois.conf" PKG_CONFIG="pkg-config --static" HAVE_ICONV=1 \
|
||||
&& strip /root/whois-5.5.18/whois
|
||||
|
||||
################################################################################
|
||||
|
||||
FROM scratch AS step_2
|
||||
ENV PATH=/
|
||||
ENV BIRDLG_WHOIS=/whois
|
||||
COPY --from=step_0 /frontend /
|
||||
COPY --from=step_1 /root/whois-5.5.18/whois /
|
||||
COPY --from=step_1 /etc/services /etc/services
|
||||
ENTRYPOINT ["/frontend"]
|
||||
|
@@ -20,6 +20,7 @@ type apiGenericResultPair struct {
|
||||
type apiSummaryResultPair struct {
|
||||
Server string `json:"server"`
|
||||
Data []SummaryRowData `json:"data"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type apiResponse struct {
|
||||
@@ -70,9 +71,12 @@ func apiSummaryHandler(request apiRequest) apiResponse {
|
||||
for i, result := range results {
|
||||
parsedSummary, err := summaryParse(result, request.Servers[i])
|
||||
if err != nil {
|
||||
return apiResponse{
|
||||
Error: err.Error(),
|
||||
}
|
||||
response.Result = append(response.Result, &apiSummaryResultPair{
|
||||
Server: request.Servers[i],
|
||||
Data: []SummaryRowData{},
|
||||
Error: err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
response.Result = append(response.Result, &apiSummaryResultPair{
|
||||
@@ -113,7 +117,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
handler := apiHandlerMap[request.Type]
|
||||
if handler == nil {
|
||||
response = apiErrorHandler(errors.New("Invalid request type"))
|
||||
response = apiErrorHandler(errors.New("invalid request type"))
|
||||
} else {
|
||||
response = handler(request)
|
||||
}
|
||||
|
211
frontend/api_test.go
Normal file
211
frontend/api_test.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/magiconair/properties/assert"
|
||||
)
|
||||
|
||||
func TestApiServerListHandler(t *testing.T) {
|
||||
setting.servers = []string{"alpha", "beta", "gamma"}
|
||||
response := apiServerListHandler(apiRequest{})
|
||||
|
||||
assert.Equal(t, len(response.Result), 3)
|
||||
assert.Equal(t, response.Result[0].(apiGenericResultPair).Server, "alpha")
|
||||
assert.Equal(t, response.Result[1].(apiGenericResultPair).Server, "beta")
|
||||
assert.Equal(t, response.Result[2].(apiGenericResultPair).Server, "gamma")
|
||||
}
|
||||
|
||||
func TestApiGenericHandlerFactory(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, BirdSummaryData)
|
||||
httpmock.RegisterResponder("GET", "http://alpha:8000/bird?q="+url.QueryEscape("show protocols"), httpResponse)
|
||||
|
||||
setting.servers = []string{"alpha"}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
|
||||
request := apiRequest{
|
||||
Servers: setting.servers,
|
||||
Type: "bird",
|
||||
Args: "show protocols",
|
||||
}
|
||||
|
||||
handler := apiGenericHandlerFactory("bird")
|
||||
response := handler(request)
|
||||
|
||||
assert.Equal(t, response.Error, "")
|
||||
|
||||
result := response.Result[0].(*apiGenericResultPair)
|
||||
assert.Equal(t, result.Server, "alpha")
|
||||
assert.Equal(t, result.Data, BirdSummaryData)
|
||||
}
|
||||
|
||||
func TestApiSummaryHandler(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, BirdSummaryData)
|
||||
httpmock.RegisterResponder("GET", "http://alpha:8000/bird?q="+url.QueryEscape("show protocols"), httpResponse)
|
||||
|
||||
setting.servers = []string{"alpha"}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
|
||||
request := apiRequest{
|
||||
Servers: setting.servers,
|
||||
Type: "summary",
|
||||
Args: "",
|
||||
}
|
||||
response := apiSummaryHandler(request)
|
||||
|
||||
assert.Equal(t, response.Error, "")
|
||||
|
||||
summary := response.Result[0].(*apiSummaryResultPair)
|
||||
assert.Equal(t, summary.Server, "alpha")
|
||||
assert.Equal(t, len(summary.Data), 7)
|
||||
// Protocol list will be sorted
|
||||
assert.Equal(t, summary.Data[0].Name, "device1")
|
||||
assert.Equal(t, summary.Data[0].Proto, "Device")
|
||||
assert.Equal(t, summary.Data[0].Table, "---")
|
||||
assert.Equal(t, summary.Data[0].State, "up")
|
||||
assert.Equal(t, summary.Data[0].Since, "2021-08-27")
|
||||
assert.Equal(t, summary.Data[0].Info, "")
|
||||
}
|
||||
|
||||
func TestApiSummaryHandlerError(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, "Mock backend error")
|
||||
httpmock.RegisterResponder("GET", "http://alpha:8000/bird?q="+url.QueryEscape("show protocols"), httpResponse)
|
||||
|
||||
setting.servers = []string{"alpha"}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
|
||||
request := apiRequest{
|
||||
Servers: setting.servers,
|
||||
Type: "summary",
|
||||
Args: "",
|
||||
}
|
||||
response := apiSummaryHandler(request)
|
||||
|
||||
assert.Equal(t, response.Error, "")
|
||||
|
||||
summary := response.Result[0].(*apiSummaryResultPair)
|
||||
assert.Equal(t, summary.Error, "Mock backend error")
|
||||
}
|
||||
|
||||
func TestApiWhoisHandler(t *testing.T) {
|
||||
expectedData := "Mock Data"
|
||||
server := WhoisServer{
|
||||
t: t,
|
||||
expectedQuery: "AS6939",
|
||||
response: expectedData,
|
||||
}
|
||||
|
||||
server.Listen()
|
||||
go server.Run()
|
||||
defer server.Close()
|
||||
|
||||
setting.whoisServer = server.server.Addr().String()
|
||||
|
||||
request := apiRequest{
|
||||
Servers: []string{},
|
||||
Type: "",
|
||||
Args: "AS6939",
|
||||
}
|
||||
response := apiWhoisHandler(request)
|
||||
|
||||
assert.Equal(t, response.Error, "")
|
||||
|
||||
whoisResult := response.Result[0].(apiGenericResultPair)
|
||||
assert.Equal(t, whoisResult.Server, "")
|
||||
assert.Equal(t, whoisResult.Data, expectedData)
|
||||
}
|
||||
|
||||
func TestApiErrorHandler(t *testing.T) {
|
||||
err := errors.New("Mock Error")
|
||||
response := apiErrorHandler(err)
|
||||
assert.Equal(t, response.Error, "Mock Error")
|
||||
}
|
||||
|
||||
func TestApiHandler(t *testing.T) {
|
||||
setting.servers = []string{"alpha", "beta", "gamma"}
|
||||
|
||||
request := apiRequest{
|
||||
Servers: []string{},
|
||||
Type: "server_list",
|
||||
Args: "",
|
||||
}
|
||||
requestJson, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/api", bytes.NewReader(requestJson))
|
||||
w := httptest.NewRecorder()
|
||||
apiHandler(w, r)
|
||||
|
||||
var response apiResponse
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(response.Result), 3)
|
||||
// Hard to unmarshal JSON into apiGenericResultPair objects, won't check here
|
||||
}
|
||||
|
||||
func TestApiHandlerBadJSON(t *testing.T) {
|
||||
setting.servers = []string{"alpha", "beta", "gamma"}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/api", strings.NewReader("{bad json}"))
|
||||
w := httptest.NewRecorder()
|
||||
apiHandler(w, r)
|
||||
|
||||
var response apiResponse
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(response.Result), 0)
|
||||
}
|
||||
|
||||
func TestApiHandlerInvalidType(t *testing.T) {
|
||||
setting.servers = []string{"alpha", "beta", "gamma"}
|
||||
|
||||
request := apiRequest{
|
||||
Servers: setting.servers,
|
||||
Type: "invalid_type",
|
||||
Args: "",
|
||||
}
|
||||
requestJson, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
r := httptest.NewRequest(http.MethodGet, "/api", bytes.NewReader(requestJson))
|
||||
w := httptest.NewRecorder()
|
||||
apiHandler(w, r)
|
||||
|
||||
var response apiResponse
|
||||
err = json.Unmarshal(w.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(response.Result), 0)
|
||||
}
|
83
frontend/asn_cache.go
Normal file
83
frontend/asn_cache.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ASNCache map[string]string
|
||||
|
||||
func (cache ASNCache) _lookup(asn string) string {
|
||||
// Try to get ASN representation using DNS
|
||||
if setting.dnsInterface != "" {
|
||||
records, err := net.LookupTXT(fmt.Sprintf("AS%s.%s", asn, setting.dnsInterface))
|
||||
if err == nil {
|
||||
result := strings.Join(records, " ")
|
||||
if resultSplit := strings.Split(result, " | "); len(resultSplit) > 1 {
|
||||
result = strings.Join(resultSplit[1:], "\n")
|
||||
}
|
||||
return fmt.Sprintf("AS%s\n%s", asn, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get ASN representation using WHOIS
|
||||
if setting.whoisServer != "" {
|
||||
if setting.bgpmapInfo == "" {
|
||||
setting.bgpmapInfo = "asn,as-name,ASName,descr"
|
||||
}
|
||||
records := whois(fmt.Sprintf("AS%s", asn))
|
||||
if records != "" {
|
||||
recordsSplit := strings.Split(records, "\n")
|
||||
var result []string
|
||||
for _, title := range strings.Split(setting.bgpmapInfo, ",") {
|
||||
if title == "asn" {
|
||||
result = append(result, "AS"+asn)
|
||||
}
|
||||
}
|
||||
for _, title := range strings.Split(setting.bgpmapInfo, ",") {
|
||||
allow_multiline := false
|
||||
if title[0] == ':' && len(title) >= 2 {
|
||||
title = title[1:]
|
||||
allow_multiline = true
|
||||
}
|
||||
for _, line := range recordsSplit {
|
||||
if len(line) == 0 || line[0] == '%' || !strings.Contains(line, ":") {
|
||||
continue
|
||||
}
|
||||
linearr := strings.SplitN(line, ":", 2)
|
||||
line_title := linearr[0]
|
||||
content := strings.TrimSpace(linearr[1])
|
||||
if line_title != title {
|
||||
continue
|
||||
}
|
||||
result = append(result, content)
|
||||
if !allow_multiline {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if len(result) > 0 {
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cache ASNCache) Lookup(asn string) string {
|
||||
cachedValue, cacheOk := cache[asn]
|
||||
if cacheOk {
|
||||
return cachedValue
|
||||
}
|
||||
|
||||
result := cache._lookup(asn)
|
||||
if len(result) == 0 {
|
||||
result = fmt.Sprintf("AS%s", asn)
|
||||
}
|
||||
|
||||
cache[asn] = result
|
||||
return result
|
||||
}
|
52
frontend/asn_cache_test.go
Normal file
52
frontend/asn_cache_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
)
|
||||
|
||||
func TestGetASNRepresentationDNS(t *testing.T) {
|
||||
checkNetwork(t)
|
||||
|
||||
setting.dnsInterface = "asn.cymru.com"
|
||||
setting.whoisServer = ""
|
||||
cache := make(ASNCache)
|
||||
result := cache.Lookup("6939")
|
||||
if !strings.Contains(result, "HURRICANE") {
|
||||
t.Errorf("Lookup AS6939 failed, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetASNRepresentationDNSFallback(t *testing.T) {
|
||||
checkNetwork(t)
|
||||
|
||||
setting.dnsInterface = "invalid.example.com"
|
||||
setting.whoisServer = "whois.arin.net"
|
||||
cache := make(ASNCache)
|
||||
result := cache.Lookup("6939")
|
||||
if !strings.Contains(result, "HURRICANE") {
|
||||
t.Errorf("Lookup AS6939 failed, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetASNRepresentationWhois(t *testing.T) {
|
||||
checkNetwork(t)
|
||||
|
||||
setting.dnsInterface = ""
|
||||
setting.whoisServer = "whois.arin.net"
|
||||
cache := make(ASNCache)
|
||||
result := cache.Lookup("6939")
|
||||
if !strings.Contains(result, "HURRICANE") {
|
||||
t.Errorf("Lookup AS6939 failed, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetASNRepresentationFallback(t *testing.T) {
|
||||
setting.dnsInterface = ""
|
||||
setting.whoisServer = ""
|
||||
cache := make(ASNCache)
|
||||
result := cache.Lookup("6939")
|
||||
assert.Equal(t, result, "AS6939")
|
||||
}
|
73
frontend/assets/static/sortTable.js
Normal file
73
frontend/assets/static/sortTable.js
Normal file
@@ -0,0 +1,73 @@
|
||||
// adapted from https://stackoverflow.com/a/57080195
|
||||
|
||||
document.querySelectorAll('table.sortable')
|
||||
.forEach((table)=> {
|
||||
table.querySelectorAll('th')
|
||||
.forEach((element, columnNo) => {
|
||||
element.addEventListener('click', event => {
|
||||
if(element.classList.contains('ascSorted')) {
|
||||
dir = -1;
|
||||
element.classList.remove('ascSorted');
|
||||
element.classList.add('descSorted');
|
||||
element.innerText = element.innerText.slice(0,-2) + " ↓";
|
||||
} else if(element.classList.contains('descSorted')) {
|
||||
dir = 1;
|
||||
element.classList.remove('descSorted');
|
||||
element.classList.add('ascSorted');
|
||||
element.innerText = element.innerText.slice(0,-2) + " ↑";
|
||||
} else {
|
||||
dir = 1;
|
||||
element.classList.add('ascSorted');
|
||||
element.innerText += " ↑";
|
||||
}
|
||||
sortTable(table, columnNo, 0, dir, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function sortTable(table, priCol, secCol, priDir, secDir) {
|
||||
const tableBody = table.querySelector('tbody');
|
||||
const tableData = table2data(tableBody);
|
||||
tableData.sort((a, b) => {
|
||||
if(a[priCol] === b[priCol]) {
|
||||
if(a[secCol] > b[secCol]) {
|
||||
return secDir;
|
||||
} else {
|
||||
return -secDir;
|
||||
}
|
||||
} else if(a[priCol] > b[priCol]) {
|
||||
return priDir;
|
||||
} else {
|
||||
return -priDir;
|
||||
}
|
||||
});
|
||||
data2table(tableBody, tableData);
|
||||
}
|
||||
|
||||
function table2data(tableBody) {
|
||||
const tableData = [];
|
||||
tableBody.querySelectorAll('tr')
|
||||
.forEach(row => {
|
||||
const rowData = [];
|
||||
row.querySelectorAll('td')
|
||||
.forEach(cell => {
|
||||
rowData.push(cell.innerHTML);
|
||||
});
|
||||
rowData.classList = row.classList.toString();
|
||||
tableData.push(rowData);
|
||||
});
|
||||
return tableData;
|
||||
}
|
||||
|
||||
function data2table(tableBody, tableData) {
|
||||
tableBody.querySelectorAll('tr')
|
||||
.forEach((row, i) => {
|
||||
const rowData = tableData[i];
|
||||
row.classList = rowData.classList;
|
||||
row.querySelectorAll('td')
|
||||
.forEach((cell, j) => {
|
||||
cell.innerHTML = rowData[j];
|
||||
});
|
||||
tableData.push(rowData);
|
||||
});
|
||||
}
|
@@ -5,8 +5,19 @@
|
||||
<script src="/static/jsdelivr/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script>
|
||||
<script src="/static/jsdelivr/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
function decodeBase64(base64) {
|
||||
const text = atob(base64);
|
||||
const length = text.length;
|
||||
const bytes = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
bytes[i] = text.charCodeAt(i);
|
||||
}
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(bytes);
|
||||
}
|
||||
|
||||
var viz = new Viz();
|
||||
viz.renderSVGElement(atob({{ .Result }}))
|
||||
viz.renderSVGElement(decodeBase64({{ .Result }}))
|
||||
.then(element => {
|
||||
document.getElementById("bgpmap").appendChild(element);
|
||||
})
|
||||
|
@@ -7,7 +7,20 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta name="renderer" content="webkit">
|
||||
<title>{{ html .Title }}</title>
|
||||
<link rel="stylesheet" href="/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" integrity="sha256-VoFZSlmyTXsegReQCNmbXrS4hBBUl/cexZvPmPWoJsY=" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" crossorigin="anonymous">
|
||||
<style>
|
||||
.navbar-nav {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.navbar form {
|
||||
min-width: 400px;
|
||||
}
|
||||
.nav-link {
|
||||
padding: 0.2rem 0.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
</head>
|
||||
<body>
|
||||
@@ -60,7 +73,7 @@
|
||||
<option value="{{ html $k }}"{{ if eq $k $.URLOption }} selected{{end}}>{{ html $v }}</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
<input name="server" class="d-none" value="{{ html $server }}">
|
||||
<input name="server" class="d-none" value="{{ html ($server | pathescape) }}">
|
||||
<input name="target" class="form-control" placeholder="Target" aria-label="Target" value="{{ html $target }}">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-success" type="submit">»</button>
|
||||
@@ -74,8 +87,9 @@
|
||||
{{ .Content }}
|
||||
</div>
|
||||
|
||||
<script src="/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
|
||||
<script src="/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js" integrity="sha256-0IiaoZCI++9oAAvmCb5Y0r93XkuhvJpRalZLffQXLok=" crossorigin="anonymous"></script>
|
||||
<script src="/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js" crossorigin="anonymous"></script>
|
||||
<script src="/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
|
||||
<script src="/static/sortTable.js"></script>
|
||||
|
||||
<script>
|
||||
function goto() {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{{ $ServerName := urlquery .ServerName }}
|
||||
|
||||
<table class="table table-striped table-bordered table-sm">
|
||||
<table class="table table-striped table-bordered table-sm sortable">
|
||||
<thead>
|
||||
{{ range .Header }}
|
||||
<th scope="col">{{ html . }}</th>
|
||||
|
File diff suppressed because it is too large
Load Diff
178
frontend/bgpmap_graph.go
Normal file
178
frontend/bgpmap_graph.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RouteAttrs map[string]string
|
||||
|
||||
type RoutePoint struct {
|
||||
performLookup bool
|
||||
attrs RouteAttrs
|
||||
}
|
||||
|
||||
type RouteEdgeKey struct {
|
||||
src string
|
||||
dest string
|
||||
}
|
||||
|
||||
type RouteEdgeValue struct {
|
||||
label []string
|
||||
attrs RouteAttrs
|
||||
}
|
||||
|
||||
type RouteGraph struct {
|
||||
points map[string]RoutePoint
|
||||
edges map[RouteEdgeKey]RouteEdgeValue
|
||||
}
|
||||
|
||||
func makeRouteGraph() RouteGraph {
|
||||
return RouteGraph{
|
||||
points: make(map[string]RoutePoint),
|
||||
edges: make(map[RouteEdgeKey]RouteEdgeValue),
|
||||
}
|
||||
}
|
||||
|
||||
func makeRoutePoint() RoutePoint {
|
||||
return RoutePoint{
|
||||
performLookup: false,
|
||||
attrs: make(RouteAttrs),
|
||||
}
|
||||
}
|
||||
|
||||
func makeRouteEdgeValue() RouteEdgeValue {
|
||||
return RouteEdgeValue{
|
||||
label: []string{},
|
||||
attrs: make(RouteAttrs),
|
||||
}
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) attrsToString(attrs RouteAttrs) string {
|
||||
if len(attrs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
result := ""
|
||||
isFirst := true
|
||||
for k, v := range attrs {
|
||||
if isFirst {
|
||||
isFirst = false
|
||||
} else {
|
||||
result += ","
|
||||
}
|
||||
result += graph.escape(k) + "=" + graph.escape(v) + ""
|
||||
}
|
||||
|
||||
return "[" + result + "]"
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) escape(s string) string {
|
||||
buffer := &bytes.Buffer{}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
err := encoder.Encode(s)
|
||||
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
} else {
|
||||
return string(buffer.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) AddEdge(src string, dest string, label string, attrs RouteAttrs) {
|
||||
// Add edges with same src/dest separately, multiple edges with same src/dest could exist
|
||||
edge := RouteEdgeKey{
|
||||
src: src,
|
||||
dest: dest,
|
||||
}
|
||||
|
||||
newValue, exists := graph.edges[edge]
|
||||
if !exists {
|
||||
newValue = makeRouteEdgeValue()
|
||||
}
|
||||
|
||||
if len(label) != 0 {
|
||||
newValue.label = append(newValue.label, label)
|
||||
}
|
||||
for k, v := range attrs {
|
||||
newValue.attrs[k] = v
|
||||
}
|
||||
|
||||
graph.edges[edge] = newValue
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) AddPoint(name string, performLookup bool, attrs RouteAttrs) {
|
||||
newValue, exists := graph.points[name]
|
||||
if !exists {
|
||||
newValue = makeRoutePoint()
|
||||
}
|
||||
|
||||
newValue.performLookup = performLookup
|
||||
for k, v := range attrs {
|
||||
newValue.attrs[k] = v
|
||||
}
|
||||
|
||||
graph.points[name] = newValue
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) GetEdge(src string, dest string) *RouteEdgeValue {
|
||||
key := RouteEdgeKey{
|
||||
src: src,
|
||||
dest: dest,
|
||||
}
|
||||
value, ok := graph.edges[key]
|
||||
if ok {
|
||||
return &value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) GetPoint(name string) *RoutePoint {
|
||||
value, ok := graph.points[name]
|
||||
if ok {
|
||||
return &value
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (graph *RouteGraph) ToGraphviz() string {
|
||||
var result string
|
||||
|
||||
asnCache := make(ASNCache)
|
||||
|
||||
for name, value := range graph.points {
|
||||
var representation string
|
||||
|
||||
if value.performLookup {
|
||||
representation = asnCache.Lookup(name)
|
||||
} else {
|
||||
representation = name
|
||||
}
|
||||
|
||||
attrsCopy := value.attrs
|
||||
if attrsCopy == nil {
|
||||
attrsCopy = make(RouteAttrs)
|
||||
}
|
||||
attrsCopy["label"] = representation
|
||||
|
||||
result += fmt.Sprintf("%s %s;\n", graph.escape(name), graph.attrsToString(value.attrs))
|
||||
}
|
||||
|
||||
for key, value := range graph.edges {
|
||||
attrsCopy := value.attrs
|
||||
if attrsCopy == nil {
|
||||
attrsCopy = make(RouteAttrs)
|
||||
}
|
||||
if len(value.label) > 0 {
|
||||
attrsCopy["label"] = strings.Join(value.label, "\n")
|
||||
}
|
||||
result += fmt.Sprintf("%s -> %s %s;\n", graph.escape(key.src), graph.escape(key.dest), graph.attrsToString(attrsCopy))
|
||||
}
|
||||
|
||||
return "digraph {\n" + result + "}\n"
|
||||
}
|
@@ -1,71 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetASNRepresentationDNS(t *testing.T) {
|
||||
checkNetwork(t)
|
||||
func readDataFile(t *testing.T, filename string) string {
|
||||
_, sourceName, _, _ := runtime.Caller(0)
|
||||
projectRoot := path.Join(path.Dir(sourceName), "..")
|
||||
dir := path.Join(projectRoot, filename)
|
||||
|
||||
setting.dnsInterface = "asn.cymru.com"
|
||||
setting.whoisServer = ""
|
||||
result := getASNRepresentation("6939")
|
||||
if !strings.Contains(result, "HURRICANE") {
|
||||
t.Errorf("Lookup AS6939 failed, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetASNRepresentationWhois(t *testing.T) {
|
||||
checkNetwork(t)
|
||||
|
||||
setting.dnsInterface = ""
|
||||
setting.whoisServer = "whois.arin.net"
|
||||
result := getASNRepresentation("6939")
|
||||
if !strings.Contains(result, "HURRICANE") {
|
||||
t.Errorf("Lookup AS6939 failed, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetASNRepresentationFallback(t *testing.T) {
|
||||
setting.dnsInterface = ""
|
||||
setting.whoisServer = ""
|
||||
result := getASNRepresentation("6939")
|
||||
if result != "AS6939" {
|
||||
t.Errorf("Lookup AS6939 failed, got %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBirdRouteToGraphviz(t *testing.T) {
|
||||
setting.dnsInterface = ""
|
||||
|
||||
// Don't change formatting of the following strings!
|
||||
|
||||
fakeResult := `192.168.0.1/32 unicast [alpha 2021-01-14 from 192.168.0.2] * (100) [AS12345i]
|
||||
via 192.168.0.2 on eth0
|
||||
Type: BGP univ
|
||||
BGP.origin: IGP
|
||||
BGP.as_path: 4242422601
|
||||
BGP.next_hop: 172.18.0.2`
|
||||
|
||||
expectedResult := `digraph {
|
||||
"Target: 192.168.0.1" [color=red,shape=diamond];
|
||||
"alpha" [color=blue,shape=box];
|
||||
"alpha" -> "AS4242422601" [fontsize=12.0,color=red,label="alpha*\n172.18.0.2"];
|
||||
"AS4242422601" -> "Target: 192.168.0.1" [color=red];
|
||||
}`
|
||||
|
||||
result := birdRouteToGraphviz([]string{
|
||||
"alpha",
|
||||
}, []string{
|
||||
fakeResult,
|
||||
}, "192.168.0.1")
|
||||
|
||||
for _, line := range strings.Split(result, "\n") {
|
||||
if !strings.Contains(expectedResult, line) {
|
||||
t.Errorf("Unexpected line in result: %s", line)
|
||||
}
|
||||
data, err := ioutil.ReadFile(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func TestBirdRouteToGraphvizXSS(t *testing.T) {
|
||||
@@ -81,7 +33,52 @@ func TestBirdRouteToGraphvizXSS(t *testing.T) {
|
||||
fakeResult,
|
||||
}, fakeResult)
|
||||
|
||||
if strings.Contains(result, "<script>") {
|
||||
if strings.Contains(result, fakeResult) {
|
||||
t.Errorf("XSS injection succeeded: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBirdRouteToGraph(t *testing.T) {
|
||||
setting.dnsInterface = ""
|
||||
|
||||
input := readDataFile(t, "frontend/test_data/bgpmap_case1.txt")
|
||||
result := birdRouteToGraph([]string{"node"}, []string{input}, "target")
|
||||
|
||||
// Source node must exist
|
||||
if result.GetPoint("node") == nil {
|
||||
t.Error("Result doesn't contain point node")
|
||||
}
|
||||
// Last hop must exist
|
||||
if result.GetPoint("4242423914") == nil {
|
||||
t.Error("Result doesn't contain point 4242423914")
|
||||
}
|
||||
// Destination must exist
|
||||
if result.GetPoint("target") == nil {
|
||||
t.Error("Result doesn't contain point target")
|
||||
}
|
||||
|
||||
// Verify that a few paths exist
|
||||
if result.GetEdge("node", "4242423914") == nil {
|
||||
t.Error("Result doesn't contain edge from node to 4242423914")
|
||||
}
|
||||
if result.GetEdge("node", "4242422688") == nil {
|
||||
t.Error("Result doesn't contain edge from node to 4242422688")
|
||||
}
|
||||
if result.GetEdge("4242422688", "4242423914") == nil {
|
||||
t.Error("Result doesn't contain edge from 4242422688 to 4242423914")
|
||||
}
|
||||
if result.GetEdge("4242423914", "target") == nil {
|
||||
t.Error("Result doesn't contain edge from 4242423914 to target")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBirdRouteToGraphviz(t *testing.T) {
|
||||
setting.dnsInterface = ""
|
||||
|
||||
input := readDataFile(t, "frontend/test_data/bgpmap_case1.txt")
|
||||
result := birdRouteToGraphviz([]string{"node"}, []string{input}, "target")
|
||||
|
||||
if !strings.Contains(result, "digraph {") {
|
||||
t.Error("Response is not Graphviz data")
|
||||
}
|
||||
}
|
||||
|
@@ -65,7 +65,7 @@ func shortenWhoisFilter(whois string) string {
|
||||
shouldSkip := false
|
||||
shouldSkip = shouldSkip || len(s) == 0
|
||||
shouldSkip = shouldSkip || len(s) > 0 && s[0] == '#'
|
||||
shouldSkip = shouldSkip || strings.Contains(strings.ToUpper(s), "REDACTED FOR PRIVACY")
|
||||
shouldSkip = shouldSkip || strings.Contains(strings.ToUpper(s), "REDACTED")
|
||||
|
||||
if shouldSkip {
|
||||
skippedLinesLonger++
|
||||
|
@@ -28,3 +28,79 @@ func TestDN42WhoisFilterUnneeded(t *testing.T) {
|
||||
t.Errorf("Output doesn't match expected: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortenWhoisFilterShorterMode(t *testing.T) {
|
||||
input := `
|
||||
Information line that will be removed
|
||||
|
||||
# Comment that will be removed
|
||||
Name: Redacted for privacy
|
||||
Descr: This is a vvvvvvvvvvvvvvvvvvvvvvveeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrryyyyyyyyyyyyyyyyyyy long line that will be skipped.
|
||||
Looooooooooooooooooooooong key: this line will be skipped.
|
||||
|
||||
Preserved1: this line isn't removed.
|
||||
Preserved2: this line isn't removed.
|
||||
Preserved3: this line isn't removed.
|
||||
Preserved4: this line isn't removed.
|
||||
Preserved5: this line isn't removed.
|
||||
|
||||
`
|
||||
|
||||
result := shortenWhoisFilter(input)
|
||||
|
||||
expectedResult := `Preserved1: this line isn't removed.
|
||||
Preserved2: this line isn't removed.
|
||||
Preserved3: this line isn't removed.
|
||||
Preserved4: this line isn't removed.
|
||||
Preserved5: this line isn't removed.
|
||||
|
||||
3 line(s) skipped.
|
||||
`
|
||||
|
||||
if result != expectedResult {
|
||||
t.Errorf("Output doesn't match expected: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortenWhoisFilterLongerMode(t *testing.T) {
|
||||
input := `
|
||||
Information line that will be removed
|
||||
|
||||
# Comment that will be removed
|
||||
Name: Redacted for privacy
|
||||
Descr: This is a vvvvvvvvvvvvvvvvvvvvvvveeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrryyyyyyyyyyyyyyyyyyy long line that will be skipped.
|
||||
Looooooooooooooooooooooong key: this line will be skipped.
|
||||
|
||||
Preserved1: this line isn't removed.
|
||||
|
||||
`
|
||||
|
||||
result := shortenWhoisFilter(input)
|
||||
|
||||
expectedResult := `Information line that will be removed
|
||||
Descr: This is a vvvvvvvvvvvvvvvvvvvvvvveeeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrrrrrrrrrryyyyyyyyyyyyyyyyyyy long line that will be skipped.
|
||||
Looooooooooooooooooooooong key: this line will be skipped.
|
||||
Preserved1: this line isn't removed.
|
||||
|
||||
7 line(s) skipped.
|
||||
`
|
||||
|
||||
if result != expectedResult {
|
||||
t.Errorf("Output doesn't match expected: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortenWhoisFilterSkipNothing(t *testing.T) {
|
||||
input := `Preserved1: this line isn't removed.
|
||||
Preserved2: this line isn't removed.
|
||||
Preserved3: this line isn't removed.
|
||||
Preserved4: this line isn't removed.
|
||||
Preserved5: this line isn't removed.
|
||||
`
|
||||
|
||||
result := shortenWhoisFilter(input)
|
||||
|
||||
if result != input {
|
||||
t.Errorf("Output doesn't match expected: %s", result)
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,27 @@
|
||||
module github.com/xddxdd/bird-lg-go/frontend
|
||||
|
||||
go 1.16
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/gorilla/handlers v1.5.1
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/subosito/gotenv v1.4.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/jarcoal/httpmock v1.4.1
|
||||
github.com/magiconair/properties v1.8.10
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/spf13/viper v1.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
)
|
||||
|
919
frontend/go.sum
919
frontend/go.sum
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
)
|
||||
|
||||
type channelData struct {
|
||||
@@ -14,6 +17,29 @@ type channelData struct {
|
||||
data string
|
||||
}
|
||||
|
||||
func createConnectionTimeoutRoundTripper(timeout int) http.RoundTripper {
|
||||
context := net.Dialer{
|
||||
Timeout: time.Duration(timeout) * time.Second,
|
||||
}
|
||||
|
||||
// Prefer httpmock's transport if activated, so unit tests can work
|
||||
if http.DefaultTransport == httpmock.DefaultTransport {
|
||||
return httpmock.DefaultTransport
|
||||
}
|
||||
|
||||
return &http.Transport{
|
||||
DialContext: context.DialContext,
|
||||
|
||||
// Default options from transport.go
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// Send commands to lgproxy instances in parallel, and retrieve their responses
|
||||
func batchRequest(servers []string, endpoint string, command string) []string {
|
||||
// Channel and array for storing responses
|
||||
@@ -47,7 +73,10 @@ func batchRequest(servers []string, endpoint string, command string) []string {
|
||||
}
|
||||
url := "http://" + hostname + ":" + strconv.Itoa(setting.proxyPort) + "/" + url.PathEscape(endpoint) + "?q=" + url.QueryEscape(command)
|
||||
go func(url string, i int) {
|
||||
client := http.Client{Timeout: time.Duration(setting.timeOut) * time.Second}
|
||||
client := http.Client{
|
||||
Transport: createConnectionTimeoutRoundTripper(setting.connectionTimeOut),
|
||||
Timeout: time.Duration(setting.timeOut) * time.Second,
|
||||
}
|
||||
response, err := client.Get(url)
|
||||
if err != nil {
|
||||
ch <- channelData{i, "request failed: " + err.Error() + "\n"}
|
||||
@@ -56,8 +85,8 @@ func batchRequest(servers []string, endpoint string, command string) []string {
|
||||
|
||||
buf := make([]byte, 65536)
|
||||
n, err := io.ReadFull(response.Body, buf)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
ch <- channelData{i, err.Error()}
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
ch <- channelData{i, "request failed: " + err.Error()}
|
||||
} else {
|
||||
ch <- channelData{i, string(buf[:n])}
|
||||
}
|
||||
|
163
frontend/lgproxy_test.go
Normal file
163
frontend/lgproxy_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
)
|
||||
|
||||
func TestBatchRequestIPv4(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, "Mock Result")
|
||||
httpmock.RegisterResponder("GET", "http://1.1.1.1:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://2.2.2.2:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://3.3.3.3:8000/mock?q=cmd", httpResponse)
|
||||
|
||||
setting.servers = []string{
|
||||
"1.1.1.1",
|
||||
"2.2.2.2",
|
||||
"3.3.3.3",
|
||||
}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
response := batchRequest(setting.servers, "mock", "cmd")
|
||||
|
||||
if len(response) != 3 {
|
||||
t.Error("Did not get response of all three mock servers")
|
||||
}
|
||||
for i := 0; i < len(response); i++ {
|
||||
if response[i] != "Mock Result" {
|
||||
t.Error("HTTP response mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchRequestIPv6(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, "Mock Result")
|
||||
httpmock.RegisterResponder("GET", "http://[2001:db8::1]:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://[2001:db8::2]:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://[2001:db8::3]:8000/mock?q=cmd", httpResponse)
|
||||
|
||||
setting.servers = []string{
|
||||
"2001:db8::1",
|
||||
"2001:db8::2",
|
||||
"2001:db8::3",
|
||||
}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
response := batchRequest(setting.servers, "mock", "cmd")
|
||||
|
||||
if len(response) != 3 {
|
||||
t.Error("Did not get response of all three mock servers")
|
||||
}
|
||||
for i := 0; i < len(response); i++ {
|
||||
if response[i] != "Mock Result" {
|
||||
t.Error("HTTP response mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchRequestEmptyResponse(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, "")
|
||||
httpmock.RegisterResponder("GET", "http://alpha:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://beta:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://gamma:8000/mock?q=cmd", httpResponse)
|
||||
|
||||
setting.servers = []string{
|
||||
"alpha",
|
||||
"beta",
|
||||
"gamma",
|
||||
}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
response := batchRequest(setting.servers, "mock", "cmd")
|
||||
|
||||
if len(response) != 3 {
|
||||
t.Error("Did not get response of all three mock servers")
|
||||
}
|
||||
for i := 0; i < len(response); i++ {
|
||||
if !strings.Contains(response[i], "node returned empty response") {
|
||||
t.Error("Did not produce error for empty response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchRequestDomainSuffix(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpResponse := httpmock.NewStringResponder(200, "Mock Result")
|
||||
httpmock.RegisterResponder("GET", "http://alpha.suffix:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://beta.suffix:8000/mock?q=cmd", httpResponse)
|
||||
httpmock.RegisterResponder("GET", "http://gamma.suffix:8000/mock?q=cmd", httpResponse)
|
||||
|
||||
setting.servers = []string{
|
||||
"alpha",
|
||||
"beta",
|
||||
"gamma",
|
||||
}
|
||||
setting.domain = "suffix"
|
||||
setting.proxyPort = 8000
|
||||
response := batchRequest(setting.servers, "mock", "cmd")
|
||||
|
||||
if len(response) != 3 {
|
||||
t.Error("Did not get response of all three mock servers")
|
||||
}
|
||||
for i := 0; i < len(response); i++ {
|
||||
if response[i] != "Mock Result" {
|
||||
t.Error("HTTP response mismatch")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchRequestHTTPError(t *testing.T) {
|
||||
httpmock.Activate()
|
||||
defer httpmock.DeactivateAndReset()
|
||||
|
||||
httpError := httpmock.NewErrorResponder(errors.New("Oops!"))
|
||||
httpmock.RegisterResponder("GET", "http://alpha:8000/mock?q=cmd", httpError)
|
||||
httpmock.RegisterResponder("GET", "http://beta:8000/mock?q=cmd", httpError)
|
||||
httpmock.RegisterResponder("GET", "http://gamma:8000/mock?q=cmd", httpError)
|
||||
|
||||
setting.servers = []string{
|
||||
"alpha",
|
||||
"beta",
|
||||
"gamma",
|
||||
}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
response := batchRequest(setting.servers, "mock", "cmd")
|
||||
|
||||
if len(response) != 3 {
|
||||
t.Error("Did not get response of all three mock servers")
|
||||
}
|
||||
for i := 0; i < len(response); i++ {
|
||||
if !strings.Contains(response[i], "request failed") {
|
||||
t.Error("Did not produce HTTP error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchRequestInvalidServer(t *testing.T) {
|
||||
setting.servers = []string{}
|
||||
setting.domain = ""
|
||||
setting.proxyPort = 8000
|
||||
response := batchRequest([]string{"invalid"}, "mock", "cmd")
|
||||
|
||||
if len(response) != 1 {
|
||||
t.Error("Did not get response of all mock servers")
|
||||
}
|
||||
if !strings.Contains(response[0], "invalid server") {
|
||||
t.Error("Did not produce invalid server error")
|
||||
}
|
||||
}
|
@@ -7,24 +7,26 @@ import (
|
||||
)
|
||||
|
||||
type settingType struct {
|
||||
servers []string
|
||||
serversDisplay []string
|
||||
domain string
|
||||
proxyPort int
|
||||
whoisServer string
|
||||
listen string
|
||||
dnsInterface string
|
||||
netSpecificMode string
|
||||
titleBrand string
|
||||
navBarBrand string
|
||||
navBarBrandURL string
|
||||
navBarAllServer string
|
||||
navBarAllURL string
|
||||
bgpmapInfo string
|
||||
telegramBotName string
|
||||
protocolFilter []string
|
||||
nameFilter string
|
||||
timeOut int
|
||||
servers []string
|
||||
serversDisplay []string
|
||||
domain string
|
||||
proxyPort int
|
||||
whoisServer string
|
||||
listen []string
|
||||
dnsInterface string
|
||||
netSpecificMode string
|
||||
titleBrand string
|
||||
navBarBrand string
|
||||
navBarBrandURL string
|
||||
navBarAllServer string
|
||||
navBarAllURL string
|
||||
bgpmapInfo string
|
||||
telegramBotName string
|
||||
protocolFilter []string
|
||||
nameFilter string
|
||||
timeOut int
|
||||
connectionTimeOut int
|
||||
trustProxyHeaders bool
|
||||
}
|
||||
|
||||
var setting settingType
|
||||
@@ -33,24 +35,29 @@ func main() {
|
||||
parseSettings()
|
||||
ImportTemplates()
|
||||
|
||||
var l net.Listener
|
||||
var err error
|
||||
for _, listenAddr := range setting.listen {
|
||||
go func(listenAddr string) {
|
||||
var l net.Listener
|
||||
var err error
|
||||
|
||||
if strings.HasPrefix(setting.listen, "/") {
|
||||
// Delete existing socket file, ignore errors (will fail later anyway)
|
||||
os.Remove(setting.listen)
|
||||
l, err = net.Listen("unix", setting.listen)
|
||||
} else {
|
||||
listenAddr := setting.listen
|
||||
if !strings.Contains(listenAddr, ":") {
|
||||
listenAddr = ":" + listenAddr
|
||||
}
|
||||
l, err = net.Listen("tcp", listenAddr)
|
||||
if strings.HasPrefix(listenAddr, "/") {
|
||||
// Delete existing socket file, ignore errors (will fail later anyway)
|
||||
os.Remove(listenAddr)
|
||||
l, err = net.Listen("unix", listenAddr)
|
||||
} else {
|
||||
if !strings.Contains(listenAddr, ":") {
|
||||
listenAddr = ":" + listenAddr
|
||||
}
|
||||
l, err = net.Listen("tcp", listenAddr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
webServerStart(l)
|
||||
}(listenAddr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
webServerStart(l)
|
||||
select {}
|
||||
}
|
||||
|
@@ -13,27 +13,26 @@ import (
|
||||
|
||||
// static options map
|
||||
var optionsMap = map[string]string{
|
||||
"summary": "show protocols",
|
||||
"detail": "show protocols all",
|
||||
"route": "show route for ...",
|
||||
"route_all": "show route for ... all",
|
||||
"route_bgpmap": "show route for ... (bgpmap)",
|
||||
"route_where": "show route where net ~ [ ... ]",
|
||||
"route_where_all": "show route where net ~ [ ... ] all",
|
||||
"route_where_bgpmap": "show route where net ~ [ ... ] (bgpmap)",
|
||||
"route_generic": "show route ...",
|
||||
"generic": "show ...",
|
||||
"whois": "whois ...",
|
||||
"traceroute": "traceroute ...",
|
||||
}
|
||||
|
||||
// pre-compiled regexp and constant statemap for summary rendering
|
||||
var splitSummaryLine = regexp.MustCompile(`(\w+)(\s+)(\w+)(\s+)([\w-]+)(\s+)(\w+)(\s+)([0-9\-\. :]+)(.*)`)
|
||||
var summaryStateMap = map[string]string{
|
||||
"up": "success",
|
||||
"down": "secondary",
|
||||
"start": "danger",
|
||||
"passive": "info",
|
||||
"summary": "show protocols",
|
||||
"detail": "show protocols all ...",
|
||||
"route_from_protocol": "show route protocol ...",
|
||||
"route_from_protocol_all": "show route protocol ... all",
|
||||
"route_from_protocol_all_primary": "show route protocol ... all primary",
|
||||
"route_filtered_from_protocol": "show route filtered protocol ...",
|
||||
"route_filtered_from_protocol_all": "show route filtered protocol ... all",
|
||||
"route_from_origin": "show route where bgp_path.last = ...",
|
||||
"route_from_origin_all": "show route where bgp_path.last = ... all",
|
||||
"route_from_origin_all_primary": "show route where bgp_path.last = ... all primary",
|
||||
"route": "show route for ...",
|
||||
"route_all": "show route for ... all",
|
||||
"route_bgpmap": "show route for ... (bgpmap)",
|
||||
"route_where": "show route where net ~ [ ... ]",
|
||||
"route_where_all": "show route where net ~ [ ... ] all",
|
||||
"route_where_bgpmap": "show route where net ~ [ ... ] (bgpmap)",
|
||||
"route_generic": "show route ...",
|
||||
"generic": "show ...",
|
||||
"whois": "whois ...",
|
||||
"traceroute": "traceroute ...",
|
||||
}
|
||||
|
||||
// render the page template
|
||||
@@ -135,58 +134,23 @@ func summaryParse(data string, serverName string) (TemplateSummary, error) {
|
||||
|
||||
// parse each line
|
||||
for _, line := range rows {
|
||||
|
||||
// Ignore empty lines
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
row := SummaryRowDataFromLine(line)
|
||||
if row == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse a total of 6 columns from bird summary
|
||||
lineSplitted := splitSummaryLine.FindStringSubmatch(line)
|
||||
if lineSplitted == nil {
|
||||
// Filter row name
|
||||
if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
var row SummaryRowData
|
||||
|
||||
if len(lineSplitted) >= 2 {
|
||||
row.Name = strings.TrimSpace(lineSplitted[1])
|
||||
if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(lineSplitted) >= 4 {
|
||||
row.Proto = strings.TrimSpace(lineSplitted[3])
|
||||
// Filter away unwanted protocol types, if setting.protocolFilter is non-empty
|
||||
found := false
|
||||
for _, protocol := range setting.protocolFilter {
|
||||
if strings.EqualFold(row.Proto, protocol) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(setting.protocolFilter) > 0 && !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(lineSplitted) >= 6 {
|
||||
row.Table = strings.TrimSpace(lineSplitted[5])
|
||||
}
|
||||
if len(lineSplitted) >= 8 {
|
||||
row.State = strings.TrimSpace(lineSplitted[7])
|
||||
row.MappedState = summaryStateMap[row.State]
|
||||
}
|
||||
if len(lineSplitted) >= 10 {
|
||||
row.Since = strings.TrimSpace(lineSplitted[9])
|
||||
}
|
||||
if len(lineSplitted) >= 11 {
|
||||
row.Info = strings.TrimSpace(lineSplitted[10])
|
||||
// Filter away unwanted protocol types, if setting.protocolFilter is non-empty
|
||||
if len(setting.protocolFilter) > 0 && !row.ProtocolMatches(setting.protocolFilter) {
|
||||
continue
|
||||
}
|
||||
|
||||
// add to the result
|
||||
args.Rows = append(args.Rows, row)
|
||||
args.Rows = append(args.Rows, *row)
|
||||
}
|
||||
|
||||
return args, nil
|
||||
|
@@ -8,6 +8,16 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const BirdSummaryData = `Name Proto Table State Since Info
|
||||
static1 Static master4 up 2021-08-27
|
||||
static2 Static master6 up 2021-08-27
|
||||
device1 Device --- up 2021-08-27
|
||||
kernel1 Kernel master6 up 2021-08-27
|
||||
kernel2 Kernel master4 up 2021-08-27
|
||||
direct1 Direct --- up 2021-08-27
|
||||
int_babel Babel --- up 2021-08-27
|
||||
`
|
||||
|
||||
func initSettings() {
|
||||
setting.servers = []string{"alpha"}
|
||||
setting.serversDisplay = []string{"alpha"}
|
||||
@@ -101,17 +111,8 @@ func TestSummaryTableXSS(t *testing.T) {
|
||||
func TestSummaryTableProtocolFilter(t *testing.T) {
|
||||
initSettings()
|
||||
setting.protocolFilter = []string{"Static", "Direct", "Babel"}
|
||||
data := `BIRD 2.0.8 ready.
|
||||
Name Proto Table State Since Info
|
||||
static1 Static master4 up 2021-08-27
|
||||
static2 Static master6 up 2021-08-27
|
||||
device1 Device --- up 2021-08-27
|
||||
kernel1 Kernel master6 up 2021-08-27
|
||||
kernel2 Kernel master4 up 2021-08-27
|
||||
direct1 Direct --- up 2021-08-27
|
||||
int_babel Babel --- up 2021-08-27 `
|
||||
|
||||
result := string(summaryTable(data, "testserver"))
|
||||
result := string(summaryTable(BirdSummaryData, "testserver"))
|
||||
expectedInclude := []string{"static1", "static2", "int_babel", "direct1"}
|
||||
expectedExclude := []string{"device1", "kernel1", "kernel2"}
|
||||
|
||||
@@ -134,17 +135,8 @@ int_babel Babel --- up 2021-08-27 `
|
||||
func TestSummaryTableNameFilter(t *testing.T) {
|
||||
initSettings()
|
||||
setting.nameFilter = "^static"
|
||||
data := `BIRD 2.0.8 ready.
|
||||
Name Proto Table State Since Info
|
||||
static1 Static master4 up 2021-08-27
|
||||
static2 Static master6 up 2021-08-27
|
||||
device1 Device --- up 2021-08-27
|
||||
kernel1 Kernel master6 up 2021-08-27
|
||||
kernel2 Kernel master4 up 2021-08-27
|
||||
direct1 Direct --- up 2021-08-27
|
||||
int_babel Babel --- up 2021-08-27 `
|
||||
|
||||
result := string(summaryTable(data, "testserver"))
|
||||
result := string(summaryTable(BirdSummaryData, "testserver"))
|
||||
expectedInclude := []string{"device1", "kernel1", "kernel2", "direct1", "int_babel"}
|
||||
expectedExclude := []string{"static1", "static2"}
|
||||
|
||||
|
@@ -9,23 +9,25 @@ import (
|
||||
)
|
||||
|
||||
type viperSettingType struct {
|
||||
Servers string `mapstructure:"servers"`
|
||||
Domain string `mapstructure:"domain"`
|
||||
ProxyPort int `mapstructure:"proxy_port"`
|
||||
WhoisServer string `mapstructure:"whois"`
|
||||
Listen string `mapstructure:"listen"`
|
||||
DNSInterface string `mapstructure:"dns_interface"`
|
||||
NetSpecificMode string `mapstructure:"net_specific_mode"`
|
||||
TitleBrand string `mapstructure:"title_brand"`
|
||||
NavBarBrand string `mapstructure:"navbar_brand"`
|
||||
NavBarBrandURL string `mapstructure:"navbar_brand_url"`
|
||||
NavBarAllServer string `mapstructure:"navbar_all_servers"`
|
||||
NavBarAllURL string `mapstructure:"navbar_all_url"`
|
||||
BgpmapInfo string `mapstructure:"bgpmap_info"`
|
||||
TelegramBotName string `mapstructure:"telegram_bot_name"`
|
||||
ProtocolFilter string `mapstructure:"protocol_filter"`
|
||||
NameFilter string `mapstructure:"name_filter"`
|
||||
TimeOut int `mapstructure:"timeout"`
|
||||
Servers string `mapstructure:"servers"`
|
||||
Domain string `mapstructure:"domain"`
|
||||
ProxyPort int `mapstructure:"proxy_port"`
|
||||
WhoisServer string `mapstructure:"whois"`
|
||||
Listen []string `mapstructure:"listen"`
|
||||
DNSInterface string `mapstructure:"dns_interface"`
|
||||
NetSpecificMode string `mapstructure:"net_specific_mode"`
|
||||
TitleBrand string `mapstructure:"title_brand"`
|
||||
NavBarBrand string `mapstructure:"navbar_brand"`
|
||||
NavBarBrandURL string `mapstructure:"navbar_brand_url"`
|
||||
NavBarAllServer string `mapstructure:"navbar_all_servers"`
|
||||
NavBarAllURL string `mapstructure:"navbar_all_url"`
|
||||
BgpmapInfo string `mapstructure:"bgpmap_info"`
|
||||
TelegramBotName string `mapstructure:"telegram_bot_name"`
|
||||
ProtocolFilter string `mapstructure:"protocol_filter"`
|
||||
NameFilter string `mapstructure:"name_filter"`
|
||||
TimeOut int `mapstructure:"timeout"`
|
||||
ConnectionTimeOut int `mapstructure:"connection_timeout"`
|
||||
TrustProxyHeaders bool `mapstructure:"trust_proxy_headers"`
|
||||
}
|
||||
|
||||
// Parse settings with viper, and convert to legacy setting format
|
||||
@@ -33,6 +35,7 @@ func parseSettings() {
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("/etc/bird-lg")
|
||||
viper.SetConfigName("bird-lg")
|
||||
viper.AllowEmptyEnv(true)
|
||||
viper.AutomaticEnv()
|
||||
viper.SetEnvPrefix("birdlg")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
|
||||
@@ -49,7 +52,7 @@ func parseSettings() {
|
||||
pflag.String("whois", "whois.verisign-grs.com", "whois server for queries")
|
||||
viper.BindPFlag("whois", pflag.Lookup("whois"))
|
||||
|
||||
pflag.String("listen", "5000", "address or unix socket bird-lg is listening on")
|
||||
pflag.StringSlice("listen", []string{"5000"}, "address or unix socket bird-lg is listening on")
|
||||
viper.BindPFlag("listen", pflag.Lookup("listen"))
|
||||
|
||||
pflag.String("dns-interface", "asn.cymru.com", "dns zone to query ASN information")
|
||||
@@ -86,9 +89,15 @@ func parseSettings() {
|
||||
pflag.String("name-filter", "", "protocol name regex to hide in summary tables (RE2 syntax); defaults to none if not set")
|
||||
viper.BindPFlag("name_filter", pflag.Lookup("name-filter"))
|
||||
|
||||
pflag.Int("time-out", 120, "time before request timed out, in seconds; defaults to 120 if not set")
|
||||
pflag.Int("time-out", 120, "time before backend HTTP request times out, in seconds; defaults to 120 if not set")
|
||||
viper.BindPFlag("timeout", pflag.Lookup("time-out"))
|
||||
|
||||
pflag.Int("connection-time-out", 5, "time before backend TCP connection times out, in seconds; defaults to 5 if not set")
|
||||
viper.BindPFlag("connection_timeout", pflag.Lookup("connection-time-out"))
|
||||
|
||||
pflag.Bool("trust-proxy-headers", false, "Trust X-Forwared-For, X-Real-IP, X-Forwarded-Proto, X-Forwarded-Scheme and X-Forwarded-Host sent by the client")
|
||||
viper.BindPFlag("trust_proxy_headers", pflag.Lookup("trust-proxy-headers"))
|
||||
|
||||
pflag.Parse()
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
@@ -138,6 +147,8 @@ func parseSettings() {
|
||||
|
||||
setting.nameFilter = viperSettings.NameFilter
|
||||
setting.timeOut = viperSettings.TimeOut
|
||||
setting.connectionTimeOut = viperSettings.ConnectionTimeOut
|
||||
setting.trustProxyHeaders = viperSettings.TrustProxyHeaders
|
||||
|
||||
fmt.Printf("%#v\n", setting)
|
||||
}
|
||||
|
8
frontend/settings_test.go
Normal file
8
frontend/settings_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseSettings(t *testing.T) {
|
||||
parseSettings()
|
||||
// Good as long as it doesn't panic
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user