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
	
		
			94 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 4a8c752157 | ||
|   | 5ad6a4d35c | ||
|   | 5422c8fd8c | ||
|   | 5042980d79 | ||
|   | 7884531a24 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | cc804e81b6 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c9bab2ae2b | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | b9a4f95978 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7cd69746df | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | e719859d68 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 5d06affefc | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 060fe9bf8e | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | f23d36f357 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 1dbc0fccd2 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | b2d64d19e3 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 4dee4b0806 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | cb279e0459 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 31ba36beaf | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 5ab3b95d64 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7bf654f35f | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 5b33629a9d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0868c5d42c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | bc61579e6a | ||
|   | e9750a8278 | ||
|   | d40dd3a4d3 | ||
|   | ffdeeac06e | ||
|   | 7eb4d75bbf | ||
|   | 6e5e190d32 | ||
|   | 1b2573d87c | ||
|   | 0d5337508b | ||
|   | b9094d3d6c | ||
|   | ec7f348418 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a632739443 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a9e278357a | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | e4c00c897f | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 4df3918b35 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 45dc24470d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 55ea5c3b28 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7eb44c3828 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 124fdedbda | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | e6a98358b5 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 761eb2160a | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | cc2a146a88 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 3db9454350 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c30bed112c | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | af5ab3c78f | ||
|   | 0fdde8afc7 | ||
|   | 39a129db9d | ||
|   | 0dd1c07b66 | ||
|   | f0f072c4a6 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 657565857b | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7ac2158e70 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 5c433bc27a | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 1b0b923da9 | ||
|   | 01438edaef | ||
|   | 90f36610dc | ||
|   | 6174208d07 | ||
|   | 76174cdc08 | ||
|   | 088bb6fe5a | ||
|   | 3951eed011 | ||
|   | 91c0a8962b | ||
|   | 5f7850a903 | ||
|   | 6a78cf2e80 | ||
|   | 5b5a44bcb6 | ||
|   | ac31862237 | ||
|   | 86129190ab | ||
|   | ff55064a20 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | dbb02c04ed | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c2b7de2e17 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | c1b578e8db | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7b0e5689d4 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 3c46bda49d | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 32e00d2ce3 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | a19750cdef | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 7f1cdaa4ee | ||
|   | 2d2193041e | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | aad8ee98d7 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 00b5c12787 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 55a1eb54fd | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 0594edc69d | ||
|   | 38bf6aba09 | ||
|   | d261c22235 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 19aa8c77c5 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | fe07ebb5a5 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 66547ebfa9 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | d253e4311b | ||
|   | 026498ba2f | ||
|   | 27c348a864 | ||
|   | 43b4ad93dd | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 6176c45006 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 47113184f4 | ||
|   | 3c9a3e4339 | ||
|   | 8457b18d46 | ||
|   | f8f64b03a6 | 
							
								
								
									
										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 }} | ||||
							
								
								
									
										23
									
								
								.github/workflows/develop.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.github/workflows/develop.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - '**' | ||||
|       - 'master' | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - 'master' | ||||
| @@ -47,7 +47,7 @@ jobs: | ||||
|       - 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 -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 | ||||
|  | ||||
| @@ -57,6 +57,12 @@ jobs: | ||||
|           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: | ||||
| @@ -106,3 +112,16 @@ jobs: | ||||
|             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 | ||||
|   | ||||
							
								
								
									
										17
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -70,7 +70,9 @@ jobs: | ||||
|           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 | ||||
| @@ -80,4 +82,19 @@ jobs: | ||||
|           push: true | ||||
|           tags: | | ||||
|             xddxdd/bird-lgproxy-go:latest | ||||
|             xddxdd/bird-lgproxy-go:${{ github.event.release.tag_name }} | ||||
|             ghcr.io/xddxdd/bird-lg-go:proxy | ||||
|             ghcr.io/xddxdd/bird-lg-go:proxy-${{ github.event.release.tag_name }} | ||||
|  | ||||
|       - name: Build proxy docker image | ||||
|         uses: docker/build-push-action@v4 | ||||
|         with: | ||||
|           context: '{{defaultContext}}:proxy' | ||||
|           file: 'Dockerfile.mtr' | ||||
|           platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v7 | ||||
|           push: true | ||||
|           tags: | | ||||
|             xddxdd/bird-lgproxy-go:latest-mtr | ||||
|             xddxdd/bird-lgproxy-go:${{ github.event.release.tag_name }}-mtr | ||||
|             ghcr.io/xddxdd/bird-lg-go:proxy-mtr | ||||
|             ghcr.io/xddxdd/bird-lg-go:proxy-${{ github.event.release.tag_name }}-mtr | ||||
|   | ||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -20,7 +20,3 @@ proxy/proxy | ||||
|  | ||||
| # don't include generated bindata file | ||||
| frontend/bindata.go | ||||
|  | ||||
| # don't include generated Dockerfiles | ||||
| frontend/Dockerfile.* | ||||
| proxy/Dockerfile.* | ||||
| @@ -125,7 +125,7 @@ Configuration is handled by [viper](https://github.com/spf13/viper), any config | ||||
|  | ||||
| | Config Key | Parameter | Environment Variable | Description | | ||||
| | ---------- | --------- | -------------------- | ----------- | | ||||
| | allowed_ips | --allowed | ALLOWED_IPS | IPs allowed to access this proxy, separated by commas. Don't set to allow all IPs. (default "") | | ||||
| | allowed_ips | --allowed | ALLOWED_IPS | IPs or networks allowed to access this proxy, separated by commas. Don't set to allow all IPs. (default "") | | ||||
| | bird_socket | --bird | BIRD_SOCKET | socket file for bird, set either in parameter or environment variable BIRD_SOCKET (default "/var/run/bird/bird.ctl") | | ||||
| | listen | --listen | BIRDLG_PROXY_PORT | listen address, set either in parameter or environment variable  BIRDLG_PROXY_PORT(default "8000") | | ||||
| | traceroute_bin | --traceroute_bin | BIRDLG_TRACEROUTE_BIN | traceroute binary file, set either in parameter or environment variable  BIRDLG_TRACEROUTE_BIN | | ||||
| @@ -167,6 +167,7 @@ Example: the following docker-compose.yml entry does the same as above, but by s | ||||
| services: | ||||
|   bird-lgproxy: | ||||
|     # Use xddxdd/bird-lgproxy-go:develop for the latest build from master branch | ||||
|     # Use xddxdd/bird-lgproxy-go:latest-mtr to use MTR instead of Traceroute | ||||
|     image: xddxdd/bird-lgproxy-go:latest | ||||
|     container_name: bird-lgproxy | ||||
|     restart: always | ||||
|   | ||||
| @@ -20,6 +20,7 @@ type apiGenericResultPair struct { | ||||
| type apiSummaryResultPair struct { | ||||
| 	Server string           `json:"server"` | ||||
| 	Data   []SummaryRowData `json:"data"` | ||||
| 	Error  string           `json:"error,omitempty"` | ||||
| } | ||||
|  | ||||
| type apiResponse struct { | ||||
| @@ -70,9 +71,12 @@ func apiSummaryHandler(request apiRequest) apiResponse { | ||||
| 	for i, result := range results { | ||||
| 		parsedSummary, err := summaryParse(result, request.Servers[i]) | ||||
| 		if err != nil { | ||||
| 			return apiResponse{ | ||||
| 				Error: err.Error(), | ||||
| 			} | ||||
| 			response.Result = append(response.Result, &apiSummaryResultPair{ | ||||
| 				Server: request.Servers[i], | ||||
| 				Data:   []SummaryRowData{}, | ||||
| 				Error:  err.Error(), | ||||
| 			}) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		response.Result = append(response.Result, &apiSummaryResultPair{ | ||||
|   | ||||
| @@ -73,13 +73,14 @@ func TestApiSummaryHandler(t *testing.T) { | ||||
|  | ||||
| 	summary := response.Result[0].(*apiSummaryResultPair) | ||||
| 	assert.Equal(t, summary.Server, "alpha") | ||||
| 	assert.Equal(t, len(summary.Data), 7) | ||||
| 	// Protocol list will be sorted | ||||
| 	assert.Equal(t, summary.Data[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, "") | ||||
| 	assert.Equal(t, summary.Data[0].Name, "device1") | ||||
| 	assert.Equal(t, summary.Data[0].Proto, "Device") | ||||
| 	assert.Equal(t, summary.Data[0].Table, "---") | ||||
| 	assert.Equal(t, summary.Data[0].State, "up") | ||||
| 	assert.Equal(t, summary.Data[0].Since, "2021-08-27") | ||||
| 	assert.Equal(t, summary.Data[0].Info, "") | ||||
| } | ||||
|  | ||||
| func TestApiSummaryHandlerError(t *testing.T) { | ||||
| @@ -100,7 +101,10 @@ func TestApiSummaryHandlerError(t *testing.T) { | ||||
| 	} | ||||
| 	response := apiSummaryHandler(request) | ||||
|  | ||||
| 	assert.Equal(t, response.Error, "Mock backend error") | ||||
| 	assert.Equal(t, response.Error, "") | ||||
|  | ||||
| 	summary := response.Result[0].(*apiSummaryResultPair) | ||||
| 	assert.Equal(t, summary.Error, "Mock backend error") | ||||
| } | ||||
|  | ||||
| func TestApiWhoisHandler(t *testing.T) { | ||||
|   | ||||
| @@ -5,8 +5,19 @@ | ||||
| <script src="/static/jsdelivr/npm/viz.js@2.1.2/viz.min.js" crossorigin="anonymous"></script> | ||||
| <script src="/static/jsdelivr/npm/viz.js@2.1.2/lite.render.js" crossorigin="anonymous"></script> | ||||
| <script> | ||||
|   function decodeBase64(base64) { | ||||
|     const text = atob(base64); | ||||
|     const length = text.length; | ||||
|     const bytes = new Uint8Array(length); | ||||
|     for (let i = 0; i < length; i++) { | ||||
|         bytes[i] = text.charCodeAt(i); | ||||
|     } | ||||
|     const decoder = new TextDecoder(); | ||||
|     return decoder.decode(bytes); | ||||
|   } | ||||
|  | ||||
|   var viz = new Viz(); | ||||
|   viz.renderSVGElement(atob({{ .Result }})) | ||||
|   viz.renderSVGElement(decodeBase64({{ .Result }})) | ||||
|   .then(element => { | ||||
|     document.getElementById("bgpmap").appendChild(element); | ||||
|   }) | ||||
|   | ||||
| @@ -7,7 +7,20 @@ | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | ||||
| <meta name="renderer" content="webkit"> | ||||
| <title>{{ html .Title }}</title> | ||||
| <link rel="stylesheet" href="/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" integrity="sha256-VoFZSlmyTXsegReQCNmbXrS4hBBUl/cexZvPmPWoJsY=" crossorigin="anonymous"> | ||||
| <link rel="stylesheet" href="/static/jsdelivr/npm/bootstrap@4.5.1/dist/css/bootstrap.min.css" crossorigin="anonymous"> | ||||
| <style> | ||||
| .navbar-nav { | ||||
| 	flex-wrap: wrap; | ||||
| } | ||||
| @media (min-width: 768px) { | ||||
| 	.navbar form { | ||||
| 		min-width: 400px; | ||||
| 	} | ||||
| 	.nav-link { | ||||
| 		padding: 0.2rem 0.5rem !important; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
| <meta name="robots" content="noindex, nofollow"> | ||||
| </head> | ||||
| <body> | ||||
| @@ -74,8 +87,8 @@ | ||||
| 	{{ .Content }} | ||||
| </div> | ||||
|  | ||||
| <script src="/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> | ||||
| <script src="/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js" integrity="sha256-0IiaoZCI++9oAAvmCb5Y0r93XkuhvJpRalZLffQXLok=" crossorigin="anonymous"></script> | ||||
| <script src="/static/jsdelivr/npm/jquery@3.5.1/dist/jquery.min.js" crossorigin="anonymous"></script> | ||||
| <script src="/static/jsdelivr/npm/bootstrap@4.5.1/dist/js/bootstrap.min.js" crossorigin="anonymous"></script> | ||||
| <script src="/static/sortTable.js"></script> | ||||
|  | ||||
| <script> | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| @@ -69,11 +70,15 @@ func (graph *RouteGraph) attrsToString(attrs RouteAttrs) string { | ||||
| } | ||||
|  | ||||
| func (graph *RouteGraph) escape(s string) string { | ||||
| 	result, err := json.Marshal(s) | ||||
| 	buffer := &bytes.Buffer{} | ||||
| 	encoder := json.NewEncoder(buffer) | ||||
| 	encoder.SetEscapeHTML(false) | ||||
| 	err := encoder.Encode(s) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err.Error() | ||||
| 	} else { | ||||
| 		return string(result) | ||||
| 		return string(buffer.Bytes()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ func TestBirdRouteToGraphvizXSS(t *testing.T) { | ||||
| 		fakeResult, | ||||
| 	}, fakeResult) | ||||
|  | ||||
| 	if strings.Contains(result, "<script>") { | ||||
| 	if strings.Contains(result, fakeResult) { | ||||
| 		t.Errorf("XSS injection succeeded: %s", result) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,29 +1,27 @@ | ||||
| module github.com/xddxdd/bird-lg-go/frontend | ||||
|  | ||||
| go 1.17 | ||||
| go 1.23.0 | ||||
|  | ||||
| require ( | ||||
| 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | ||||
| 	github.com/gorilla/handlers v1.5.1 | ||||
| 	github.com/jarcoal/httpmock v1.3.1 | ||||
| 	github.com/magiconair/properties v1.8.7 | ||||
| 	github.com/spf13/pflag v1.0.5 | ||||
| 	github.com/spf13/viper v1.16.0 | ||||
| 	github.com/gorilla/handlers v1.5.2 | ||||
| 	github.com/jarcoal/httpmock v1.4.1 | ||||
| 	github.com/magiconair/properties v1.8.10 | ||||
| 	github.com/spf13/pflag v1.0.10 | ||||
| 	github.com/spf13/viper v1.21.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/felixge/httpsnoop v1.0.3 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.6.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.0.8 // indirect | ||||
| 	github.com/spf13/afero v1.9.5 // indirect | ||||
| 	github.com/spf13/cast v1.5.1 // indirect | ||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||
| 	github.com/subosito/gotenv v1.4.2 // indirect | ||||
| 	golang.org/x/sys v0.8.0 // indirect | ||||
| 	golang.org/x/text v0.9.0 // indirect | ||||
| 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | ||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| 	github.com/felixge/httpsnoop v1.0.4 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.9.0 // indirect | ||||
| 	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.2.4 // indirect | ||||
| 	github.com/sagikazarmark/locafero v0.11.0 // indirect | ||||
| 	github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect | ||||
| 	github.com/spf13/afero v1.15.0 // indirect | ||||
| 	github.com/spf13/cast v1.10.0 // indirect | ||||
| 	github.com/subosito/gotenv v1.6.0 // indirect | ||||
| 	go.yaml.in/yaml/v3 v3.0.4 // indirect | ||||
| 	golang.org/x/sys v0.29.0 // indirect | ||||
| 	golang.org/x/text v0.28.0 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										1789
									
								
								frontend/go.sum
									
									
									
									
									
								
							
							
						
						
									
										1789
									
								
								frontend/go.sum
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,11 +2,14 @@ package main | ||||
|  | ||||
| import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/jarcoal/httpmock" | ||||
| ) | ||||
|  | ||||
| type channelData struct { | ||||
| @@ -14,6 +17,29 @@ type channelData struct { | ||||
| 	data string | ||||
| } | ||||
|  | ||||
| func createConnectionTimeoutRoundTripper(timeout int) http.RoundTripper { | ||||
| 	context := net.Dialer{ | ||||
| 		Timeout: time.Duration(timeout) * time.Second, | ||||
| 	} | ||||
|  | ||||
| 	// Prefer httpmock's transport if activated, so unit tests can work | ||||
| 	if http.DefaultTransport == httpmock.DefaultTransport { | ||||
| 		return httpmock.DefaultTransport | ||||
| 	} | ||||
|  | ||||
| 	return &http.Transport{ | ||||
| 		DialContext: context.DialContext, | ||||
|  | ||||
| 		// Default options from transport.go | ||||
| 		Proxy:                 http.ProxyFromEnvironment, | ||||
| 		ForceAttemptHTTP2:     true, | ||||
| 		MaxIdleConns:          100, | ||||
| 		IdleConnTimeout:       90 * time.Second, | ||||
| 		TLSHandshakeTimeout:   10 * time.Second, | ||||
| 		ExpectContinueTimeout: 1 * time.Second, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Send commands to lgproxy instances in parallel, and retrieve their responses | ||||
| func batchRequest(servers []string, endpoint string, command string) []string { | ||||
| 	// Channel and array for storing responses | ||||
| @@ -47,7 +73,10 @@ func batchRequest(servers []string, endpoint string, command string) []string { | ||||
| 			} | ||||
| 			url := "http://" + hostname + ":" + strconv.Itoa(setting.proxyPort) + "/" + url.PathEscape(endpoint) + "?q=" + url.QueryEscape(command) | ||||
| 			go func(url string, i int) { | ||||
| 				client := http.Client{Timeout: time.Duration(setting.timeOut) * time.Second} | ||||
| 				client := http.Client{ | ||||
| 					Transport: createConnectionTimeoutRoundTripper(setting.connectionTimeOut), | ||||
| 					Timeout:   time.Duration(setting.timeOut) * time.Second, | ||||
| 				} | ||||
| 				response, err := client.Get(url) | ||||
| 				if err != nil { | ||||
| 					ch <- channelData{i, "request failed: " + err.Error() + "\n"} | ||||
|   | ||||
| @@ -7,24 +7,26 @@ import ( | ||||
| ) | ||||
|  | ||||
| type settingType struct { | ||||
| 	servers         []string | ||||
| 	serversDisplay  []string | ||||
| 	domain          string | ||||
| 	proxyPort       int | ||||
| 	whoisServer     string | ||||
| 	listen          string | ||||
| 	dnsInterface    string | ||||
| 	netSpecificMode string | ||||
| 	titleBrand      string | ||||
| 	navBarBrand     string | ||||
| 	navBarBrandURL  string | ||||
| 	navBarAllServer string | ||||
| 	navBarAllURL    string | ||||
| 	bgpmapInfo      string | ||||
| 	telegramBotName string | ||||
| 	protocolFilter  []string | ||||
| 	nameFilter      string | ||||
| 	timeOut         int | ||||
| 	servers           []string | ||||
| 	serversDisplay    []string | ||||
| 	domain            string | ||||
| 	proxyPort         int | ||||
| 	whoisServer       string | ||||
| 	listen            []string | ||||
| 	dnsInterface      string | ||||
| 	netSpecificMode   string | ||||
| 	titleBrand        string | ||||
| 	navBarBrand       string | ||||
| 	navBarBrandURL    string | ||||
| 	navBarAllServer   string | ||||
| 	navBarAllURL      string | ||||
| 	bgpmapInfo        string | ||||
| 	telegramBotName   string | ||||
| 	protocolFilter    []string | ||||
| 	nameFilter        string | ||||
| 	timeOut           int | ||||
| 	connectionTimeOut int | ||||
| 	trustProxyHeaders bool | ||||
| } | ||||
|  | ||||
| var setting settingType | ||||
| @@ -33,24 +35,29 @@ func main() { | ||||
| 	parseSettings() | ||||
| 	ImportTemplates() | ||||
|  | ||||
| 	var l net.Listener | ||||
| 	var err error | ||||
| 	for _, listenAddr := range setting.listen { | ||||
| 		go func(listenAddr string) { | ||||
| 			var l net.Listener | ||||
| 			var err error | ||||
|  | ||||
| 	if strings.HasPrefix(setting.listen, "/") { | ||||
| 		// Delete existing socket file, ignore errors (will fail later anyway) | ||||
| 		os.Remove(setting.listen) | ||||
| 		l, err = net.Listen("unix", setting.listen) | ||||
| 	} else { | ||||
| 		listenAddr := setting.listen | ||||
| 		if !strings.Contains(listenAddr, ":") { | ||||
| 			listenAddr = ":" + listenAddr | ||||
| 		} | ||||
| 		l, err = net.Listen("tcp", listenAddr) | ||||
| 			if strings.HasPrefix(listenAddr, "/") { | ||||
| 				// Delete existing socket file, ignore errors (will fail later anyway) | ||||
| 				os.Remove(listenAddr) | ||||
| 				l, err = net.Listen("unix", listenAddr) | ||||
| 			} else { | ||||
| 				if !strings.Contains(listenAddr, ":") { | ||||
| 					listenAddr = ":" + listenAddr | ||||
| 				} | ||||
| 				l, err = net.Listen("tcp", listenAddr) | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
|  | ||||
| 			webServerStart(l) | ||||
| 		}(listenAddr) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	webServerStart(l) | ||||
| 	select {} | ||||
| } | ||||
|   | ||||
| @@ -35,15 +35,6 @@ var optionsMap = map[string]string{ | ||||
| 	"traceroute":                       "traceroute ...", | ||||
| } | ||||
|  | ||||
| // pre-compiled regexp and constant statemap for summary rendering | ||||
| var splitSummaryLine = regexp.MustCompile(`(\w+)(\s+)(\w+)(\s+)([\w-]+)(\s+)(\w+)(\s+)([0-9\-\. :]+)(.*)`) | ||||
| var summaryStateMap = map[string]string{ | ||||
| 	"up":      "success", | ||||
| 	"down":    "secondary", | ||||
| 	"start":   "danger", | ||||
| 	"passive": "info", | ||||
| } | ||||
|  | ||||
| // render the page template | ||||
| func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, content template.HTML) { | ||||
| 	path := r.URL.Path[1:] | ||||
| @@ -143,58 +134,23 @@ func summaryParse(data string, serverName string) (TemplateSummary, error) { | ||||
|  | ||||
| 	// parse each line | ||||
| 	for _, line := range rows { | ||||
|  | ||||
| 		// Ignore empty lines | ||||
| 		line = strings.TrimSpace(line) | ||||
| 		if len(line) == 0 { | ||||
| 		row := SummaryRowDataFromLine(line) | ||||
| 		if row == nil { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Parse a total of 6 columns from bird summary | ||||
| 		lineSplitted := splitSummaryLine.FindStringSubmatch(line) | ||||
| 		if lineSplitted == nil { | ||||
| 		// Filter row name | ||||
| 		if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		var row SummaryRowData | ||||
|  | ||||
| 		if len(lineSplitted) >= 2 { | ||||
| 			row.Name = strings.TrimSpace(lineSplitted[1]) | ||||
| 			if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		if len(lineSplitted) >= 4 { | ||||
| 			row.Proto = strings.TrimSpace(lineSplitted[3]) | ||||
| 			// Filter away unwanted protocol types, if setting.protocolFilter is non-empty | ||||
| 			found := false | ||||
| 			for _, protocol := range setting.protocolFilter { | ||||
| 				if strings.EqualFold(row.Proto, protocol) { | ||||
| 					found = true | ||||
| 					break | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if len(setting.protocolFilter) > 0 && !found { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		if len(lineSplitted) >= 6 { | ||||
| 			row.Table = strings.TrimSpace(lineSplitted[5]) | ||||
| 		} | ||||
| 		if len(lineSplitted) >= 8 { | ||||
| 			row.State = strings.TrimSpace(lineSplitted[7]) | ||||
| 			row.MappedState = summaryStateMap[row.State] | ||||
| 		} | ||||
| 		if len(lineSplitted) >= 10 { | ||||
| 			row.Since = strings.TrimSpace(lineSplitted[9]) | ||||
| 		} | ||||
| 		if len(lineSplitted) >= 11 { | ||||
| 			row.Info = strings.TrimSpace(lineSplitted[10]) | ||||
| 		// Filter away unwanted protocol types, if setting.protocolFilter is non-empty | ||||
| 		if len(setting.protocolFilter) > 0 && !row.ProtocolMatches(setting.protocolFilter) { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// add to the result | ||||
| 		args.Rows = append(args.Rows, row) | ||||
| 		args.Rows = append(args.Rows, *row) | ||||
| 	} | ||||
|  | ||||
| 	return args, nil | ||||
|   | ||||
| @@ -8,8 +8,7 @@ import ( | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| const BirdSummaryData = `BIRD 2.0.8 ready. | ||||
| Name       Proto      Table      State  Since         Info | ||||
| const BirdSummaryData = `Name       Proto      Table      State  Since         Info | ||||
| static1    Static     master4    up     2021-08-27 | ||||
| static2    Static     master6    up     2021-08-27 | ||||
| device1    Device     ---        up     2021-08-27 | ||||
|   | ||||
| @@ -9,23 +9,25 @@ import ( | ||||
| ) | ||||
|  | ||||
| type viperSettingType struct { | ||||
| 	Servers         string `mapstructure:"servers"` | ||||
| 	Domain          string `mapstructure:"domain"` | ||||
| 	ProxyPort       int    `mapstructure:"proxy_port"` | ||||
| 	WhoisServer     string `mapstructure:"whois"` | ||||
| 	Listen          string `mapstructure:"listen"` | ||||
| 	DNSInterface    string `mapstructure:"dns_interface"` | ||||
| 	NetSpecificMode string `mapstructure:"net_specific_mode"` | ||||
| 	TitleBrand      string `mapstructure:"title_brand"` | ||||
| 	NavBarBrand     string `mapstructure:"navbar_brand"` | ||||
| 	NavBarBrandURL  string `mapstructure:"navbar_brand_url"` | ||||
| 	NavBarAllServer string `mapstructure:"navbar_all_servers"` | ||||
| 	NavBarAllURL    string `mapstructure:"navbar_all_url"` | ||||
| 	BgpmapInfo      string `mapstructure:"bgpmap_info"` | ||||
| 	TelegramBotName string `mapstructure:"telegram_bot_name"` | ||||
| 	ProtocolFilter  string `mapstructure:"protocol_filter"` | ||||
| 	NameFilter      string `mapstructure:"name_filter"` | ||||
| 	TimeOut         int    `mapstructure:"timeout"` | ||||
| 	Servers           string   `mapstructure:"servers"` | ||||
| 	Domain            string   `mapstructure:"domain"` | ||||
| 	ProxyPort         int      `mapstructure:"proxy_port"` | ||||
| 	WhoisServer       string   `mapstructure:"whois"` | ||||
| 	Listen            []string `mapstructure:"listen"` | ||||
| 	DNSInterface      string   `mapstructure:"dns_interface"` | ||||
| 	NetSpecificMode   string   `mapstructure:"net_specific_mode"` | ||||
| 	TitleBrand        string   `mapstructure:"title_brand"` | ||||
| 	NavBarBrand       string   `mapstructure:"navbar_brand"` | ||||
| 	NavBarBrandURL    string   `mapstructure:"navbar_brand_url"` | ||||
| 	NavBarAllServer   string   `mapstructure:"navbar_all_servers"` | ||||
| 	NavBarAllURL      string   `mapstructure:"navbar_all_url"` | ||||
| 	BgpmapInfo        string   `mapstructure:"bgpmap_info"` | ||||
| 	TelegramBotName   string   `mapstructure:"telegram_bot_name"` | ||||
| 	ProtocolFilter    string   `mapstructure:"protocol_filter"` | ||||
| 	NameFilter        string   `mapstructure:"name_filter"` | ||||
| 	TimeOut           int      `mapstructure:"timeout"` | ||||
| 	ConnectionTimeOut int      `mapstructure:"connection_timeout"` | ||||
| 	TrustProxyHeaders bool     `mapstructure:"trust_proxy_headers"` | ||||
| } | ||||
|  | ||||
| // Parse settings with viper, and convert to legacy setting format | ||||
| @@ -50,7 +52,7 @@ func parseSettings() { | ||||
| 	pflag.String("whois", "whois.verisign-grs.com", "whois server for queries") | ||||
| 	viper.BindPFlag("whois", pflag.Lookup("whois")) | ||||
|  | ||||
| 	pflag.String("listen", "5000", "address or unix socket bird-lg is listening on") | ||||
| 	pflag.StringSlice("listen", []string{"5000"}, "address or unix socket bird-lg is listening on") | ||||
| 	viper.BindPFlag("listen", pflag.Lookup("listen")) | ||||
|  | ||||
| 	pflag.String("dns-interface", "asn.cymru.com", "dns zone to query ASN information") | ||||
| @@ -87,9 +89,15 @@ func parseSettings() { | ||||
| 	pflag.String("name-filter", "", "protocol name regex to hide in summary tables (RE2 syntax); defaults to none if not set") | ||||
| 	viper.BindPFlag("name_filter", pflag.Lookup("name-filter")) | ||||
|  | ||||
| 	pflag.Int("time-out", 120, "time before request timed out, in seconds; defaults to 120 if not set") | ||||
| 	pflag.Int("time-out", 120, "time before backend HTTP request times out, in seconds; defaults to 120 if not set") | ||||
| 	viper.BindPFlag("timeout", pflag.Lookup("time-out")) | ||||
|  | ||||
| 	pflag.Int("connection-time-out", 5, "time before backend TCP connection times out, in seconds; defaults to 5 if not set") | ||||
| 	viper.BindPFlag("connection_timeout", pflag.Lookup("connection-time-out")) | ||||
|  | ||||
| 	pflag.Bool("trust-proxy-headers", false, "Trust X-Forwared-For, X-Real-IP, X-Forwarded-Proto, X-Forwarded-Scheme and X-Forwarded-Host sent by the client") | ||||
| 	viper.BindPFlag("trust_proxy_headers", pflag.Lookup("trust-proxy-headers")) | ||||
|  | ||||
| 	pflag.Parse() | ||||
|  | ||||
| 	if err := viper.ReadInConfig(); err != nil { | ||||
| @@ -139,6 +147,8 @@ func parseSettings() { | ||||
|  | ||||
| 	setting.nameFilter = viperSettings.NameFilter | ||||
| 	setting.timeOut = viperSettings.TimeOut | ||||
| 	setting.connectionTimeOut = viperSettings.ConnectionTimeOut | ||||
| 	setting.trustProxyHeaders = viperSettings.TrustProxyHeaders | ||||
|  | ||||
| 	fmt.Printf("%#v\n", setting) | ||||
| } | ||||
|   | ||||
| @@ -3,11 +3,13 @@ package main | ||||
| import ( | ||||
| 	"embed" | ||||
| 	"html/template" | ||||
|         "net/url" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // import templates and other assets | ||||
| // | ||||
| //go:embed assets | ||||
| var assets embed.FS | ||||
|  | ||||
| @@ -64,6 +66,47 @@ func (r SummaryRowData) NameContains(prefix string) bool { | ||||
| 	return strings.Contains(r.Name, prefix) | ||||
| } | ||||
|  | ||||
| func (r SummaryRowData) ProtocolMatches(protocols []string) bool { | ||||
| 	for _, protocol := range protocols { | ||||
| 		if strings.EqualFold(r.Proto, protocol) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // pre-compiled regexp and constant statemap for summary rendering | ||||
| var splitSummaryLine = regexp.MustCompile(`^([\w-]+)\s+(\w+)\s+([\w-]+)\s+(\w+)\s+([0-9\-\. :]+)(.*)$`) | ||||
| var summaryStateMap = map[string]string{ | ||||
| 	"up":      "success", | ||||
| 	"down":    "secondary", | ||||
| 	"start":   "danger", | ||||
| 	"passive": "info", | ||||
| } | ||||
|  | ||||
| func SummaryRowDataFromLine(line string) *SummaryRowData { | ||||
| 	lineSplitted := splitSummaryLine.FindStringSubmatch(line) | ||||
| 	if lineSplitted == nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var row SummaryRowData | ||||
| 	row.Name = strings.TrimSpace(lineSplitted[1]) | ||||
| 	row.Proto = strings.TrimSpace(lineSplitted[2]) | ||||
| 	row.Table = strings.TrimSpace(lineSplitted[3]) | ||||
| 	row.State = strings.TrimSpace(lineSplitted[4]) | ||||
| 	row.Since = strings.TrimSpace(lineSplitted[5]) | ||||
| 	row.Info = strings.TrimSpace(lineSplitted[6]) | ||||
|  | ||||
| 	if strings.Contains(row.Info, "Passive") { | ||||
| 		row.MappedState = summaryStateMap["passive"] | ||||
| 	} else { | ||||
| 		row.MappedState = summaryStateMap[row.State] | ||||
| 	} | ||||
|  | ||||
| 	return &row | ||||
| } | ||||
|  | ||||
| type TemplateSummary struct { | ||||
| 	ServerName string | ||||
| 	Raw        string | ||||
| @@ -108,7 +151,7 @@ var requiredTemplates = [...]string{ | ||||
| // define functions to be made available in templates | ||||
|  | ||||
| var funcMap = template.FuncMap{ | ||||
|         "pathescape": url.PathEscape, | ||||
| 	"pathescape": url.PathEscape, | ||||
| } | ||||
|  | ||||
| // import templates from embedded assets | ||||
|   | ||||
| @@ -23,3 +23,67 @@ func TestSummaryRowDataNameContains(t *testing.T) { | ||||
| 	assert.Equal(t, data.NameContains("oc"), true) | ||||
| 	assert.Equal(t, data.NameContains("no"), false) | ||||
| } | ||||
|  | ||||
| func TestSummaryRowDataFromLine(t *testing.T) { | ||||
| 	data := SummaryRowDataFromLine("sys_device Device     ---        up     2025-06-27 21:23:08") | ||||
|  | ||||
| 	assert.Equal(t, data.Name, "sys_device") | ||||
| 	assert.Equal(t, data.Proto, "Device") | ||||
| 	assert.Equal(t, data.Table, "---") | ||||
| 	assert.Equal(t, data.State, "up") | ||||
| 	assert.Equal(t, data.Since, "2025-06-27 21:23:08") | ||||
| } | ||||
|  | ||||
| func TestSummaryRowDataFromLineNumeric(t *testing.T) { | ||||
| 	data := SummaryRowDataFromLine("12345 Device     ---        up     2025-06-27 21:23:08") | ||||
|  | ||||
| 	assert.Equal(t, data.Name, "12345") | ||||
| 	assert.Equal(t, data.Proto, "Device") | ||||
| 	assert.Equal(t, data.Table, "---") | ||||
| 	assert.Equal(t, data.State, "up") | ||||
| 	assert.Equal(t, data.Since, "2025-06-27 21:23:08") | ||||
| } | ||||
|  | ||||
| func TestSummaryRowDataFromLinePipe(t *testing.T) { | ||||
| 	data := SummaryRowDataFromLine("pipe Pipe       ---        up     2025-06-27 21:23:08  master4 <=> pipe_v4") | ||||
|  | ||||
| 	assert.Equal(t, data.Name, "pipe") | ||||
| 	assert.Equal(t, data.Proto, "Pipe") | ||||
| 	assert.Equal(t, data.Table, "---") | ||||
| 	assert.Equal(t, data.State, "up") | ||||
| 	assert.Equal(t, data.Since, "2025-06-27 21:23:08") | ||||
| 	assert.Equal(t, data.Info, "master4 <=> pipe_v4") | ||||
| } | ||||
|  | ||||
| func TestSummaryRowDataFromLineBGP(t *testing.T) { | ||||
| 	data := SummaryRowDataFromLine("bgp BGP        ---        up     2025-06-30 20:45:33  Established") | ||||
|  | ||||
| 	assert.Equal(t, data.Name, "bgp") | ||||
| 	assert.Equal(t, data.Proto, "BGP") | ||||
| 	assert.Equal(t, data.Table, "---") | ||||
| 	assert.Equal(t, data.State, "up") | ||||
| 	assert.Equal(t, data.Since, "2025-06-30 20:45:33") | ||||
| 	assert.Equal(t, data.Info, "Established") | ||||
| } | ||||
|  | ||||
| func TestSummaryRowDataFromLineBGPPassive(t *testing.T) { | ||||
| 	data := SummaryRowDataFromLine("passive   BGP        ---        start  2025-06-27 21:23:08  Passive") | ||||
|  | ||||
| 	assert.Equal(t, data.Name, "passive") | ||||
| 	assert.Equal(t, data.Proto, "BGP") | ||||
| 	assert.Equal(t, data.Table, "---") | ||||
| 	assert.Equal(t, data.State, "start") | ||||
| 	assert.Equal(t, data.Since, "2025-06-27 21:23:08") | ||||
| 	assert.Equal(t, data.Info, "Passive") | ||||
| } | ||||
|  | ||||
| func TestSummaryRowDataFromLineWithDash(t *testing.T) { | ||||
| 	data := SummaryRowDataFromLine("ibgp_test-01 BGP        ---        up     07:16:51.656  Established") | ||||
|  | ||||
| 	assert.Equal(t, data.Name, "ibgp_test-01") | ||||
| 	assert.Equal(t, data.Proto, "BGP") | ||||
| 	assert.Equal(t, data.Table, "---") | ||||
| 	assert.Equal(t, data.State, "up") | ||||
| 	assert.Equal(t, data.Since, "07:16:51.656") | ||||
| 	assert.Equal(t, data.Info, "Established") | ||||
| } | ||||
|   | ||||
| @@ -12,19 +12,20 @@ import ( | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync/atomic" | ||||
|  | ||||
| 	"github.com/gorilla/handlers" | ||||
| ) | ||||
|  | ||||
| var primitiveMap = map[string]string{ | ||||
| 	"summary":                          "show protocols", | ||||
| 	"detail":                           "show protocols all %s", | ||||
| 	"route_from_protocol":              "show route protocol %s", | ||||
| 	"route_from_protocol_all":          "show route protocol %s all", | ||||
| 	"route_from_protocol_primary":      "show route protocol %s primary", | ||||
| 	"route_from_protocol_all_primary":  "show route protocol %s all primary", | ||||
| 	"route_filtered_from_protocol":     "show route filtered protocol %s", | ||||
| 	"route_filtered_from_protocol_all": "show route filtered protocol %s all", | ||||
| 	"detail":                           "show protocols all \"%s\"", | ||||
| 	"route_from_protocol":              "show route protocol \"%s\"", | ||||
| 	"route_from_protocol_all":          "show route protocol \"%s\" all", | ||||
| 	"route_from_protocol_primary":      "show route protocol \"%s\" primary", | ||||
| 	"route_from_protocol_all_primary":  "show route protocol \"%s\" all primary", | ||||
| 	"route_filtered_from_protocol":     "show route filtered protocol \"%s\"", | ||||
| 	"route_filtered_from_protocol_all": "show route filtered protocol \"%s\" all", | ||||
| 	"route_from_origin":                "show route where bgp_path.last = %s", | ||||
| 	"route_from_origin_all":            "show route where bgp_path.last = %s all", | ||||
| 	"route_from_origin_primary":        "show route where bgp_path.last = %s primary", | ||||
| @@ -39,8 +40,10 @@ var primitiveMap = map[string]string{ | ||||
| 	"traceroute":                       "%s", | ||||
| } | ||||
|  | ||||
| var webServerPrepared uint32 = 0 | ||||
|  | ||||
| // serve up a generic error | ||||
| func serverError(w http.ResponseWriter, r *http.Request) { | ||||
| func serverError(w http.ResponseWriter, _ *http.Request) { | ||||
| 	w.WriteHeader(http.StatusInternalServerError) | ||||
| 	w.Write([]byte("500 Internal Server Error")) | ||||
| } | ||||
| @@ -75,7 +78,6 @@ func webHandlerWhois(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| // serve up results from bird | ||||
| func webBackendCommunicator(endpoint string, command string) func(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| 	backendCommandPrimitive, commandPresent := primitiveMap[command] | ||||
| 	if !commandPresent { | ||||
| 		panic("invalid command: " + command) | ||||
| @@ -193,12 +195,11 @@ func webHandlerBGPMap(endpoint string, command string) func(w http.ResponseWrite | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // set up routing paths and start webserver | ||||
| func webServerStart(l net.Listener) { | ||||
|  | ||||
| // set up routing paths | ||||
| func webServerPrepare() { | ||||
| 	// redirect main page to all server summary | ||||
| 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		http.Redirect(w, r, "/summary/"+url.PathEscape(strings.Join(setting.servers, "+")), 302) | ||||
| 		http.Redirect(w, r, "/summary/"+url.PathEscape(strings.Join(setting.servers, "+")), http.StatusFound) | ||||
| 	}) | ||||
|  | ||||
| 	// serve static pages using embedded assets from template.go | ||||
| @@ -237,7 +238,19 @@ func webServerStart(l net.Listener) { | ||||
| 	http.HandleFunc("/whois/", webHandlerWhois) | ||||
| 	http.HandleFunc("/api/", apiHandler) | ||||
| 	http.HandleFunc("/telegram/", webHandlerTelegramBot) | ||||
|  | ||||
| 	// Start HTTP server | ||||
| 	http.Serve(l, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux)) | ||||
| } | ||||
|  | ||||
| // start webserver | ||||
| func webServerStart(l net.Listener) { | ||||
| 	if atomic.SwapUint32(&webServerPrepared, 1) == 0 { | ||||
| 		webServerPrepare() | ||||
| 	} | ||||
|  | ||||
| 	var handler http.Handler | ||||
| 	handler = http.DefaultServeMux | ||||
| 	if setting.trustProxyHeaders { | ||||
| 		handler = handlers.ProxyHeaders(handler) | ||||
| 	} | ||||
| 	handler = handlers.LoggingHandler(os.Stdout, handler) | ||||
| 	http.Serve(l, handler) | ||||
| } | ||||
|   | ||||
| @@ -88,7 +88,7 @@ func TestWhoisWithoutServer(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestWhoisConnectionError(t *testing.T) { | ||||
| 	setting.whoisServer = "127.0.0.1:0" | ||||
| 	setting.whoisServer = "127.0.0.1:1" | ||||
| 	result := whois("AS6939") | ||||
| 	if !strings.Contains(result, "connect: connection refused") { | ||||
| 		t.Errorf("Whois AS6939 without server produced output, got %s", result) | ||||
|   | ||||
							
								
								
									
										31
									
								
								proxy/Dockerfile.mtr
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								proxy/Dockerfile.mtr
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| FROM golang AS step_0 | ||||
|  | ||||
| ENV CGO_ENABLED=0 GO111MODULE=on | ||||
| WORKDIR /root | ||||
| COPY . . | ||||
| RUN go build -ldflags "-w -s" -o /proxy | ||||
|  | ||||
| ################################################################################ | ||||
|  | ||||
| FROM alpine:edge AS step_1 | ||||
|  | ||||
| WORKDIR /root | ||||
| RUN apk add --no-cache build-base linux-headers | ||||
|  | ||||
| RUN wget https://www.bitwizard.nl/mtr/files/mtr-0.94.tar.gz \ | ||||
|     -O mtr-0.94.tar.gz | ||||
| RUN tar xvf mtr-0.94.tar.gz \ | ||||
|     && cd mtr-0.94 \ | ||||
|     && ./configure --without-gtk --without-ncurses --without-jansson --without-ipinfo --disable-bash-completion \ | ||||
|     && make -j4 LDFLAGS="-static" \ | ||||
|     && strip /root/mtr-0.94/mtr \ | ||||
|     && strip /root/mtr-0.94/mtr-packet | ||||
|  | ||||
| ################################################################################ | ||||
|  | ||||
| FROM scratch AS step_2 | ||||
| ENV PATH=/ | ||||
| COPY --from=step_0 /proxy / | ||||
| COPY --from=step_1 /root/mtr-0.94/mtr / | ||||
| COPY --from=step_1 /root/mtr-0.94/mtr-packet / | ||||
| ENTRYPOINT ["/proxy"] | ||||
							
								
								
									
										36
									
								
								proxy/go.mod
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								proxy/go.mod
									
									
									
									
									
								
							| @@ -1,28 +1,26 @@ | ||||
| module github.com/xddxdd/bird-lg-go/proxy | ||||
|  | ||||
| go 1.17 | ||||
| go 1.23.0 | ||||
|  | ||||
| require ( | ||||
| 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | ||||
| 	github.com/gorilla/handlers v1.5.1 | ||||
| 	github.com/magiconair/properties v1.8.7 | ||||
| 	github.com/spf13/pflag v1.0.5 | ||||
| 	github.com/spf13/viper v1.16.0 | ||||
| 	github.com/gorilla/handlers v1.5.2 | ||||
| 	github.com/magiconair/properties v1.8.10 | ||||
| 	github.com/spf13/pflag v1.0.10 | ||||
| 	github.com/spf13/viper v1.21.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/felixge/httpsnoop v1.0.3 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.6.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.0.8 // indirect | ||||
| 	github.com/spf13/afero v1.9.5 // indirect | ||||
| 	github.com/spf13/cast v1.5.1 // indirect | ||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||
| 	github.com/subosito/gotenv v1.4.2 // indirect | ||||
| 	golang.org/x/sys v0.8.0 // indirect | ||||
| 	golang.org/x/text v0.9.0 // indirect | ||||
| 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | ||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| 	github.com/felixge/httpsnoop v1.0.4 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.9.0 // indirect | ||||
| 	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.2.4 // indirect | ||||
| 	github.com/sagikazarmark/locafero v0.11.0 // indirect | ||||
| 	github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect | ||||
| 	github.com/spf13/afero v1.15.0 // indirect | ||||
| 	github.com/spf13/cast v1.10.0 // indirect | ||||
| 	github.com/subosito/gotenv v1.6.0 // indirect | ||||
| 	go.yaml.in/yaml/v3 v3.0.4 // indirect | ||||
| 	golang.org/x/sys v0.29.0 // indirect | ||||
| 	golang.org/x/text v0.28.0 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										1781
									
								
								proxy/go.sum
									
									
									
									
									
								
							
							
						
						
									
										1781
									
								
								proxy/go.sum
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -22,8 +22,8 @@ func invalidHandler(httpW http.ResponseWriter, httpR *http.Request) { | ||||
| } | ||||
|  | ||||
| func hasAccess(remoteAddr string) bool { | ||||
| 	// setting.allowedIPs will always have at least one element because of how it's defined | ||||
| 	if len(setting.allowedIPs) == 0 { | ||||
| 	// setting.allowedNets will always have at least one element because of how it's defined | ||||
| 	if len(setting.allowedNets) == 0 { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| @@ -40,8 +40,8 @@ func hasAccess(remoteAddr string) bool { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	for _, allowedIP := range setting.allowedIPs { | ||||
| 		if ipObject.Equal(allowedIP) { | ||||
| 	for _, net := range setting.allowedNets { | ||||
| 		if net.Contains(ipObject) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| @@ -49,7 +49,7 @@ func hasAccess(remoteAddr string) bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // Access handler, check to see if client IP in allowed IPs, continue if it is, send to invalidHandler if not | ||||
| // Access handler, check to see if client IP in allowed nets, continue if it is, send to invalidHandler if not | ||||
| func accessHandler(next http.Handler) http.Handler { | ||||
| 	return http.HandlerFunc(func(httpW http.ResponseWriter, httpR *http.Request) { | ||||
| 		if hasAccess(httpR.RemoteAddr) { | ||||
| @@ -61,12 +61,12 @@ func accessHandler(next http.Handler) http.Handler { | ||||
| } | ||||
|  | ||||
| type settingType struct { | ||||
| 	birdSocket string | ||||
| 	listen     string | ||||
| 	allowedIPs []net.IP | ||||
| 	tr_bin     string | ||||
| 	tr_flags   []string | ||||
| 	tr_raw     bool | ||||
| 	birdSocket  string | ||||
| 	listen      []string | ||||
| 	allowedNets []*net.IPNet | ||||
| 	tr_bin      string | ||||
| 	tr_flags    []string | ||||
| 	tr_raw      bool | ||||
| } | ||||
|  | ||||
| var setting settingType | ||||
| @@ -76,32 +76,40 @@ func main() { | ||||
| 	parseSettings() | ||||
| 	tracerouteAutodetect() | ||||
|  | ||||
| 	fmt.Printf("Listening on %s...\n", setting.listen) | ||||
| 	mux := http.NewServeMux() | ||||
|  | ||||
| 	var l net.Listener | ||||
| 	var err error | ||||
| 	// Prepare HTTP server | ||||
| 	mux.HandleFunc("/", invalidHandler) | ||||
| 	mux.HandleFunc("/bird", birdHandler) | ||||
| 	mux.HandleFunc("/bird6", birdHandler) | ||||
| 	mux.HandleFunc("/traceroute", tracerouteHandler) | ||||
| 	mux.HandleFunc("/traceroute6", tracerouteHandler) | ||||
|  | ||||
| 	if strings.HasPrefix(setting.listen, "/") { | ||||
| 		// Delete existing socket file, ignore errors (will fail later anyway) | ||||
| 		os.Remove(setting.listen) | ||||
| 		l, err = net.Listen("unix", setting.listen) | ||||
| 	} else { | ||||
| 		listenAddr := setting.listen | ||||
| 		if !strings.Contains(listenAddr, ":") { | ||||
| 			listenAddr = ":" + listenAddr | ||||
| 		} | ||||
| 		l, err = net.Listen("tcp", listenAddr) | ||||
| 	for _, listenAddr := range setting.listen { | ||||
| 		go func(addr string) { | ||||
| 			fmt.Printf("Listening on %s...\n", addr) | ||||
|  | ||||
| 			var l net.Listener | ||||
| 			var err error | ||||
|  | ||||
| 			if strings.HasPrefix(addr, "/") { | ||||
| 				// Delete existing socket file, ignore errors (will fail later anyway) | ||||
| 				os.Remove(addr) | ||||
| 				l, err = net.Listen("unix", addr) | ||||
| 			} else { | ||||
| 				if !strings.Contains(addr, ":") { | ||||
| 					addr = ":" + addr | ||||
| 				} | ||||
| 				l, err = net.Listen("tcp", addr) | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				panic(err) | ||||
| 			} | ||||
|  | ||||
| 			http.Serve(l, handlers.LoggingHandler(os.Stdout, accessHandler(mux))) | ||||
| 		}(listenAddr) | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	// Start HTTP server | ||||
| 	http.HandleFunc("/", invalidHandler) | ||||
| 	http.HandleFunc("/bird", birdHandler) | ||||
| 	http.HandleFunc("/bird6", birdHandler) | ||||
| 	http.HandleFunc("/traceroute", tracerouteHandler) | ||||
| 	http.HandleFunc("/traceroute6", tracerouteHandler) | ||||
| 	http.Serve(l, handlers.LoggingHandler(os.Stdout, accessHandler(http.DefaultServeMux))) | ||||
| 	select {} | ||||
| } | ||||
|   | ||||
| @@ -10,42 +10,61 @@ import ( | ||||
| ) | ||||
|  | ||||
| func TestHasAccessNotConfigured(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{} | ||||
| 	setting.allowedNets = []*net.IPNet{} | ||||
| 	assert.Equal(t, hasAccess("whatever"), true) | ||||
| } | ||||
|  | ||||
| func TestHasAccessAllowIPv4(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("1.2.3.4")} | ||||
| 	_, netip, _ := net.ParseCIDR("1.2.3.4/32") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("1.2.3.4:4321"), true) | ||||
| } | ||||
|  | ||||
| func TestHasAccessAllowIPv4Net(t *testing.T) { | ||||
| 	_, netip, _ := net.ParseCIDR("1.2.3.0/24") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("1.2.3.4:4321"), true) | ||||
| } | ||||
|  | ||||
| func TestHasAccessDenyIPv4(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("4.3.2.1")} | ||||
| 	_, netip, _ := net.ParseCIDR("4.3.2.1/32") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("1.2.3.4:4321"), false) | ||||
| } | ||||
|  | ||||
| func TestHasAccessAllowIPv6(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("2001:db8::1")} | ||||
| 	_, netip, _ := net.ParseCIDR("2001:db8::1/128") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("[2001:db8::1]:4321"), true) | ||||
| } | ||||
|  | ||||
| func TestHasAccessAllowIPv6Net(t *testing.T) { | ||||
| 	_, netip, _ := net.ParseCIDR("2001:db8::/64") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("[2001:db8::1]:4321"), true) | ||||
| } | ||||
|  | ||||
| func TestHasAccessAllowIPv6DifferentForm(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("2001:0db8::1")} | ||||
| 	_, netip, _ := net.ParseCIDR("2001:db8::1/128") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("[2001:db8::1]:4321"), true) | ||||
| } | ||||
|  | ||||
| func TestHasAccessDenyIPv6(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("2001:db8::2")} | ||||
| 	_, netip, _ := net.ParseCIDR("2001:db8::2/128") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("[2001:db8::1]:4321"), false) | ||||
| } | ||||
|  | ||||
| func TestHasAccessBadClientIP(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("1.2.3.4")} | ||||
| 	_, netip, _ := net.ParseCIDR("1.2.3.4/32") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("not an IP"), false) | ||||
| } | ||||
|  | ||||
| func TestHasAccessBadClientIPPort(t *testing.T) { | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("1.2.3.4")} | ||||
| 	_, netip, _ := net.ParseCIDR("1.2.3.4/32") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
| 	assert.Equal(t, hasAccess("not an IP:not a port"), false) | ||||
| } | ||||
|  | ||||
| @@ -57,7 +76,8 @@ func TestAccessHandlerAllow(t *testing.T) { | ||||
| 	r.RemoteAddr = "1.2.3.4:4321" | ||||
| 	w := httptest.NewRecorder() | ||||
|  | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("1.2.3.4")} | ||||
| 	_, netip, _ := net.ParseCIDR("1.2.3.4/32") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
|  | ||||
| 	wrappedHandler.ServeHTTP(w, r) | ||||
| 	assert.Equal(t, w.Code, http.StatusNotFound) | ||||
| @@ -71,7 +91,8 @@ func TestAccessHandlerDeny(t *testing.T) { | ||||
| 	r.RemoteAddr = "1.2.3.4:4321" | ||||
| 	w := httptest.NewRecorder() | ||||
|  | ||||
| 	setting.allowedIPs = []net.IP{net.ParseIP("4.3.2.1")} | ||||
| 	_, netip, _ := net.ParseCIDR("4.3.2.1/32") | ||||
| 	setting.allowedNets = []*net.IPNet{netip} | ||||
|  | ||||
| 	wrappedHandler.ServeHTTP(w, r) | ||||
| 	assert.Equal(t, w.Code, http.StatusInternalServerError) | ||||
|   | ||||
| @@ -11,12 +11,12 @@ import ( | ||||
| ) | ||||
|  | ||||
| type viperSettingType struct { | ||||
| 	BirdSocket      string `mapstructure:"bird_socket"` | ||||
| 	Listen          string `mapstructure:"listen"` | ||||
| 	AllowedIPs      string `mapstructure:"allowed_ips"` | ||||
| 	TracerouteBin   string `mapstructure:"traceroute_bin"` | ||||
| 	TracerouteFlags string `mapstructure:"traceroute_flags"` | ||||
| 	TracerouteRaw   bool   `mapstructure:"traceroute_raw"` | ||||
| 	BirdSocket      string   `mapstructure:"bird_socket"` | ||||
| 	Listen          []string `mapstructure:"listen"` | ||||
| 	AllowedNets     string   `mapstructure:"allowed_ips"` | ||||
| 	TracerouteBin   string   `mapstructure:"traceroute_bin"` | ||||
| 	TracerouteFlags string   `mapstructure:"traceroute_flags"` | ||||
| 	TracerouteRaw   bool     `mapstructure:"traceroute_raw"` | ||||
| } | ||||
|  | ||||
| // Parse settings with viper, and convert to legacy setting format | ||||
| @@ -37,10 +37,10 @@ func parseSettings() { | ||||
| 	pflag.String("bird", "/var/run/bird/bird.ctl", "socket file for bird, set either in parameter or environment variable BIRD_SOCKET") | ||||
| 	viper.BindPFlag("bird_socket", pflag.Lookup("bird")) | ||||
|  | ||||
| 	pflag.String("listen", "8000", "listen address, set either in parameter or environment variable BIRDLG_PROXY_PORT") | ||||
| 	pflag.StringSlice("listen", []string{"8000"}, "listen address, set either in parameter or environment variable BIRDLG_PROXY_PORT") | ||||
| 	viper.BindPFlag("listen", pflag.Lookup("listen")) | ||||
|  | ||||
| 	pflag.String("allowed", "", "IPs allowed to access this proxy, separated by commas. Don't set to allow all IPs.") | ||||
| 	pflag.String("allowed", "", "IPs or networks allowed to access this proxy, separated by commas. Don't set to allow all IPs.") | ||||
| 	viper.BindPFlag("allowed_ips", pflag.Lookup("allowed")) | ||||
|  | ||||
| 	pflag.String("traceroute_bin", "", "traceroute binary file, set either in parameter or environment variable BIRDLG_TRACEROUTE_BIN") | ||||
| @@ -66,18 +66,31 @@ func parseSettings() { | ||||
| 	setting.birdSocket = viperSettings.BirdSocket | ||||
| 	setting.listen = viperSettings.Listen | ||||
|  | ||||
| 	if viperSettings.AllowedIPs != "" { | ||||
| 		for _, ip := range strings.Split(viperSettings.AllowedIPs, ",") { | ||||
| 			ipObject := net.ParseIP(ip) | ||||
| 			if ipObject == nil { | ||||
| 				fmt.Printf("Parse IP %s failed\n", ip) | ||||
| 				continue | ||||
| 	if viperSettings.AllowedNets != "" { | ||||
| 		for _, arg := range strings.Split(viperSettings.AllowedNets, ",") { | ||||
|  | ||||
| 			// if argument is an IP address, convert to CIDR by adding a suitable mask | ||||
| 			if !strings.Contains(arg, "/") { | ||||
| 				if strings.Contains(arg, ":") { | ||||
| 					// IPv6 address with /128 mask | ||||
| 					arg += "/128" | ||||
| 				} else { | ||||
| 					// IPv4 address with /32 mask | ||||
| 					arg += "/32" | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			setting.allowedIPs = append(setting.allowedIPs, ipObject) | ||||
| 			// parse the network | ||||
| 			_, netip, err := net.ParseCIDR(arg) | ||||
| 			if err != nil { | ||||
| 				fmt.Printf("Failed to parse CIDR %s: %s\n", arg, err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
| 			setting.allowedNets = append(setting.allowedNets, netip) | ||||
|  | ||||
| 		} | ||||
| 	} else { | ||||
| 		setting.allowedIPs = []net.IP{} | ||||
| 		setting.allowedNets = []*net.IPNet{} | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
|   | ||||
		Reference in New Issue
	
	Block a user