1
mirror of https://github.com/xddxdd/bird-lg-go synced 2025-10-27 06:42:13 +01:00

34 Commits

Author SHA1 Message Date
Lan Tian
bb933954a9 release: v1.3.12.1 2025-10-13 19:35:11 -07:00
Lan Tian
4898a095b1 ci: update go release action dependency 2025-10-13 19:34:55 -07:00
Lan Tian
76fa6c6d06 ci: disable CGO when building release binaries 2025-10-13 19:34:23 -07:00
Lan Tian
ae6460d2d3 release: v1.3.12 2025-10-04 13:41:36 -07:00
Ljcbaby
d312f7de1b fix: " cuase unexpected TEXT but ' not 2025-10-04 13:40:45 -07:00
Ljcbaby
1097e69070 fix: " cuase unexpected TEXT but ' not 2025-10-04 13:40:45 -07:00
Lan Tian
4a8c752157 release: v1.3.11 2025-09-27 01:54:01 -07:00
Lan Tian
5ad6a4d35c frontend: fix race condition when setting up web server 2025-09-27 01:50:53 -07:00
Ljcbaby
5422c8fd8c refactor: deplcate Handle register 2025-09-27 01:50:53 -07:00
Ljcbaby
5042980d79 frontend multi listen 2025-09-27 01:50:53 -07:00
Ljcbaby
7884531a24 proxy multi listen 2025-09-27 01:50:53 -07:00
dependabot[bot]
cc804e81b6 Merge pull request #129 from xddxdd/dependabot/go_modules/frontend/github.com/spf13/viper-1.21.0 2025-09-09 00:07:01 +00:00
dependabot[bot]
c9bab2ae2b Merge pull request #130 from xddxdd/dependabot/go_modules/proxy/github.com/spf13/viper-1.21.0 2025-09-09 00:05:57 +00:00
dependabot[bot]
b9a4f95978 build(deps): bump github.com/spf13/viper from 1.19.0 to 1.21.0 in /proxy
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.19.0 to 1.21.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.19.0...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 00:04:35 +00:00
dependabot[bot]
7cd69746df build(deps): bump github.com/spf13/viper in /frontend
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.19.0 to 1.21.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.19.0...v1.21.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-version: 1.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-09 00:04:30 +00:00
dependabot[bot]
e719859d68 Merge pull request #128 from xddxdd/dependabot/go_modules/proxy/github.com/spf13/pflag-1.0.10 2025-09-03 22:06:35 +00:00
dependabot[bot]
5d06affefc build(deps): bump github.com/spf13/pflag from 1.0.8 to 1.0.10 in /proxy
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.8 to 1.0.10.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.8...v1.0.10)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 22:05:01 +00:00
dependabot[bot]
060fe9bf8e Merge pull request #127 from xddxdd/dependabot/go_modules/frontend/github.com/spf13/pflag-1.0.10 2025-09-03 19:13:13 +00:00
dependabot[bot]
f23d36f357 build(deps): bump github.com/spf13/pflag in /frontend
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.8 to 1.0.10.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.8...v1.0.10)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-03 19:11:24 +00:00
dependabot[bot]
1dbc0fccd2 Merge pull request #126 from xddxdd/dependabot/go_modules/frontend/github.com/spf13/pflag-1.0.8 2025-09-01 00:08:36 +00:00
dependabot[bot]
b2d64d19e3 build(deps): bump github.com/spf13/pflag in /frontend
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.7...v1.0.8)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 00:06:28 +00:00
dependabot[bot]
4dee4b0806 Merge pull request #125 from xddxdd/dependabot/go_modules/proxy/github.com/spf13/pflag-1.0.8 2025-09-01 00:06:18 +00:00
dependabot[bot]
cb279e0459 build(deps): bump github.com/spf13/pflag from 1.0.7 to 1.0.8 in /proxy
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.7...v1.0.8)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 00:04:16 +00:00
dependabot[bot]
31ba36beaf Merge pull request #123 from xddxdd/dependabot/go_modules/frontend/github.com/jarcoal/httpmock-1.4.1 2025-08-20 05:14:21 +00:00
dependabot[bot]
5ab3b95d64 build(deps): bump github.com/jarcoal/httpmock in /frontend
Bumps [github.com/jarcoal/httpmock](https://github.com/jarcoal/httpmock) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/jarcoal/httpmock/releases)
- [Commits](https://github.com/jarcoal/httpmock/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/jarcoal/httpmock
  dependency-version: 1.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 05:12:43 +00:00
dependabot[bot]
7bf654f35f Merge pull request #122 from xddxdd/dependabot/go_modules/proxy/github.com/spf13/pflag-1.0.7 2025-07-17 00:25:25 +00:00
dependabot[bot]
5b33629a9d build(deps): bump github.com/spf13/pflag from 1.0.6 to 1.0.7 in /proxy
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.6...v1.0.7)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-17 00:23:21 +00:00
dependabot[bot]
0868c5d42c Merge pull request #121 from xddxdd/dependabot/go_modules/frontend/github.com/spf13/pflag-1.0.7 2025-07-17 00:09:29 +00:00
dependabot[bot]
bc61579e6a build(deps): bump github.com/spf13/pflag in /frontend
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.6...v1.0.7)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-17 00:07:25 +00:00
Lan Tian
e9750a8278 release: v1.3.10 2025-07-02 19:58:51 -07:00
Lan Tian
d40dd3a4d3 frontend: handle protocol names with dash 2025-07-01 17:45:12 -07:00
Lan Tian
ffdeeac06e frontend: refactor summary table parsing 2025-07-01 00:55:15 -07:00
Lan Tian
7eb4d75bbf frontend: handle protocol names starting with number 2025-07-01 00:04:03 -07:00
Lan Tian
6e5e190d32 frontend: try fix TestWhoisConnectionError on darwin 2025-06-08 22:01:39 -07:00
17 changed files with 322 additions and 5864 deletions

View File

@@ -23,22 +23,26 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Release frontend - name: Release frontend
uses: wangyoucao577/go-release-action@v1.40 uses: wangyoucao577/go-release-action@v1.53
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }} goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }} goarch: ${{ matrix.goarch }}
project_path: "./frontend" project_path: "./frontend"
binary_name: "bird-lg-go" binary_name: "bird-lg-go"
pre_command: |
export CGO_ENABLED=0
- name: Release proxy - name: Release proxy
uses: wangyoucao577/go-release-action@v1.40 uses: wangyoucao577/go-release-action@v1.53
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
goos: ${{ matrix.goos }} goos: ${{ matrix.goos }}
goarch: ${{ matrix.goarch }} goarch: ${{ matrix.goarch }}
project_path: "./proxy" project_path: "./proxy"
binary_name: "bird-lgproxy-go" binary_name: "bird-lgproxy-go"
pre_command: |
export CGO_ENABLED=0
docker-release: docker-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -1 +1 @@
v1.3.9 v1.3.12.1

View File

@@ -73,13 +73,14 @@ func TestApiSummaryHandler(t *testing.T) {
summary := response.Result[0].(*apiSummaryResultPair) summary := response.Result[0].(*apiSummaryResultPair)
assert.Equal(t, summary.Server, "alpha") assert.Equal(t, summary.Server, "alpha")
assert.Equal(t, len(summary.Data), 7)
// Protocol list will be sorted // Protocol list will be sorted
assert.Equal(t, summary.Data[1].Name, "device1") assert.Equal(t, summary.Data[0].Name, "device1")
assert.Equal(t, summary.Data[1].Proto, "Device") assert.Equal(t, summary.Data[0].Proto, "Device")
assert.Equal(t, summary.Data[1].Table, "---") assert.Equal(t, summary.Data[0].Table, "---")
assert.Equal(t, summary.Data[1].State, "up") assert.Equal(t, summary.Data[0].State, "up")
assert.Equal(t, summary.Data[1].Since, "2021-08-27") assert.Equal(t, summary.Data[0].Since, "2021-08-27")
assert.Equal(t, summary.Data[1].Info, "") assert.Equal(t, summary.Data[0].Info, "")
} }
func TestApiSummaryHandlerError(t *testing.T) { func TestApiSummaryHandlerError(t *testing.T) {

View File

@@ -1,33 +1,27 @@
module github.com/xddxdd/bird-lg-go/frontend module github.com/xddxdd/bird-lg-go/frontend
go 1.17 go 1.23.0
require ( require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
github.com/jarcoal/httpmock v1.4.0 github.com/jarcoal/httpmock v1.4.1
github.com/magiconair/properties v1.8.10 github.com/magiconair/properties v1.8.10
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.21.0
) )
require ( require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
go.uber.org/multierr v1.9.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/text v0.28.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ type settingType struct {
domain string domain string
proxyPort int proxyPort int
whoisServer string whoisServer string
listen string listen []string
dnsInterface string dnsInterface string
netSpecificMode string netSpecificMode string
titleBrand string titleBrand string
@@ -35,24 +35,29 @@ func main() {
parseSettings() parseSettings()
ImportTemplates() ImportTemplates()
var l net.Listener for _, listenAddr := range setting.listen {
var err error go func(listenAddr string) {
var l net.Listener
var err error
if strings.HasPrefix(setting.listen, "/") { if strings.HasPrefix(listenAddr, "/") {
// Delete existing socket file, ignore errors (will fail later anyway) // Delete existing socket file, ignore errors (will fail later anyway)
os.Remove(setting.listen) os.Remove(listenAddr)
l, err = net.Listen("unix", setting.listen) l, err = net.Listen("unix", listenAddr)
} else { } else {
listenAddr := setting.listen if !strings.Contains(listenAddr, ":") {
if !strings.Contains(listenAddr, ":") { listenAddr = ":" + listenAddr
listenAddr = ":" + listenAddr }
} l, err = net.Listen("tcp", listenAddr)
l, err = net.Listen("tcp", listenAddr) }
if err != nil {
panic(err)
}
webServerStart(l)
}(listenAddr)
} }
if err != nil { select {}
panic(err)
}
webServerStart(l)
} }

View File

@@ -35,15 +35,6 @@ var optionsMap = map[string]string{
"traceroute": "traceroute ...", "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 // render the page template
func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, content template.HTML) { func renderPageTemplate(w http.ResponseWriter, r *http.Request, title string, content template.HTML) {
path := r.URL.Path[1:] path := r.URL.Path[1:]
@@ -143,63 +134,23 @@ func summaryParse(data string, serverName string) (TemplateSummary, error) {
// parse each line // parse each line
for _, line := range rows { for _, line := range rows {
row := SummaryRowDataFromLine(line)
// Ignore empty lines if row == nil {
line = strings.TrimSpace(line)
if len(line) == 0 {
continue continue
} }
// Parse a total of 6 columns from bird summary // Filter row name
lineSplitted := splitSummaryLine.FindStringSubmatch(line) if setting.nameFilter != "" && nameFilterRegexp.MatchString(row.Name) {
if lineSplitted == nil {
continue continue
} }
var row SummaryRowData // Filter away unwanted protocol types, if setting.protocolFilter is non-empty
if len(setting.protocolFilter) > 0 && !row.ProtocolMatches(setting.protocolFilter) {
if len(lineSplitted) >= 2 { continue
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])
}
// Dynamic BGP session, show without any color
if strings.Contains(row.Info, "Passive") {
row.MappedState = summaryStateMap["passive"]
} }
// add to the result // add to the result
args.Rows = append(args.Rows, row) args.Rows = append(args.Rows, *row)
} }
return args, nil return args, nil

View File

@@ -8,8 +8,7 @@ import (
"testing" "testing"
) )
const BirdSummaryData = `BIRD 2.0.8 ready. const BirdSummaryData = `Name Proto Table State Since Info
Name Proto Table State Since Info
static1 Static master4 up 2021-08-27 static1 Static master4 up 2021-08-27
static2 Static master6 up 2021-08-27 static2 Static master6 up 2021-08-27
device1 Device --- up 2021-08-27 device1 Device --- up 2021-08-27

View File

@@ -9,25 +9,25 @@ import (
) )
type viperSettingType struct { type viperSettingType struct {
Servers string `mapstructure:"servers"` Servers string `mapstructure:"servers"`
Domain string `mapstructure:"domain"` Domain string `mapstructure:"domain"`
ProxyPort int `mapstructure:"proxy_port"` ProxyPort int `mapstructure:"proxy_port"`
WhoisServer string `mapstructure:"whois"` WhoisServer string `mapstructure:"whois"`
Listen string `mapstructure:"listen"` Listen []string `mapstructure:"listen"`
DNSInterface string `mapstructure:"dns_interface"` DNSInterface string `mapstructure:"dns_interface"`
NetSpecificMode string `mapstructure:"net_specific_mode"` NetSpecificMode string `mapstructure:"net_specific_mode"`
TitleBrand string `mapstructure:"title_brand"` TitleBrand string `mapstructure:"title_brand"`
NavBarBrand string `mapstructure:"navbar_brand"` NavBarBrand string `mapstructure:"navbar_brand"`
NavBarBrandURL string `mapstructure:"navbar_brand_url"` NavBarBrandURL string `mapstructure:"navbar_brand_url"`
NavBarAllServer string `mapstructure:"navbar_all_servers"` NavBarAllServer string `mapstructure:"navbar_all_servers"`
NavBarAllURL string `mapstructure:"navbar_all_url"` NavBarAllURL string `mapstructure:"navbar_all_url"`
BgpmapInfo string `mapstructure:"bgpmap_info"` BgpmapInfo string `mapstructure:"bgpmap_info"`
TelegramBotName string `mapstructure:"telegram_bot_name"` TelegramBotName string `mapstructure:"telegram_bot_name"`
ProtocolFilter string `mapstructure:"protocol_filter"` ProtocolFilter string `mapstructure:"protocol_filter"`
NameFilter string `mapstructure:"name_filter"` NameFilter string `mapstructure:"name_filter"`
TimeOut int `mapstructure:"timeout"` TimeOut int `mapstructure:"timeout"`
ConnectionTimeOut int `mapstructure:"connection_timeout"` ConnectionTimeOut int `mapstructure:"connection_timeout"`
TrustProxyHeaders bool `mapstructure:"trust_proxy_headers"` TrustProxyHeaders bool `mapstructure:"trust_proxy_headers"`
} }
// Parse settings with viper, and convert to legacy setting format // Parse settings with viper, and convert to legacy setting format
@@ -52,7 +52,7 @@ func parseSettings() {
pflag.String("whois", "whois.verisign-grs.com", "whois server for queries") pflag.String("whois", "whois.verisign-grs.com", "whois server for queries")
viper.BindPFlag("whois", pflag.Lookup("whois")) 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")) viper.BindPFlag("listen", pflag.Lookup("listen"))
pflag.String("dns-interface", "asn.cymru.com", "dns zone to query ASN information") pflag.String("dns-interface", "asn.cymru.com", "dns zone to query ASN information")

View File

@@ -3,11 +3,13 @@ package main
import ( import (
"embed" "embed"
"html/template" "html/template"
"net/url" "net/url"
"regexp"
"strings" "strings"
) )
// import templates and other assets // import templates and other assets
//
//go:embed assets //go:embed assets
var assets embed.FS var assets embed.FS
@@ -64,6 +66,47 @@ func (r SummaryRowData) NameContains(prefix string) bool {
return strings.Contains(r.Name, prefix) 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 { type TemplateSummary struct {
ServerName string ServerName string
Raw string Raw string
@@ -108,7 +151,7 @@ var requiredTemplates = [...]string{
// define functions to be made available in templates // define functions to be made available in templates
var funcMap = template.FuncMap{ var funcMap = template.FuncMap{
"pathescape": url.PathEscape, "pathescape": url.PathEscape,
} }
// import templates from embedded assets // import templates from embedded assets

View File

@@ -23,3 +23,67 @@ func TestSummaryRowDataNameContains(t *testing.T) {
assert.Equal(t, data.NameContains("oc"), true) assert.Equal(t, data.NameContains("oc"), true)
assert.Equal(t, data.NameContains("no"), false) 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")
}

View File

@@ -12,19 +12,20 @@ import (
"net/url" "net/url"
"os" "os"
"strings" "strings"
"sync/atomic"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
) )
var primitiveMap = map[string]string{ var primitiveMap = map[string]string{
"summary": "show protocols", "summary": "show protocols",
"detail": "show protocols all %s", "detail": "show protocols all '%s'",
"route_from_protocol": "show route protocol %s", "route_from_protocol": "show route protocol '%s'",
"route_from_protocol_all": "show route protocol %s all", "route_from_protocol_all": "show route protocol '%s' all",
"route_from_protocol_primary": "show route protocol %s primary", "route_from_protocol_primary": "show route protocol '%s' primary",
"route_from_protocol_all_primary": "show route protocol %s all 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": "show route filtered protocol '%s'",
"route_filtered_from_protocol_all": "show route filtered protocol %s all", "route_filtered_from_protocol_all": "show route filtered protocol '%s' all",
"route_from_origin": "show route where bgp_path.last = %s", "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_all": "show route where bgp_path.last = %s all",
"route_from_origin_primary": "show route where bgp_path.last = %s primary", "route_from_origin_primary": "show route where bgp_path.last = %s primary",
@@ -39,8 +40,10 @@ var primitiveMap = map[string]string{
"traceroute": "%s", "traceroute": "%s",
} }
var webServerPrepared uint32 = 0
// serve up a generic error // 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.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 Internal Server Error")) w.Write([]byte("500 Internal Server Error"))
} }
@@ -192,11 +195,11 @@ func webHandlerBGPMap(endpoint string, command string) func(w http.ResponseWrite
} }
} }
// set up routing paths and start webserver // set up routing paths
func webServerStart(l net.Listener) { func webServerPrepare() {
// redirect main page to all server summary // redirect main page to all server summary
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 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 // serve static pages using embedded assets from template.go
@@ -235,8 +238,14 @@ func webServerStart(l net.Listener) {
http.HandleFunc("/whois/", webHandlerWhois) http.HandleFunc("/whois/", webHandlerWhois)
http.HandleFunc("/api/", apiHandler) http.HandleFunc("/api/", apiHandler)
http.HandleFunc("/telegram/", webHandlerTelegramBot) http.HandleFunc("/telegram/", webHandlerTelegramBot)
}
// start webserver
func webServerStart(l net.Listener) {
if atomic.SwapUint32(&webServerPrepared, 1) == 0 {
webServerPrepare()
}
// Start HTTP server
var handler http.Handler var handler http.Handler
handler = http.DefaultServeMux handler = http.DefaultServeMux
if setting.trustProxyHeaders { if setting.trustProxyHeaders {

View File

@@ -88,7 +88,7 @@ func TestWhoisWithoutServer(t *testing.T) {
} }
func TestWhoisConnectionError(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") result := whois("AS6939")
if !strings.Contains(result, "connect: connection refused") { if !strings.Contains(result, "connect: connection refused") {
t.Errorf("Whois AS6939 without server produced output, got %s", result) t.Errorf("Whois AS6939 without server produced output, got %s", result)

View File

@@ -1,32 +1,26 @@
module github.com/xddxdd/bird-lg-go/proxy module github.com/xddxdd/bird-lg-go/proxy
go 1.17 go 1.23.0
require ( require (
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/gorilla/handlers v1.5.2 github.com/gorilla/handlers v1.5.2
github.com/magiconair/properties v1.8.10 github.com/magiconair/properties v1.8.10
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.19.0 github.com/spf13/viper v1.21.0
) )
require ( require (
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
go.uber.org/multierr v1.9.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/text v0.28.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@ func accessHandler(next http.Handler) http.Handler {
type settingType struct { type settingType struct {
birdSocket string birdSocket string
listen string listen []string
allowedNets []*net.IPNet allowedNets []*net.IPNet
tr_bin string tr_bin string
tr_flags []string tr_flags []string
@@ -76,32 +76,40 @@ func main() {
parseSettings() parseSettings()
tracerouteAutodetect() tracerouteAutodetect()
fmt.Printf("Listening on %s...\n", setting.listen) mux := http.NewServeMux()
var l net.Listener // Prepare HTTP server
var err error mux.HandleFunc("/", invalidHandler)
mux.HandleFunc("/bird", birdHandler)
mux.HandleFunc("/bird6", birdHandler)
mux.HandleFunc("/traceroute", tracerouteHandler)
mux.HandleFunc("/traceroute6", tracerouteHandler)
if strings.HasPrefix(setting.listen, "/") { for _, listenAddr := range setting.listen {
// Delete existing socket file, ignore errors (will fail later anyway) go func(addr string) {
os.Remove(setting.listen) fmt.Printf("Listening on %s...\n", addr)
l, err = net.Listen("unix", setting.listen)
} else { var l net.Listener
listenAddr := setting.listen var err error
if !strings.Contains(listenAddr, ":") {
listenAddr = ":" + listenAddr if strings.HasPrefix(addr, "/") {
} // Delete existing socket file, ignore errors (will fail later anyway)
l, err = net.Listen("tcp", listenAddr) 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 { select {}
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)))
} }

View File

@@ -11,12 +11,12 @@ import (
) )
type viperSettingType struct { type viperSettingType struct {
BirdSocket string `mapstructure:"bird_socket"` BirdSocket string `mapstructure:"bird_socket"`
Listen string `mapstructure:"listen"` Listen []string `mapstructure:"listen"`
AllowedNets string `mapstructure:"allowed_ips"` AllowedNets string `mapstructure:"allowed_ips"`
TracerouteBin string `mapstructure:"traceroute_bin"` TracerouteBin string `mapstructure:"traceroute_bin"`
TracerouteFlags string `mapstructure:"traceroute_flags"` TracerouteFlags string `mapstructure:"traceroute_flags"`
TracerouteRaw bool `mapstructure:"traceroute_raw"` TracerouteRaw bool `mapstructure:"traceroute_raw"`
} }
// Parse settings with viper, and convert to legacy setting format // Parse settings with viper, and convert to legacy setting format
@@ -37,7 +37,7 @@ func parseSettings() {
pflag.String("bird", "/var/run/bird/bird.ctl", "socket file for bird, set either in parameter or environment variable BIRD_SOCKET") 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")) 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")) viper.BindPFlag("listen", pflag.Lookup("listen"))
pflag.String("allowed", "", "IPs or networks 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.")