You've already forked bird-lg-go
							
							
				mirror of
				https://github.com/xddxdd/bird-lg-go
				synced 2025-10-30 09:40:51 +01:00 
			
		
		
		
	Compare commits
	
		
			139 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b4c1bed9ba | ||
|   | abb32abff3 | ||
|   | b368c75aa3 | ||
|   | 09405cdb38 | ||
|   | f999d47d9f | ||
|   | 005dfb1435 | ||
|   | 4bd7a6bb95 | ||
|   | 462d76a2d0 | ||
|   | 58f217578c | ||
|   | 0e95727de1 | ||
|   | a48f1c8040 | ||
|   | 81acde3a37 | ||
|   | 7c0fe0d512 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a5f4452d02 | ||
|   | b237185ef7 | ||
|   | e949646790 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | bb479d22ae | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | d40f41b4d5 | ||
|   | cdc34704b5 | ||
|   | db58bd3354 | ||
|   | a0246ccee2 | ||
|   | ccd14af0c8 | ||
|   | 594ca80f50 | ||
|   | 5625058e71 | ||
|   | 7efa3237a9 | ||
|   | 7b0c8c0556 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | ffd9165062 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 24fd5203e8 | ||
|   | 49a05767c1 | ||
|   | e7010f75f8 | ||
|   | dba2af7634 | ||
|   | 049775319b | ||
|   | 47c66b125c | ||
|   | 9e17b116f1 | ||
|   | 335ad40634 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 6ec0f2e7a6 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 4b73cf0fcb | ||
|   | 3b1d001543 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 675cb26ed1 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 556d3e50d3 | ||
|   | 06796f546e | ||
|   | d029d6684c | ||
|   | 5ce0f55f35 | ||
|   | 890ab51b07 | ||
|   | 8e4a35cc8c | ||
|   | 97f3c6088f | ||
|   | 982326a678 | ||
|   | 4b3980f6bd | ||
|   | 6f6b2bd283 | ||
|   | 892a7bee22 | ||
|   | 348295b9aa | ||
|   | 950c018b18 | ||
|   | 26efeb4996 | ||
|   | 5a5dfbc93f | ||
|   | f60a292129 | ||
|   | e7f6026854 | ||
|   | a4e0f4c193 | ||
|   | af5b653326 | ||
|   | 58847759b3 | ||
|   | 6481e7cc8d | ||
|   | 2166d73b3d | ||
|   | a64d839e2c | ||
|   | 1a3c618522 | ||
|   | fbd190628c | ||
|   | 823b639245 | ||
|   | b0c0e5442d | ||
|   | 4e4ce89418 | ||
|   | 234aadadd9 | ||
|   | bee26f421c | ||
|   | 2e0cb131ca | ||
|   | 4c248c638a | ||
|   | 3550362a4d | ||
|   | 256a80646f | ||
|   | 03c42eb1e8 | ||
|   | aea85e774c | ||
|   | 80d9351a58 | ||
|   | 5e0bc081e6 | ||
|   | 4d53d1f095 | ||
|   | 5883015294 | ||
|   | 80e66a7a81 | ||
|   | 41329da7cb | ||
|   | 8e56705205 | ||
|   | 6a8b3a0e55 | ||
|   | 83ab403706 | ||
|   | 7c7814cc7b | ||
|   | 8598060cc0 | ||
|   | bda06ddd5e | ||
|   | f404072ab8 | ||
| ![dependabot-preview[bot]](/assets/img/avatar_default.png)  | fa827502cf | ||
|   | 794125a96f | ||
|   | de9d9101b1 | ||
|   | 056ef3769e | ||
|   | 974e809deb | ||
|   | 6e19b5ae64 | ||
|   | 874089117b | ||
|   | f77a8a28fe | ||
|   | 9e8a845658 | ||
|   | fd3e7b8379 | ||
|   | 8765189deb | ||
|   | 492942cce1 | ||
|   | f81a5308ae | ||
|   | 5b5a09ccbd | ||
|   | dc4d7e6532 | ||
|   | 28a7d2a53f | ||
|   | 4413f1032f | ||
|   | 007b66e036 | ||
|   | 3f612d2e76 | ||
|   | f49f8bac5e | ||
|   | f6ddc5761b | ||
|   | 1c3d9ec594 | ||
|   | 6cc0c617b4 | ||
|   | e2cc580da3 | ||
|   | 472cec74b0 | ||
|   | da2c3d9aed | ||
|   | aa76bc3de7 | ||
|   | a984095282 | ||
|   | 1baf325149 | ||
|   | 72946e1113 | ||
|   | 90e5012840 | ||
|   | 8d5eb56199 | ||
|   | 8d0618fed9 | ||
|   | f8ea511d44 | ||
|   | b99eb60c30 | ||
|   | 9f934ca53c | ||
|   | ee7cc1675b | ||
|   | f4b6955343 | ||
|   | 78ce724171 | ||
|   | 6179c688be | ||
|   | 8d0e210572 | ||
|   | 26c51176e4 | ||
|   | 5cf2ac57b8 | ||
|   | 75bc63ffa7 | ||
|   | 438c6a1f82 | ||
|   | b98d783739 | ||
|   | 5000ad1bbf | ||
|   | 538699ccd2 | ||
|   | 9e77de6b46 | ||
|   | c15942cc32 | ||
|   | 3bcfc3d36c | 
							
								
								
									
										16
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| version: 2 | ||||
| updates: | ||||
| - package-ecosystem: gomod | ||||
|   directory: "/frontend" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "08:00" | ||||
|     timezone: Asia/Shanghai | ||||
|   open-pull-requests-limit: 10 | ||||
| - package-ecosystem: gomod | ||||
|   directory: "/proxy" | ||||
|   schedule: | ||||
|     interval: daily | ||||
|     time: "08:00" | ||||
|     timezone: Asia/Shanghai | ||||
|   open-pull-requests-limit: 10 | ||||
							
								
								
									
										108
									
								
								.github/workflows/develop.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								.github/workflows/develop.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - '**' | ||||
|   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 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 | ||||
|  | ||||
|   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 }} | ||||
							
								
								
									
										82
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| on: | ||||
|   release: | ||||
|     types: [created] | ||||
|  | ||||
| jobs: | ||||
|   go-release: | ||||
|     name: Release Go Binary | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         goos: [linux, windows, darwin] | ||||
|         goarch: ["386", amd64, "arm", arm64] | ||||
|         exclude: | ||||
|           - goarch: "386" | ||||
|             goos: darwin | ||||
|           - goarch: "arm" | ||||
|             goos: darwin | ||||
|           - goarch: "arm" | ||||
|             goos: windows | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v3 | ||||
|  | ||||
|     - name: Release frontend | ||||
|       uses: wangyoucao577/go-release-action@v1.34 | ||||
|       with: | ||||
|         github_token: ${{ secrets.GITHUB_TOKEN }} | ||||
|         goos: ${{ matrix.goos }} | ||||
|         goarch: ${{ matrix.goarch }} | ||||
|         project_path: "./frontend" | ||||
|         binary_name: "bird-lg-go" | ||||
|  | ||||
|     - name: Release proxy | ||||
|       uses: wangyoucao577/go-release-action@v1.34 | ||||
|       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 | ||||
|             ghcr.io/xddxdd/bird-lg-go:frontend | ||||
|  | ||||
|       - 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 | ||||
|             ghcr.io/xddxdd/bird-lg-go:proxy | ||||
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -17,3 +17,10 @@ | ||||
| .DS_Store | ||||
| frontend/frontend | ||||
| proxy/proxy | ||||
|  | ||||
| # don't include generated bindata file | ||||
| frontend/bindata.go | ||||
|  | ||||
| # don't include generated Dockerfiles | ||||
| frontend/Dockerfile.* | ||||
| proxy/Dockerfile.* | ||||
							
								
								
									
										35
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,35 +0,0 @@ | ||||
| language: minimal | ||||
| os: linux | ||||
| dist: focal | ||||
| services: | ||||
|   - docker | ||||
| env: | ||||
|   - PROGRAM=frontend    IMAGE_NAME=bird-lg-go       IMAGE_ARCH=i386 | ||||
|   - PROGRAM=frontend    IMAGE_NAME=bird-lg-go       IMAGE_ARCH=amd64 | ||||
|   - PROGRAM=frontend    IMAGE_NAME=bird-lg-go       IMAGE_ARCH=arm32v7 | ||||
|   - PROGRAM=frontend    IMAGE_NAME=bird-lg-go       IMAGE_ARCH=arm64v8 | ||||
|   - PROGRAM=proxy       IMAGE_NAME=bird-lgproxy-go  IMAGE_ARCH=i386 | ||||
|   - PROGRAM=proxy       IMAGE_NAME=bird-lgproxy-go  IMAGE_ARCH=amd64 | ||||
|   - PROGRAM=proxy       IMAGE_NAME=bird-lgproxy-go  IMAGE_ARCH=arm32v7 | ||||
|   - PROGRAM=proxy       IMAGE_NAME=bird-lgproxy-go  IMAGE_ARCH=arm64v8 | ||||
|  | ||||
| install: | ||||
|   - docker run --rm --privileged multiarch/qemu-user-static:register --reset | ||||
|   - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin | ||||
|  | ||||
| script: | ||||
|   - | | ||||
|     # Build image | ||||
|     docker build \ | ||||
|       -t $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_ARCH \ | ||||
|       -f $PROGRAM/Dockerfile.$IMAGE_ARCH \ | ||||
|       $PROGRAM | ||||
|  | ||||
|     # Tag image :{arch} and :{arch}-build{build number} | ||||
|     docker tag $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_ARCH $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_ARCH-build$TRAVIS_BUILD_NUMBER | ||||
|     if [ "$IMAGE_ARCH" = "amd64" ]; then | ||||
|       # Tag as latest for amd64 images | ||||
|       docker tag $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_ARCH $DOCKER_USERNAME/$IMAGE_NAME:latest | ||||
|       docker tag $DOCKER_USERNAME/$IMAGE_NAME:$IMAGE_ARCH $DOCKER_USERNAME/$IMAGE_NAME:build$TRAVIS_BUILD_NUMBER | ||||
|     fi | ||||
|   - docker push $DOCKER_USERNAME/$IMAGE_NAME | ||||
							
								
								
									
										13
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| frontend: | ||||
| 	$(MAKE) -C frontend all | ||||
|  | ||||
| proxy: | ||||
| 	$(MAKE) -C proxy all | ||||
|  | ||||
| .DEFAULT_GOAL := all | ||||
| .PHONY: all frontend proxy | ||||
| all: frontend proxy | ||||
|  | ||||
| install: | ||||
| 	install -m 755 frontend/frontend /usr/local/bin/bird-lg-go | ||||
| 	install -m 755 proxy/proxy /usr/local/bin/bird-lgproxy-go | ||||
							
								
								
									
										202
									
								
								docs/API.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								docs/API.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| # Bird-lg-go API documentation | ||||
|  | ||||
| The frontend provides an API for running BIRD/traceroute/whois queries. | ||||
|  | ||||
| API Endpoint: `https://your.frontend.com/api/` (the last slash must not be omitted!) | ||||
|  | ||||
| Requests are sent as POSTS with JSON bodies. | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
|    * [Bird-lg-go API documentation](#bird-lg-go-api-documentation) | ||||
|       * [Table of Contents](#table-of-contents) | ||||
|       * [Request fields](#request-fields) | ||||
|          * [Example request of type bird](#example-request-of-type-bird) | ||||
|          * [Example request of type server_list](#example-request-of-type-server_list) | ||||
|       * [Response fields (when type is summary)](#response-fields-when-type-is-summary) | ||||
|          * [Fields for apiSummaryResultPair](#fields-for-apisummaryresultpair) | ||||
|          * [Fields for SummaryRowData](#fields-for-summaryrowdata) | ||||
|          * [Example response](#example-response) | ||||
|       * [Response fields (when type is bird, traceroute, whois or server_list)](#response-fields-when-type-is-bird-traceroute-whois-or-server_list) | ||||
|          * [Fields for apiGenericResultPair](#fields-for-apigenericresultpair) | ||||
|          * [Example response of type bird](#example-response-of-type-bird) | ||||
|          * [Example response of type server_list](#example-response-of-type-server_list) | ||||
|  | ||||
| Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) | ||||
|  | ||||
| ## Request fields | ||||
|  | ||||
| | Name | Type | Value | | ||||
| | ---- | ---- | -------- | | ||||
| | `servers` | array of `string` | List of servers to be queried | | ||||
| | `type` | `string` | Can be `summary`, `bird`, `traceroute`, `whois` or `server_list` | | ||||
| | `args` | `string` | Arguments to be passed, see below | | ||||
|  | ||||
| Argument examples for each type: | ||||
|  | ||||
| - `summary`: `args` is ignored. Recommended to set to empty string. | ||||
| - `bird`: `args` is the command to be passed to bird, e.g. `show route for 8.8.8.8` | ||||
| - `traceroute`: `args` is the traceroute target, e.g. `8.8.8.8` or `google.com` | ||||
| - `whois`: `args` is the whois target, e.g. `8.8.8.8` or `google.com` | ||||
| - `server_list`: `args` is ignored. In addition, `servers` is also ignored. | ||||
|  | ||||
| ### Example request of type `bird` | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "servers": [ | ||||
|         "alpha" | ||||
|     ], | ||||
|     "type": "bird", | ||||
|     "args": "show route for 8.8.8.8" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Example request of type `server_list` | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "servers": [], | ||||
|     "type": "server_list", | ||||
|     "args": "" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Response fields (when `type` is `summary`) | ||||
|  | ||||
| | Name | Type | Value | | ||||
| | ---- | ---- | -------- | | ||||
| | `error` | `string` | Error message when something is wrong. Empty when everything is good | | ||||
| | `result` | array of `apiSummaryResultPair` | See below | | ||||
|  | ||||
| ### Fields for `apiSummaryResultPair` | ||||
|  | ||||
| | Name | Type | Value | | ||||
| | ---- | ---- | -------- | | ||||
| | `server` | `string` | Name of the server | | ||||
| | `data` | array of `SummaryRowData` | Summaries of the server, see below | | ||||
|  | ||||
| ### Fields for `SummaryRowData` | ||||
|  | ||||
| All fields below is 1:1 correspondent to the output of `birdc show protocols`. | ||||
|  | ||||
| | Name | Type | | ||||
| | ---- | ---- | | ||||
| | `name` | `string` | | ||||
| | `proto` | `string` | | ||||
| | `table` | `string` | | ||||
| | `state` | `string` | | ||||
| | `since` | `string` | | ||||
| | `info` | `string` | | ||||
|  | ||||
| ### Example response | ||||
|  | ||||
| Request: | ||||
| ```json | ||||
| { | ||||
|     "servers": [ | ||||
|         "alpha" | ||||
|     ], | ||||
|     "type": "summary", | ||||
|     "args": "" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Response: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "error": "", | ||||
|     "result": [ | ||||
|         { | ||||
|             "server": "alpha", | ||||
|             "data": [ | ||||
|                 { | ||||
|                     "name": "bgp1", | ||||
|                     "proto": "BGP", | ||||
|                     "table": "---", | ||||
|                     "state": "start", | ||||
|                     "since": "2021-01-15 22:40:01", | ||||
|                     "info": "Active        Socket: Operation timed out" | ||||
|                 }, | ||||
|                 { | ||||
|                     "name": "bgp2", | ||||
|                     "proto": "BGP", | ||||
|                     "table": "---", | ||||
|                     "state": "start", | ||||
|                     "since": "2021-01-03 08:15:48", | ||||
|                     "info": "Established" | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Response fields (when `type` is `bird`, `traceroute`, `whois` or `server_list`) | ||||
|  | ||||
| | Name | Type | Value | | ||||
| | ---- | ---- | -------- | | ||||
| | `error` | `string` | Error message, empty when everything is good | | ||||
| | `result` | array of `apiGenericResultPair` | See below | | ||||
|  | ||||
| ### Fields for `apiGenericResultPair` | ||||
|  | ||||
| | Name | Type | Value | | ||||
| | ---- | ---- | -------- | | ||||
| | `server` | `string` | Name of the server; is empty when type is `whois` | | ||||
| | `data` | `string` | Result from the server; is empty when type is `server_list` | | ||||
|  | ||||
| ### Example response of type `bird` | ||||
|  | ||||
| Request: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "servers": [ | ||||
|         "alpha" | ||||
|     ], | ||||
|     "type": "bird", | ||||
|     "args": "show status" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Response: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "error": "", | ||||
|     "result": [ | ||||
|         { | ||||
|             "server": "alpha", | ||||
|             "data": "BIRD v2.0.7-137-g61dae32b\nRouter ID is 1.2.3.4\nCurrent server time is 2021-01-17 04:21:14.792\nLast reboot on 2021-01-03 08:15:48.494\nLast reconfiguration on 2021-01-17 00:49:10.573\nDaemon is up and running\n" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Example response of type `server_list` | ||||
|  | ||||
| Request: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "servers": [], | ||||
|     "type": "server_list", | ||||
|     "args": "" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Response: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|     "error": "", | ||||
|     "result": [ | ||||
|         { | ||||
|             "server": "gigsgigscloud", | ||||
|             "data": "" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| ``` | ||||
							
								
								
									
										22
									
								
								docs/Telegram.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								docs/Telegram.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| # Telegram Bot Webhook | ||||
|  | ||||
| The frontend can act as a Telegram Bot webhook endpoint, to add BGP route/traceroute/whois lookup functionality to your tech group. | ||||
|  | ||||
| There is no configuration necessary on the frontend, just start it up normally. | ||||
|  | ||||
| Set your Telegram Bot webhook URL to `https://your.frontend.com/telegram/alpha+beta+gamma`, where `alpha+beta+gamma` is the list of servers to be queried on Telegram commands, separated by `+`. | ||||
|  | ||||
| You may omit `alpha+beta+gamma` to use all your servers, but it is not recommended when you have lots of servers, or the message would be too long and hard to read. | ||||
|  | ||||
| ## Example of setting the webhook | ||||
|  | ||||
| ```bash | ||||
| curl "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook?url=https://your.frontend.com:5000/telegram/alpha+beta+gamma" | ||||
| ``` | ||||
|  | ||||
| ## Supported commands | ||||
|  | ||||
| - `path`: Show bird's ASN path to target IP | ||||
| - `route`: Show bird's preferred route to target IP | ||||
| - `trace`: Traceroute to target IP/domain | ||||
| - `whois`: Whois query | ||||
							
								
								
									
										33
									
								
								frontend/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								frontend/Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| FROM golang AS step_0 | ||||
| ENV CGO_ENABLED=0 GO111MODULE=on | ||||
| WORKDIR /root | ||||
| COPY . . | ||||
| RUN go build -ldflags "-w -s" -o /frontend | ||||
|  | ||||
| ################################################################################ | ||||
|  | ||||
| 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"] | ||||
| @@ -1,13 +0,0 @@ | ||||
| FROM amd64/debian:buster | ||||
|  | ||||
| LABEL Lan Tian "lantian@lantian.pub" | ||||
| ENV GOOS=linux GOARCH=amd64 | ||||
| WORKDIR /root | ||||
| COPY . . | ||||
| RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y golang git \ | ||||
|   && cd /root && go get github.com/gorilla/handlers && go build -o /frontend \ | ||||
|   && cd / && rm -rf /root/* \ | ||||
|   && apt-get -qq purge -y golang git \ | ||||
|   && apt-get -qq autoremove --purge -y && apt-get clean && rm -rf /var/lib/apt/lists | ||||
|  | ||||
| ENTRYPOINT ["/frontend"] | ||||
| @@ -1,13 +0,0 @@ | ||||
| FROM multiarch/debian-debootstrap:armhf-buster | ||||
|  | ||||
| LABEL Lan Tian "lantian@lantian.pub" | ||||
| ENV GOOS=linux GOARCH=arm | ||||
| WORKDIR /root | ||||
| COPY . . | ||||
| RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y golang git \ | ||||
|   && cd /root && go get github.com/gorilla/handlers && go build -o /frontend \ | ||||
|   && cd / && rm -rf /root/* \ | ||||
|   && apt-get -qq purge -y golang git \ | ||||
|   && apt-get -qq autoremove --purge -y && apt-get clean && rm -rf /var/lib/apt/lists | ||||
|  | ||||
| ENTRYPOINT ["/frontend"] | ||||
| @@ -1,13 +0,0 @@ | ||||
| FROM multiarch/debian-debootstrap:arm64-buster | ||||
|  | ||||
| LABEL Lan Tian "lantian@lantian.pub" | ||||
| ENV GOOS=linux GOARCH=arm64 | ||||
| WORKDIR /root | ||||
| COPY . . | ||||
| RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y golang git \ | ||||
|   && cd /root && go get github.com/gorilla/handlers && go build -o /frontend \ | ||||
|   && cd / && rm -rf /root/* \ | ||||
|   && apt-get -qq purge -y golang git \ | ||||
|   && apt-get -qq autoremove --purge -y && apt-get clean && rm -rf /var/lib/apt/lists | ||||
|  | ||||
| ENTRYPOINT ["/frontend"] | ||||
| @@ -1,13 +0,0 @@ | ||||
| FROM i386/debian:buster | ||||
|  | ||||
| LABEL Lan Tian "lantian@lantian.pub" | ||||
| ENV GOOS=linux GOARCH=386 | ||||
| WORKDIR /root | ||||
| COPY . . | ||||
| RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y golang git \ | ||||
|   && cd /root && go get github.com/gorilla/handlers && go build -o /frontend \ | ||||
|   && cd / && rm -rf /root/* \ | ||||
|   && apt-get -qq purge -y golang git \ | ||||
|   && apt-get -qq autoremove --purge -y && apt-get clean && rm -rf /var/lib/apt/lists | ||||
|  | ||||
| ENTRYPOINT ["/frontend"] | ||||
							
								
								
									
										3
									
								
								frontend/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								frontend/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| .PHONY: all | ||||
| all: | ||||
| 	go build -ldflags "-w -s" -o frontend | ||||
							
								
								
									
										130
									
								
								frontend/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								frontend/api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| ) | ||||
|  | ||||
| type apiRequest struct { | ||||
| 	Servers []string `json:"servers"` | ||||
| 	Type    string   `json:"type"` | ||||
| 	Args    string   `json:"args"` | ||||
| } | ||||
|  | ||||
| type apiGenericResultPair struct { | ||||
| 	Server string `json:"server"` | ||||
| 	Data   string `json:"data"` | ||||
| } | ||||
|  | ||||
| type apiSummaryResultPair struct { | ||||
| 	Server string           `json:"server"` | ||||
| 	Data   []SummaryRowData `json:"data"` | ||||
| } | ||||
|  | ||||
| type apiResponse struct { | ||||
| 	Error  string        `json:"error"` | ||||
| 	Result []interface{} `json:"result"` | ||||
| } | ||||
|  | ||||
| var apiHandlerMap = map[string](func(request apiRequest) apiResponse){ | ||||
| 	"summary":     apiSummaryHandler, | ||||
| 	"bird":        apiGenericHandlerFactory("bird"), | ||||
| 	"traceroute":  apiGenericHandlerFactory("traceroute"), | ||||
| 	"whois":       apiWhoisHandler, | ||||
| 	"server_list": apiServerListHandler, | ||||
| } | ||||
|  | ||||
| func apiGenericHandlerFactory(endpoint string) func(request apiRequest) apiResponse { | ||||
| 	return func(request apiRequest) apiResponse { | ||||
| 		results := batchRequest(request.Servers, endpoint, request.Args) | ||||
| 		var response apiResponse | ||||
|  | ||||
| 		for i, result := range results { | ||||
| 			response.Result = append(response.Result, &apiGenericResultPair{ | ||||
| 				Server: request.Servers[i], | ||||
| 				Data:   result, | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		return response | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func apiServerListHandler(request apiRequest) apiResponse { | ||||
| 	var response apiResponse | ||||
|  | ||||
| 	for _, server := range setting.servers { | ||||
| 		response.Result = append(response.Result, apiGenericResultPair{ | ||||
| 			Server: server, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return response | ||||
| } | ||||
|  | ||||
| func apiSummaryHandler(request apiRequest) apiResponse { | ||||
| 	results := batchRequest(request.Servers, "bird", "show protocols") | ||||
| 	var response 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:   parsedSummary.Rows, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return response | ||||
| } | ||||
|  | ||||
| func apiWhoisHandler(request apiRequest) apiResponse { | ||||
| 	return apiResponse{ | ||||
| 		Error: "", | ||||
| 		Result: []interface{}{ | ||||
| 			apiGenericResultPair{ | ||||
| 				Server: "", | ||||
| 				Data:   whois(request.Args), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func apiErrorHandler(err error) apiResponse { | ||||
| 	return apiResponse{ | ||||
| 		Error: err.Error(), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func apiHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	var request apiRequest | ||||
| 	var response apiResponse | ||||
| 	err := json.NewDecoder(r.Body).Decode(&request) | ||||
| 	if err != nil { | ||||
| 		response = apiResponse{ | ||||
| 			Error: err.Error(), | ||||
| 		} | ||||
| 	} else { | ||||
| 		handler := apiHandlerMap[request.Type] | ||||
| 		if handler == nil { | ||||
| 			response = apiErrorHandler(errors.New("invalid request type")) | ||||
| 		} else { | ||||
| 			response = handler(request) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	w.Header().Add("Content-Type", "application/json") | ||||
| 	w.Header().Add("Access-Control-Allow-Origin", "*") | ||||
| 	bytes, err := json.Marshal(response) | ||||
| 	if err != nil { | ||||
| 		println(err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	w.Write(bytes) | ||||
| } | ||||
							
								
								
									
										207
									
								
								frontend/api_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								frontend/api_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | ||||
| 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, "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") | ||||
| } | ||||
							
								
								
									
										
											BIN
										
									
								
								frontend/assets/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/assets/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 32 KiB | 
							
								
								
									
										2
									
								
								frontend/assets/robots.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								frontend/assets/robots.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| User-agent: * | ||||
| Disallow: / | ||||
							
								
								
									
										7
									
								
								frontend/assets/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/assets/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										7
									
								
								frontend/assets/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								frontend/assets/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								frontend/assets/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								frontend/assets/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								frontend/assets/static/jsdelivr/npm/viz.js@2.1.2/viz.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/assets/static/jsdelivr/npm/viz.js@2.1.2/viz.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										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); | ||||
|         }); | ||||
| } | ||||
							
								
								
									
										16
									
								
								frontend/assets/templates/bgpmap.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								frontend/assets/templates/bgpmap.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <h2>BGPmap: {{ html .Target }}</h2> | ||||
| <div id="bgpmap"> | ||||
| </div> | ||||
|  | ||||
| <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> | ||||
|   var viz = new Viz(); | ||||
|   viz.renderSVGElement(atob({{ .Result }})) | ||||
|   .then(element => { | ||||
|     document.getElementById("bgpmap").appendChild(element); | ||||
|   }) | ||||
|   .catch(error => { | ||||
|     document.getElementById("bgpmap").innerHTML = "<pre>"+error+"</pre>" | ||||
|   }); | ||||
| </script> | ||||
							
								
								
									
										2
									
								
								frontend/assets/templates/bird.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								frontend/assets/templates/bird.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| <h2>{{ html .ServerName }}: {{ html .Target }}</h2> | ||||
| {{ .Result }} | ||||
							
								
								
									
										100
									
								
								frontend/assets/templates/page.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								frontend/assets/templates/page.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en-US"> | ||||
| <head> | ||||
| <link rel="icon" href="/favicon.ico" type="image/x-icon" /> | ||||
| <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> | ||||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
| <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"> | ||||
| <meta name="robots" content="noindex, nofollow"> | ||||
| </head> | ||||
| <body> | ||||
|  | ||||
| <nav class="navbar navbar-expand-lg navbar-light bg-light"> | ||||
| 	<a class="navbar-brand" href="{{ .BrandURL }}">{{ .Brand }}</a> | ||||
| 	<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> | ||||
| 		<span class="navbar-toggler-icon"></span> | ||||
| 	</button> | ||||
|  | ||||
| 	<div class="collapse navbar-collapse" id="navbarSupportedContent"> | ||||
| 		{{ $option := .URLOption }} | ||||
| 		{{ $server := .URLServer }} | ||||
| 		{{ $target := .URLCommand }} | ||||
| 		{{ if .IsWhois }} | ||||
| 			{{ $option = "summary" }} | ||||
| 			{{ $server = .AllServersURL }} | ||||
| 			{{ $target = "" }} | ||||
| 		{{ end }} | ||||
| 		<ul class="navbar-nav mr-auto"> | ||||
| 			<li class="nav-item"> | ||||
| 				{{ if eq .AllServersURLCustom "all" }} | ||||
| 				<a class="nav-link{{ if .AllServersLinkActive }} active{{ end }}" | ||||
| 					href="/{{ $option }}/{{ .AllServersURL }}/{{ $target }}"> {{ .AllServerTitle }} </a> | ||||
| 				{{ else }} | ||||
| 				<a class="nav-link active" | ||||
| 					href="{{ .AllServersURLCustom }}"> {{ .AllServerTitle }} </a> | ||||
| 				{{ end }} | ||||
| 			</li> | ||||
| 			{{ $length := len .Servers }}  | ||||
| 			{{ range $k, $v := .Servers }} | ||||
| 			<li class="nav-item"> | ||||
| 				{{ if gt $length 1 }} | ||||
| 				<a class="nav-link{{ if eq $server $v }} active{{ end }}" | ||||
| 					href="/{{ $option }}/{{ $v }}/{{ $target }}">{{ html (index $.ServersDisplay $k) }}</a> | ||||
| 				{{ else }} | ||||
| 				<a class="nav-link{{ if eq $server $v }} active{{ end }}" | ||||
| 					href="/">{{ html (index $.ServersDisplay $k) }}</a> | ||||
| 				{{ end }} | ||||
| 			</li> | ||||
| 			{{ end }} | ||||
| 		</ul> | ||||
| 		{{ if .IsWhois }} | ||||
| 			{{ $target = .WhoisTarget }} | ||||
| 		{{ end }} | ||||
| 		<form name="goto" class="form-inline" action="javascript:goto();"> | ||||
| 			<div class="input-group"> | ||||
| 				<select name="action" class="form-control"> | ||||
| 					{{ range $k, $v := .Options }} | ||||
| 					<option value="{{ html $k }}"{{ if eq $k $.URLOption }} selected{{end}}>{{ html $v }}</option> | ||||
| 					{{ end }} | ||||
| 				</select> | ||||
| 				<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> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</form> | ||||
| 	</div> | ||||
| </nav> | ||||
|  | ||||
| <div class="container"> | ||||
| 	{{ .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/sortTable.js"></script> | ||||
|  | ||||
| <script> | ||||
| function goto() { | ||||
| 	let action = $('[name="action"]').val(); | ||||
| 	let server = $('[name="server"]').val(); | ||||
| 	let target = $('[name="target"]').val(); | ||||
| 	let url = ""; | ||||
|  | ||||
| 	if (action == "whois") { | ||||
| 		url = "/" + action + "/" + target; | ||||
| 	} else if (action == "summary") { | ||||
| 		url = "/" + action + "/" + server + "/"; | ||||
| 	} else { | ||||
| 		url = "/" + action + "/" + server + "/" + target; | ||||
| 	} | ||||
|  | ||||
| 	window.location.href = url; | ||||
| } | ||||
| </script> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										21
									
								
								frontend/assets/templates/summary.tpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/assets/templates/summary.tpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| {{ $ServerName := urlquery .ServerName }} | ||||
|  | ||||
| <table class="table table-striped table-bordered table-sm sortable"> | ||||
|   <thead> | ||||
| {{ range .Header }} | ||||
|     <th scope="col">{{ html . }}</th> | ||||
| {{ end }} | ||||
|   </thead> | ||||
|   <tbody> | ||||
| {{ range .Rows }} | ||||
|     <tr class="table-{{ .MappedState }}"> | ||||
|       <td><a href="/detail/{{ $ServerName }}/{{ urlquery .Name }}">{{ html .Name }}</a></td> | ||||
|       <td>{{ html .Proto }}</td> | ||||
|       <td>{{ html .Table }}</td> | ||||
|       <td>{{ html .State }}</td> | ||||
|       <td>{{ html .Since }}</td> | ||||
|       <td>{{ html .Info  }}</td> | ||||
|     </tr> | ||||
| {{ end }} | ||||
|   </tbody> | ||||
| </table> | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user