package birdfmt import ( "strconv" "strings" ) // BGPSession is one BGP protocol block from `birdc show protocols all`. type BGPSession struct { Name string `json:"name"` Neighbor string `json:"neighbor,omitempty"` NeighborAS int64 `json:"neighbor_as,omitempty"` NeighborID string `json:"neighbor_id,omitempty"` State string `json:"state"` } // DynamicPeerProtocolPrefix is the BIRD protocol name prefix for discovery-spawned sessions. const DynamicPeerProtocolPrefix = "evobgp_dyn_" // IsDynamicDiscoverySession reports whether the protocol was spawned by the discovery listener. func IsDynamicDiscoverySession(name string) bool { return strings.HasPrefix(strings.TrimSpace(name), DynamicPeerProtocolPrefix) } // ParseBGPSessions extracts BGP protocol name, state, neighbor, Neighbor AS, and Neighbor ID from birdc output. func ParseBGPSessions(output string) []BGPSession { var out []BGPSession var cur *BGPSession for _, raw := range strings.Split(output, "\n") { line := strings.TrimRight(raw, "\r") trim := strings.TrimSpace(line) if trim == "" { cur = nil continue } low := strings.ToLower(trim) if strings.HasPrefix(low, "bird ") || strings.HasPrefix(low, "name ") || strings.HasPrefix(low, "table ") { continue } if !strings.HasPrefix(line, " ") && !strings.HasPrefix(line, "\t") { if isBGPProtocolSummaryRow(trim) { fields := strings.Fields(trim) state := extractBGPSessionStateLine(trim) if state == "" && len(fields) >= 4 { state = fields[3] } out = append(out, BGPSession{Name: fields[0], State: state}) cur = &out[len(out)-1] } else { cur = nil } continue } if cur == nil { continue } parseBGPSessionDetailLine(cur, trim) } return out } func parseBGPSessionDetailLine(cur *BGPSession, trim string) { for _, prefix := range []string{"Neighbor address:", "Neighbor Address:", "Neighbor:"} { if idx := strings.Index(trim, prefix); idx >= 0 { // Avoid matching "Neighbor AS:" / "Neighbor ID:" via bare "Neighbor:" if prefix == "Neighbor:" { rest := strings.TrimSpace(trim[idx+len(prefix):]) if strings.HasPrefix(strings.ToLower(rest), "as:") || strings.HasPrefix(strings.ToLower(rest), "id:") { continue } if strings.Contains(strings.ToLower(trim), "neighbor as:") || strings.Contains(strings.ToLower(trim), "neighbor id:") { continue } } cur.Neighbor = strings.TrimSpace(trim[idx+len(prefix):]) return } } for _, prefix := range []string{"Neighbor AS:", "Neighbor As:", "Neighbor as:"} { if idx := strings.Index(trim, prefix); idx >= 0 { raw := strings.TrimSpace(trim[idx+len(prefix):]) if n, err := strconv.ParseInt(raw, 10, 64); err == nil { cur.NeighborAS = n } return } } for _, prefix := range []string{"Neighbor ID:", "Neighbor Id:", "Neighbor id:", "BGP Identifier:", "BGP identifier:"} { if idx := strings.Index(trim, prefix); idx >= 0 { cur.NeighborID = strings.TrimSpace(trim[idx+len(prefix):]) return } } }