import yaml
import re
from netmiko import (ConnectHandler, NetmikoAuthenticationException,
NetmikoTimeoutException)
from concurrent.futures import ThreadPoolExecutor
from itertools import repeat
def send_show_command(device, command):
try:
with ConnectHandler(**device) as ssh:
ssh.enable()
output = ssh.send_command(command)
return output
except (NetmikoTimeoutException, NetmikoAuthenticationException) as error:
print(error)
def parsing(show_output):
regex = r'Routing entry for (?P<prefix>\S+/\d+).+Known via "(?P<proto>\S+ \d+)".+ on (?P<int>\S+),.+'
result = re.search(regex, show_output, re.DOTALL).groups()
return result
if __name__ == "__main__":
with open("devices.yaml") as f:
devices = yaml.safe_load(f)
with ThreadPoolExecutor(max_workers=3) as executor:
result = executor.map(send_show_command, devices, repeat('sh ip route 4.4.4.4'))
for device, output in zip(devices, result):
print(f"Raw output for router {device['host']}:\n{output}\n")
parse_result = parsing(output)
print(f"Parsed output for router {device['host']}:\n{parse_result}\n")admin@netlab:~/netops/netmiko# python run_show_command.py
Raw output for router 192.168.1.166:
Routing entry for 4.4.4.4/32
Known via "ospf 1", distance 110, metric 2, type intra area
Last update from 10.0.0.9 on GigabitEthernet4, 1w2d ago
SR Incoming Label: 16004
Routing Descriptor Blocks:
* 10.0.0.9, from 4.4.4.4, 1w2d ago, via GigabitEthernet4, prefer-non-rib-labels, merge-labels
Route metric is 2, traffic share count is 1
MPLS label: implicit-null
MPLS Flags: NSF
Parsed output for router 192.168.1.166:
('4.4.4.4/32', 'ospf 1', 'GigabitEthernet4')
Внутри находится обычный BGP-update, к которому добавлена метаинформация
!common daemon settings
logfile: /var/log/pmbmpd.log
bmp_daemon: true
bmp_daemon_port: 6000
bmp_daemon_max_peers: 100
!kafka settings
bmp_daemon_msglog_output: json
bmp_daemon_msglog_kafka_topic: bmp
!kafka cluster DNS record
bmp_daemon_msglog_kafka_broker_host: kafka-cluster.example.org
bmp_daemon_msglog_kafka_broker_port: 9192
bmp_daemon_msglog_kafka_config_file: /etc/pmacct/librdkafka.confglobal,security.protocol,SASL_SSL
global,sasl.mechanisms,SCRAM-SHA-512
global,sasl.username,<username>
global,sasl.password,<password>
global,ssl.ca.location,/etc/pmacct/certs/ca-certificates.crt
global,bootstrap.servers,kafka-cluster.example.org:9192#Указываем режим работы, в нашем случае всегда active, роутер первым должен инициировать TCP-соединение к коллектору
set routing-options bmp connection-mode active
#Указываем, с какого адреса роутера мы хотим строить сессию.
set routing-options bmp station pmbmpd local-address 10.4.96.2
# Указываем, какие BGP-таблицы хотим мониторить.
set routing-options bmp station pmbmpd route-monitoring loc-rib
set routing-options bmp station pmbmpd route-monitoring pre-policy
set routing-options bmp station pmbmpd route-monitoring post-policy
set routing-options bmp station pmbmpd route-monitoring rib-out pre-policy
set routing-options bmp station pmbmpd route-monitoring rib-out post-policy
# Указываем период сбора статистики по bgp-пирам, по умолчанию каждый час.
set routing-options bmp station monitor02 statistics-timeout 60
#Адрес коллектора и порт.
set routing-options bmp station pmbmpd station-address 10.0.2.0
set routing-options bmp station pmbmpd station-port 6000
Note: BMP на junos собирает информацию по всем BGP-пирам (как c global пиров, так и с пиров в vrf-ах), которые у него есть.# Настройка аналогичная, как и у juniper.
router bgp <AS>
bmp-server 1
description bmp_test
address 10.104.65.122 port 5000
stats-reporting-period 720
update-source loopback0
# За исключением, что для каждого пира или шаблона нужно активировать bmp.
template peer DCI
bfd singlehop
bmp-activate-server 1{
"seq": 20774,
"log_type": "update",
"timestamp": "2025-07-24 09:58:45.056991",
"is_post": 0,
"is_in": 1,
"event_type": "log",
"afi": 1,
"safi": 1,
"ip_prefix": "10.4.96.252/32",
"bgp_nexthop": "fe80::d:9:2",
"as_path": "4200109999",
"comms": "65100:0",
"origin": "i",
"timestamp_arrival": "2025-08-18 13:58:38.902214",
"bmp_router": "10.4.96.2",
"bmp_router_port": 64395,
"peer_ip": "fe80::d:9:2",
"peer_tcp_port": 0,
"bmp_msg_type": "route_monitor",
"writer_id": "default/1"
}CREATE TABLE IF NOT EXISTS bmp.bmp_kafka ON CLUSTER '{cluster}'
(
seq UInt64,
timestamp DateTime64,
timestamp_arrival DateTime64,
bmp_router String,
bmp_router_port UInt16,
writer_id String,
bmp_msg_type String,
log_type String,
is_loc UInt8,
is_in UInt8,
is_out UInt8,
is_post UInt8,
is_filtered UInt8,
bmp_rib_type String,
peer_ip String,
peer_tcp_port UInt16,
peer_asn UInt32,
peer_type UInt8,
peer_type_str String,
bmp_peer_up_info_string String,
afi UInt8,
safi UInt8,
ip_prefix String,
bgp_nexthop String,
as_path String,
comms String,
ecomms String,
origin String,
local_pref UInt32,
med UInt32,
rd String,
rd_origin String,
bgp_id String,
mpls_label String,
as_path_id String,
aigp String,
psid_li String,
counter_type UInt32,
counter_type_str String,
counter_value UInt64,
reason_type UInt8,
reason_str String,
local_port UInt16,
remote_port UInt16,
local_ip String,
bmp_init_info_sysdescr String,
bmp_init_info_sysname String,
bmp_term_info_reason String
)
ENGINE = Kafka(kafka_storage_ch_bmp_collector) SETTINGS
kafka_topic_list = 'bmp',
kafka_group_name = 'clickhouse',
kafka_format = 'JSONEachRow',
kafka_skip_broken_messages = 1,
kafka_thread_per_consumer = 0,
kafka_num_consumers = 1,
kafka_commit_on_select = true
COMMENT 'This table consumes bmp data from kafka broker';CREATE MATERIALIZED VIEW IF NOT EXISTS bmp.bmp_stats_mv
ON CLUSTER '{cluster}'
TO bmp.bmp_stats
AS
SELECT
seq,
timestamp,
timestamp_arrival,
bmp_router,
bmp_router_port,
writer_id,
bmp_msg_type,
is_loc,
is_in,
is_out,
is_post,
is_filtered,
bmp_rib_type,
peer_ip,
peer_asn,
peer_type,
rd,
rd_origin,
bgp_id,
counter_type,
counter_type_str,
counter_value
FROM bmp.bmp_kafka
WHERE bmp_msg_type = 'stats'
COMMENT 'This MV forwards only BMP stats report messages into dedicated stats table';CREATE TABLE IF NOT EXISTS bmp.bmp_stats ON CLUSTER '{cluster}'
(
seq UInt64,
timestamp DateTime64,
timestamp_arrival DateTime64,
bmp_router String,
bmp_router_port UInt16,
writer_id String,
bmp_msg_type String,
is_loc UInt8 DEFAULT 0,
is_in UInt8 DEFAULT 0,
is_out UInt8 DEFAULT 0,
is_post UInt8 DEFAULT 0,
is_filtered UInt8 DEFAULT 0,
bmp_rib_type String DEFAULT '',
peer_ip String DEFAULT '',
peer_asn UInt32 DEFAULT 0,
peer_type UInt8 DEFAULT 0,
rd String DEFAULT '',
rd_origin String DEFAULT '',
bgp_id String DEFAULT '',
counter_type UInt32 DEFAULT 0,
counter_type_str String DEFAULT '',
counter_value UInt64 DEFAULT 0
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/bmp_stats','{replica}')
ORDER BY seq
TTL toDateTime(timestamp_arrival) + INTERVAL 30 DAY DELETE
COMMENT 'This table saves BMP statistic report messages (Mess type 1)';