You've already forked bird-lg-go
mirror of
https://github.com/xddxdd/bird-lg-go
synced 2025-10-21 01:42:15 +02:00
Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,127 +0,0 @@
|
|||||||
version: 2.1
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
docker:
|
|
||||||
jobs:
|
|
||||||
- build
|
|
||||||
- docker-frontend-deploy:
|
|
||||||
context:
|
|
||||||
- docker
|
|
||||||
requires:
|
|
||||||
- build
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
only: 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-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-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]
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
releases-matrix:
|
go-release:
|
||||||
name: Release Go Binary
|
name: Release Go Binary
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux, windows, darwin]
|
goos: [linux, windows, darwin]
|
||||||
goarch: ["386", amd64, "arm", arm64]
|
goarch: ["386", amd64, "arm", arm64]
|
||||||
@@ -18,18 +19,82 @@ jobs:
|
|||||||
- goarch: "arm"
|
- goarch: "arm"
|
||||||
goos: windows
|
goos: windows
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: Checkout
|
||||||
- uses: wangyoucao577/go-release-action@v1.30
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Release frontend
|
||||||
|
uses: wangyoucao577/go-release-action@v1.40
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
goarch: ${{ matrix.goarch }}
|
||||||
project_path: "./frontend"
|
project_path: "./frontend"
|
||||||
binary_name: "bird-lg-go"
|
binary_name: "bird-lg-go"
|
||||||
- uses: wangyoucao577/go-release-action@v1.30
|
|
||||||
|
- name: Release proxy
|
||||||
|
uses: wangyoucao577/go-release-action@v1.40
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
goos: ${{ matrix.goos }}
|
goos: ${{ matrix.goos }}
|
||||||
goarch: ${{ matrix.goarch }}
|
goarch: ${{ matrix.goarch }}
|
||||||
project_path: "./proxy"
|
project_path: "./proxy"
|
||||||
binary_name: "bird-lgproxy-go"
|
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
|
# don't include generated bindata file
|
||||||
frontend/bindata.go
|
frontend/bindata.go
|
||||||
|
|
||||||
# don't include generated Dockerfiles
|
|
||||||
frontend/Dockerfile.*
|
|
||||||
proxy/Dockerfile.*
|
|
||||||
35
README.md
35
README.md
@@ -58,8 +58,6 @@ Configuration can be set in:
|
|||||||
|
|
||||||
Configuration is handled by [viper](https://github.com/spf13/viper), any config format supported by it can be used.
|
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 |
|
| Config Key | Parameter | Environment Variable | Description |
|
||||||
| ---------- | --------- | -------------------- | ----------- |
|
| ---------- | --------- | -------------------- | ----------- |
|
||||||
| servers | --servers | BIRDLG_SERVERS | server name prefixes, separated by comma |
|
| servers | --servers | BIRDLG_SERVERS | server name prefixes, separated by comma |
|
||||||
@@ -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 |
|
| 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 |
|
| 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.
|
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
|
```bash
|
||||||
@@ -90,7 +90,8 @@ Example: the following docker-compose.yml entry does the same as above, but by s
|
|||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
bird-lg:
|
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
|
container_name: bird-lg
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
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.
|
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 |
|
| 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") |
|
| 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") |
|
| 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_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:
|
Example: start proxy with default configuration, should work "out of the box" on Debian 9 with BIRDv1:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -149,7 +166,9 @@ Example: the following docker-compose.yml entry does the same as above, but by s
|
|||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
bird-lgproxy:
|
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
|
container_name: bird-lgproxy
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:buster AS step_0
|
FROM golang AS step_0
|
||||||
ENV CGO_ENABLED=0 GO111MODULE=on
|
ENV CGO_ENABLED=0 GO111MODULE=on
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
COPY . .
|
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_0 /frontend /
|
||||||
|
COPY --from=step_1 /root/whois-5.5.18/whois /
|
||||||
|
COPY --from=step_1 /etc/services /etc/services
|
||||||
ENTRYPOINT ["/frontend"]
|
ENTRYPOINT ["/frontend"]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type apiGenericResultPair struct {
|
|||||||
type apiSummaryResultPair struct {
|
type apiSummaryResultPair struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Data []SummaryRowData `json:"data"`
|
Data []SummaryRowData `json:"data"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiResponse struct {
|
type apiResponse struct {
|
||||||
@@ -70,9 +71,12 @@ func apiSummaryHandler(request apiRequest) apiResponse {
|
|||||||
for i, result := range results {
|
for i, result := range results {
|
||||||
parsedSummary, err := summaryParse(result, request.Servers[i])
|
parsedSummary, err := summaryParse(result, request.Servers[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apiResponse{
|
response.Result = append(response.Result, &apiSummaryResultPair{
|
||||||
Error: err.Error(),
|
Server: request.Servers[i],
|
||||||
}
|
Data: []SummaryRowData{},
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Result = append(response.Result, &apiSummaryResultPair{
|
response.Result = append(response.Result, &apiSummaryResultPair{
|
||||||
@@ -113,7 +117,7 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
handler := apiHandlerMap[request.Type]
|
handler := apiHandlerMap[request.Type]
|
||||||
if handler == nil {
|
if handler == nil {
|
||||||
response = apiErrorHandler(errors.New("Invalid request type"))
|
response = apiErrorHandler(errors.New("invalid request type"))
|
||||||
} else {
|
} else {
|
||||||
response = handler(request)
|
response = handler(request)
|
||||||
}
|
}
|
||||||
|
|||||||
210
frontend/api_test.go
Normal file
210
frontend/api_test.go
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
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")
|
||||||
|
// Protocol list will be sorted
|
||||||
|
assert.Equal(t, summary.Data[1].Name, "device1")
|
||||||
|
assert.Equal(t, summary.Data[1].Proto, "Device")
|
||||||
|
assert.Equal(t, summary.Data[1].Table, "---")
|
||||||
|
assert.Equal(t, summary.Data[1].State, "up")
|
||||||
|
assert.Equal(t, summary.Data[1].Since, "2021-08-27")
|
||||||
|
assert.Equal(t, summary.Data[1].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")
|
||||||
|
}
|
||||||
@@ -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/viz.min.js" crossorigin="anonymous"></script>
|
||||||
<script src="/static/jsdelivr/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script>
|
<script src="/static/jsdelivr/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script>
|
||||||
<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();
|
var viz = new Viz();
|
||||||
viz.renderSVGElement(atob({{ .Result }}))
|
viz.renderSVGElement(decodeBase64({{ .Result }}))
|
||||||
.then(element => {
|
.then(element => {
|
||||||
document.getElementById("bgpmap").appendChild(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="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
<meta name="renderer" content="webkit">
|
<meta name="renderer" content="webkit">
|
||||||
<title>{{ html .Title }}</title>
|
<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">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -60,7 +73,7 @@
|
|||||||
<option value="{{ html $k }}"{{ if eq $k $.URLOption }} selected{{end}}>{{ html $v }}</option>
|
<option value="{{ html $k }}"{{ if eq $k $.URLOption }} selected{{end}}>{{ html $v }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</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 }}">
|
<input name="target" class="form-control" placeholder="Target" aria-label="Target" value="{{ html $target }}">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-outline-success" type="submit">»</button>
|
<button class="btn btn-outline-success" type="submit">»</button>
|
||||||
@@ -74,8 +87,8 @@
|
|||||||
{{ .Content }}
|
{{ .Content }}
|
||||||
</div>
|
</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/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" integrity="sha256-0IiaoZCI++9oAAvmCb5Y0r93XkuhvJpRalZLffQXLok=" 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 src="/static/sortTable.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetASNRepresentationDNS(t *testing.T) {
|
func readDataFile(t *testing.T, filename string) string {
|
||||||
checkNetwork(t)
|
_, sourceName, _, _ := runtime.Caller(0)
|
||||||
|
projectRoot := path.Join(path.Dir(sourceName), "..")
|
||||||
|
dir := path.Join(projectRoot, filename)
|
||||||
|
|
||||||
setting.dnsInterface = "asn.cymru.com"
|
data, err := ioutil.ReadFile(dir)
|
||||||
setting.whoisServer = ""
|
if err != nil {
|
||||||
result := getASNRepresentation("6939")
|
t.Fatal(err)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return string(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBirdRouteToGraphvizXSS(t *testing.T) {
|
func TestBirdRouteToGraphvizXSS(t *testing.T) {
|
||||||
@@ -81,7 +33,52 @@ func TestBirdRouteToGraphvizXSS(t *testing.T) {
|
|||||||
fakeResult,
|
fakeResult,
|
||||||
}, fakeResult)
|
}, fakeResult)
|
||||||
|
|
||||||
if strings.Contains(result, "<script>") {
|
if strings.Contains(result, fakeResult) {
|
||||||
t.Errorf("XSS injection succeeded: %s", result)
|
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 := false
|
||||||
shouldSkip = shouldSkip || len(s) == 0
|
shouldSkip = shouldSkip || len(s) == 0
|
||||||
shouldSkip = shouldSkip || len(s) > 0 && 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 {
|
if shouldSkip {
|
||||||
skippedLinesLonger++
|
skippedLinesLonger++
|
||||||
|
|||||||
@@ -28,3 +28,79 @@ func TestDN42WhoisFilterUnneeded(t *testing.T) {
|
|||||||
t.Errorf("Output doesn't match expected: %s", result)
|
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,10 +1,33 @@
|
|||||||
module github.com/xddxdd/bird-lg-go/frontend
|
module github.com/xddxdd/bird-lg-go/frontend
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.2
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/jarcoal/httpmock v1.4.0
|
||||||
github.com/spf13/viper v1.14.0
|
github.com/magiconair/properties v1.8.10
|
||||||
|
github.com/spf13/pflag v1.0.6
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
1896
frontend/go.sum
1896
frontend/go.sum
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jarcoal/httpmock"
|
||||||
)
|
)
|
||||||
|
|
||||||
type channelData struct {
|
type channelData struct {
|
||||||
@@ -14,6 +17,29 @@ type channelData struct {
|
|||||||
data string
|
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
|
// Send commands to lgproxy instances in parallel, and retrieve their responses
|
||||||
func batchRequest(servers []string, endpoint string, command string) []string {
|
func batchRequest(servers []string, endpoint string, command string) []string {
|
||||||
// Channel and array for storing responses
|
// 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)
|
url := "http://" + hostname + ":" + strconv.Itoa(setting.proxyPort) + "/" + url.PathEscape(endpoint) + "?q=" + url.QueryEscape(command)
|
||||||
go func(url string, i int) {
|
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)
|
response, err := client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- channelData{i, "request failed: " + err.Error() + "\n"}
|
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)
|
buf := make([]byte, 65536)
|
||||||
n, err := io.ReadFull(response.Body, buf)
|
n, err := io.ReadFull(response.Body, buf)
|
||||||
if err != nil && err != io.ErrUnexpectedEOF {
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
ch <- channelData{i, err.Error()}
|
ch <- channelData{i, "request failed: " + err.Error()}
|
||||||
} else {
|
} else {
|
||||||
ch <- channelData{i, string(buf[:n])}
|
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 {
|
type settingType struct {
|
||||||
servers []string
|
servers []string
|
||||||
serversDisplay []string
|
serversDisplay []string
|
||||||
domain string
|
domain string
|
||||||
proxyPort int
|
proxyPort int
|
||||||
whoisServer string
|
whoisServer string
|
||||||
listen string
|
listen string
|
||||||
dnsInterface string
|
dnsInterface string
|
||||||
netSpecificMode string
|
netSpecificMode string
|
||||||
titleBrand string
|
titleBrand string
|
||||||
navBarBrand string
|
navBarBrand string
|
||||||
navBarBrandURL string
|
navBarBrandURL string
|
||||||
navBarAllServer string
|
navBarAllServer string
|
||||||
navBarAllURL string
|
navBarAllURL string
|
||||||
bgpmapInfo string
|
bgpmapInfo string
|
||||||
telegramBotName string
|
telegramBotName string
|
||||||
protocolFilter []string
|
protocolFilter []string
|
||||||
nameFilter string
|
nameFilter string
|
||||||
timeOut int
|
timeOut int
|
||||||
|
connectionTimeOut int
|
||||||
|
trustProxyHeaders bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var setting settingType
|
var setting settingType
|
||||||
|
|||||||
@@ -193,6 +193,11 @@ func summaryParse(data string, serverName string) (TemplateSummary, error) {
|
|||||||
row.Info = strings.TrimSpace(lineSplitted[10])
|
row.Info = strings.TrimSpace(lineSplitted[10])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamic BGP session, show without any color
|
||||||
|
if strings.Contains(row.Info, "Passive") {
|
||||||
|
row.MappedState = summaryStateMap["passive"]
|
||||||
|
}
|
||||||
|
|
||||||
// add to the result
|
// add to the result
|
||||||
args.Rows = append(args.Rows, row)
|
args.Rows = append(args.Rows, row)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,17 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const BirdSummaryData = `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
|
||||||
|
`
|
||||||
|
|
||||||
func initSettings() {
|
func initSettings() {
|
||||||
setting.servers = []string{"alpha"}
|
setting.servers = []string{"alpha"}
|
||||||
setting.serversDisplay = []string{"alpha"}
|
setting.serversDisplay = []string{"alpha"}
|
||||||
@@ -101,17 +112,8 @@ func TestSummaryTableXSS(t *testing.T) {
|
|||||||
func TestSummaryTableProtocolFilter(t *testing.T) {
|
func TestSummaryTableProtocolFilter(t *testing.T) {
|
||||||
initSettings()
|
initSettings()
|
||||||
setting.protocolFilter = []string{"Static", "Direct", "Babel"}
|
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"}
|
expectedInclude := []string{"static1", "static2", "int_babel", "direct1"}
|
||||||
expectedExclude := []string{"device1", "kernel1", "kernel2"}
|
expectedExclude := []string{"device1", "kernel1", "kernel2"}
|
||||||
|
|
||||||
@@ -134,17 +136,8 @@ int_babel Babel --- up 2021-08-27 `
|
|||||||
func TestSummaryTableNameFilter(t *testing.T) {
|
func TestSummaryTableNameFilter(t *testing.T) {
|
||||||
initSettings()
|
initSettings()
|
||||||
setting.nameFilter = "^static"
|
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"}
|
expectedInclude := []string{"device1", "kernel1", "kernel2", "direct1", "int_babel"}
|
||||||
expectedExclude := []string{"static1", "static2"}
|
expectedExclude := []string{"static1", "static2"}
|
||||||
|
|
||||||
|
|||||||
@@ -9,23 +9,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type viperSettingType struct {
|
type viperSettingType struct {
|
||||||
Servers string `mapstructure:"servers"`
|
Servers string `mapstructure:"servers"`
|
||||||
Domain string `mapstructure:"domain"`
|
Domain string `mapstructure:"domain"`
|
||||||
ProxyPort int `mapstructure:"proxy_port"`
|
ProxyPort int `mapstructure:"proxy_port"`
|
||||||
WhoisServer string `mapstructure:"whois"`
|
WhoisServer string `mapstructure:"whois"`
|
||||||
Listen string `mapstructure:"listen"`
|
Listen string `mapstructure:"listen"`
|
||||||
DNSInterface string `mapstructure:"dns_interface"`
|
DNSInterface string `mapstructure:"dns_interface"`
|
||||||
NetSpecificMode string `mapstructure:"net_specific_mode"`
|
NetSpecificMode string `mapstructure:"net_specific_mode"`
|
||||||
TitleBrand string `mapstructure:"title_brand"`
|
TitleBrand string `mapstructure:"title_brand"`
|
||||||
NavBarBrand string `mapstructure:"navbar_brand"`
|
NavBarBrand string `mapstructure:"navbar_brand"`
|
||||||
NavBarBrandURL string `mapstructure:"navbar_brand_url"`
|
NavBarBrandURL string `mapstructure:"navbar_brand_url"`
|
||||||
NavBarAllServer string `mapstructure:"navbar_all_servers"`
|
NavBarAllServer string `mapstructure:"navbar_all_servers"`
|
||||||
NavBarAllURL string `mapstructure:"navbar_all_url"`
|
NavBarAllURL string `mapstructure:"navbar_all_url"`
|
||||||
BgpmapInfo string `mapstructure:"bgpmap_info"`
|
BgpmapInfo string `mapstructure:"bgpmap_info"`
|
||||||
TelegramBotName string `mapstructure:"telegram_bot_name"`
|
TelegramBotName string `mapstructure:"telegram_bot_name"`
|
||||||
ProtocolFilter string `mapstructure:"protocol_filter"`
|
ProtocolFilter string `mapstructure:"protocol_filter"`
|
||||||
NameFilter string `mapstructure:"name_filter"`
|
NameFilter string `mapstructure:"name_filter"`
|
||||||
TimeOut int `mapstructure:"timeout"`
|
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
|
// Parse settings with viper, and convert to legacy setting format
|
||||||
@@ -33,6 +35,7 @@ func parseSettings() {
|
|||||||
viper.AddConfigPath(".")
|
viper.AddConfigPath(".")
|
||||||
viper.AddConfigPath("/etc/bird-lg")
|
viper.AddConfigPath("/etc/bird-lg")
|
||||||
viper.SetConfigName("bird-lg")
|
viper.SetConfigName("bird-lg")
|
||||||
|
viper.AllowEmptyEnv(true)
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
viper.SetEnvPrefix("birdlg")
|
viper.SetEnvPrefix("birdlg")
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
|
||||||
@@ -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")
|
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"))
|
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"))
|
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()
|
pflag.Parse()
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
@@ -138,6 +147,8 @@ func parseSettings() {
|
|||||||
|
|
||||||
setting.nameFilter = viperSettings.NameFilter
|
setting.nameFilter = viperSettings.NameFilter
|
||||||
setting.timeOut = viperSettings.TimeOut
|
setting.timeOut = viperSettings.TimeOut
|
||||||
|
setting.connectionTimeOut = viperSettings.ConnectionTimeOut
|
||||||
|
setting.trustProxyHeaders = viperSettings.TrustProxyHeaders
|
||||||
|
|
||||||
fmt.Printf("%#v\n", setting)
|
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
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -104,6 +105,12 @@ var requiredTemplates = [...]string{
|
|||||||
"bird",
|
"bird",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// define functions to be made available in templates
|
||||||
|
|
||||||
|
var funcMap = template.FuncMap{
|
||||||
|
"pathescape": url.PathEscape,
|
||||||
|
}
|
||||||
|
|
||||||
// import templates from embedded assets
|
// import templates from embedded assets
|
||||||
|
|
||||||
func ImportTemplates() {
|
func ImportTemplates() {
|
||||||
@@ -121,7 +128,7 @@ func ImportTemplates() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// and add it to the template library
|
// and add it to the template library
|
||||||
template, err := template.New(tmpl).Parse(string(def))
|
template, err := template.New(tmpl).Funcs(funcMap).Parse(string(def))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Unable to parse template (" + TEMPLATE_PATH + tmpl + ": " + err.Error())
|
panic("Unable to parse template (" + TEMPLATE_PATH + tmpl + ": " + err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
25
frontend/template_test.go
Normal file
25
frontend/template_test.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/magiconair/properties/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSummaryRowDataNameHasPrefix(t *testing.T) {
|
||||||
|
data := SummaryRowData{
|
||||||
|
Name: "mock",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, data.NameHasPrefix("m"), true)
|
||||||
|
assert.Equal(t, data.NameHasPrefix("n"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSummaryRowDataNameContains(t *testing.T) {
|
||||||
|
data := SummaryRowData{
|
||||||
|
Name: "mock",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, data.NameContains("oc"), true)
|
||||||
|
assert.Equal(t, data.NameContains("no"), false)
|
||||||
|
}
|
||||||
151
frontend/test_data/bgpmap_case1.txt
Normal file
151
frontend/test_data/bgpmap_case1.txt
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
Table master4:
|
||||||
|
172.20.0.53/32 unicast [ibgp_sjc2 2023-04-29 from fd86:bad:11b7:22::1] * (100/38) [AS4242423914i]
|
||||||
|
via 169.254.108.122 on igp-sjc2
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423914
|
||||||
|
BGP.next_hop: 172.20.229.122
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,1) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 101, 44) (4242421080, 103, 122) (4242421080, 104, 1)
|
||||||
|
unicast [miaotony_2688 2023-04-29 from fe80::2688] (100) [AS4242423914i]
|
||||||
|
via 172.23.6.6 on dn42las-miaoton
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242422688 4242423914
|
||||||
|
BGP.next_hop: 172.23.6.6
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,3) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [imlonghao_1888 2023-04-17] (100) [AS4242423914i]
|
||||||
|
via fe80::1888 on dn42-imlonghao
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242421888 4242423914
|
||||||
|
BGP.next_hop: :: fe80::1888
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,1) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [ciplc_3021 2023-04-29 from fe80::943e] (100) [AS4242423914i]
|
||||||
|
via 172.23.33.161 on dn42-ciplc
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423021 4242423914
|
||||||
|
BGP.next_hop: 172.23.33.161
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,1) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [iedon_2189 2023-04-29 from fe80::2189:ef] (100) [AS4242423914i]
|
||||||
|
via 172.23.91.114 on dn42-iedon
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242422189 4242423914
|
||||||
|
BGP.next_hop: 172.23.91.114
|
||||||
|
BGP.med: 65
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,24) (64511,33) (64511,3)
|
||||||
|
BGP.large_community: (4242422189, 1, 4) (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [prevarinite_2475 2023-04-19] (100) [AS4242423914i]
|
||||||
|
via fe80::7072:6576:6172:1 on dn42-prevarinit
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242422475 4242423192 4242423914
|
||||||
|
BGP.next_hop: :: fe80::7072:6576:6172:1
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,1) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [lare_3035 2023-04-29] (100) [AS4242423914i]
|
||||||
|
via fe80::3035:132 on dn42-lare
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423035 4242423914
|
||||||
|
BGP.next_hop: :: fe80::3035:132
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,3) (64511,34) (64511,24)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [hinata_3724 2023-04-29 from fe80::3724] (100) [AS4242423914i]
|
||||||
|
via 172.23.215.228 on dn42las-hinata
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423724 4201271111 4242423914
|
||||||
|
BGP.next_hop: 172.23.215.228
|
||||||
|
BGP.med: 70
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,22) (64511,1) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [liki4_0927 2023-04-21] (100) [AS4242423914i]
|
||||||
|
via fe80::927 on dn42-liki4
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242420927 4242421888 4242423914
|
||||||
|
BGP.next_hop: :: fe80::927
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,2) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [eastbound_2633 2023-04-29 from fe80::2633] (100) [AS4242423914i]
|
||||||
|
via 172.23.250.42 on dn42las-eastbnd
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242422633 4242423914
|
||||||
|
BGP.next_hop: 172.23.250.42
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,24) (64511,34) (64511,3)
|
||||||
|
BGP.large_community: (4242422633, 101, 44) (4242422633, 103, 36) (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [yura_2464 2023-04-29] (100) [AS4242423914i]
|
||||||
|
via fe80::2464 on dn42las-yura
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242422464 4242423914
|
||||||
|
BGP.next_hop: :: fe80::2464
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,1) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242422464, 2, 4242423914) (4242422464, 64511, 44) (4242422464, 64511, 1840) (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
|
unicast [ibgp_fra 2023-04-29 from fd86:bad:11b7:117::1] (100/186) [AS4242423914i]
|
||||||
|
via 169.254.108.113 on igp-chi
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423914
|
||||||
|
BGP.next_hop: 172.20.229.117
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,1) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 101, 41) (4242421080, 103, 117) (4242421080, 104, 3)
|
||||||
|
unicast [ibgp_sgp 2023-04-29 from fd86:bad:11b7:239::1] (100/200) [AS4242423914i]
|
||||||
|
via 169.254.108.39 on igp-sgp
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423914
|
||||||
|
BGP.next_hop: 172.22.108.39
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,4) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 101, 51) (4242421080, 103, 39) (4242421080, 104, 4)
|
||||||
|
unicast [ibgp_ymq 2023-04-30 from fd86:bad:11b7:23::1] (100/105) [AS4242423914i]
|
||||||
|
via 169.254.108.113 on igp-chi
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423914
|
||||||
|
BGP.next_hop: 172.20.229.123
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,3) (64511,24) (64511,34)
|
||||||
|
BGP.large_community: (4242421080, 101, 42) (4242421080, 103, 123) (4242421080, 104, 2)
|
||||||
|
unicast [cola_3391 18:41:16.608 from fe80::3391] (100) [AS4242423914i]
|
||||||
|
via 172.22.96.65 on dn42-cola
|
||||||
|
Type: BGP univ
|
||||||
|
BGP.origin: IGP
|
||||||
|
BGP.as_path: 4242423391 4242420604 4242423914
|
||||||
|
BGP.next_hop: 172.22.96.65
|
||||||
|
BGP.med: 50
|
||||||
|
BGP.local_pref: 100
|
||||||
|
BGP.community: (64511,4) (64511,34) (64511,24)
|
||||||
|
BGP.large_community: (4242420604, 2, 50) (4242420604, 501, 4242423914) (4242420604, 502, 44) (4242420604, 504, 4) (4242421080, 104, 1) (4242421080, 101, 44) (4242421080, 103, 126)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user