aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJakub Ružička <jakub.ruzicka@nic.cz>2023-10-09 16:59:21 +0200
committerJakub Ružička <jakub.ruzicka@nic.cz>2023-10-09 16:59:21 +0200
commit87d2cc5a91e1563d604c5923bc7a57ef1d4f7aa1 (patch)
treee6a95a5337ebca8a31b75797f7f517de8f1bdf4b
parentee773cd6712b268ac28f1cee78c7b51a064dc264 (diff)
downloadbird-87d2cc5a91e1563d604c5923bc7a57ef1d4f7aa1.tar.gz
New upstream version 2.14
-rw-r--r--.gitlab-ci.yml52
-rw-r--r--ChangeLog1696
-rw-r--r--Makefile.in9
-rw-r--r--NEWS56
-rw-r--r--bird-gdb.py1
-rw-r--r--conf/cf-lex.l250
-rw-r--r--conf/conf.c19
-rw-r--r--conf/conf.h83
-rw-r--r--conf/confbase.Y73
-rw-r--r--conf/gen_keywords.m412
-rw-r--r--conf/gen_parser.m416
-rwxr-xr-xconfigure5
-rw-r--r--configure.ac5
-rw-r--r--distro/config/apkg.toml4
-rw-r--r--distro/pkg/deb/bird.xml33
-rw-r--r--distro/pkg/deb/bird2.lintian-overrides1
-rw-r--r--distro/pkg/deb/bird2.manpages1
-rw-r--r--distro/pkg/deb/changelog2
-rw-r--r--distro/pkg/deb/compat2
-rw-r--r--distro/pkg/deb/control11
-rwxr-xr-xdistro/pkg/deb/rules7
-rw-r--r--distro/pkg/deb/watch2
-rw-r--r--distro/pkg/rpm/bird.spec38
-rw-r--r--distro/pkg/rpm/bird.sysusersd2
-rw-r--r--distro/tests/control3
-rwxr-xr-xdistro/tests/test-bird.sh105
-rw-r--r--doc/bird.sgml700
-rw-r--r--doc/reply_codes1
-rw-r--r--doc/roadmap.md307
-rw-r--r--filter/config.Y489
-rw-r--r--filter/data.c72
-rw-r--r--filter/data.h43
-rw-r--r--filter/decl.m4158
-rw-r--r--filter/f-inst.c784
-rw-r--r--filter/f-inst.h34
-rw-r--r--filter/f-util.c177
-rw-r--r--filter/filter.c56
-rw-r--r--filter/filter.h11
-rw-r--r--filter/filter_test.c8
-rw-r--r--filter/test.conf577
-rw-r--r--filter/test.conf.overlay3
-rw-r--r--filter/trie_test.c33
-rw-r--r--lib/birdlib.h27
-rw-r--r--lib/bitmap.c289
-rw-r--r--lib/bitmap.h22
-rw-r--r--lib/bitmap_test.c77
-rw-r--r--lib/hash.h10
-rw-r--r--lib/ip.h33
-rw-r--r--lib/mempool.c15
-rw-r--r--lib/net.c32
-rw-r--r--lib/net.h36
-rw-r--r--lib/printf_test.c9
-rw-r--r--lib/string.h3
-rw-r--r--lib/strtoul.c104
-rw-r--r--lib/timer.h3
-rw-r--r--nest/Doc1
-rw-r--r--nest/Makefile9
-rw-r--r--nest/a-path.c23
-rw-r--r--nest/attrs.h2
-rw-r--r--nest/cmds.c23
-rw-r--r--nest/config.Y59
-rw-r--r--nest/iface.c4
-rw-r--r--nest/iface.h4
-rw-r--r--nest/mpls.Y200
-rw-r--r--nest/mpls.c1296
-rw-r--r--nest/mpls.h191
-rw-r--r--nest/neighbor.c4
-rw-r--r--nest/proto.c89
-rw-r--r--nest/protocol.h20
-rw-r--r--nest/route.h18
-rw-r--r--nest/rt-attr.c34
-rw-r--r--nest/rt-fib_test.c246
-rw-r--r--nest/rt-show.c15
-rw-r--r--nest/rt-table.c97
-rw-r--r--proto/Doc1
-rw-r--r--proto/aggregator/Doc1
-rw-r--r--proto/aggregator/Makefile6
-rw-r--r--proto/aggregator/aggregator.c804
-rw-r--r--proto/aggregator/aggregator.h86
-rw-r--r--proto/aggregator/config.Y134
-rw-r--r--proto/aggregator/test.conf116
-rw-r--r--proto/babel/babel.c183
-rw-r--r--proto/babel/babel.h32
-rw-r--r--proto/babel/config.Y23
-rw-r--r--proto/babel/packets.c242
-rw-r--r--proto/bfd/bfd.c20
-rw-r--r--proto/bfd/config.Y2
-rw-r--r--proto/bgp/attrs.c23
-rw-r--r--proto/bgp/bgp.c65
-rw-r--r--proto/bgp/bgp.h32
-rw-r--r--proto/bgp/config.Y27
-rw-r--r--proto/bgp/packets.c205
-rw-r--r--proto/bmp/Doc1
-rw-r--r--proto/bmp/LICENSE2
-rw-r--r--proto/bmp/Makefile6
-rw-r--r--proto/bmp/README.txt6
-rw-r--r--proto/bmp/bmp.c1367
-rw-r--r--proto/bmp/bmp.h129
-rw-r--r--proto/bmp/buffer.c65
-rw-r--r--proto/bmp/buffer.h77
-rw-r--r--proto/bmp/config.Y78
-rw-r--r--proto/bmp/map.c119
-rw-r--r--proto/bmp/map.h68
-rw-r--r--proto/l3vpn/Doc1
-rw-r--r--proto/l3vpn/Makefile6
-rw-r--r--proto/l3vpn/config.Y101
-rw-r--r--proto/l3vpn/l3vpn.c491
-rw-r--r--proto/l3vpn/l3vpn.h36
-rw-r--r--proto/mrt/mrt.c2
-rw-r--r--proto/ospf/config.Y9
-rw-r--r--proto/ospf/iface.c4
-rw-r--r--proto/radv/config.Y16
-rw-r--r--proto/radv/packets.c43
-rw-r--r--proto/radv/radv.c2
-rw-r--r--proto/radv/radv.h10
-rw-r--r--proto/rip/config.Y5
-rw-r--r--proto/rip/rip.c2
-rw-r--r--proto/rpki/config.Y30
-rw-r--r--proto/static/config.Y9
-rw-r--r--proto/static/static.c63
-rw-r--r--proto/static/static.h1
-rw-r--r--sysdep/autoconf.h.in9
-rw-r--r--sysdep/bsd/krt-sock.c4
-rw-r--r--sysdep/config.h2
-rw-r--r--sysdep/linux/netlink.c32
-rw-r--r--sysdep/unix/config.Y5
-rw-r--r--sysdep/unix/io.c19
-rw-r--r--sysdep/unix/krt.Y17
-rw-r--r--sysdep/unix/krt.c13
-rw-r--r--sysdep/unix/krt.h4
-rw-r--r--sysdep/unix/main.c23
-rw-r--r--test/birdtest.h6
-rw-r--r--test/bt-utils.c134
-rw-r--r--test/bt-utils.h6
134 files changed, 12548 insertions, 1413 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d1a824ff..c913d0f8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -60,14 +60,6 @@ stages:
- linux
- amd64
-build-debian-8-amd64:
- <<: *build-linux
- image: registry.nic.cz/labs/bird:debian-8-amd64
-
-build-debian-8-i386:
- <<: *build-linux
- image: registry.nic.cz/labs/bird:debian-8-i386
-
build-debian-9-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:debian-9-amd64
@@ -140,18 +132,14 @@ build-fedora-34-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:fedora-33-amd64
-build-centos-7-amd64:
- <<: *build-linux
- image: registry.nic.cz/labs/bird:centos-7-amd64
+#build-centos-7-amd64:
+# <<: *build-linux
+# image: registry.nic.cz/labs/bird:centos-7-amd64
build-centos-8-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:centos-8-amd64
-build-ubuntu-14_04-amd64:
- <<: *build-linux
- image: registry.nic.cz/labs/bird:ubuntu-14.04-amd64
-
build-ubuntu-16_04-amd64:
<<: *build-linux
image: registry.nic.cz/labs/bird:ubuntu-16.04-amd64
@@ -232,28 +220,6 @@ build-opensuse-15.3-amd64:
paths:
- pkg/pkgs/*
-# Dpkg error: PATH is not set
-#pkg-debian-8-amd64:
-# <<: *pkg-deb
-# needs: [build-debian-8-amd64]
-# image: registry.nic.cz/labs/bird:debian-8-amd64
-
-# Dpkg error: PATH is not set
-#pkg-debian-8-i386:
-# <<: *pkg-deb
-# needs: [build-debian-8-i386]
-# image: registry.nic.cz/labs/bird:debian-8-i386
-
-pkg-debian-9-amd64:
- <<: *pkg-deb
- needs: [build-debian-9-amd64]
- image: registry.nic.cz/labs/bird:debian-9-amd64
-
-pkg-debian-9-i386:
- <<: *pkg-deb
- needs: [build-debian-9-i386]
- image: registry.nic.cz/labs/bird:debian-9-i386
-
pkg-debian-10-amd64:
<<: *pkg-deb
needs: [build-debian-10-amd64]
@@ -294,12 +260,12 @@ pkg-fedora-34-amd64:
needs: [build-fedora-34-amd64]
image: registry.nic.cz/labs/bird:fedora-34-amd64
-pkg-centos-7-amd64:
- <<: *pkg-rpm-wa
- variables:
- LC_ALL: en_US.UTF-8
- needs: [build-centos-7-amd64]
- image: registry.nic.cz/labs/bird:centos-7-amd64
+#pkg-centos-7-amd64:
+# <<: *pkg-rpm-wa
+# variables:
+# LC_ALL: en_US.UTF-8
+# needs: [build-centos-7-amd64]
+# image: registry.nic.cz/labs/bird:centos-7-amd64
pkg-centos-8-amd64:
<<: *pkg-rpm-wa
diff --git a/ChangeLog b/ChangeLog
index e938d583..bdba1918 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,1699 @@
+commit 0e1fbaa5b21db8e5c64a732dbaf0b8afe707a147
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Oct 6 18:53:25 2023 +0200
+
+ NEWS and version update
+
+commit 23f94b1368b71faa0a03e50b9e9d0cf8535cff3a
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Oct 6 18:52:02 2023 +0200
+
+ Doc: Minor fixes
+
+commit c5c3a22bccda5454775b48ad318a7fd4bf197f86
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Oct 6 02:36:59 2023 +0200
+
+ Conf: Bytestrings with hex: should use the same general format as ones without.
+
+ Either hex:01234567, or hex:01:23:45:67. No confusing formats like
+ hex:0123:4567:ab:cdef, which looks like there is an implicit zero byte.
+
+commit e83beb70bd14923cece5b35411606ade6fb8fbee
+Author: Pavel Šorejs <mail@sorejs.eu>
+Date: Fri Oct 6 04:31:19 2023 +0200
+
+ KRT: Allow to learn routes with RTPROT_KERNEL
+
+ The Kernel protocol, even with the option 'learn' enabled, ignores
+ direct routes created by the OS kernel (on Linux these are routes
+ with rtm_protocol == RTPROT_KERNEL).
+
+ Implement optional behavior where both OS kernel and third-party routes
+ are learned, it can be enabled by 'learn all' option.
+
+ Minor changes by committer.
+
+commit 57aa077227d1f2440dc1b2bb6cbbebd418a6b898
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Oct 5 21:29:04 2023 +0200
+
+ BGP: Improve custom BGP attributes
+
+ - Implement EA_GET for custom BGP attributes
+ - Forbid EA_SET on existing opaque attributes
+ - Forbid redefining existing attributes
+ - Document possible compatibility problems
+
+commit ba01a6f2e66d03eb5d9426fdf4973f30e09c5710
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Oct 5 17:54:43 2023 +0200
+
+ MPLS: Handle compatibility with old configs
+
+ Old configs do not define MPLS domains and may use a static protocol
+ to define static MPLS routes.
+
+ When MPLS channel is the only channel of static protocol, handle it
+ as a main channel. Also, define implicit MPLS domain if needed and
+ none is defined.
+
+commit de09fda5dcef16bf50663f7ffb56074e6ad20b5a
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Oct 5 14:26:22 2023 +0200
+
+ Filter: Fix scope handling in for loops
+
+ Changes in scope implementation broke scope handling in for loops.
+ The term in for loops is supposed to be parsed in the parent scope.
+
+commit abae806efdf6ac5f919c93057dd8df1faff1058c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Oct 4 20:14:12 2023 +0200
+
+ Conf: Fix 'show symbols'
+
+ Seems like the root scope was not marked as active.
+
+commit ab47c2ae466f785212f0519388417ebb31b2cf24
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Oct 4 19:45:00 2023 +0200
+
+ BGP: Custom attribute definitions should use cfg_alloc(), not malloc()
+
+ Otherwise we would get memory leaks.
+
+commit d41b06238dc2167f8e470aa2fb31cb937ff8fb3e
+Author: Maria Matejka <mq@ucw.cz>
+Date: Wed Oct 4 19:57:55 2023 +0200
+
+ Filter: explicitly forbidden for-loop with pre-defined variable
+
+commit 6a242b3ec66f2ab89f9277e67125eab3e3676644
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Oct 4 17:36:03 2023 +0200
+
+ IO: Fix race condition in event processing
+
+ When regular event was added from work event, we did remember that
+ regular event list was empty and therefore we did not use zero time
+ in poll(). This leads to ~3 s latency in route reload during
+ reconfiguration.
+
+commit 0bfa216f496279905b843abcfb1242477b86783c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Oct 4 15:25:05 2023 +0200
+
+ Doc: Fix syntax errors in SGML
+
+commit be09b030ed40c76ee0727dd3980df77094fdc907
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Oct 4 15:00:24 2023 +0200
+
+ MPLS: Update to support and use 64bit source id
+
+commit cab5fce2b6822b7f3a892103077f26e197fecfbc
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Oct 4 13:00:06 2023 +0200
+
+ Doc: L3VPN documentation
+
+commit 8a70885694c5a3b384deba0f8cd25b7f63b99fcd
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Oct 3 16:11:18 2023 +0200
+
+ Doc: MPLS documentation
+
+commit 8f5511dafb54b5788546065c9b572f9b4f9d47ee
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sun Oct 1 05:02:46 2023 +0200
+
+ L3VPN: Import/export target reconfiguration
+
+commit d8130da86bda6244e1f5c06eb9b9033625b781e6
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sat Sep 30 23:18:04 2023 +0200
+
+ BGP, L3VPN: Fix MPLS channel reload
+
+ When a MPLS channel is reloaded, it should reload all regular MPLS-aware
+ channels. This causes re-evaluation of routes in FEC map and possibly
+ reannouncement of MPLS routes.
+
+commit a7a9df8639c701dd020cf61d19b205230742a17e
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sat Sep 30 20:07:40 2023 +0200
+
+ MPLS: Implement FEC map reconfiguration
+
+ This allows changing label policy or label range without restart.
+
+commit 9b775859cd7fd54a6fe2bd88359955fce079999d
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Sep 26 18:50:20 2023 +0200
+
+ MPLS: Handle label allocation failures
+
+commit e915f99e1cd4f6c90e640f7290c201633ab992f0
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sun Sep 24 00:24:50 2023 +0200
+
+ L3VPN: Fix bug in reconfiguration
+
+ Fields import_target / export_target link to config structures, must be
+ updated during reconfiguration.
+
+commit b6385decb3f9f4d4029ee7bfc2f013b495a725f8
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sat Sep 23 17:55:01 2023 +0200
+
+ MPLS: Improve handling of static label allocations
+
+ Use mpls_new_label() / mpls_free_label() also for static labels, to keep
+ track of allocated labels and to enforce label ranges.
+
+ Static label allocations always use static label range, regardless of
+ configured label range.
+
+commit 81a20ca5d8508f7317f2e023a3be5e5da454d740
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Sep 22 19:49:15 2023 +0200
+
+ Static: Add syntax for static MPLS labels
+
+ Instead of just using route attributes, static routes with
+ static MPLS labels can be defined just by e.g.:
+
+ route 10.1.1.0/24 mpls 100 via 10.1.2.1 mpls 200;
+
+commit 35726051517aaf1869458caed03c7ee1c516721c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Sep 22 15:47:48 2023 +0200
+
+ MPLS: Label range non-intersection check
+
+commit fcf22586200000f0d19ffed339524d2530ed0d6f
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Sep 18 17:47:17 2023 +0200
+
+ MPLS: Improve label range reconfiguration
+
+ Allow to shorten label range over unused area.
+
+commit 8e9e013b0ddec438151c9d12fd4bac079c350310
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Sep 18 17:32:24 2023 +0200
+
+ MPLS: Add command 'show mpls ranges'
+
+ Add command to show MPLS label ranges and their stats.
+
+commit 3397ca51f8a33e6ef97ab5ec7209dfce7051bc5d
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Sep 18 14:19:20 2023 +0200
+
+ Nest: Fix missing RTS_* values in filters
+
+commit e338c4b63c6a9258d858f158049943e1e8f00e6f
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Sep 18 14:12:22 2023 +0200
+
+ Lib: Extend MPLS label allocator bitmap
+
+ Add function lmap_last_one_in_range() for finding the last active label
+ in a label range.
+
+commit bcff3ae79acfd938459869ac98db4f83e3d422b6
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Oct 3 20:06:13 2022 +0200
+
+ L3VPN: BGP/MPLS VPNs using MPLS backbone
+
+ The L3VPN protocol implements RFC 4364 BGP/MPLS VPNs using MPLS backbone.
+ It works similarly to pipe. It connects IP table (one per VRF) with (global)
+ VPN table. Routes passed from VPN table to IP table are stripped of RD and
+ filtered by import targets, routes passed in the other direction are extended
+ with RD, MPLS labels and export targets in extended communities. A separate
+ MPLS channel is used to announce MPLS routes for the labels.
+
+commit 9ca86ef69cc56cb75e48e6f46bfdbe1b1e3e99b6
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sat Oct 1 22:38:49 2022 +0200
+
+ MPLS: Add support for per-VRF labeling policy
+
+ The new labeling policy MPLS_POLICY_VRF assigns one label to all routes
+ (from the same FEC map associated with one VRF), while replaces their
+ next hops with a lookup to a VRF table. This is useful for L3VPN
+ protocol.
+
+commit 9d456d5366593c7b0ebfde003be0517adb554541
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Sep 15 02:30:15 2022 +0200
+
+ BGP: Add MPLS support
+
+ When MPLS is active, received routes on MPLS-aware SAFIs (ipvX-mpls,
+ vpnX-mpls) are automatically labeled according to active label policy and
+ corresponding MPLS routes are automatically generated. Also routes sent
+ on MPLS-aware SAFIs announce local labels when it should be done.
+
+commit 15c86ed061d3dbc7e4ef863e396cda5ec3ed0d4c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Sep 15 02:29:12 2022 +0200
+
+ Static: Add MPLS support
+
+ When MPLS is active, static IP/VPN routes are automatically labeled
+ according to active label policy and corresponding MPLS routes are
+ automatically generated.
+
+commit 333ddd4f981b90d5d3dff166b6abf9bf40bede9f
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Sep 15 01:38:18 2022 +0200
+
+ MPLS subsystem
+
+ The MPLS subsystem manages MPLS labels and handles their allocation to
+ MPLS-aware routing protocols. These labels are then attached to IP or VPN
+ routes representing label switched paths -- LSPs.
+
+ There was already a preliminary MPLS support consisting of MPLS label
+ net_addr, MPLS routing tables with static MPLS routes, remote labels in
+ next hops, and kernel protocol support.
+
+ This patch adds the MPLS domain as a basic structure representing local
+ label space with dynamic label allocator and configurable label ranges.
+ To represent LSPs, allocated local labels can be attached as route
+ attributes to IP or VPN routes with local labels as attributes.
+
+ There are several steps for handling LSP routes in routing protocols --
+ deciding to which forwarding equivalence class (FEC) the LSP route
+ belongs, allocating labels for new FECs, announcing MPLS routes for new
+ FECs, attaching labels to LSP routes. The FEC map structure implements
+ basic code for managing FECs in routing protocols, therefore existing
+ protocols can be made MPLS-aware by adding FEC map and delegating
+ most work related to local label management to it.
+
+commit e55696a4f88b63c622bb3a0360f9114d01253e53
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Sat May 22 12:31:47 2021 +0200
+
+ Lib: Indirect bitmap for MPLS label allocator
+
+commit 21213be523baa7f2cbf0feaa617f265c55e9b17a
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Oct 2 15:09:30 2023 +0200
+
+ Nest: Expand rte_src.private_id to u64
+
+ In general, private_id is sparse and protocols may want to map some
+ internal values directly into it. For example, L3VPN needs to
+ map VPN route discriminators to private_id.
+
+ OTOH, u32 is enough for global_id, as these identifiers are dense.
+
+commit 8ad9c4bb339172d445d1346876b2c9f3c27199c1
+Author: Maria Matejka <mq@ucw.cz>
+Date: Wed Sep 27 12:51:55 2023 +0200
+
+ BGP config: Splitting Route Refresh and Enhanced Route Refresh
+
+ Both toggles are on by default but if some implementation needs one or
+ another to be switched off separately, then it's possible now.
+
+commit a4adb09f5a5c3806488fb121eafc0e6c969135e7
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Sep 22 15:10:45 2023 +0200
+
+ Aggregator: brief documentation
+
+commit 018a77fc0bc90f74e7267b40ce3bc82074ec8582
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Sep 22 14:21:21 2023 +0200
+
+ Aggregator: Forbidden dangerous filter computations
+
+commit 8674d7ab4bf76cadc256ca24609ffe4b1e8fcbf4
+Author: Maria Matejka <mq@ucw.cz>
+Date: Wed Jul 12 15:11:00 2023 +0200
+
+ Aggregator: Fixed hashing of adata
+
+commit 977b82fba49b22d9548546d88b105945921efaed
+Author: Igor Putovny <igor.putovny@nic.cz>
+Date: Wed Jun 21 13:15:07 2023 +0200
+
+ Basic route aggregation
+
+ Add a new protocol offering route aggregation.
+
+ User can specify list of route attributes in the configuration file and
+ run route aggregation on the export side of the pipe protocol. Routes are
+ sorted and for every group of equivalent routes new route is created and
+ exported to the routing table. It is also possible to specify filter
+ which will run for every route before aggregation.
+
+ Furthermore, it will be possible to set attributes of new routes
+ according to attributes of the aggregated routes.
+
+ This is a work in progress.
+
+ Original work by Igor Putovny, subsequent cleanups and finalization by
+ Maria Matejka.
+
+commit 0a729b509c2c4476cbf66c64620a863e6a381c8c
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Jun 23 09:05:48 2023 +0200
+
+ Simple testing of reconfiguration to a slightly different one
+
+commit a0fb0eaa6780e60b7c5434dfe0e2ed402e5a4ea4
+Author: katerina.kubecova <katerina.kubecova@nic.cz>
+Date: Tue Sep 19 11:11:24 2023 +0200
+
+ BGP: Setting and unsetting unknown attributes
+
+ All these must be declared as bytestring. Allows operators to delete
+ unwanted attributes breaking the Internet:
+
+ https://blog.benjojo.co.uk/post/bgp-path-attributes-grave-error-handling
+
+commit cc122bf0c295207a909061a365eccd49462b1b16
+Author: katerina.kubecova <katerina.kubecova@nic.cz>
+Date: Mon Sep 18 14:07:59 2023 +0200
+
+ Attributes declared in config can be bytestrings
+
+commit 8cc9d198c7ab906e176a7926484ea85d4f53cab6
+Author: katerina.kubecova <katerina.kubecova@nic.cz>
+Date: Wed Sep 20 09:50:22 2023 +0200
+
+ Filter: Function unset() accepts attributes declared in config
+
+commit bb8e28248be0ad1f728433e4884dfe2db3772b30
+Author: katerina.kubecova <katerina.kubecova@nic.cz>
+Date: Mon Sep 18 14:43:58 2023 +0200
+
+ Structures bytestring and adata merged into adata.
+
+commit 9ffea830b673a8b8506877a371e0c9fc52c99b94
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Sep 14 17:24:09 2023 +0200
+
+ Conf: Move definition of struct keyword to conf.h
+
+commit a3dc26455d125310ddf3c903208b0168dbbd81d4
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Sep 13 06:21:26 2023 +0200
+
+ Filter: Use common initializer for undefined variables and eattrs.
+
+ Undefined paths and clists should use typed f_val with empty adata
+ instead of just void f_val. Use common initializer to handle both
+ variables and eattrs.
+
+commit 7395b97daf529cab1065f107c924bed310a8ad75
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Sep 12 18:44:20 2023 +0200
+
+ Filter: Minor updates to methods
+
+ Remove warning when function-like syntax is used for calling
+ add/remove/... methods.
+
+ Fix argument offset in error messages for function-like syntax.
+
+commit 132f1edaf402aba79ae3983966795b2f1220afbb
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Jul 25 19:33:51 2023 +0200
+
+ Filter: Split clist add/delete operations to multiple methods
+
+commit f0d1396073dc7c3a6ab5f88baa07f2494cbd5328
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Jul 25 17:43:52 2023 +0200
+
+ Filter: Implement constant promotion for multiple dispatch methods
+
+commit e88695913148ae63e1f8c8ac64f7b039dd3fc286
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Jul 12 20:11:34 2023 +0200
+
+ Filter: Print proper error response in multiple method dispatch
+
+ When no matching method is found, print an error response containing
+ position and type of infringing argument and a set of expected types.
+
+commit e4ce88cc50a7af21e0b7042f60abd11e4288c6fe
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Jul 12 20:01:03 2023 +0200
+
+ Filter: Move argument list reversal from function_call to var_list
+
+ List of arguments for function calls is constructed in reverse and then
+ reverted. This was done in function_call grammar rule. Do the reverse
+ directly in var_list grammar rule. This fixes reverse order of arguments
+ in method calls.
+
+commit fc4398b4e1d18142a5c428a7c90484071a81ab9c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Jul 4 19:07:30 2023 +0200
+
+ Filter: Better syntax for function return types
+
+ The C-style syntax does not really fit into rest of our syntax.
+
+commit cc1099a04169b768cb4803686ee20423a6d3fede
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Jul 3 17:00:58 2023 +0200
+
+ Filter: Implement multiple dispatch for methods
+
+ - Extend method descriptors with type signature
+ - Daisy chain method descriptors for the same symbol
+ - Dispatch methods for same symbol based on type signature
+ - Split add/delete/filter operations to multiple methods
+ - Replace ad-hoc dispatch of old-style syntax with scope-based dispatch
+ - Also change method->arg_num to count initial arg
+
+ It still needs some improvements, like better handling of untyped
+ expressions and better error reporting when no dispatch can be done.
+
+ The multiple dispatch could also be extended to dispatch regular
+ function-like expressions in a uniform way.
+
+commit c696e3cb8e53229a438a8509a05e9d0ff27b8b4a
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Jun 28 01:21:23 2023 +0200
+
+ Filter: Make f_method_call_*() usage symmetric
+
+commit ab61476ebf2b8873d542f77069ec111030329268
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Jun 27 21:38:05 2023 +0200
+
+ Filter: Convert more methods to use METHOD_R()
+
+commit 4cdd6f2ea0af64843082c05147c307eb62165352
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Jun 27 21:16:11 2023 +0200
+
+ Filter: Remove number of args from METHOD_R()
+
+ Macro METHOD_R() is used for simplest methods, there is no place to
+ define argument types, so let's force it to be 0.
+
+commit c0231b092960c66dd17119f75ce3d2b4101f42aa
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Jun 27 14:49:28 2023 +0200
+
+ Conf: Remove toksym from symbol_known
+
+ No need to have toksym in symbol_known, as defined symbols are preferred
+ (by scope) to keywords anyway. Adding it just creates grammar conflicts.
+
+commit fdd39c81bd4cb04dc9f0f5e5cc8db95909b76cbf
+Author: Maria Matejka <mq@ucw.cz>
+Date: Mon Jun 19 17:24:30 2023 +0200
+
+ Filter: Print instructions take only one value (simplification)
+
+commit 6d411fc7bdd82ee40a96fee1ded228fa102b62c9
+Author: Maria Matejka <mq@ucw.cz>
+Date: Mon Jun 19 15:49:51 2023 +0200
+
+ Filter: Shortened method declarations
+
+commit 21faa54ec3251cb730a23a663ebf7775834ee7a8
+Author: Maria Matejka <mq@ucw.cz>
+Date: Sun Jun 18 22:50:45 2023 +0200
+
+ Filter: The for loop uses the method system for type dispatch
+
+commit 1d38726c646ee3a4b6431d55f921fa6ede92fca1
+Author: Maria Matejka <mq@ucw.cz>
+Date: Sat Jun 17 13:05:23 2023 +0200
+
+ Removing unused terminals from filter config
+
+commit fc9d471b36b91429e5d86aa794716683a5281449
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Jun 16 17:35:37 2023 +0200
+
+ Filter: Methods rework
+
+ Methods can now be called as x.m(y), as long as x can have its type
+ inferred in config time. If used as a command, it modifies the object,
+ if used as a value, it keeps the original object intact.
+
+ Also functions add(x,y), delete(x,y), filter(x,y) and prepend(x,y) now
+ spit a warning and are considered deprecated.
+
+ It's also possible to call a method on a constant, see filter/test.conf
+ for examples like bgp_path = +empty+.prepend(1).
+
+ Inside instruction definitions (filter/f-inst.c), a METHOD_CONSTRUCTOR()
+ call is added, which registers the instruction as a method for the type
+ of its first argument. Each type has its own method symbol table and
+ filter parser switches between them based on the inferred type of the
+ object calling the method.
+
+ Also FI_CLIST_(ADD|DELETE|FILTER) instructions have been split to allow
+ for this method dispatch. With type inference, it's now possible.
+
+commit 39f8f46d81203ebd6b976da56e83930f972dccab
+Author: Maria Matejka <mq@ucw.cz>
+Date: Sat Jun 17 10:16:28 2023 +0200
+
+ Uninitialized filter variables of path/[el]?clist types are now explicitly empty
+
+commit 6f798683a34aa87f5e4f590be4c90253a1135e08
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Jun 16 22:11:03 2023 +0200
+
+ Conf: config warnings show the file position
+
+commit 062ff656830f89bd3bca5b39a86c4d41b535a7bf
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Jun 15 13:25:40 2023 +0200
+
+ Filter: functions can and should have typed return values
+
+commit f86c86b7913f55c1221d8c5e1ff27700aa663a6e
+Author: Maria Matejka <mq@ucw.cz>
+Date: Tue Jun 13 09:39:29 2023 +0200
+
+ Filter/Conf: Method names have their own keyword hash
+
+ To allow for future dynamic method definition, parsing method names is
+ done via a dedicated keyword hash/scope.
+
+commit 6b95353ebdaa724252492f941ebe75f80e9e545a
+Author: Maria Matejka <mq@ucw.cz>
+Date: Tue Jun 13 11:09:41 2023 +0200
+
+ Conf: Allowing conf scope to be explicitly read only
+
+commit 51f2e7afaf069508685281e8c1b8bb1ceda79d8f
+Author: Maria Matejka <mq@ucw.cz>
+Date: Tue Jun 13 10:51:03 2023 +0200
+
+ Conf: Symbol manipulation gets its context explicitly
+
+commit 5951dfbd5ed21d973e7627740c069d6612d7b899
+Author: Maria Matejka <mq@ucw.cz>
+Date: Mon Jun 12 11:37:50 2023 +0200
+
+ Filter: any lvalue can get its methods called
+
+commit ae8ecafda9e28bfd417795fbb43408d6857df76d
+Author: Maria Matejka <mq@ucw.cz>
+Date: Mon Jun 12 11:20:49 2023 +0200
+
+ Filter: split out dot-notation methods to separate targets
+
+ This is just a preparationary refactoring to allow type-based method
+ tables.
+
+commit 58efa94460381309c754a3162df257ae043c2cbd
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Jun 9 12:49:19 2023 +0200
+
+ Conf: Keywords have their default symbols
+
+ This avoids unnecessary collapsed soft scopes caused by keyword symbol multiallocation.
+
+commit 8e177cf35b582ec973c1abce4709c80847adb711
+Author: Maria Matejka <mq@ucw.cz>
+Date: Mon May 2 20:29:03 2022 +0200
+
+ Conf: Symbol hashes for all scopes
+
+ This is a backport cherry-pick of commits
+ 165156beeb2926472bbceca3c103aacc3f81a8cc
+ cce974e8ea992d0e6d2f649eca7880b436d91d71
+
+ from the v3.0 branch as we need symbol hashes directly inside their
+ scopes for more general usage than before.
+
+commit a5a6de581b79641b21b26277580cb4cc118da7ea
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Jun 9 11:02:05 2023 +0200
+
+ Dropping empty-type const f_vals, they were copied anyway
+
+commit 86598183917cd9ff1d21900e0d8cc18dd1e791e1
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Jun 9 13:49:17 2023 +0200
+
+ Conf: Adding dummy thread-number setting for easier sharing of configuration between v2 and v3
+
+commit aa70e14c9e4cfeb70d2dc9cee497c40057dc105e
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Aug 29 18:23:29 2023 +0200
+
+ BFD: Improve handling of AdminDown
+
+ According to RFC 5882, system should not interpret the local or remote
+ session state transition to AdminDown as failure. We followed that for
+ the local session state but not for the remote session state (which
+ just triggered a transition of the local state to Down). The patch
+ fixes that.
+
+ We do not properly generate AdminDown on our side, so the patch is
+ relevant just for interoperability with other systems.
+
+ Thanks to Sunnat Samadov for the bugreport.
+
+commit f5140d1027f514bc59d46ab8aa09181f5870afbd
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Dec 13 19:31:46 2022 +0100
+
+ Conf: Allow keywords to be redefined by user symbols
+
+ Most syntactic constructs in BIRD configuration (e.g. protocol options)
+ are defined as keywords, which are distinct from symbols (user-defined
+ names for protocols, variables, ...). That may cause backwards
+ compatibility issue when a new feature is added, as it may collide with
+ existing user names.
+
+ We can allow keywords to be shadowed by symbols in almost all cases to
+ avoid this issue.
+
+ This replaces the previous mechanism, where shadowable symbols have to be
+ explictly added to kw_syms.
+
+commit cce48c6cdd9484c606879ea76d4c633fce12ba36
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Aug 25 23:14:36 2023 +0200
+
+ Filter: Add separate instruction for uninitialized variable declaration
+
+ The previous approach (use VOID constant for variable initialization)
+ failed due to dynamic type check failure.
+
+ Thanks to Alexander Zubkov <green@qrator.net> for the bugreport.
+
+commit 116285f2b000a4b78044f1bcf1eb1d6655f1d2fe
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Aug 25 04:32:01 2023 +0200
+
+ RPKI: Fix conflict in config grammar
+
+commit 32427c9ce119df5457d3d2c6e677429e31a5edad
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Aug 25 04:29:32 2023 +0200
+
+ Nest: Fix missing bar in kw_sym
+
+ Thanks to Alexander Zubkov for the notice.
+
+commit 9d8e4b01bc53d684f5a5b3230c371382860de58b
+Author: Alexander Zubkov <green@qrator.net>
+Date: Thu Aug 24 18:04:33 2023 +0200
+
+ Doc: Document bytestring type
+
+commit cbb43d6ff31fcae8a682659ec514cbea21ee4e25
+Author: Alexander Zubkov <green@qrator.net>
+Date: Thu Aug 24 17:41:51 2023 +0200
+
+ Doc: Document RAdv "custom option" configuration definition
+
+commit f411a19bb0467cfc421f8aa6f5ead49972bab058
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Aug 24 16:59:23 2023 +0200
+
+ Conf: Use nonterminal bytestring instead of BYTETEXT
+
+ Nonterminal bytestring allows to provide expressions to be evaluated in
+ places where BYTETEXT is used now: passwords, radv custom option.
+
+ Based on the patch from Alexander Zubkov <green@qrator.net>, thanks!
+
+commit 0dbcc927260c6da918fa1bd78c86800e41ab05a8
+Author: Alexander Zubkov <green@qrator.net>
+Date: Thu Aug 24 04:45:55 2023 +0200
+
+ Filter: Use more generic approach for intra-config expressions
+
+ Replace f_eval_int() function with a type-generic variant: cf_eval().
+ Implement similar fuction: cf_eval_int() via inline call to cf_eval().
+
+commit fc3547880aafad726509f0514df2d5e0bb140728
+Author: Alexander Zubkov <green@qrator.net>
+Date: Thu Aug 24 04:30:42 2023 +0200
+
+ Filter: Add bytestring type
+
+ - Rename BYTESTRING lexem to BYTETEXT, not to collide with 'bytestring' type name
+ - Add bytestring type with id T_BYTESTRING (0x2c)
+ - Add from_hex() filter function to create bytestring from hex string
+ - Add filter test cases for bytestring type
+
+ Minor changes by committer.
+
+commit eddc0ffdab239c61cc0e064b6ebd33dfadcef3cd
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Aug 24 03:04:58 2023 +0200
+
+ Lib: Add functions for reading and writing of bytestrings
+
+ Based on patch from Alexander Zubkov, thanks!
+
+commit e3c0eca95642a846ab65261424a51dd99d954017
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Aug 23 15:55:31 2023 +0200
+
+ Nest: Treat VRF interfaces as inside respective VRFs
+
+ Despite not having defined 'master interface', VRF interfaces should be
+ treated as being inside respective VRFs. They behave as a loopback for
+ respective VRFs. Treating the VRF interface as inside the VRF allows
+ e.g. OSPF to pick up IP addresses defined on the VRF interface.
+
+ For this, we also need to tell apart VRF interfaces and regular interfaces.
+ Extend Netlink code to parse interface type and mark VRF interfaces with
+ IF_VRF flag.
+
+ Based on the patch from Erin Shepherd, thanks!
+
+commit 5121101136cb80151a9361c63dc4822afeb44eef
+Merge: d2dbe854 4558adab
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Aug 22 15:28:05 2023 +0200
+
+ Merge branch 'bmp'
+
+commit d2dbe854631813eae9fbf3e264ced4460ea4c432
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Tue Aug 22 14:13:55 2023 +0200
+
+ RPM: Sync bird.spec from Fedora dist-git
+
+ It seems all Fedora packages are built from epel7 branch.
+
+commit 31ef5645e9155365db96705b847968008d378658
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Wed Apr 26 17:26:30 2023 +0200
+
+ Distro: Add apkg packaging test
+
+ The test was written by Maria Matejka, thanks!
+
+ Run this using
+
+ apkg test
+
+commit 215f7161c426975ca821f5d3c1866889287dcdbc
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Fri May 21 04:48:27 2021 +0200
+
+ Distro: Add apkg compat level
+
+ This will allow compatibility on future apkg config updates.
+
+commit 5fb871def46c9fc0d0e02d5bf1f2c8f76c40dc30
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Wed May 3 14:13:21 2023 +0200
+
+ Debian: Fix for arm64 cross build
+
+ Mirrors debian patch.
+
+commit 5fe00df693a7a06888d6fba2a967fe0ca04f29ae
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Thu Oct 20 18:47:09 2022 +0200
+
+ Debian: Use {{ now }} in changelog
+
+commit 2b4ab79de1f5afdfd821689e81883db662f433c6
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Wed Feb 22 19:01:31 2023 +0100
+
+ Debian: Bump compat level to 11
+
+ Current is 13, remaining blockers:
+
+ - Debian 9 @ 11 (EOL)
+ - Ubuntu 18.04 @ 12 (EOL 2023-04-02)
+
+commit b983b5916594731b5af1ed8f7ef12c64ce36e7fb
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Tue Feb 7 19:20:52 2023 +0100
+
+ Debian: Sync packaging with Debian
+
+commit b0c3c286a54050b56af0c0fe398e0761cc320581
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Tue Feb 7 19:19:57 2023 +0100
+
+ Debian: Add birdcl manpage
+
+ Mirrors Debian package change.
+
+commit ad514e603329807e28ddda6beaaa3ebc8f36b069
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Thu May 20 18:07:46 2021 +0200
+
+ RPM: Add missing BuildRequires: autoconf
+
+commit fea04d7c34b92d854cfcd4cc3db2dcba60caba7b
+Author: Alexander Zubkov <green@qrator.net>
+Date: Tue Aug 22 14:44:18 2023 +0200
+
+ Use more proper pointers to constant bytestrings
+
+commit 9bf20484764364b9f32bef0a3a7f877abbc29914
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Aug 22 14:20:59 2023 +0200
+
+ BGP: Update RFC reference
+
+ RFC 5549 was obsoleted by RFC 8950.
+
+commit 4558adabfbbe3696156d20767168010d6238f434
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Aug 22 01:24:21 2023 +0200
+
+ BMP: Improve peer_down handling
+
+ Move all bmp_peer_down() calls to one place and make it synchronous with
+ BGP session down, ensuring that BMP receives peer_down before route
+ withdraws from flushing.
+
+ Also refactor bmp_peer_down_() message generating code.
+
+commit 52641e086675832e9f43f86237b0cddbd5ec551e
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Aug 21 04:20:32 2023 +0200
+
+ BMP: Use generic channel feed instead of direct walk over rtable
+
+ Now we use rt_notify() and channels for both feed and notifications,
+ in both import tables (pre-policy) and regular tables (post-policy).
+
+ Remove direct walk in bmp_route_monitor_snapshot().
+
+commit ef6ab5ce86af925ee10687fde4e62912fb892433
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Aug 21 04:17:21 2023 +0200
+
+ Nest: Use generic rte_announce() also for import tables
+
+ Remove special rte_announce_in(), so we can use generic rte_announce()
+ for bot feed and notifications.
+
+commit c40f29a79035b54a8b48ece0e91d38d9066529e3
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Aug 18 15:39:08 2023 +0200
+
+ BMP: Fix route timestamps
+
+commit f4deef89bebae6e41654217e20f2a7531c65bf49
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Aug 18 03:53:58 2023 +0200
+
+ BMP: Refactor route monitoring
+
+ - Manage BMP state through bmp_peer, bmp_stream, bmp_table structures
+ - Use channels and rt_notify() hook for route announcements
+ - Add support for post-policy monitoring
+ - Send End-of-RIB even when there is no routes
+ - Remove rte_update_in_notify() hook from import tables
+ - Update import tables to support channels
+ - Add bmp_hack (no feed / no flush) flag to channels
+
+commit aec21cda249f9460d63c14ca83a9fa4210bcc20d
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Aug 1 18:39:38 2023 +0200
+
+ BMP: Remove useless buffer
+
+commit 8ded8baba2a95cec81f20b10160c81d229f8fae8
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Aug 1 17:56:56 2023 +0200
+
+ BMP: Simplify route monitoring hooks
+
+ No need for *_begin(), *_commit(), and *_end() hooks. The hook *_notify()
+ is sufficient for everything.
+
+commit ecbae010bf057dbaf0ec46e888fb7446fee9c736
+Author: Alexander Zubkov <green@qrator.net>
+Date: Fri Jun 30 13:16:09 2023 +0200
+
+ Fixed a typo in documentation
+
+ The problem was the "/" symbol in the prefix mask that finished the formatting definition prematurely.
+
+commit ccfa48a24aea64d1a844fb97cfe15965197c0908
+Author: Alexander Zubkov <green@qrator.net>
+Date: Mon Jun 26 15:35:22 2023 +0200
+
+ RAdv: Use new syntax for custom options
+
+ And use WALK_LIST macro
+
+commit 9c81250c04798fd274ae9d77380e93b941ac2d7f
+Author: Alexander Zubkov <green@qrator.net>
+Date: Fri Jun 23 17:21:05 2023 +0200
+
+ RAdv: Add custom options
+
+ Currently one can use only a predefined set of advertised options in RAdv
+ protocol, which are supported by BIRD configuration. It would be convenient
+ to be able to specify other possible options at least manually as a blob
+ so one should not wait until it is supported in the code, released, etc.
+
+ This idea is inspired by presentation by Ondřej Caletka at CSNOG, in which
+ he noticed the lack of either PREF64 option or possibility to add custom
+ options in various software.
+
+ The patch makes it possible to define such options with the syntax:
+
+ other type <num> <bytestring>
+
+commit 65d6a525944faa3f77041419991d77106d8f0a0d
+Author: Alexander Zubkov <green@qrator.net>
+Date: Fri Jun 23 16:47:37 2023 +0200
+
+ Add hex:XYZ syntax for short hex strings
+
+ Hexadecimal bytestring literals have minimal length to not collide
+ with IP addresses or regular (hexadecimal) number literals.
+
+ Allow to use shorter literals with explicit hex: prefix.
+
+commit 5f2ecb2298a6fe4b2fcd9efc0b39c36202496c2c
+Merge: f8bcb037 52bae235
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Jun 22 17:25:18 2023 +0200
+
+ Merge tag 'v2.13.1'
+
+commit 52bae235b716a3c8d629ddf1306178568c69833f
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Jun 22 16:14:12 2023 +0200
+
+ NEWS and version update
+
+commit 1499a335f6f44a0fd85365e404c2a11842d7f75c
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Jun 22 16:07:28 2023 +0200
+
+ Filter: Fixed segfault when a case option had an empty block
+
+ Thanks to Kobayashi_Bairuo <noc@tohunet.com> for reporting.
+
+commit ac7341a18146bf0f0b2c60477c4292a9cd428a87
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri May 19 01:02:57 2023 +0200
+
+ BGP: Fix role check when no capability option is present
+
+ When an OPEN message without capability options was parsed, the remote
+ role field was not initialized with the proper (non-zero) default value,
+ so it was interpreted as if 'provider' was announced.
+
+ Thanks to Mikhail Grishin for the bugreport.
+
+commit f8bcb037b5b71a19209f1b63d52895c8c34c675b
+Author: Luiz Amaral <email@luiz.eng.br>
+Date: Thu Jun 15 15:01:50 2023 +0200
+
+ Netlink: Allow RTA_VIA even without MPLS support
+
+ It is necessary for IPv4 over IPv6 nexthop support on FreeBSD,
+ and RTA_VIA is not really related to MPLS.
+
+ It breaks build for some very old systems like Debian 8 and CentOS 7,
+ but we generally do not support older kernels than 4.14 LTS anyway.
+
+commit 90f417604518cbaa6f5b8a833352d73c51e53e57
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Jun 15 15:50:18 2023 +0200
+
+ CI: Remove obsolete CI targets
+
+ Remove build-debian-8, build-ubuntu-14_04, build-centos-7, pkg-debian-9,
+ and pkg-centos-7 targets.
+
+ Debian 8, Ubuntu 14.04, and CentOS 7 have unsupported kernels, Debian 9
+ has okay kernel, but is EOL.
+
+commit 43d41d8449a4eb196422d6d309dbea998d920541
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Jun 8 05:10:05 2023 +0200
+
+ BMP: Ensure that bmp_fire_tx() does nothing when not up
+
+commit e8838d930cd5c875f32aa2b7da5d84995b14ccac
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Jun 8 04:56:41 2023 +0200
+
+ BMP: Support multiple instances of BMP protocol
+
+ Add internal BMP functions with plicit bmp_proto *p as first argument,
+ which allows using TRACE() macro. Keep list of BMP instances and call
+ internal functions. Old BMP functions are wrappers that call internal
+ functions for all enabled BMP instances.
+
+ Extract End-of-RIB mark into separate function.
+
+ Based on patch from Michal Zagorski <mzagorsk@akamai.com>. Thanks!
+
+commit 69372dc9aa8b234b79999c4cdcdfa3aa05e3a672
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Jun 2 02:34:05 2023 +0200
+
+ Babel: Minor changes to RTT formatting
+
+ Use existing %t printf code and move 'ms' in CLI output to table header.
+
+commit f08abcb8dda6ff488fa1b52314a67d97aee059d7
+Author: Toke Høiland-Jørgensen <toke@toke.dk>
+Date: Fri Jun 2 00:31:53 2023 +0200
+
+ Babel: Add support for the RTT extension
+
+ This adds support to the Babel protocol for the RTT extension specified
+ in draft-ietf-babel-rtt-extension. While this extension is not yet at the
+ RFC stage, it is one of the more useful extensions to Babel[0], so it
+ seems worth having in Bird as well.
+
+ The extension adds timestamps to Hello and IHU TLVs and uses these to
+ compute an RTT to each neighbour. An extra per-neighbour cost is then
+ computed from the RTT based on a minimum and maximum interval and cost
+ value specified in the configuration. The primary use case for this is
+ improving routing in a geographically distributed tunnel-based overlay
+ network.
+
+ The implementation follows the babeld implementation when picking
+ constants and default configuration values. It also uses the same RTT
+ smoothing algorithm as babeld, and follows it in adding a new 'tunnel'
+ interface type which enables RTT by default.
+
+ [0] https://alioth-lists.debian.net/pipermail/babel-users/2022-April/003932.html
+
+commit d8cf3cad5104ab7a9887397b2a34d94c8a5f2aef
+Author: Toke Høiland-Jørgensen <toke@toke.dk>
+Date: Fri Jun 2 00:26:41 2023 +0200
+
+ IO: Add current_time_now() function for immediate timestamp
+
+ Add a current_time_now() function which gets an immediate monotonic
+ timestamp instead of using the cached value from the event loop. This is
+ useful for callers that need precise times, such as the Babel RTT
+ measurement code.
+
+ Minor changes by committer.
+
+commit 0799fc99abb523432bc3f903f6a32eafbe37d043
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed May 31 18:32:53 2023 +0200
+
+ BMP: Fix bug in buffer resize
+
+ The buffer code in bmp_buffer_grow(), reuse the MRT buffer handling code.
+
+ Based on comments by Michal Zagorski <mzagorsk@akamai.com>, Thanks!
+
+commit e8be7a7080be2ffd800ead5377b06c7a535b564a
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed May 31 17:41:53 2023 +0200
+
+ BMP: Proper reconfiguration and protocol status
+
+ Based on patches from Michal Zagorski <mzagorsk@akamai.com> co-authored
+ with Pawel Maslanka <pmaslank@akamai.com>. Thanks!
+
+commit 1e45e2aa4e173869be071bfa28057d8b52e8948c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue May 30 17:23:56 2023 +0200
+
+ BMP: Add station address check
+
+ Also, do not initialize it to IPA_NONE4, use regular IPA_NONE.
+
+commit 62d8fbdc1cb3d358b0d20d7b10bd3f17f357d567
+Author: Michal Zagorski <mzagorsk@akamai.com>
+Date: Tue May 30 17:09:25 2023 +0200
+
+ BMP: Add local address option
+
+ Also remove unused local and ip_post_policy options.
+
+ Co-authored with Pawel Maslanka <pmaslank@akamai.com>.
+
+ Minor changes by committer.
+
+commit ae4d934c53cdc4cc5ec3d4535cd5138fdf9b25cb
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue May 30 15:52:01 2023 +0200
+
+ BMP: Minor formatting cleanups
+
+ Based on patches from Michal Zagorski <mzagorsk@akamai.com> co-authored
+ with Pawel Maslanka <pmaslank@akamai.com>. Thanks!
+
+commit c1821a9aba3c7fbdb48cee7d86e4661317900a0a
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue May 30 15:37:52 2023 +0200
+
+ BGP: Improve bgp_create_update_bmp()
+
+ Fix issue with missing AF cap (e.g. IPv4 unicast when no capabilities
+ are announced).
+
+ Add Linpool save/restore action similar to bgp_create_update().
+
+ Based on patch from Michal Zagorski <mzagorsk@akamai.com> co-authored
+ with Pawel Maslanka <pmaslank@akamai.com>. Thanks!
+
+commit f8ba82804faba5cc1520d4545330502e29b9e920
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri May 19 01:02:57 2023 +0200
+
+ BGP: Fix role check when no capability option is present
+
+ When an OPEN message without capability options was parsed, the remote
+ role field was not initialized with the proper (non-zero) default value,
+ so it was interpreted as if 'provider' was announced.
+
+ Thanks to Mikhail Grishin for the bugreport.
+
+commit b0e97617d98ed02235de37b7e498d81f01330b50
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu May 18 15:55:45 2023 +0200
+
+ Lib: Improve IP/net hashing
+
+ Backport some changes from branch oz-parametric-hashes. Replace naive
+ hash function for IPv6 addresses, fix hashing of VPNx (where upper half
+ of RD was ignored), fix hashing of MPLS labels (where identity was used).
+
+commit 3cf91fb9eb5e6aa51e63edcd237ee266373aec79
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue May 16 13:25:48 2023 +0200
+
+ Nest: Add tests and benchmark for FIB
+
+ Basic fib_get() / fib_find() test for random prefixes, FIB_WALK() test,
+ and benchmark for fib_find(). Also generalize and reuse some code from
+ trie tests.
+
+commit aa3c35498d3c5ae7ec7fd34bf8758652fc2748f1
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon May 1 03:35:21 2023 +0200
+
+ BMP: Use OPEN messages stored in BGP
+
+ The BMP protocol needs OPEN messages of established BGP sessions to
+ construct appropriate Peer Up messages. Instead of saving them internally
+ we use OPEN messages stored in BGP instances. This allows BMP instances
+ to be restarted or enabled later.
+
+ Because of this change, we can simplify BMP data structures. No need to
+ keep track of BGP sessions when we are not started. We have to iterate
+ over all (established) BGP sessions when the BMP session is established.
+ This is just a scaffolding now, but some kind of iteration would be
+ necessary anyway.
+
+ Also, the commit cleans up handling of msg/msg_length arguments to be
+ body/body_length consistently in both rx/tx and peer_up/peer_down calls.
+
+commit 1be0be1b71f0127740a4aa6f35d4a256d6c34fb9
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Apr 28 19:13:56 2023 +0200
+
+ BGP: Save sent and received OPEN messages
+
+ These are necessary for BMP Peer UP message and it is better to keep them
+ in BGP than in BMP (so BMP could be restarted or added later).
+
+commit a8a64ca0fed41c78376b27880e934296bd3c3a7f
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Apr 27 18:20:49 2023 +0200
+
+ Conf: Improve handling of keywords
+
+ For whatever reason, parser allocated a symbol for every parsed keyword
+ in each scope. That wasted time and memory. The effect is worsened with
+ recent changes allowing local scopes, so keywords often promote soft
+ scopes (with no symbols) to real scopes.
+
+ Do not allocate a symbol for a keyword. Take care of keywords that could
+ be promoted to symbols (kw_sym) and do it explicitly.
+
+commit 9b471e72d75c154f3b8c4fa134c7c9f1a55fe27f
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Apr 27 17:09:00 2023 +0200
+
+ Conf: Fix symbol lookup
+
+ The symbol table used just symbol name as a key, and used a trick with
+ active flag to find symbols in active scopes with one hash table lookup.
+
+ The disadvantage is that it can degenerate to O(n) for negative queries
+ in situations where are many symbols with the same name in different
+ scopes.
+
+ Thanks to Yanko Kaneti for the bugreport.
+
+commit ca0f239c72486cebfe171e335e3b8a86f5999714
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Apr 21 20:24:43 2023 +0200
+
+ NEWS and version update
+
+commit 1a1e13cc2fc2df82e2319b7beeb18bb8eb92fbd2
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Apr 21 19:29:17 2023 +0200
+
+ Filter: Disable some trie formatting tests
+
+ Trie formatting works slightly different with 4-way tries than with
+ 16-way ones, so these tests generated false error. Block them for now.
+
+commit 52450bc96dcedbc30cbb2e282c6706ad9e5e5774
+Merge: d61505b0 f3b599af
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Apr 21 04:47:55 2023 +0200
+
+ Merge branch 'bmp'
+
+commit f3b599afe5bde0c7f232421743041b305bb8afa7
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Fri Apr 21 04:42:13 2023 +0200
+
+ BMP: Add some basic documentation
+
+commit d61505b039bf0aa6697e28b2a4e07907c89ba1fb
+Author: Luiz Amaral <email@luiz.eng.br>
+Date: Fri Apr 21 01:37:30 2023 +0200
+
+ BSD: IPv4 over IPv6 nexthop support on FreeBSD
+
+ The support for IPv4 routes with IPv6 nexthops was implemented in FreeBSD
+ 13.1, this patch allows to import and export such routes from/to kernel.
+
+ Minor change from committer.
+
+commit 335409248ea932e93ce4361564b8e92d0b83b071
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Apr 20 19:33:00 2023 +0200
+
+ Linpool: Fix lp_restore()
+
+ When lp_save() is called on an empty linpool, then some allocation is
+ done, then lp_restore() is called, the linpool is restored but the used
+ chunks are inaccessible. Fix it.
+
+commit 976dec048a25fc22efb07fa73be1316c95046420
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Apr 20 17:14:45 2023 +0200
+
+ BMP: Silence some log messages
+
+ Hooks called from BGP to BMP should not log warning when BMP is not
+ connected, that is not an error (and we do not want to flood logs with
+ a ton of messages).
+
+ Blocked sk_send() should not log warning, that is expected situation.
+ Error during sk_send() is handled in error hook anyway.
+
+commit 2c7d2141ac86b0d482d3221447d1ad920c557108
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Thu Apr 20 16:13:58 2023 +0200
+
+ BMP: Fix connection management
+
+ Replace broken TCP connection management with a simple state machine.
+ Handle failed attempts properly with a timeout, detect and handle TCP
+ connection close and try to reconnect after that. Remove useless
+ 'station_connected' flag.
+
+ Keep open messages saved even after the BMP session establishment,
+ so they can be used after BMP session flaps.
+
+ Use proper log messages for session events.
+
+commit 010df43519b12e83b0ff2cba9e344cba698586bb
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Apr 18 18:57:54 2023 +0200
+
+ BMP: Fix reconfiguration
+
+ It is not supported, but at least it must update internal config
+ pointer to not keep old one.
+
+commit 02164814b49a3385caae0ea10aa042487c6002d2
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Apr 18 17:21:13 2023 +0200
+
+ BMP: Allow build without BMP and disable BMP build by default
+
+ It has still several important issues to be enabled by default.
+
+commit fbeef4b74dfd73fb86b1ccc5dd1c6109e3c21624
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Apr 18 15:13:24 2023 +0200
+
+ BMP: Move initialization to bmp_start()
+
+ That fixes BMP socket allocation from an invalid pool.
+
+commit 04e3a76c9417d35acdfba96a11327e99000fe47d
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Apr 18 15:09:21 2023 +0200
+
+ BMP: Fix missing template
+
+ It is mandatory for protocol.
+
+commit 3925e65938e7c778f650d62a721dec7a66c19ab3
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Thu Apr 15 18:32:47 2021 +0200
+
+ BMP: Add some missing bmp_buffer_free() calls
+
+ They were inadvertently removed during recent code refactoring.
+
+ Thanks to Dawid Macek for the bugreport and patch.
+
+commit 4d56b70dc5facdf4b839b76bf80c93856bcbb121
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Mon Mar 29 04:43:04 2021 +0200
+
+ BMP: Remove duplicate functions for update encoding
+
+ Use existing BGP functions also for BMP update encoding.
+
+commit 568fd666136fcf7a37eb445d18b478b6464536c4
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Sun Mar 28 16:41:53 2021 +0200
+
+ BMP: Integrate bmp_conn to bmp_proto
+
+ There is only one socket per BMP instance, no need to have separate
+ struct (like in BGP).
+
+commit 4adebdf198d6e2ca1afcd7cb9bfac81725e7b24e
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Sun Mar 28 15:36:59 2021 +0200
+
+ BMP: Minor cleanups
+
+ Remove redundant 'disable' option, simplify IP address serialization,
+ and remove useless macros.
+
+commit a995ed43860eb139a13456242aa12486179fac86
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Sun Mar 28 15:13:23 2021 +0200
+
+ BMP: Do not use global instance ptr internally
+
+ Use local variable to refence relevant instance instead of using global
+ instance ptr. Also, use 'p' variable instead of 'bmp' so we can use
+ common macros like TRACE().
+
+commit ad16e351773f4b606dd8b4dbbe77c2cb35bf5133
+Author: Ondrej Zajicek (work) <santiago@crfreenet.org>
+Date: Sun Mar 28 04:30:11 2021 +0200
+
+ BMP: Remove superfluous error handling
+
+ Most error handling code was was for cases that cannot happen,
+ or they would be code bugs (and should use ASSERT()). Keep error
+ handling for just for I/O errors, like in rest of BIRD.
+
+commit a848dad40aa618e5e24417e4ef46b62c860de679
+Author: Pawel Maslanka <pmaslank@akamai.com>
+Date: Mon Mar 29 22:45:21 2021 +0200
+
+ BMP protocol support
+
+ Initial implementation of a basic subset of the BMP (BGP Monitoring
+ Protocol, RFC 7854) from Akamai team. Submitted for further review
+ and improvement.
+
+commit 9e44ace3928a19560058dc713fcbff3a8bad3b3c
+Author: Trisha Biswas <tbiswas@fastly.com>
+Date: Fri Apr 14 04:28:37 2023 +0200
+
+ BGP: Add 'allow bgp_med' option for EBGP sessions
+
+ This option allows to treat bgp_med as regular transitive attribute
+ on EBGP sessions (without hacks in filters).
+
+ Minor changes from committer.
+
+commit dc139fb6438f0864e83a9dc71e7c212c5acaf3ef
+Author: Jakub Ružička <jakub.ruzicka@nic.cz>
+Date: Thu Apr 13 20:06:55 2023 +0200
+
+ Increase tests timeout
+
+ Tests may take longer than 5 s to complete on slow/virtual machines.
+
+commit f881b98d9b48e7a60c46dffc29009a86dac63233
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Tue Apr 4 05:20:49 2023 +0200
+
+ BGP: Fix bgp_med handling
+
+ Missing translation from BGP attribute ID to eattr ID in bgp_unset_attr()
+ broke automatic removal of bgp_med during export to EBGP peers.
+
+ Thanks to Edward Sun for the bugreport.
+
+commit 231c63851e3a56201dd02abfbf3fce47a80f8ae0
+Author: Johannes Moos <johannes.moos@de-cix.net>
+Date: Sat Mar 18 15:33:48 2023 +0100
+
+ Add missing references to "show route in" in the cli-help and doc.
+
+ The feature of showing all prefixes inside the given one has been added
+ in v2.0.9 but not well documented. Fixing it by this update.
+
+ Text in doc and commit message added by commiter.
+
+commit 2b712554d18dfb09274c003315a573f8578270ec
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Mar 16 19:23:19 2023 +0100
+
+ BGP: Free bind applies also to outbound connections
+
+ Even though the free bind option is primarily meant to alleviate problems
+ with addresses assigned too late, it's also possible to use BIRD with AnyIP
+ configuration, assigning whole ranges to the machine. Therefore free bind
+ allows also to create an outbound connection from specific address even though
+ such address is not assigned.
+
+commit 6b38285f587be9d4b128ae816bc25cb338f7f07c
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Mar 6 11:57:40 2023 +0100
+
+ Net: Replace runtime checks with STATIC_ASSERT()
+
+commit 0f679438f36d8c2a31dfe490007e983b085caef6
+Author: Petr Vaněk <arkamar@atlas.cz>
+Date: Mon Mar 6 11:19:30 2023 +0100
+
+ Printf test suite fails on systems with musl libc because tests for "%m"
+ and "%M" formats expect "Input/output error" message but musl returns
+ "I/O error". Proposed change compares the printf output with string
+ returned from strerror function for EIO constant.
+
+ See-also: https://bugs.gentoo.org/836713
+
+ Minor change from committer.
+
+commit 2f080b543296aa2fa18bf7451b5174d942b0a952
+Author: Maria Matejka <mq@ucw.cz>
+Date: Thu Feb 16 14:22:23 2023 +0100
+
+ Config: Dropping filter instruction trees after linearization
+
+commit 6c058ae40cf33d6d36c0159d0c40c9925c8e60d8
+Author: Maria Matejka <mq@ucw.cz>
+Date: Wed Feb 22 14:45:20 2023 +0100
+
+ Linpool flush drops all the allocated pages but one
+
+ When a linpool is used to allocate a one-off big load of memory, it
+ makes no sense to keep that amount of memory for future use inside the
+ linpool. Contrary to previous implementations where the memory was
+ directly free()d, we now use the page allocator which has an internal
+ cache which keeps the released pages for us and subsequent allocations
+ simply get these released pages back.
+
+ And even if the page cleanup routine kicks in inbetween, the pages get
+ only madvise()d, not munmap()ed so performance aspects are negligible.
+
+ This may fix some memory usage peaks in extreme cases.
+
+commit 913ec57f27b06845e3698e8ea08821d39b9575cf
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sun Feb 19 15:35:07 2023 +0100
+
+ BGP: Update RFC references
+
+commit 501256cfc8c1fb5e225c81c4d3300b7c219baf63
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sun Feb 19 15:34:43 2023 +0100
+
+ Babel: Update RFC references
+
+commit eefb29679957fed3724e6d5db2ddf112e28f646f
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Sun Feb 19 03:59:10 2023 +0100
+
+ Conf: Fix too early free of old configuration
+
+ The change 371eb49043d225d2bab8149187b813a14b4b86d2 introduced early free
+ of old_config. Unfortunately, it did not properly check whether it is not
+ still in use (blocked by obstacle during reconfiguration). Fix that.
+
+ It also means that we still could have a short peak when three configs
+ are in use (when a new reconfig is requeste while the previous one is
+ still active).
+
+commit ee919658948772105d0bd3b4535ba28883484f2c
+Author: Toke Høiland-Jørgensen <toke@toke.dk>
+Date: Tue Feb 14 18:18:32 2023 +0100
+
+ Babel: Keep separate auth PC counters for unicast and multicast
+
+ The babel protocol normally sends all its messages as multicast packets,
+ but the protocol specification allows most messages to be sent as either
+ unicast or multicast, and the two can be mixed freely. In particular, the
+ babeld implementation can be configured to unicast updates to all peers
+ instead of sending them as unicast.
+
+ Daniel discovered that this can cause problems with the packet counter
+ checks in the MAC extension due to packet reordering. This happens on WiFi
+ networks where clients have power save enabled (which is quite common in
+ infrastructure networks): in this case, the access point will buffer all
+ multicast traffic and only send it out along with its beacons, leading to a
+ maximum buffering in default Linux-based access point configuration of up
+ to 200 ms.
+
+ This means that a Babel sender that mixes unicast and multicast messages
+ can have the unicast messages overtake the multicast messages because of
+ this buffering; when authentication is enabled, this causes the receiver to
+ discard the multicast message when it does arrive because it now has a
+ packet counter value less than the unicast message that arrived before it.
+ Daniel observed that this happens frequently enough that Babel ceases to
+ work entirely when runner over a WiFi network.
+
+ The issue has been described in draft-ietf-babel-mac-relaxed, which is
+ currently pending RFC publication. That also describes two mitigation
+ mechanisms: Keeping separate PC counters for unicast and multicast, and
+ using a reorder window for PC values. This patch implements the former as
+ that is the simplest, and resolves the particular issue seen on WiFi.
+
+ Thanks to Daniel Gröber for the bugreport.
+
+ Minor changes from committer.
+
+commit eecc3f02e41bcb91d463c4c1189fd56bc44e6514
+Author: Andreas Rammhold <andreas@rammhold.de>
+Date: Tue Feb 14 16:17:03 2023 +0100
+
+ Babel: Implement IPv4 via IPv6 extension (RFC 9229)
+
+ The patch implements an IPv4 via IPv6 extension (RFC 9229) to the Babel
+ routing protocol (RFC 8966) that allows annoncing routes to an IPv4
+ prefix with an IPv6 next hop, which makes it possible for IPv4 traffic
+ to flow through interfaces that have not been assigned an IPv4 address.
+
+ The implementation is compatible with the current Babeld version.
+
+ Thanks to Toke Høiland-Jørgensen for early review on this work.
+
+ Minor changes from committer.
+
+commit 0851fcde651301a886fefc574a4f739bf68119b9
+Author: Maria Matejka <mq@ucw.cz>
+Date: Fri Feb 3 09:12:34 2023 +0100
+
+ Documentation: Adding roadmap as decided in January 2023
+
+commit 0ab62f267449d6496e753625e37924357fb0aa95
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Feb 1 19:30:21 2023 +0100
+
+ Build: Partial revert of one of previous changes
+
+ There are many compatibility issues with echo -e, scratch that.
+
+commit ba348b2029f3c51559bed0bd752c4365793f0ea9
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Feb 1 17:46:48 2023 +0100
+
+ Build: Minor improvement to build output
+
+commit 23f3dd5cfbe8681b4e71c8d7345bdeaafc86e077
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Wed Feb 1 16:15:13 2023 +0100
+
+ Nest: Minor cleanup in buildsystem
+
+ There ware missing dependencies for proto-build.c generation, which
+ sometimes lead to failed builds, and ignores changes in the set of
+ built protocols. Fix that, and also improve formatting of proto-build.c
+
+commit dc4c5f51f83f97100b207136ecfde8ff94e597e6
+Author: Toke Høiland-Jørgensen <toke@toke.dk>
+Date: Tue Jan 31 15:52:14 2023 +0100
+
+ Babel: Initialise source seqno from incoming message
+
+ When creating a new babel_source object we initialise the seqno to 0. The
+ caller will update the source object with the right metric and seqno value,
+ for both newly created and old source objects. However if we initialise the
+ source object seqno to 0 that may actually turn out to be a valid (higher)
+ seqno than the one in the routing table, because of seqno wrapping. In this
+ case the source metric will not be set properly, which breaks feasibility
+ tracking for subsequent updates.
+
+ To fix this, add a new initial_seqno argument to babel_get_source() which
+ is used when allocating a new object, and set that to the seqno value of
+ the update we're sending.
+
+ Thanks to Juliusz Chroboczek for the bugreport.
+
+commit 96d7c4679df49b34be004177b10a99210af5f141
+Author: Ondrej Zajicek <santiago@crfreenet.org>
+Date: Mon Jan 30 23:49:20 2023 +0100
+
+ Babel: Improve clarity of unfeasible update handling.
+
+ Add a comment and (unnecessary) check to make correctness obvious.
+
+commit 3e7e4a71868bc519aacc0eb785471b46fc345a5c
+Author: Toke Høiland-Jørgensen <toke@toke.dk>
+Date: Mon Jan 30 23:36:39 2023 +0100
+
+ Babel: Fix missing modulo comparison of seqnos
+
+ Juliusz noticed there were a couple of places we were doing straight
+ inequality comparisons of seqnos in Babel. This is wrong because seqnos can
+ wrap: so we need to use the modulo-64k comparison function for these cases
+ as well.
+
+ Introduce a strict-inequality version of the modulo-comparison for this
+ purpose.
+
+commit 72230d3ca37d34cafa0442c23f83121ae1fc41be
+Author: Alexander Zubkov <green@qrator.net>
+Date: Mon Jan 23 14:23:00 2023 +0100
+
+ Small fix of indenting
+
commit 5437104afad90f2a74ce52946f06da607d021e2b
Author: Ondrej Zajicek <santiago@crfreenet.org>
Date: Mon Jan 23 02:23:19 2023 +0100
diff --git a/Makefile.in b/Makefile.in
index 95ab1489..839efe24 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -161,10 +161,11 @@ $(client) $(daemon):
$(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
$(objdir)/sysdep/paths.h: Makefile
- echo >$@ "/* Generated by Makefile, don't edit manually! */"
- echo >>$@ "#define PATH_CONFIG_FILE \"@CONFIG_FILE@\""
- echo >>$@ "#define PATH_CONTROL_SOCKET \"@CONTROL_SOCKET@\""
- if test -n "@iproutedir@" ; then echo >>$@ "#define PATH_IPROUTE_DIR \"@iproutedir@\"" ; fi
+ $(E)echo GEN $@
+ $(Q)echo >$@ "/* Generated by Makefile, don't edit manually! */"
+ $(Q)echo >>$@ "#define PATH_CONFIG_FILE \"@CONFIG_FILE@\""
+ $(Q)echo >>$@ "#define PATH_CONTROL_SOCKET \"@CONTROL_SOCKET@\""
+ $(Q)if test -n "@iproutedir@" ; then echo >>$@ "#define PATH_IPROUTE_DIR \"@iproutedir@\"" ; fi
# Unit tests rules
diff --git a/NEWS b/NEWS
index 575df30d..d05bfe68 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,59 @@
+Version 2.14 (2023-10-06)
+ o MPLS subsystem
+ o L3VPN: BGP/MPLS VPNs (RFC 4364)
+ o BGP: Access to unknown route attributes
+ o RAdv: Custom options
+ o Babel: RTT metric extension
+ o BMP: Refactored route monitoring
+ o BMP: Multiple instances of BMP protocol
+ o BMP: Both pre-policy and post-policy monitoring
+ o Experimental route aggregation
+ o Filter: Method framework
+ o Filter: Functions have return type statements
+ o Filter: New bytestring data type
+ o Kernel: Option to learn kernel routes
+ o Many bugfixes and improvements
+
+ Notes:
+
+ User-defined filter functions that return values now should have return type
+ statements. We still accept functions without such statement, if they could be
+ properly typed.
+
+ For loops allowed to use both existing iterator variables or ones defined in
+ the for statement. We no longer support the first case, all iterator variables
+ must be defined in the for statement (e.g. 'for int i in bgp_path ...').
+
+ Due to oversight, VRF interfaces were not included in respective VRFs, this is
+ fixed now.
+
+
+Version 2.13.1 (2023-06-23)
+ o BGP: Fix role check when no capability option is present
+ o Filter: Fixed segfault when a case option had an empty block
+
+ This is a bugfix version.
+
+Version 2.13 (2023-04-21)
+ o Babel: IPv4 via IPv6 extension (RFC 9229)
+ o Babel: Improve authentication on lossy networks
+ o BGP: New 'allow bgp_med' option
+ o BSD: Support for IPv4 routes with IPv6 nexthop on FreeBSD
+ o Experimental BMP protocol implementation
+ o Important bugfixes
+
+ Notes:
+
+ We changed versioning scheme from <epoch>.<major>.<minor> to more common
+ <major>.<minor>.<patch> . From now on, you may expect that BIRD 2.13.x will be
+ strictly only fixing bugs found in 2.13, whereas BIRD 2.14 will also contain
+ new features.
+
+ This BIRD version contains an alpha release of BMP protocol implementation.
+ It is not ready for production usage and therefore it is not compiled by
+ default and have to be enabled during installation.
+
+
Version 2.0.12 (2023-01-23)
o Filter: New 'onlink' route attribute
o Compile-time option to use 4-way tries instead of 16-way ones
diff --git a/bird-gdb.py b/bird-gdb.py
index 3cf65a9c..262035dc 100644
--- a/bird-gdb.py
+++ b/bird-gdb.py
@@ -34,6 +34,7 @@ class BIRDFValPrinter(BIRDPrinter):
"T_IP": "ip",
"T_NET": "net",
"T_STRING": "s",
+ "T_BYTESTRING": "bs",
"T_PATH_MASK": "path_mask",
"T_PATH": "ad",
"T_CLIST": "ad",
diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index ceedee8a..2f95f2e1 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -51,12 +51,6 @@
#include "lib/string.h"
#include "lib/hash.h"
-struct keyword {
- byte *name;
- int value;
- struct keyword *next;
-};
-
#include "conf/keywords.h"
/* Could be defined by Bison in cf-parse.tab.h, inteferes with SYM hash */
@@ -67,28 +61,21 @@ struct keyword {
static uint cf_hash(const byte *c);
-#define KW_KEY(n) n->name
-#define KW_NEXT(n) n->next
-#define KW_EQ(a,b) !strcmp(a,b)
-#define KW_FN(k) cf_hash(k)
-#define KW_ORDER 8 /* Fixed */
-
-#define SYM_KEY(n) n->name, n->scope->active
+#define SYM_KEY(n) n->name
#define SYM_NEXT(n) n->next
-#define SYM_EQ(a,s1,b,s2) !strcmp(a,b) && s1 == s2
-#define SYM_FN(k,s) cf_hash(k)
-#define SYM_ORDER 6 /* Initial */
+#define SYM_EQ(a,b) !strcmp(a,b)
+#define SYM_FN(k) cf_hash(k)
+#define SYM_ORDER 4 /* Initial */
#define SYM_REHASH sym_rehash
-#define SYM_PARAMS /8, *1, 2, 2, 6, 20
+#define SYM_PARAMS /8, *1, 2, 2, 4, 20
HASH_DEFINE_REHASH_FN(SYM, struct symbol)
-HASH(struct keyword) kw_hash;
-
-
-struct sym_scope *conf_this_scope;
+struct sym_scope *global_root_scope;
+pool *global_root_scope_pool;
+linpool *global_root_scope_linpool;
linpool *cfg_mem;
@@ -255,35 +242,24 @@ WHITE [ \t]
return IP4;
}
-{XIGIT}{2}((:{XIGIT}{2}){15,}|({XIGIT}{2}){15,}) {
+({XIGIT}{2}){16,}|{XIGIT}{2}(:{XIGIT}{2}){15,}|hex:({XIGIT}{2}*|{XIGIT}{2}(:{XIGIT}{2})*) {
char *s = yytext;
- size_t len = 0, i;
- struct bytestring *bytes;
- byte *b;
-
- while (*s) {
- len++;
- s += 2;
- if (*s == ':')
- s++;
- }
- bytes = cfg_allocz(sizeof(*bytes) + len);
+ struct adata *bs;
- bytes->length = len;
- b = &bytes->data[0];
- s = yytext;
- errno = 0;
- for (i = 0; i < len; i++) {
- *b = bstrtobyte16(s);
- if (errno == ERANGE)
- cf_error("Invalid hex string");
- b++;
- s += 2;
- if (*s == ':')
- s++;
- }
- cf_lval.bs = bytes;
- return BYTESTRING;
+ /* Skip 'hex:' prefix */
+ if (s[0] == 'h' && s[1] == 'e' && s[2] == 'x' && s[3] == ':')
+ s += 4;
+
+ int len = bstrhextobin(s, NULL);
+ if (len < 0)
+ cf_error("Invalid hex string");
+
+ bs = cfg_allocz(sizeof(struct adata) + len);
+ bs->length = bstrhextobin(s, bs->data);
+ ASSERT(bs->length == len);
+
+ cf_lval.bs = bs;
+ return BYTETEXT;
}
({XIGIT}*::|({XIGIT}*:){3,})({XIGIT}*|{DIGIT}+\.{DIGIT}+\.{DIGIT}+\.{DIGIT}+) {
@@ -399,6 +375,7 @@ else: {
\>\= return GEQ;
\&\& return AND;
\|\| return OR;
+\-\> return IMP;
\[\= return PO;
\=\] return PC;
@@ -574,58 +551,57 @@ check_eof(void)
return 0;
}
-static inline void cf_swap_soft_scope(void);
+static inline void cf_swap_soft_scope(struct config *conf);
-static struct symbol *
-cf_new_symbol(const byte *c)
+struct symbol *
+cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c)
{
+ if (scope->readonly)
+ cf_error("Unknown symbol %s", c);
+
struct symbol *s;
uint l = strlen(c);
if (l > SYM_MAX_LEN)
cf_error("Symbol too long");
- cf_swap_soft_scope();
-
- s = cfg_allocz(sizeof(struct symbol) + l + 1);
- *s = (struct symbol) { .scope = conf_this_scope, .class = SYM_VOID, };
+ s = lp_alloc(lp, sizeof(struct symbol) + l + 1);
+ *s = (struct symbol) { .scope = scope, .class = SYM_VOID, };
strcpy(s->name, c);
- if (!new_config->sym_hash.data)
- HASH_INIT(new_config->sym_hash, new_config->pool, SYM_ORDER);
+ if (!scope->hash.data)
+ HASH_INIT(scope->hash, p, SYM_ORDER);
- HASH_INSERT2(new_config->sym_hash, SYM, new_config->pool, s);
+ HASH_INSERT2(scope->hash, SYM, p, s);
- add_tail(&(new_config->symbols), &(s->n));
+ if (new_config && (scope == new_config->root_scope))
+ add_tail(&(new_config->symbols), &(s->n));
return s;
}
/**
- * cf_find_symbol - find a symbol by name
- * @cfg: specificed config
+ * cf_find_symbol_scope - find a symbol by name
+ * @scope: config scope
* @c: symbol name
*
- * This functions searches the symbol table in the config @cfg for a symbol of
- * given name. First it examines the current scope, then the second recent one
+ * This functions searches the symbol table in the scope @scope for a symbol of
+ * given name. First it examines the current scope, then the underlying one
* and so on until it either finds the symbol and returns a pointer to its
* &symbol structure or reaches the end of the scope chain and returns %NULL to
* signify no match.
*/
struct symbol *
-cf_find_symbol(const struct config *cfg, const byte *c)
+cf_find_symbol_scope(const struct sym_scope *scope, const byte *c)
{
struct symbol *s;
- if (cfg->sym_hash.data &&
- (s = HASH_FIND(cfg->sym_hash, SYM, c, 1)))
- return s;
-
- /* In CLI command parsing, fallback points to the current config, otherwise it is NULL. */
- if (cfg->fallback &&
- cfg->fallback->sym_hash.data &&
- (s = HASH_FIND(cfg->fallback->sym_hash, SYM, c, 1)))
- return s;
+ /* Find the symbol here or anywhere below */
+ while (scope)
+ if (scope->hash.data && (s = HASH_FIND(scope->hash, SYM, c)))
+ return s;
+ else
+ scope = scope->next;
return NULL;
}
@@ -640,9 +616,12 @@ cf_find_symbol(const struct config *cfg, const byte *c)
* existing symbol is found.
*/
struct symbol *
-cf_get_symbol(const byte *c)
+cf_get_symbol(struct config *conf, const byte *c)
{
- return cf_find_symbol(new_config, c) ?: cf_new_symbol(c);
+ return cf_find_symbol_scope(conf->current_scope, c) ?: (
+ cf_swap_soft_scope(conf),
+ cf_new_symbol(conf->current_scope, conf->pool, conf->mem, c)
+ );
}
/**
@@ -653,22 +632,23 @@ cf_get_symbol(const byte *c)
* for purposes of cf_define_symbol().
*/
struct symbol *
-cf_localize_symbol(struct symbol *sym)
+cf_localize_symbol(struct config *conf, struct symbol *sym)
{
/* If the symbol type is void, it has been recently allocated just in this scope. */
if (!sym->class)
return sym;
/* If the scope is the current, it is already defined in this scope. */
- if (cf_symbol_is_local(sym))
+ if (cf_symbol_is_local(conf, sym))
cf_error("Symbol '%s' already defined", sym->name);
/* Not allocated here yet, doing it now. */
- return cf_new_symbol(sym->name);
+ cf_swap_soft_scope(conf);
+ return cf_new_symbol(conf->current_scope, conf->pool, conf->mem, sym->name);
}
struct symbol *
-cf_default_name(char *template, int *counter)
+cf_default_name(struct config *conf, char *template, int *counter)
{
char buf[SYM_MAX_LEN];
struct symbol *s;
@@ -677,7 +657,7 @@ cf_default_name(char *template, int *counter)
for(;;)
{
bsprintf(buf, template, ++(*counter));
- s = cf_get_symbol(buf);
+ s = cf_get_symbol(conf, buf);
if (s->class == SYM_VOID)
return s;
if (!perc)
@@ -690,39 +670,28 @@ static enum yytokentype
cf_lex_symbol(const char *data)
{
/* Have we defined such a symbol? */
- struct symbol *sym = cf_get_symbol(data);
+ struct symbol *sym = cf_get_symbol(new_config, data);
cf_lval.s = sym;
- if (sym->class != SYM_VOID)
- return CF_SYM_KNOWN;
-
- /* Is it a keyword? */
- struct keyword *k = HASH_FIND(kw_hash, KW, data);
- if (k)
+ switch (sym->class)
{
- if (k->value > 0)
- return k->value;
- else
+ case SYM_KEYWORD:
{
- cf_lval.i = -k->value;
+ int val = sym->keyword->value;
+ if (val > 0) return val;
+ cf_lval.i = -val;
return ENUM;
}
+ case SYM_METHOD:
+ return (sym->method->arg_num > 1) ? CF_SYM_METHOD_ARGS : CF_SYM_METHOD_BARE;
+ case SYM_VOID:
+ return CF_SYM_UNDEFINED;
+ default:
+ return CF_SYM_KNOWN;
}
-
- /* OK, undefined symbol */
- cf_lval.s = sym;
- return CF_SYM_UNDEFINED;
}
-static void
-cf_lex_init_kh(void)
-{
- HASH_INIT(kw_hash, config_pool, KW_ORDER);
-
- struct keyword *k;
- for (k=keyword_list; k->name; k++)
- HASH_INSERT(kw_hash, KW, k);
-}
+void f_type_methods_register(void);
/**
* cf_lex_init - initialize the lexer
@@ -735,8 +704,23 @@ cf_lex_init_kh(void)
void
cf_lex_init(int is_cli, struct config *c)
{
- if (!kw_hash.data)
- cf_lex_init_kh();
+ if (!global_root_scope_pool)
+ {
+ global_root_scope_pool = rp_new(&root_pool, "Keywords pool");
+ global_root_scope_linpool = lp_new(global_root_scope_pool);
+ global_root_scope = lp_allocz(global_root_scope_linpool, sizeof(*global_root_scope));
+
+ for (const struct keyword *k = keyword_list; k->name; k++)
+ {
+ struct symbol *sym = cf_new_symbol(global_root_scope, global_root_scope_pool, global_root_scope_linpool, k->name);
+ sym->class = SYM_KEYWORD;
+ sym->keyword = k;
+ }
+
+ global_root_scope->readonly = 1;
+
+ f_type_methods_register();
+ }
ifs_head = ifs = push_ifs(NULL);
if (!is_cli)
@@ -754,9 +738,13 @@ cf_lex_init(int is_cli, struct config *c)
else
BEGIN(INITIAL);
- c->root_scope = cfg_allocz(sizeof(struct sym_scope));
- conf_this_scope = c->root_scope;
- conf_this_scope->active = 1;
+ c->root_scope = c->current_scope = cfg_allocz(sizeof(struct sym_scope));
+ c->root_scope->active = 1;
+
+ if (is_cli)
+ c->current_scope->next = config->root_scope;
+ else
+ c->current_scope->next = global_root_scope;
}
/**
@@ -770,12 +758,12 @@ cf_lex_init(int is_cli, struct config *c)
* in all scopes stored on the stack.
*/
void
-cf_push_scope(struct symbol *sym)
+cf_push_scope(struct config *conf, struct symbol *sym)
{
struct sym_scope *s = cfg_allocz(sizeof(struct sym_scope));
- s->next = conf_this_scope;
- conf_this_scope = s;
+ s->next = conf->current_scope;
+ conf->current_scope = s;
s->active = 1;
s->name = sym;
s->slots = 0;
@@ -789,14 +777,14 @@ cf_push_scope(struct symbol *sym)
* invisible to the rest of the config.
*/
void
-cf_pop_scope(void)
+cf_pop_scope(struct config *conf)
{
- ASSERT(!conf_this_scope->soft_scopes);
+ ASSERT(!conf->current_scope->soft_scopes);
- conf_this_scope->active = 0;
- conf_this_scope = conf_this_scope->next;
+ conf->current_scope->active = 0;
+ conf->current_scope = conf->current_scope->next;
- ASSERT(conf_this_scope);
+ ASSERT(conf->current_scope);
}
/**
@@ -807,12 +795,12 @@ cf_pop_scope(void)
* Such scope will be converted to a regular scope on first use.
*/
void
-cf_push_soft_scope(void)
+cf_push_soft_scope(struct config *conf)
{
- if (conf_this_scope->soft_scopes < 0xfe)
- conf_this_scope->soft_scopes++;
+ if (conf->current_scope->soft_scopes < 0xfe)
+ conf->current_scope->soft_scopes++;
else
- cf_push_block_scope();
+ cf_push_block_scope(conf);
}
/**
@@ -821,12 +809,12 @@ cf_push_soft_scope(void)
* Leave a soft scope entered by cf_push_soft_scope().
*/
void
-cf_pop_soft_scope(void)
+cf_pop_soft_scope(struct config *conf)
{
- if (conf_this_scope->soft_scopes)
- conf_this_scope->soft_scopes--;
+ if (conf->current_scope->soft_scopes)
+ conf->current_scope->soft_scopes--;
else
- cf_pop_block_scope();
+ cf_pop_block_scope(conf);
}
/**
@@ -836,12 +824,12 @@ cf_pop_soft_scope(void)
* on first use. It is done automatically by cf_new_symbol().
*/
static inline void
-cf_swap_soft_scope(void)
+cf_swap_soft_scope(struct config *conf)
{
- if (conf_this_scope->soft_scopes)
+ if (conf->current_scope->soft_scopes)
{
- conf_this_scope->soft_scopes--;
- cf_push_block_scope();
+ conf->current_scope->soft_scopes--;
+ cf_push_block_scope(conf);
}
}
@@ -871,6 +859,10 @@ cf_symbol_class_name(struct symbol *sym)
return "routing table";
case SYM_ATTRIBUTE:
return "custom attribute";
+ case SYM_MPLS_DOMAIN:
+ return "MPLS domain";
+ case SYM_MPLS_RANGE:
+ return "MPLS label range";
case SYM_CONSTANT_RANGE:
return "constant";
case SYM_VARIABLE_RANGE:
diff --git a/conf/conf.c b/conf/conf.c
index 4e31de29..d98d421c 100644
--- a/conf/conf.c
+++ b/conf/conf.c
@@ -49,6 +49,7 @@
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
+#include "nest/mpls.h"
#include "lib/resource.h"
#include "lib/string.h"
#include "lib/event.h"
@@ -139,6 +140,7 @@ config_parse(struct config *c)
cf_lex_init(0, c);
sysdep_preconfig(c);
protos_preconfig(c);
+ mpls_preconfig(c);
rt_preconfig(c);
cf_parse();
rt_postconfig(c);
@@ -170,7 +172,6 @@ int
cli_parse(struct config *c)
{
int done = 0;
- c->fallback = config;
new_config = c;
cfg_mem = c->mem;
if (setjmp(conf_jmpbuf))
@@ -181,7 +182,6 @@ cli_parse(struct config *c)
done = 1;
cleanup:
- c->fallback = NULL;
new_config = NULL;
cfg_mem = NULL;
return done;
@@ -197,8 +197,12 @@ cleanup:
void
config_free(struct config *c)
{
- if (c)
- rfree(c->pool);
+ if (!c)
+ return;
+
+ ASSERT(!c->obstacle_count);
+
+ rfree(c->pool);
}
/**
@@ -207,10 +211,14 @@ config_free(struct config *c)
* This function frees the old configuration (%old_config) that is saved for the
* purpose of undo. It is useful before parsing a new config when reconfig is
* requested, to avoid keeping three (perhaps memory-heavy) configs together.
+ * Configuration is not freed when it is still active during reconfiguration.
*/
void
config_free_old(void)
{
+ if (!old_config || old_config->obstacle_count)
+ return;
+
tm_stop(config_timer);
undo_available = 0;
@@ -293,6 +301,7 @@ config_do_commit(struct config *c, int type)
int force_restart = sysdep_commit(c, old_config);
DBG("global_commit\n");
force_restart |= global_commit(c, old_config);
+ mpls_commit(c, old_config);
DBG("rt_commit\n");
rt_commit(c, old_config);
DBG("protos_commit\n");
@@ -541,9 +550,9 @@ order_shutdown(int gr)
memcpy(c, config, sizeof(struct config));
init_list(&c->protos);
init_list(&c->tables);
+ init_list(&c->mpls_domains);
init_list(&c->symbols);
memset(c->def_tables, 0, sizeof(c->def_tables));
- HASH_INIT(c->sym_hash, c->pool, 4);
c->shutdown = 1;
c->gr_down = gr;
diff --git a/conf/conf.h b/conf/conf.h
index b409750e..8fd6713e 100644
--- a/conf/conf.h
+++ b/conf/conf.h
@@ -16,12 +16,12 @@
#include "lib/timer.h"
/* Configuration structure */
-
struct config {
pool *pool; /* Pool the configuration is stored in */
linpool *mem; /* Linear pool containing configuration data */
list protos; /* Configured protocol instances (struct proto_config) */
list tables; /* Configured routing tables (struct rtable_config) */
+ list mpls_domains; /* Configured MPLS domains (struct mpls_domain_config) */
list logfiles; /* Configured log files (sysdep) */
list tests; /* Configured unit tests (f_bt_test_suite) */
list symbols; /* Configured symbols in config order */
@@ -53,9 +53,9 @@ struct config {
char *err_file_name; /* File name containing error */
char *file_name; /* Name of main configuration file */
int file_fd; /* File descriptor of main configuration file */
- HASH(struct symbol) sym_hash; /* Lexer: symbol hash table */
- struct config *fallback; /* Link to regular config for CLI parsing */
+
struct sym_scope *root_scope; /* Scope for root symbols */
+ struct sym_scope *current_scope; /* Current scope where we are actually in while parsing */
int obstacle_count; /* Number of items blocking freeing of this config */
int shutdown; /* This is a pseudo-config for daemon shutdown */
int gr_down; /* This is a pseudo-config for graceful restart */
@@ -78,6 +78,7 @@ int config_status(void);
btime config_timer_status(void);
void config_init(void);
void cf_error(const char *msg, ...) NORET;
+#define cf_warn(msg, args...) log(L_WARN "%s:%d:%d: " msg, ifs->file_name, ifs->lino, ifs->chno - ifs->toklen + 1, ##args)
void config_add_obstacle(struct config *);
void config_del_obstacle(struct config *);
void order_shutdown(int gr);
@@ -110,6 +111,11 @@ void cfg_copy_list(list *dest, list *src, unsigned node_size);
extern int (*cf_read_hook)(byte *buf, uint max, int fd);
+struct keyword {
+ byte *name;
+ int value;
+};
+
struct symbol {
node n; /* In list of symbols in config */
struct symbol *next;
@@ -123,8 +129,12 @@ struct symbol {
const struct filter *filter; /* For SYM_FILTER */
struct rtable_config *table; /* For SYM_TABLE */
struct f_dynamic_attr *attribute; /* For SYM_ATTRIBUTE */
+ struct mpls_domain_config *mpls_domain; /* For SYM_MPLS_DOMAIN */
+ struct mpls_range_config *mpls_range; /* For SYM_MPLS_RANGE */
struct f_val *val; /* For SYM_CONSTANT */
uint offset; /* For SYM_VARIABLE */
+ const struct keyword *keyword; /* For SYM_KEYWORD */
+ const struct f_method *method; /* For SYM_METHOD */
};
char name[0];
@@ -133,16 +143,20 @@ struct symbol {
struct sym_scope {
struct sym_scope *next; /* Next on scope stack */
struct symbol *name; /* Name of this scope */
+
+ HASH(struct symbol) hash; /* Local symbol hash */
+
uint slots; /* Variable slots */
- byte active; /* Currently entered */
- byte block; /* No independent stack frame */
byte soft_scopes; /* Number of soft scopes above */
+ byte active:1; /* Currently entered */
+ byte block:1; /* No independent stack frame */
+ byte readonly:1; /* Do not add new symbols */
};
-struct bytestring {
- size_t length;
- byte data[];
-};
+extern struct sym_scope *global_root_scope;
+extern pool *global_root_scope_pool;
+extern linpool *global_root_scope_linpool;
+
#define SYM_MAX_LEN 64
@@ -154,6 +168,10 @@ struct bytestring {
#define SYM_FILTER 4
#define SYM_TABLE 5
#define SYM_ATTRIBUTE 6
+#define SYM_KEYWORD 7
+#define SYM_METHOD 8
+#define SYM_MPLS_DOMAIN 9
+#define SYM_MPLS_RANGE 10
#define SYM_VARIABLE 0x100 /* 0x100-0x1ff are variable types */
#define SYM_VARIABLE_RANGE SYM_VARIABLE ... (SYM_VARIABLE | 0xff)
@@ -181,20 +199,28 @@ struct include_file_stack {
extern struct include_file_stack *ifs;
-extern struct sym_scope *conf_this_scope;
-
int cf_lex(void);
void cf_lex_init(int is_cli, struct config *c);
void cf_lex_unwind(void);
-struct symbol *cf_find_symbol(const struct config *cfg, const byte *c);
+struct symbol *cf_find_symbol_scope(const struct sym_scope *scope, const byte *c);
+static inline struct symbol *cf_find_symbol_cfg(const struct config *cfg, const byte *c)
+{ return cf_find_symbol_scope(cfg->root_scope, c); }
-struct symbol *cf_get_symbol(const byte *c);
-struct symbol *cf_default_name(char *template, int *counter);
-struct symbol *cf_localize_symbol(struct symbol *sym);
+#define cf_find_symbol(where, what) _Generic(*(where), \
+ struct config: cf_find_symbol_cfg, \
+ struct sym_scope: cf_find_symbol_scope \
+ )((where), (what))
-static inline int cf_symbol_is_local(struct symbol *sym)
-{ return (sym->scope == conf_this_scope) && !conf_this_scope->soft_scopes; }
+struct symbol *cf_get_symbol(struct config *conf, const byte *c);
+struct symbol *cf_default_name(struct config *conf, char *template, int *counter);
+struct symbol *cf_localize_symbol(struct config *conf, struct symbol *sym);
+
+static inline int cf_symbol_is_local(struct config *conf, struct symbol *sym)
+{ return (sym->scope == conf->current_scope) && !conf->current_scope->soft_scopes; }
+
+/* internal */
+struct symbol *cf_new_symbol(struct sym_scope *scope, pool *p, struct linpool *lp, const byte *c);
/**
* cf_define_symbol - define meaning of a symbol
@@ -211,22 +237,25 @@ static inline int cf_symbol_is_local(struct symbol *sym)
* Result: Pointer to the newly defined symbol. If we are in the top-level
* scope, it's the same @sym as passed to the function.
*/
-#define cf_define_symbol(osym_, type_, var_, def_) ({ \
- struct symbol *sym_ = cf_localize_symbol(osym_); \
+#define cf_define_symbol(conf_, osym_, type_, var_, def_) ({ \
+ struct symbol *sym_ = cf_localize_symbol(conf_, osym_); \
sym_->class = type_; \
sym_->var_ = def_; \
sym_; })
-void cf_push_scope(struct symbol *);
-void cf_pop_scope(void);
-void cf_push_soft_scope(void);
-void cf_pop_soft_scope(void);
+#define cf_create_symbol(conf_, name_, type_, var_, def_) \
+ cf_define_symbol(conf_, cf_get_symbol(conf_, name_), type_, var_, def_)
+
+void cf_push_scope(struct config *, struct symbol *);
+void cf_pop_scope(struct config *);
+void cf_push_soft_scope(struct config *);
+void cf_pop_soft_scope(struct config *);
-static inline void cf_push_block_scope(void)
-{ cf_push_scope(NULL); conf_this_scope->block = 1; }
+static inline void cf_push_block_scope(struct config *conf)
+{ cf_push_scope(conf, NULL); conf->current_scope->block = 1; }
-static inline void cf_pop_block_scope(void)
-{ ASSERT(conf_this_scope->block); cf_pop_scope(); }
+static inline void cf_pop_block_scope(struct config *conf)
+{ ASSERT(conf->current_scope->block); cf_pop_scope(conf); }
char *cf_symbol_class_name(struct symbol *sym);
diff --git a/conf/confbase.Y b/conf/confbase.Y
index 1d5738ff..2c37bd4d 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -43,6 +43,8 @@ static inline void cf_assert_symbol(const struct symbol *sym, uint class) {
case SYM_FILTER: cf_assert(sym->class == SYM_FILTER, "Filter name required"); break;
case SYM_TABLE: cf_assert(sym->class == SYM_TABLE, "Table name required"); break;
case SYM_ATTRIBUTE: cf_assert(sym->class == SYM_ATTRIBUTE, "Custom attribute name required"); break;
+ case SYM_MPLS_DOMAIN: cf_assert(sym->class == SYM_MPLS_DOMAIN, "MPLS domain name required"); break;
+ case SYM_MPLS_RANGE: cf_assert(sym->class == SYM_MPLS_RANGE, "MPLS range name required"); break;
case SYM_VARIABLE: cf_assert((sym->class & ~0xff) == SYM_VARIABLE, "Variable name required"); break;
case SYM_CONSTANT: cf_assert((sym->class & ~0xff) == SYM_CONSTANT, "Constant name required"); break;
default: bug("This shall not happen");
@@ -61,6 +63,7 @@ CF_DECLS
net_addr net;
net_addr *net_ptr;
struct symbol *s;
+ struct keyword *kw;
const char *t;
struct rtable_config *r;
struct channel_config *cc;
@@ -85,6 +88,7 @@ CF_DECLS
struct sym_show_data *sd;
struct lsadb_show_data *ld;
struct mrt_dump_data *md;
+ struct mpls_show_ranges_cmd *msrc;
struct iface *iface;
void *g;
btime time;
@@ -93,19 +97,20 @@ CF_DECLS
struct channel_limit cl;
struct timeformat *tf;
mpls_label_stack *mls;
- struct bytestring *bs;
+ const struct adata *bs;
+ struct aggr_item_node *ai;
}
%token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT
-%token GEQ LEQ NEQ AND OR
+%token GEQ LEQ NEQ AND OR IMP
%token PO PC
%token <i> NUM ENUM
%token <ip4> IP4
%token <ip6> IP6
%token <i64> VPN_RD
-%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED
+%token <s> CF_SYM_KNOWN CF_SYM_UNDEFINED CF_SYM_METHOD_BARE CF_SYM_METHOD_ARGS
%token <t> TEXT
-%token <bs> BYTESTRING
+%token <bs> BYTETEXT
%type <iface> ipa_scope
%type <i> expr bool pxlen4
@@ -116,11 +121,15 @@ CF_DECLS
%type <mls> label_stack_start label_stack
%type <t> text opttext
-%type <s> symbol
+%type <bs> bytestring
+%type <s> symbol symbol_known
+
+%type <v> bytestring_text text_or_ipa
+%type <x> bytestring_expr
%nonassoc PREFIX_DUMMY
%left AND OR
-%nonassoc '=' '<' '>' '~' GEQ LEQ NEQ NMA PO PC
+%nonassoc '=' '<' '>' '~' GEQ LEQ NEQ NMA IMP PO PC
%left '+' '-'
%left '*' '/' '%'
%left '!'
@@ -128,7 +137,7 @@ CF_DECLS
%start config
-CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM)
+CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM, MAX, AS)
CF_GRAMMAR
@@ -153,15 +162,15 @@ conf: definition ;
definition:
DEFINE symbol '=' term ';' {
struct f_val *val = cfg_allocz(sizeof(struct f_val));
- if (f_eval(f_linearize($4, 1), cfg_mem, val) > F_RETURN) cf_error("Runtime error");
- cf_define_symbol($2, SYM_CONSTANT | val->type, val, val);
+ *val = cf_eval($4, T_VOID);
+ cf_define_symbol(new_config, $2, SYM_CONSTANT | val->type, val, val);
}
;
expr:
NUM
- | '(' term ')' { $$ = f_eval_int(f_linearize($2, 1)); }
- | CF_SYM_KNOWN {
+ | '(' term ')' { $$ = cf_eval_int($2); }
+ | symbol_known {
if ($1->class != (SYM_CONSTANT | T_INT)) cf_error("Number constant expected");
$$ = SYM_VAL($1).i; }
;
@@ -172,7 +181,8 @@ expr_us:
| expr US { $$ = $1 US_; }
;
-symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN ;
+symbol: CF_SYM_UNDEFINED | CF_SYM_KNOWN | KEYWORD ;
+symbol_known: CF_SYM_KNOWN ;
/* Switches */
@@ -393,6 +403,45 @@ opttext:
| /* empty */ { $$ = NULL; }
;
+text_or_ipa:
+ TEXT { $$.type = T_STRING; $$.val.s = $1; }
+ | IP4 { $$.type = T_IP; $$.val.ip = ipa_from_ip4($1); }
+ | IP6 { $$.type = T_IP; $$.val.ip = ipa_from_ip6($1); }
+ | CF_SYM_KNOWN {
+ if (($1->class == (SYM_CONSTANT | T_STRING)) ||
+ ($1->class == (SYM_CONSTANT | T_IP)))
+ $$ = *($1->val);
+ else
+ cf_error("String or IP constant expected");
+ }
+ | '(' term ')' {
+ $$ = cf_eval($2, T_VOID);
+ if (($$.type != T_BYTESTRING) && ($$.type != T_STRING))
+ cf_error("Bytestring or string value expected");
+ }
+ ;
+
+bytestring:
+ BYTETEXT
+ | bytestring_expr { $$ = cf_eval($1, T_BYTESTRING).val.bs; }
+ ;
+
+bytestring_text:
+ BYTETEXT { $$.type = T_BYTESTRING; $$.val.bs = $1; }
+ | TEXT { $$.type = T_STRING; $$.val.s = $1; }
+ | bytestring_expr {
+ $$ = cf_eval($1, T_VOID);
+ if (($$.type != T_BYTESTRING) && ($$.type != T_STRING))
+ cf_error("Bytestring or string value expected");
+ }
+ ;
+
+bytestring_expr:
+ symbol_value
+ | term_bs
+ | '(' term ')' { $$ = $2; }
+ ;
+
CF_CODE
diff --git a/conf/gen_keywords.m4 b/conf/gen_keywords.m4
index 0c1dc545..4e8651f6 100644
--- a/conf/gen_keywords.m4
+++ b/conf/gen_keywords.m4
@@ -23,11 +23,11 @@ m4_define(CF_DECLS, `m4_divert(-1)')
m4_define(CF_DEFINES, `m4_divert(-1)')
# Keywords are translated to C initializers
-m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1, NULL },
+m4_define(CF_handle_kw, `m4_divert(1){ "m4_translit($1,[[A-Z]],[[a-z]])", $1 },
m4_divert(-1)')
m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_handle_kw($1)]])')
-m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token[[]]CF_toks
-)DNL')
+m4_define(CF_KEYWORDS, `CF_iterate([[CF_keywd]], [[$@]])DNL')
+m4_define(CF_KEYWORDS_EXCLUSIVE, `CF_KEYWORDS($@)')
# CLI commands generate keywords as well
m4_define(CF_CLI, `CF_KEYWORDS(m4_translit($1, [[ ]], [[,]]))
@@ -35,7 +35,7 @@ m4_define(CF_CLI, `CF_KEYWORDS(m4_translit($1, [[ ]], [[,]]))
# Enums are translated to C initializers: use CF_ENUM(typename, prefix, values)
# For different prefix: CF_ENUM_PX(typename, external prefix, C prefix, values)
-m4_define(CF_enum, `m4_divert(1){ "CF_enum_prefix_ext[[]]$1", -((CF_enum_type<<16) | CF_enum_prefix_int[[]]$1), NULL },
+m4_define(CF_enum, `m4_divert(1){ "CF_enum_prefix_ext[[]]$1", -((CF_enum_type<<16) | CF_enum_prefix_int[[]]$1) },
m4_divert(-1)')
m4_define(CF_ENUM, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$2)CF_iterate([[CF_enum]], [[m4_shift(m4_shift($@))]])DNL')
m4_define(CF_ENUM_PX, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$3)CF_iterate([[CF_enum]], [[m4_shift(m4_shift(m4_shift($@)))]])DNL')
@@ -43,8 +43,8 @@ m4_define(CF_ENUM_PX, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_
# After all configuration templates end, we generate the
m4_m4wrap(`
m4_divert(0)
-static struct keyword keyword_list[] = {
-m4_undivert(1){ NULL, -1, NULL } };
+static const struct keyword keyword_list[] = {
+m4_undivert(1){ NULL, -1 } };
')
# As we are processing C source, we must access all M4 primitives via
diff --git a/conf/gen_parser.m4 b/conf/gen_parser.m4
index af4b1455..80071aef 100644
--- a/conf/gen_parser.m4
+++ b/conf/gen_parser.m4
@@ -29,10 +29,18 @@ m4_define(CF_END, `m4_divert(-1)')
m4_define(CF_itera, `m4_ifelse($#, 1, [[CF_iter($1)]], [[CF_iter($1)[[]]CF_itera(m4_shift($@))]])')
m4_define(CF_iterate, `m4_define([[CF_iter]], m4_defn([[$1]]))CF_itera($2)')
-# Keywords act as untyped %token
-m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])')
+m4_define(CF_append, `m4_define([[$1]], m4_ifdef([[$1]], m4_defn([[$1]])[[$3]])[[$2]])')
+
+# Keywords act as %token<s>
+m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)CF_append([[CF_kw_rule]],$1,[[ | ]])m4_define([[CF_toks]],CF_toks $1)]])')
m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
)DNL')
+m4_define(CF_METHODS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
+)DNL')
+
+m4_define(CF_keywd2, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])')
+m4_define(CF_KEYWORDS_EXCLUSIVE, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd2]], [[$@]])m4_ifelse(CF_toks,,,%token<s>[[]]CF_toks
+)DNL')
# CLI commands
m4_define(CF_CLI, `m4_define([[CF_cmd]], cmd_[[]]m4_translit($1, [[ ]], _))DNL
@@ -55,7 +63,11 @@ m4_undivert(1)DNL
m4_undivert(2)DNL
+%type <s> KEYWORD
+
%%
+KEYWORD: CF_kw_rule;
+
m4_undivert(3)DNL
%%
diff --git a/configure b/configure
index 6394d18b..52ac40b1 100755
--- a/configure
+++ b/configure
@@ -5621,7 +5621,7 @@ printf "%s\n" "#define HAVE_MPLS_KERNEL 1" >>confdefs.h
fi
fi
-all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
+all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@@ -5640,6 +5640,9 @@ fi
+
+
+
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking protocols" >&5
printf %s "checking protocols... " >&6; }
protocols=`echo "$with_protocols" | sed 's/,/ /g'`
diff --git a/configure.ac b/configure.ac
index d73eec28..b2f18c90 100644
--- a/configure.ac
+++ b/configure.ac
@@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then
fi
fi
-all_protocols="$proto_bfd babel bgp mrt ospf perf pipe radv rip rpki static"
+all_protocols="aggregator $proto_bfd babel bgp l3vpn mrt ospf perf pipe radv rip rpki static"
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
@@ -320,9 +320,12 @@ if test "$with_protocols" = all ; then
with_protocols="$all_protocols"
fi
+AH_TEMPLATE([CONFIG_AGGREGATOR],[Aggregator protocol])
AH_TEMPLATE([CONFIG_BABEL], [Babel protocol])
AH_TEMPLATE([CONFIG_BFD], [BFD protocol])
AH_TEMPLATE([CONFIG_BGP], [BGP protocol])
+AH_TEMPLATE([CONFIG_BMP], [BMP protocol])
+AH_TEMPLATE([CONFIG_L3VPN], [L3VPN protocol])
AH_TEMPLATE([CONFIG_MRT], [MRT protocol])
AH_TEMPLATE([CONFIG_OSPF], [OSPF protocol])
AH_TEMPLATE([CONFIG_PIPE], [Pipe protocol])
diff --git a/distro/config/apkg.toml b/distro/config/apkg.toml
index c70306d6..73a79a3b 100644
--- a/distro/config/apkg.toml
+++ b/distro/config/apkg.toml
@@ -6,3 +6,7 @@ make_archive_script = "tools/make-dev-archive"
[upstream]
# needed for get-archive
archive_url = "https://bird.network.cz/download/bird-{{ version }}.tar.gz"
+
+[apkg]
+# apkg compat level
+compat = 3
diff --git a/distro/pkg/deb/bird.xml b/distro/pkg/deb/bird.xml
index 4ba3868f..2cc69575 100644
--- a/distro/pkg/deb/bird.xml
+++ b/distro/pkg/deb/bird.xml
@@ -65,11 +65,23 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<email>&dhemail;</email>
</address>
</author>
+ <author>
+ <firstname>Jakub</firstname>
+ <surname>Ružička</surname>
+ <contrib>Updated this manpage for birdcl.</contrib>
+ <address>
+ <email>jakub.ruzicka@nic.cz</email>
+ </address>
+ </author>
</authorgroup>
<copyright>
<year>2010</year>
<holder>&dhusername;</holder>
</copyright>
+ <copyright>
+ <year>2022</year>
+ <holder>Jakub Ružička</holder>
+ </copyright>
<legalnotice>
<para>This manual page was written for the Debian system
(and may be used by others).</para>
@@ -95,6 +107,10 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<refname>birdc</refname>
<refpurpose>BIRD Internet Routing Daemon remote control</refpurpose>
</refnamediv>
+ <refnamediv>
+ <refname>birdcl</refname>
+ <refpurpose>BIRD Internet Routing Daemon remote control light</refpurpose>
+ </refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
@@ -118,6 +134,13 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<arg choice="opt"><option>-s <replaceable>control-socket</replaceable></option></arg>
<arg choice="opt"><option>-v</option></arg>
</cmdsynopsis>
+ <cmdsynopsis>
+ <command>birdcl</command>
+ <arg choice="opt"><option>-l</option></arg>
+ <arg choice="opt"><option>-r</option></arg>
+ <arg choice="opt"><option>-s <replaceable>control-socket</replaceable></option></arg>
+ <arg choice="opt"><option>-v</option></arg>
+ </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
@@ -136,7 +159,10 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
communicate. Once started, <command>bird</command> will give access
to an interactive shell: commands can be completed with TAB and help
can be requested by pressing the key `?'. More documentation on
- the available commands can be foung on the website, see below.</para>
+ the available commands can be found on the website, see below.</para>
+ <para><command>birdcl</command> is a light version of <command>birdc</command>
+ remote control for <command>bird</command> without readline/ncurses support.
+ TAB completion isn't available.</para>
</refsect1>
<refsect1 id="options">
@@ -230,7 +256,7 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
</varlistentry>
</variablelist>
- <para>The <command>birdc</command> accepts these options:</para>
+ <para><command>birdc</command> and <command>birdcl</command> accept these options:</para>
<variablelist>
<varlistentry>
<term><option>-l</option></term>
@@ -279,8 +305,7 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
<refsect1 id="see_also">
<title>SEE ALSO</title>
- <para>More documentation con be found on the website:
+ <para>More documentation can be found on the website:
https://bird.network.cz/.</para>
</refsect1>
</refentry>
-
diff --git a/distro/pkg/deb/bird2.lintian-overrides b/distro/pkg/deb/bird2.lintian-overrides
deleted file mode 100644
index e64c0ef6..00000000
--- a/distro/pkg/deb/bird2.lintian-overrides
+++ /dev/null
@@ -1 +0,0 @@
-bird2: binary-without-manpage usr/sbin/birdcl
diff --git a/distro/pkg/deb/bird2.manpages b/distro/pkg/deb/bird2.manpages
index 94898ea1..cd80062c 100644
--- a/distro/pkg/deb/bird2.manpages
+++ b/distro/pkg/deb/bird2.manpages
@@ -1,2 +1,3 @@
bird.8
birdc.8
+birdcl.8
diff --git a/distro/pkg/deb/changelog b/distro/pkg/deb/changelog
index d371bc06..cdfe3d95 100644
--- a/distro/pkg/deb/changelog
+++ b/distro/pkg/deb/changelog
@@ -2,4 +2,4 @@ bird2 ({{ version }}-cznic.{{ release }}) unstable; urgency=medium
* upstream package
- -- Jakub Ružička <jakub.ruzicka@nic.cz> Mon, 29 Mar 2021 14:15:50 +0000
+ -- Jakub Ružička <jakub.ruzicka@nic.cz> {{ now }}
diff --git a/distro/pkg/deb/compat b/distro/pkg/deb/compat
index ec635144..b4de3947 100644
--- a/distro/pkg/deb/compat
+++ b/distro/pkg/deb/compat
@@ -1 +1 @@
-9
+11
diff --git a/distro/pkg/deb/control b/distro/pkg/deb/control
index d431022b..638795e2 100644
--- a/distro/pkg/deb/control
+++ b/distro/pkg/deb/control
@@ -5,18 +5,18 @@ Build-Depends: bison,
debhelper,
docbook-xsl,
flex,
- libncurses5-dev,
+ libncurses-dev,
libreadline-dev | libreadline6-dev | libreadline5-dev,
libssh-gcrypt-dev,
- linuxdoc-tools-latex,
m4,
- opensp,
quilt,
- texlive-latex-extra,
xsltproc
+Build-Depends-Indep: linuxdoc-tools-latex,
+ opensp,
+ texlive-latex-extra
Maintainer: Jakub Ružička <jakub.ruzicka@nic.cz>
Uploaders: Ondřej Surý <ondrej@debian.org>
-Standards-Version: 4.3.0
+Standards-Version: 4.6.2
Vcs-Browser: https://salsa.debian.org/debian/bird2
Vcs-Git: https://salsa.debian.org/debian/bird2.git
Homepage: https://bird.network.cz/
@@ -26,7 +26,6 @@ Architecture: kfreebsd-any linux-any
Pre-Depends: init-system-helpers,
${misc:Pre-Depends}
Depends: adduser,
- lsb-base,
ucf,
${misc:Depends},
${shlibs:Depends}
diff --git a/distro/pkg/deb/rules b/distro/pkg/deb/rules
index 5630ed1c..1740fb95 100755
--- a/distro/pkg/deb/rules
+++ b/distro/pkg/deb/rules
@@ -23,8 +23,7 @@ LDFLAGS += -g -O2 -fno-strict-aliasing -fno-strict-overflow -fPIC -Wl,-z,defs -W
override_dh_auto_configure:
CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" dh_auto_configure -- $(COMMON_FLAGS) --with-protocols=all
-override_dh_auto_build:
- dh_auto_build
+override_dh_auto_build-indep:
dh_auto_build -- docs
override_dh_auto_install:
@@ -47,10 +46,10 @@ override_dh_installman: bird.8
override_dh_clean:
dh_clean
- -rm -f bird.8 birdc.8
+ -rm -f bird.8 birdc.8 birdcl.8
override_dh_missing:
- dh_missing --fail-missing
+ dh_missing
override_dh_compress:
dh_compress -X.conf
diff --git a/distro/pkg/deb/watch b/distro/pkg/deb/watch
index 1874bfbb..cec2d664 100644
--- a/distro/pkg/deb/watch
+++ b/distro/pkg/deb/watch
@@ -1,2 +1,2 @@
-version=3
+version=4
https://bird.network.cz/download/bird-(2\.[\d.]+).tar.gz
diff --git a/distro/pkg/rpm/bird.spec b/distro/pkg/rpm/bird.spec
index cd51ef51..eb6b8aa9 100644
--- a/distro/pkg/rpm/bird.spec
+++ b/distro/pkg/rpm/bird.spec
@@ -1,17 +1,21 @@
%global _hardened_build 1
%global _without_doc 1
+%{!?_rundir:%global _rundir %%{_localstatedir}/run}
Name: bird
Version: {{ version }}
Release: cznic.{{ release }}%{?dist}
Summary: BIRD Internet Routing Daemon
+Group: System Environment/Daemons
License: GPL-2.0-or-later
URL: https://bird.network.cz/
Source0: https://bird.network.cz/download/bird-%{version}.tar.gz
Source1: bird.service
Source2: bird.tmpfilesd
+Source3: bird.sysusersd
+BuildRequires: autoconf
BuildRequires: flex
BuildRequires: bison
BuildRequires: ncurses-devel
@@ -20,14 +24,13 @@ BuildRequires: sed
BuildRequires: gcc
BuildRequires: make
BuildRequires: libssh-devel
-%if 0%{?fedora} || (0%{?rhel} && 0%{?rhel} > 7)
-BuildRequires: systemd-rpm-macros
-%else
-BuildRequires: systemd
+%if 0%{?rhel} && 0%{?rhel} < 8
+# http://trubka.network.cz/pipermail/bird-users/2019-August/013631.html
+BuildRequires: devtoolset-8-toolchain
%endif
-
-Obsoletes: bird6 < 2.0.2-1
-Provides: bird6 = %{version}-%{release}
+BuildRequires: systemd-rpm-macros
+%{?systemd_requires}
+%{?sysusers_requires_compat}
%description
BIRD is a dynamic IP routing daemon supporting both, IPv4 and IPv6, Border
@@ -41,6 +44,7 @@ powerful language for route filtering.
%if 0%{!?_without_doc:1}
%package doc
Summary: Documentation for BIRD Internet Routing Daemon
+Group: Documentation
BuildRequires: linuxdoc-tools sgml-common perl(FindBin)
BuildArch: noarch
@@ -57,9 +61,13 @@ powerful language for route filtering.
%endif
%prep
-%setup -q
+%setup -q -n bird-%{version}
%build
+%if 0%{?rhel} && 0%{?rhel} < 8
+. /opt/rh/devtoolset-8/enable
+%endif
+
%configure --runstatedir=%{_rundir}/bird
%make_build all %{!?_without_doc:docs}
@@ -70,17 +78,18 @@ powerful language for route filtering.
install -d %{buildroot}{%{_localstatedir}/lib/bird,%{_rundir}/bird}
install -D -p -m 0644 %{SOURCE1} %{buildroot}%{_unitdir}/bird.service
install -D -p -m 0644 %{SOURCE2} %{buildroot}%{_tmpfilesdir}/bird.conf
+install -D -p -m 0644 %{SOURCE3} %{buildroot}%{_sysusersdir}/bird.conf
{% endraw %}
%check
+%if 0%{?rhel} && 0%{?rhel} < 8
+. /opt/rh/devtoolset-8/enable
+%endif
+
make test
%pre
-getent group bird >/dev/null || groupadd -r bird
-getent passwd bird >/dev/null || \
- useradd -r -g bird -d %{_localstatedir}/lib/bird -s /sbin/nologin \
- -c "BIRD daemon user" bird
-exit 0
+%sysusers_create_compat %{SOURCE3}
%post
%systemd_post bird.service
@@ -95,12 +104,13 @@ exit 0
%doc NEWS README
%attr(0640,root,bird) %config(noreplace) %{_sysconfdir}/bird.conf
%{_unitdir}/bird.service
+%{_sysusersdir}/bird.conf
%{_tmpfilesdir}/bird.conf
%{_sbindir}/bird
%{_sbindir}/birdc
%{_sbindir}/birdcl
%dir %attr(0750,bird,bird) %{_localstatedir}/lib/bird
-%dir %attr(0750,bird,bird) %ghost %{_rundir}/bird
+%dir %attr(0750,bird,bird) %{_rundir}/bird
%if 0%{!?_without_doc:1}
%files doc
diff --git a/distro/pkg/rpm/bird.sysusersd b/distro/pkg/rpm/bird.sysusersd
new file mode 100644
index 00000000..1c808666
--- /dev/null
+++ b/distro/pkg/rpm/bird.sysusersd
@@ -0,0 +1,2 @@
+#Type Name ID GECOS Home directory Shell
+u bird - "BIRD daemon user" /var/lib/bird -
diff --git a/distro/tests/control b/distro/tests/control
new file mode 100644
index 00000000..f588af8f
--- /dev/null
+++ b/distro/tests/control
@@ -0,0 +1,3 @@
+Tests: test-bird.sh
+Restrictions: needs-root
+Depends: bird2, iproute2
diff --git a/distro/tests/test-bird.sh b/distro/tests/test-bird.sh
new file mode 100755
index 00000000..464be32f
--- /dev/null
+++ b/distro/tests/test-bird.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+set -e
+
+LOCAL=2001:db8:dead::
+
+EXTERNAL=2001:db8:beef::
+EXTERNAL_NET=${EXTERNAL}/48
+EXTERNAL_NH=${LOCAL}beef
+
+LEARN=2001:db8:feed::
+LEARN_NET=${LEARN}/48
+LEARN_NH=${LOCAL}feed
+
+IFACE=bird-test-dummy
+IFACE_EXISTS=false
+
+BIRD_RUNNING=false
+
+D=$(mktemp -d)
+pushd ${D} >/dev/null
+
+stop_bird() {
+ birdc -l down >/dev/null
+ sleep 1
+ grep -q "<FATAL> Shutdown completed" bird.log
+ [ ! -e bird.pid ]
+ [ ! -e bird.ctl ]
+}
+
+cleanup() {
+ if ${BIRD_RUNNING}; then
+ stop_bird
+ if [ -e bird.pid ]; then
+ kill -9 $(<bird.pid)
+ fi
+ fi
+
+ if ${IFACE_EXISTS}; then
+ ip link del ${IFACE}
+ fi
+
+
+ popd > /dev/null
+ rm -rf ${D}
+}
+
+failed() {
+ cleanup
+ exit 1
+}
+
+trap failed ERR
+trap failed INT
+trap failed HUP
+
+ip link add ${IFACE} type dummy
+IFACE_EXISTS=true
+
+ip link set ${IFACE} up
+ip -6 addr add ${LOCAL}/64 dev bird-test-dummy
+
+ip -6 route add ${LEARN_NET} via ${LEARN_NH}
+
+cat >bird.conf <<EOF
+log "bird.log" all;
+
+protocol device {}
+
+protocol kernel {
+ ipv6 { import all; export all; };
+ learn;
+}
+
+protocol static {
+ ipv6;
+ route ${EXTERNAL_NET} via ${EXTERNAL_NH};
+}
+EOF
+
+bird -l -P bird.pid
+
+if [ ! -S bird.ctl ] || [ ! -f bird.pid ] || [ ! -f bird.log ]; then
+ failed
+fi
+
+BIRD_RUNNING=true
+
+ROUTE_INSERTED=false
+for _ in $(seq 10); do
+ if ip -6 route show ${EXTERNAL_NET} | egrep -q "${EXTERNAL_NET} via ${EXTERNAL_NH} dev ${IFACE} proto bird metric [0-9]+ pref medium"; then
+ ROUTE_INSERTED=true
+ break
+ fi
+ sleep 1
+done
+
+$ROUTE_INSERTED || failed
+
+if birdc -l show route "${LEARN_NET}" | egrep -q "Network not found"; then
+ failed
+fi
+
+cleanup
+exit 0
diff --git a/doc/bird.sgml b/doc/bird.sgml
index 001fcbd4..f2063325 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -341,10 +341,9 @@ Configuration keywords are <cf/flow4/ and <cf/flow6/.
<sect1>MPLS switching rules
<label id="mpls-routes">
-<p>This nettype is currently a stub before implementing more support of <rfc id="3031">.
-BIRD currently does not support any label distribution protocol nor any label assignment method.
-Only the Kernel, Pipe and Static protocols can use MPLS tables.
-Configuration keyword is <cf/mpls/.
+<p>MPLS routes control MPLS forwarding in the same way as IP routes control IP
+forwarding. MPLS-aware routing protocols produce both labeled IP routes and
+corresponding MPLS routes. Configuration keyword is <cf/mpls/.
<itemize>
<item>(PK) MPLS label
@@ -403,6 +402,79 @@ regular <ref id="cli-down" name="down"> command. In this way routing neighbors
are notified about planned graceful restart and routes are kept in kernel table
after shutdown.
+<sect>MPLS
+<label id="mpls">
+
+<p>Multiprotocol Label Switching (MPLS) is a networking technology which works
+below IP routing but above the link (e.g. ethernet) layer. It is described in
+<rfc id="3031">.
+
+In regular IP forwarding, the destination address of a packet is independently
+examined in each hop, a route with longest prefix match is selected from the
+routing table, and packet is processed accordingly. In general, there is no
+difference between border routers and internal routers w.r.t. IP forwarding.
+
+In MPLS forwarding, when a packet enters the network, it is classified (based on
+destination address, ingress interface and other factors) into one of forwarding
+equivalence classes (FECs), then a header with a MPLS label identifying the FEC
+is attached to it, and the packet is forwarded. In internal routers, only the
+MPLS label is examined, the matching MPLS route is selected from the MPLS
+routing table, and the packet is processed accordingly. The specific value of
+MPLS label has local meaning only and may change between hops (that is why it is
+called label switching). When the packet leaves the network, the MPLS header is
+removed.
+
+The advantage of the MPLS approach is that other factors than the destination
+address can be considered and used consistently in the whole network, for
+example IP traffic with multiple overlapping private address ranges could be
+mixed together, or particular paths for specific flows could be defined. Another
+advantage is that MPLS forwarding by internal routers can be much simpler than
+IP forwarding, as instead of the longest prefix match algorithm it uses simpler
+exact match for MPLS route selection. The disadvantage is additional complexity
+in signaling. For further details, see <rfc id="3031">.
+
+MPLS-aware routing protocols not only distribute IP routing information, but
+they also distribute labels. Therefore, they produce labeled routes - routes
+representing label switched paths (LSPs) through the MPLS domain. Such routes
+have IP prefix and next hop address like regular (non-labeled) routes, but they
+also have local MPLS label (in route attribute <ref id="rta-mpls-label"
+name="mpls_label">) and outgoing MPLS label (as a part of the next hop). They
+are stored in regular IP routing tables.
+
+Labeled routes are used for exchange of routing information between routing
+protocols and for ingress (IP -&gt; MPLS) forwarding, but they are not directly
+used for MPLS forwarding. For that purpose <ref id="mpls-routes" name="MPLS
+routes"> are used. These are routes that have local MPLS label as a primary key
+and they are stored in the MPLS routing table.
+
+In BIRD, the whole process generally works this way: A MPLS-aware routing
+protocol (say BGP) receives routing information including remote label. It
+produces a route with attribute <ref id="rta-mpls-policy" name="mpls_policy">
+specifying desired <ref id="mpls-channel-label-policy" name="MPLS label policy">.
+Such route then passes the import filter (which could modify the MPLS label
+policy or perhaps assign a static label) and when it is accepted, a local MPLS
+label is selected (according to the label policy) and attached to the route,
+producing labeled route. When a new MPLS label is allocated, the MPLS-aware
+protocol automatically produces corresponding MPLS route. When all labeled
+routes that use specific local MPLS label are retracted, the corresponding MPLS
+route is retracted too.
+
+There are three important concepts for MPLS in BIRD: MPLS domains, MPLS tables
+and MPLS channels. MPLS domain represents an independent label space, all
+MPLS-aware protocols are associated with some MPLS domain. It is responsible for
+label management, handling label allocation requests from MPLS-aware protocols.
+MPLS table is just a routing table for MPLS routes. Routers usually have one
+MPLS domain and one MPLS table, with Kernel protocol to export MPLS routes into
+kernel FIB.
+
+MPLS channels make protocols MPLS-aware, they are responsible for keeping track
+of active FECs (and corresponding allocated labels), selecting FECs / local
+labels for labeled routes, and maintaining correspondence between labeled routes
+and MPLS routes.
+
+Note that local labels are allocated to individual MPLS-aware protocols and
+therefore it is not possible to share local labels between different protocols.
+
<chapt>Configuration
<label id="config">
@@ -539,7 +611,7 @@ include "tablename.conf";;
Define a filter. You can learn more about filters in the following
chapter.
- <tag><label id="opt-function">function <m/name/ (<m/parameters/) <m/local variables/ { <m/commands/ }</tag>
+ <tag><label id="opt-function">function <m/name/ (<m/parameters/) [ -&gt; <m/return type/ ] <m/local variables/ { <m/commands/ }</tag>
Define a function. You can learn more about functions in the following chapter.
<tag><label id="opt-protocol">protocol rip|ospf|bgp|<m/.../ [<m/name/ [from <m/name2/]] { <m>protocol options</m> }</tag>
@@ -626,6 +698,12 @@ include "tablename.conf";;
defined by this option. See the <ref id="rtable-opts"
name="routing table configuration section"> for routing table options.
+ <tag><label id="opt-mpls-domain">mpls domain <m/name/ [ { <m/option/; [<m/.../] } ]</tag>
+ Define a new MPLS domain. MPLS domains represent independent label
+ spaces and are responsible for MPLS label management. All MPLS-aware
+ protocols are associated with some MPLS domain. See the <ref id="mpls-opts"
+ name="MPLS configuration section"> for MPLS domain options.
+
<tag><label id="opt-eval">eval <m/expr/</tag>
Evaluates given filter expression. It is used by the developers for testing of filters.
</descrip>
@@ -838,7 +916,7 @@ agreement").
protocol packets are processed in the local TX queues. This option is
Linux specific. Default value is 7 (highest priority, privileged traffic).
- <tag><label id="proto-pass">password "<m/password/" | <m/hex_key/ [ { <m>password options</m> } ] </tag>
+ <tag><label id="proto-pass">password "<m/password/" | <m/bytestring/ [ { <m>password options</m> } ] </tag>
Specifies a password that can be used by the protocol as a shared secret
key. Password option can be used more times to specify more passwords.
If more passwords are specified, it is a protocol-dependent decision
@@ -846,10 +924,8 @@ agreement").
authentication is enabled, authentication can be enabled by separate,
protocol-dependent <cf/authentication/ option.
- A password can also be specified as a hexadecimal key. <m/hex_key/ is a
- sequence of hexadecimal digit pairs, optionally colon-separated. A key
- specified this way must be at least 16 bytes (32 digits) long (although
- specific algorithms can impose other restrictions).
+ A password can be specified as a string or as a sequence of hexadecimal
+ digit pairs (<ref id="type-bytestring" name="bytestring">).
This option is allowed in BFD, OSPF, RIP, and Babel protocols. BGP has
also <cf/password/ option, but it is slightly different and described
@@ -932,7 +1008,7 @@ inherited from templates can be updated by new definitions.
<tag><label id="proto-export">export <m/filter/</tag>
This is similar to the <cf>import</cf> keyword, except that it works in
the direction from the routing table to the protocol. Default: <cf/none/
- (except for EBGP).
+ (except for EBGP and L3VPN).
<tag><label id="proto-import-keep-filtered">import keep filtered <m/switch/</tag>
Usually, if an import filter rejects a route, the route is forgotten.
@@ -1034,6 +1110,100 @@ protocol bgp from {
</code>
+<sect>MPLS options
+<label id="mpls-opts">
+
+<p>The MPLS domain definition is mandatory for a MPLS router. All MPLS channels
+and MPLS-aware protocols are associated with some MPLS domain (although usually
+implicitly with the sole one). In the MPLS domain definition you can configure
+details of MPLS label allocation. Currently, there is just one option,
+<cf/label range/.
+
+<p>Note that the MPLS subsystem is experimental, it is likely that there will be
+some backward-incompatible changes in the future.
+
+<descrip>
+ <tag><label id="mpls-domain-label-range">label range <m/name/ { start <m/number/; length <m/number/; [<m/.../] }</tag>
+ Define a new label range, or redefine implicit label ranges <cf/static/
+ and <cf/dynamic/. MPLS channels use configured label ranges for dynamic
+ label allocation, while <cf/static/ label range is used for static label
+ allocation. The label range definition must specify the extent of the
+ range. By default, the range <cf/static/ is 16-1000, while the range
+ <cf/dynamic/ is 1000-10000.
+</descrip>
+
+<p>MPLS channel should be defined in each MPLS-aware protocol in addition to its
+regular channels. It is responsible for label allocation and for announcing MPLS
+routes to the MPLS routing table. Besides common <ref id="channel-opts"
+name="channel options">, MPLS channels have some specific options:
+
+<descrip>
+ <tag><label id="mpls-channel-domain">domain <m/name/</tag>
+ Specify a MPLS domain to which this channel and protocol belongs.
+ Default: The first defined MPLS domain.
+
+ <tag><label id="mpls-channel-label-range">label range <m/name/</tag>
+ Use specific label range for dynamic label allocation. Note that static
+ labels always use the range <cf/static/. Default: the range <cf/dynamic/.
+
+ <tag><label id="mpls-channel-label-policy">label policy static|prefix|aggregate|vrf</tag>
+ Label policy specifies how routes are grouped to forwarding equivalence
+ classes (FECs) and how labels are assigned to them.
+
+ The policy <cf/static/ means no dynamic label allocation is done, and
+ static labels must be set in import filters using the route attribute
+ <ref id="rta-mpls-label" name="mpls_label">.
+
+ The policy <cf/prefix/ means each prefix uses separate label associated
+ with that prefix. When a labeled route is updated, it keeps the label.
+ This policy is appropriate for IGPs.
+
+ The policy <cf/aggregate/ means routes are grouped to FECs according to
+ their next hops (including next hop labels), and one label is used for
+ all routes in the same FEC. When a labeled route is updated, it may
+ change next hop, change FEC and therefore change label. This policy is
+ appropriate for BGP.
+
+ The policy <cf/vrf/ is only valid in L3VPN protocols. It uses one label
+ for all routes from a VRF, while replacing the original next hop with
+ lookup in the VRF.
+
+ Default: <cf/prefix/.
+</descrip>
+
+<p>This is a trivial example of MPLS setup:
+<code>
+mpls domain mdom {
+ label range bgprange { start 2000; length 1000; };
+}
+
+mpls table mtab;
+
+protocol static {
+ ipv6;
+ mpls;
+
+ route 2001:db8:1:1/64 mpls 100 via 2001:db8:1:2::1/64 mpls 200;
+}
+
+protocol bgp {
+ # regular channels
+ ipv6 mpls { ... };
+ vpn6 mpls { ... };
+
+ # MPLS channel
+ mpls {
+ # domain mdom;
+ # table mtab;
+ label range bgprange;
+ label policy aggregate;
+ };
+
+ ...
+}
+</code>
+
+
<chapt>Remote control
<label id="remote-control">
@@ -1113,14 +1283,16 @@ This argument can be omitted if there exists only a single instance.
Show the list of symbols defined in the configuration (names of
protocols, routing tables etc.).
- <tag><label id="cli-show-route">show route [[for] <m/prefix/|<m/IP/] [table (<m/t/ | all)] [(import|export) table <m/p/.<m/c/] [filter <m/f/|where <m/c/] [(export|preexport|noexport) <m/p/] [protocol <m/p/] [(stats|count)] [<m/options/]</tag>
+ <tag><label id="cli-show-route">show route [[(for|in)] <m/prefix/|for <m/IP/] [table (<m/t/|all)] [(import|export) table <m/p/.<m/c/] [filter <m/f/|where <m/cond/] [(export|preexport|noexport) <m/p/] [protocol <m/p/] [(stats|count)] [<m/options/]</tag>
Show contents of specified routing tables, that is routes, their metrics
and (in case the <cf/all/ switch is given) all their attributes.
<p>You can specify a <m/prefix/ if you want to print routes for a
specific network. If you use <cf>for <m/prefix or IP/</cf>, you'll get
the entry which will be used for forwarding of packets to the given
- destination. By default, all routes for each network are printed with
+ destination. Finally, if you use <cf>in <m/prefix/</cf>, you get all
+ prefixes covered by the given prefix.
+ By default, all routes for each network are printed with
the selected one at the top, unless <cf/primary/ is given in which case
only the selected route is shown.
@@ -1294,20 +1466,24 @@ can group several statements to a single compound statement by using braces
(<cf>{ <M>statements</M> }</cf>) which is useful if you want to make a bigger
block of code conditional.
-<p>BIRD supports functions, so that you don't have to repeat the same blocks of
-code over and over. Functions can have zero or more parameters and they can have
-local variables. Recursion is not allowed. Function definitions look like this:
+<p>BIRD supports functions, so that you don not have to repeat the same blocks
+of code over and over. Functions can have zero or more parameters and they can
+have local variables. If the function returns value, then you should always
+specify its return type. Direct recursion is possible. Function definitions look
+like this:
<code>
-function name ()
+function name() -> int
{
int local_variable;
int another_variable = 5;
+ return 42;
}
-function with_parameters (int parameter)
+function with_parameters(int parameter) -> pair
{
print parameter;
+ return (1, 2);
}
</code>
@@ -1321,7 +1497,7 @@ may return values using the <cf>return <m/[expr]/</cf> command. Returning a
value exits from current function (this is similar to C).
<p>Filters are defined in a way similar to functions except they cannot have
-explicit parameters. They get a route table entry as an implicit parameter, it
+explicit parameters and cannot return. They get a route table entry as an implicit parameter, it
is also passed automatically to any functions called. The filter must terminate
with either <cf/accept/ or <cf/reject/ statement. If there is a runtime error in
filter, the route is rejected.
@@ -1385,6 +1561,23 @@ in the foot).
!&tilde;/) operators could be used to match a string value against
a shell pattern (represented also as a string).
+ <tag><label id="type-bytestring">bytestring</tag>
+ This is a sequences of arbitrary bytes. There are no ways to modify
+ bytestrings in filters. You can pass them between function, assign
+ them to variables of type <cf/bytestring/, print such values,
+ compare bytestings (<cf/=, !=/).
+
+ Bytestring literals are written as a sequence of hexadecimal digit
+ pairs, optionally colon-separated. A bytestring specified this way
+ must be either at least 16 bytes (32 digits) long, or prefixed by the
+ <cf/hex:/ prefix: <cf/01:23:45:67:89:ab:cd:ef:01:23:45:67:89:ab:cd:ef/,
+ <cf/0123456789abcdef0123456789abcdef/, <cf/hex:/, <cf/hex:12:34:56/,
+ <cf/hex:12345678/.
+
+ A bytestring can be made from a hex string using <cf/from_hex()/
+ function. Source strings can use any number of dots, colons, hyphens
+ and spaces as byte separators: <cf/from_hex(" 12.34 56:78 ab-cd-ef ")/.
+
<tag><label id="type-ip">ip</tag>
This type can hold a single IP address. The IPv4 addresses are stored as
IPv4-Mapped IPv6 addresses so one data type for both of them is used.
@@ -1567,23 +1760,24 @@ in the foot).
<cf><m/P/.len</cf> returns the length of path <m/P/.
- <cf><m/P/.empty</cf> makes the path <m/P/ empty.
+ <cf><m/P/.empty</cf> makes the path <m/P/ empty. Can't be used as a value, always modifies the object.
- <cf>prepend(<m/P/,<m/A/)</cf> prepends ASN <m/A/ to path <m/P/ and
+ <cf><m/P/.prepend(<m/A/)</cf> prepends ASN <m/A/ to path <m/P/ and
returns the result.
- <cf>delete(<m/P/,<m/A/)</cf> deletes all instances of ASN <m/A/ from
+ <cf><m/P/.delete(<m/A/)</cf> deletes all instances of ASN <m/A/ from
from path <m/P/ and returns the result. <m/A/ may also be an integer
set, in that case the operator deletes all ASNs from path <m/P/ that are
also members of set <m/A/.
- <cf>filter(<m/P/,<m/A/)</cf> deletes all ASNs from path <m/P/ that are
- not members of integer set <m/A/. I.e., <cf/filter/ do the same as
- <cf/delete/ with inverted set <m/A/.
+ <cf><m/P/.filter(<m/A/)</cf> deletes all ASNs from path <m/P/ that are
+ not members of integer set <m/A/, and returns the result.
+ I.e., <cf/filter/ do the same as <cf/delete/ with inverted set <m/A/.
- Statement <cf><m/P/ = prepend(<m/P/, <m/A/);</cf> can be shortened to
- <cf><m/P/.prepend(<m/A/);</cf> if <m/P/ is appropriate route attribute
- (for example <cf/bgp_path/). Similarly for <cf/delete/ and <cf/filter/.
+ Methods <cf>prepend</cf>, <cf>delete</cf> and <cf>filter</cf> keep the
+ original object intact as long as you use the result in any way. You can
+ also write e.g. <cf><m/P/.prepend(<m/A/);</cf> as a standalone statement.
+ This variant does modify the original object with the result of the operation.
<tag><label id="type-bgpmask">bgpmask</tag>
BGP masks are patterns used for BGP path matching (using <cf>path
@@ -1611,28 +1805,29 @@ in the foot).
<cf><m/C/.len</cf> returns the length of clist <m/C/.
- <cf><m/C/.empty</cf> makes the list <m/C/ empty.
+ <cf><m/C/.empty</cf> makes the list <m/C/ empty. Can't be used as a value, always modifies the object.
- <cf>add(<m/C/,<m/P/)</cf> adds pair (or quad) <m/P/ to clist <m/C/ and
+ <cf><m/C/.add(<m/P/)</cf> adds pair (or quad) <m/P/ to clist <m/C/ and
returns the result. If item <m/P/ is already in clist <m/C/, it does
nothing. <m/P/ may also be a clist, in that case all its members are
added; i.e., it works as clist union.
- <cf>delete(<m/C/,<m/P/)</cf> deletes pair (or quad) <m/P/ from clist
+ <cf><m/C/.delete(<m/P/)</cf> deletes pair (or quad) <m/P/ from clist
<m/C/ and returns the result. If clist <m/C/ does not contain item
<m/P/, it does nothing. <m/P/ may also be a pair (or quad) set, in that
case the operator deletes all items from clist <m/C/ that are also
members of set <m/P/. Moreover, <m/P/ may also be a clist, which works
analogously; i.e., it works as clist difference.
- <cf>filter(<m/C/,<m/P/)</cf> deletes all items from clist <m/C/ that are
- not members of pair (or quad) set <m/P/. I.e., <cf/filter/ do the same
+ <cf><m/C/.filter(<m/P/)</cf> deletes all items from clist <m/C/ that are
+ not members of pair (or quad) set <m/P/, and returns the result. I.e., <cf/filter/ do the same
as <cf/delete/ with inverted set <m/P/. <m/P/ may also be a clist, which
works analogously; i.e., it works as clist intersection.
- Statement <cf><m/C/ = add(<m/C/, <m/P/);</cf> can be shortened to
- <cf><m/C/.add(<m/P/);</cf> if <m/C/ is appropriate route attribute (for
- example <cf/bgp_community/). Similarly for <cf/delete/ and <cf/filter/.
+ Methods <cf>add</cf>, <cf>delete</cf> and <cf>filter</cf> keep the
+ original object intact as long as you use the result in any way. You can
+ also write e.g. <cf><m/P/.add(<m/A/);</cf> as a standalone statement.
+ This variant does modify the original object with the result of the operation.
<cf><m/C/.min</cf> returns the minimum element of clist <m/C/.
@@ -1847,6 +2042,29 @@ Common route attributes are:
network for routes that do not have a native protocol metric attribute
(like <cf/ospf_metric1/ for OSPF routes). It is used mainly by BGP to
compare internal distances to boundary routers (see below).
+
+ <tag><label id="rta-mpls-label"><m/int/ mpls_label</tag>
+ Local MPLS label attached to the route. This attribute is produced by
+ MPLS-aware protocols for labeled routes. It can also be set in import
+ filters to assign static labels, but that also requires static MPLS
+ label policy.
+
+ <tag><label id="rta-mpls-policy"><m/enum/ mpls_policy</tag>
+ For MPLS-aware protocols, this attribute defines which
+ <ref id="mpls-channel-label-policy" name="MPLS label policy"> will be
+ used for the route. It can be set in import filters to change it on
+ per-route basis. Valid values are <cf/MPLS_POLICY_NONE/ (no label),
+ <cf/MPLS_POLICY_STATIC/ (static label), <cf/MPLS_POLICY_PREFIX/
+ (per-prefix label), <cf/MPLS_POLICY_AGGREGATE/ (aggregated label),
+ and <cf/MPLS_POLICY_VRF/ (per-VRF label). See <ref
+ id="mpls-channel-label-policy" name="MPLS label policy"> for details.
+
+ <tag><label id="rta-mpls-class"><m/int/ mpls_class</tag>
+ When <ref id="mpls-channel-label-policy" name="MPLS label policy"> is
+ set to <cf/aggregate/, it may be useful to apply more fine-grained
+ aggregation than just one based on next hops. When routes have different
+ value of this attribute, they will not be aggregated under one local
+ label even if they have the same next hops.
</descrip>
<p>Protocol-specific route attributes are described in the corresponding
@@ -1878,6 +2096,70 @@ protocol sections.
<chapt>Protocols
<label id="protocols">
+<sect>Aggregator
+<label id="aggregator">
+
+<sect1>Introduction
+<label id="aggregator-intro">
+<p>The Aggregator protocol explicitly merges routes by the given rules. There
+ are four phases of aggregation. First routes are filtered, then sorted into buckets,
+ then buckets are merged and finally the results are filtered once again.
+ Aggregating an already aggregated route is forbidden.
+
+<p>This is an experimental protocol, use with caution.
+
+<sect1>Configuration
+<label id="aggregator-config">
+<p><descrip>
+ <tag><label id="aggregator-table">table <m/table/</tag>
+ The table from which routes are exported to get aggregated.
+
+ <tag><label id="aggregator-export">export <m/.../</tag>
+ A standard channel's <cf/export/ clause, defining which routes are accepted into aggregation.
+
+ <tag><label id="aggregator-rule">aggregate on <m/expr/ | <m/attribute/ [<m/, .../]</tag>
+ All the given filter expressions and route attributes are evaluated for each route. Then routes
+ are sorted into buckets where <em/all/ values are the same. Note: due to performance reasons,
+ all filter expressions must return a compact type, e.g. integer, a BGP
+ (standard, extended, large) community or an IP address. If you need to compare e.g. modified
+ AS Paths in the aggregation rule, you can define a custom route attribute and set this attribute
+ in the export filter. For now, it's mandatory to say <cf/net/ here, we can't merge prefixes yet.
+
+ <tag><label id="aggregation-merge">merge by { <m/filter code/ }</tag>
+ The given filter code has an extra symbol defined: <cf/routes/. By iterating over <cf/routes/,
+ you get all the routes in the bucket and you can construct your new route. All attributes
+ selected in <cf/aggregate on/ are already set to the common values. For now, it's not possible
+ to use a named filter here. You have to finalize the route by calling <cf/accept/.
+
+ <tag><label id="aggregator-import">import <m/.../</tag>
+ Filter applied to the route after <cf/merge by/. Here you can use a named filter.
+
+ <tag><label id="aggregator-peer-table">peer table <m/table/</tag>
+ The table to which aggregated routes are imported. It may be the same table
+ as <cf/table/.
+</descrip>
+
+<sect1>Example
+<label id="aggregator-example">
+
+<p><code>
+protocol aggregator {
+ table master6;
+ export where defined(bgp_path);
+ /* Merge all routes with the same AS Path length */
+ aggregate on net, bgp_path.len;
+ merge by {
+ for route r in routes do {
+ if ! defined(bgp_path) then { bgp_path = r.bgp_path }
+ bgp_community = bgp_community.add(r.bgp_community);
+ }
+ accept;
+ };
+ import all;
+ peer table agr_result;
+}
+</code>
+
<sect>Babel
<label id="babel">
@@ -1885,7 +2167,7 @@ protocol sections.
<label id="babel-intro">
<p>The Babel protocol
-(<rfc id="6126">) is a loop-avoiding distance-vector routing protocol that is
+(<rfc id="8966">) is a loop-avoiding distance-vector routing protocol that is
robust and efficient both in ordinary wired networks and in wireless mesh
networks. Babel is conceptually very simple in its operation and "just works"
in its default configuration, though some configuration is possible and in some
@@ -1916,7 +2198,7 @@ protocol babel [<name>] {
ipv6 [sadr] { <channel config> };
randomize router id <switch>;
interface <interface pattern> {
- type <wired|wireless>;
+ type <wired|wireless|tunnel>;
rxcost <number>;
limit <number>;
hello interval <time>;
@@ -1929,6 +2211,12 @@ protocol babel [<name>] {
check link <switch>;
next hop ipv4 <address>;
next hop ipv6 <address>;
+ extended next hop <switch>;
+ rtt cost <number>;
+ rtt min <time>;
+ rtt max <time>;
+ rtt decay <number>;
+ send timestamps <switch>;
authentication none|mac [permissive];
password "&lt;text&gt;";
password "&lt;text&gt;" {
@@ -1959,15 +2247,16 @@ protocol babel [<name>] {
router ID every time it starts up, which avoids this problem at the cost
of not having stable router IDs in the network. Default: no.
- <tag><label id="babel-type">type wired|wireless </tag>
- This option specifies the interface type: Wired or wireless. On wired
- interfaces a neighbor is considered unreachable after a small number of
- Hello packets are lost, as described by <cf/limit/ option. On wireless
+ <tag><label id="babel-type">type wired|wireless|tunnel </tag>
+ This option specifies the interface type: Wired, wireless or tunnel. On
+ wired interfaces a neighbor is considered unreachable after a small number
+ of Hello packets are lost, as described by <cf/limit/ option. On wireless
interfaces the ETX link quality estimation technique is used to compute
the metrics of routes discovered over this interface. This technique will
gradually degrade the metric of routes when packets are lost rather than
- the more binary up/down mechanism of wired type links. Default:
- <cf/wired/.
+ the more binary up/down mechanism of wired type links. A tunnel is like a
+ wired interface, but turns on RTT-based metrics with a default cost of 96.
+ Default: <cf/wired/.
<tag><label id="babel-rxcost">rxcost <m/num/</tag>
This option specifies the nominal RX cost of the interface. The effective
@@ -2033,6 +2322,42 @@ protocol babel [<name>] {
source for Babel packets will be used. In normal operation, it should not
be necessary to set this option.
+ <tag><label id="babel-extended-next-hop">extended next hop <m/switch/</tag>
+ If enabled, BIRD will accept and emit IPv4 routes with an IPv6 next
+ hop when IPv4 addresses are absent from the interface as described in
+ <rfc id="9229">. Default: yes.
+
+ <tag><label id="babel-rtt-cost">rtt cost <m/number/</tag>
+ The RTT-based cost that will be applied to all routes from each neighbour
+ based on the measured RTT to that neighbour. If this value is set,
+ timestamps will be included in generated Babel Hello and IHU messages, and
+ (if the neighbours also have timestamps enabled), the RTT to each
+ neighbour will be computed. An additional cost is added to a neighbour if
+ its RTT is above the <ref id="babel-rtt-min" name="rtt min"> value
+ configured on the interface. The added cost scales linearly from 0 up to
+ the RTT cost configured in this option; the full cost is applied if the
+ neighbour RTT reaches the RTT configured in the <ref id="babel-rtt-max"
+ name="rtt max"> option (and for all RTTs above this value). Default: 0
+ (disabled), except for tunnel interfaces, where it is 96.
+
+ <tag><label id="babel-rtt-min">rtt min <m/time/ s|ms</tag>
+ The minimum RTT above which the RTT cost will start to be applied (scaling
+ linearly from zero up to the full cost). Default: 10 ms
+
+ <tag><label id="babel-rtt-max">rtt max <m/time/ s|ms</tag>
+ The maximum RTT above which the full RTT cost will start be applied.
+ Default: 120 ms
+
+ <tag><label id="babel-rtt-decay">rtt decay <m/number/</tag>
+ The decay factor used for the exponentional moving average of the RTT
+ samples from each neighbour, in units of 1/256. Higher values discards old
+ RTT samples faster. Must be between 1 and 256. Default: 42
+
+ <tag><label id="babel-send-timestamps">send timestamps <m/switch/</tag>
+ Whether to send the timestamps used for RTT calculation on this interface.
+ Sending the timestamps enables peers to calculate an RTT to this node,
+ even if no RTT cost is applied to the route metrics. Default: yes.
+
<tag><label id="babel-authentication">authentication none|mac [permissive]</tag>
Selects authentication method to be used. <cf/none/ means that packets
are not authenticated at all, <cf/mac/ means MAC authentication is
@@ -2380,7 +2705,6 @@ avoid routing loops.
<item> <rfc id="5065"> - AS confederations for BGP
<item> <rfc id="5082"> - Generalized TTL Security Mechanism
<item> <rfc id="5492"> - Capabilities Advertisement with BGP
-<item> <rfc id="5549"> - Advertising IPv4 NLRI with an IPv6 Next Hop
<item> <rfc id="5575"> - Dissemination of Flow Specification Rules
<item> <rfc id="5668"> - 4-Octet AS Specific BGP Extended Community
<item> <rfc id="6286"> - AS-Wide Unique BGP Identifier
@@ -2394,6 +2718,9 @@ avoid routing loops.
<item> <rfc id="8092"> - BGP Large Communities Attribute
<item> <rfc id="8203"> - BGP Administrative Shutdown Communication
<item> <rfc id="8212"> - Default EBGP Route Propagation Behavior without Policies
+<item> <rfc id="8654"> - Extended Message Support for BGP
+<item> <rfc id="8950"> - Advertising IPv4 NLRI with an IPv6 Next Hop
+<item> <rfc id="9072"> - Extended Optional Parameters Length for BGP OPEN Message
<item> <rfc id="9117"> - Revised Validation Procedure for BGP Flow Specifications
<item> <rfc id="9234"> - Route Leak Prevention and Detection Using Roles
</itemize>
@@ -2615,12 +2942,19 @@ using the following configuration parameters:
keeps MED attribute). Default: disabled.
<tag><label id="bgp-allow-local-pref">allow bgp_local_pref <m/switch/</tag>
- A standard BGP implementation do not send the Local Preference attribute
- to eBGP neighbors and ignore this attribute if received from eBGP
+ Standard BGP implementations do not send the Local Preference attribute
+ to EBGP neighbors and ignore this attribute if received from EBGP
neighbors, as per <rfc id="4271">. When this option is enabled on an
- eBGP session, this attribute will be sent to and accepted from the peer,
+ EBGP session, this attribute will be sent to and accepted from the peer,
+ which is useful for example if you have a setup like in <rfc id="7938">.
+ The option does not affect IBGP sessions. Default: off.
+
+ <tag><label id="bgp-allow-med">allow bgp_med <m/switch/</tag>
+ Standard BGP implementations do not propagate the MULTI_EXIT_DESC
+ attribute unless it is configured locally. When this option is enabled
+ on an EBGP session, this attribute will be sent to the peer regardless,
which is useful for example if you have a setup like in <rfc id="7938">.
- The option does not affect iBGP sessions. Default: off.
+ The option does not affect IBGP sessions. Default: off.
<tag><label id="bgp-allow-local-as">allow local as [<m/number/]</tag>
BGP prevents routing loops by rejecting received routes with the local
@@ -2656,13 +2990,20 @@ using the following configuration parameters:
changes its import filter, or if there is suspicion of inconsistency) it
is necessary to do a new complete route exchange. BGP protocol extension
Route Refresh (<rfc id="2918">) allows BGP speaker to request
- re-advertisement of all routes from its neighbor. BGP protocol
- extension Enhanced Route Refresh (<rfc id="7313">) specifies explicit
- begin and end for such exchanges, therefore the receiver can remove
- stale routes that were not advertised during the exchange. This option
- specifies whether BIRD advertises these capabilities and supports
+ re-advertisement of all routes from its neighbor. This option
+ specifies whether BIRD advertises this capability and supports
related procedures. Note that even when disabled, BIRD can send route
- refresh requests. Default: on.
+ refresh requests. Disabling Route Refresh also disables Enhanced Route Refresh.
+ Default: on.
+
+ <tag><label id="bgp-enable-enhanced-route-refresh">enable enhanced route refresh <m/switch/</tag>
+ BGP protocol extension Enhanced Route Refresh (<rfc id="7313">) specifies explicit
+ begin and end for Route Refresh (see previous option),
+ therefore the receiver can remove
+ stale routes that were not advertised during the exchange. This option
+ specifies whether BIRD advertises this capability and supports
+ related procedures.
+ Default: on.
<tag><label id="bgp-graceful-restart">graceful restart <m/switch/|aware</tag>
When a BGP speaker restarts or crashes, neighbors will discard all
@@ -2901,6 +3242,18 @@ together with their appropriate channels follows.
</tabular>
</table>
+<p>The BGP protocol can be configured as MPLS-aware (by defining both AFI/SAFI
+channels and the MPLS channel). In such case the BGP protocol assigns labels to
+routes imported from MPLS-aware SAFIs (i.e. <cf/ipvX mpls/ and <cf/vpnX mpls/)
+and automatically announces corresponding MPLS route for each labeled route. As
+BGP generally processes a large amount of routes, it is suggested to set MPLS
+label policy to <cf/aggregate/.
+
+<p>Note that even BGP instances without MPLS channel and without local MPLS
+configuration can still propagate third-party MPLS labels, e.g. as route
+reflectors, they just will not assign local labels to imported routes and will
+not announce MPLS routes for local MPLS forwarding.
+
<p>Due to <rfc id="8212">, external BGP protocol requires explicit configuration
of import and export policies (in contrast to other protocols, where default
policies of <cf/import all/ and <cf/export none/ are used in absence of explicit
@@ -3046,7 +3399,7 @@ be used in explicit configuration.
associated network prefixes. This option provides an extension to use
IPv4 next hops with IPv6 prefixes and vice versa. For IPv4 / VPNv4
channels, the behavior is controlled by the Extended Next Hop Encoding
- capability, as described in <rfc id="5549">. For IPv6 / VPNv6 channels,
+ capability, as described in <rfc id="8950">. For IPv6 / VPNv6 channels,
just IPv4-mapped IPv6 addresses are used, as described in
<rfc id="4798"> and <rfc id="4659">. Default: off.
@@ -3204,10 +3557,21 @@ some of them (marked with `<tt/O/') are optional.
<tag><label id="bgp-otc">int bgp_otc [O]</tag>
This attribute is defined in <rfc id="9234">. OTC is a flag that marks
- routes that should be sent only to customers. If <ref id="bgp-role"
- name="local Role"> is configured it set automatically.
+ routes that should be sent only to customers. If <ref id="bgp-local-role"
+ name="local role"> is configured it set automatically.
</descrip>
+<p>For attributes unknown by BIRD, the user can assign a name (on top level) to
+an attribute by its number. This defined name can be used then to get, set (as a
+bytestring, transitive) or unset the given attribute even though BIRD knows
+nothing about it.
+
+<p>Note that it is not possible to define an attribute with the same number
+as one known by BIRD, therefore use of this statement carries a risk of
+incompatibility with future BIRD versions.
+
+<tt><label id="bgp-attribute-custom">attribute bgp <m/number/ bytestring <m/name/;</tt>
+
<sect1>Example
<label id="bgp-exam">
@@ -3251,6 +3615,37 @@ protocol bgp {
</code>
+<sect>BMP
+<label id="bmp">
+
+<p>The BGP Monitoring Protocol is used for monitoring BGP sessions and obtaining
+routing table data. The current implementation in BIRD is a preliminary release
+with a limited feature set, it will be subject to significant changes in the
+future. It is not ready for production usage and therefore it is not compiled
+by default and have to be enabled during installation by the configure option
+<tt/--with-protocols=/.
+
+<p>The implementation supports monitoring protocol state changes, pre-policy
+routes (in <ref id="bgp-import-table" name="BGP import tables">) and post-policy
+routes (in regular routing tables). All BGP protocols are monitored automatically.
+
+<sect1>Example
+<label id="bmp-exam">
+
+<p><code>
+protocol bmp {
+ # The monitoring station to connect to
+ station address ip 198.51.100.10 port 1790;
+
+ # Monitor received routes (in import table)
+ monitoring rib in pre_policy;
+
+ # Monitor accepted routes (passed import filters)
+ monitoring rib in post_policy;
+}
+</code>
+
+
<sect>Device
<label id="device">
@@ -3371,9 +3766,8 @@ on the <cf/learn/ switch, such routes are either ignored or accepted to our
table).
<p>Note that routes created by OS kernel itself, namely direct routes
-representing IP subnets of associated interfaces, are not imported even with
-<cf/learn/ enabled. You can use <ref id="direct" name="Direct protocol"> to
-generate these direct routes.
+representing IP subnets of associated interfaces, are imported only with
+<cf/learn all/ enabled.
<p>If your OS supports only a single routing table, you can configure only one
instance of the Kernel protocol. If it supports multiple tables (in order to
@@ -3404,10 +3798,12 @@ channels.
Time in seconds between two consecutive scans of the kernel routing
table.
- <tag><label id="krt-learn">learn <m/switch/</tag>
+ <tag><label id="krt-learn">learn <m/switch/|all</tag>
Enable learning of routes added to the kernel routing tables by other
routing daemons or by the system administrator. This is possible only on
- systems which support identification of route authorship.
+ systems which support identification of route authorship. By default,
+ routes created by kernel (marked as "proto kernel") are not imported.
+ Use <cf/learn all/ option to import even these routes.
<tag><label id="krt-kernel-table">kernel table <m/number/</tag>
Select which kernel table should this particular instance of the Kernel
@@ -3517,7 +3913,7 @@ protocol kernel {
<p><code>
protocol kernel { # Primary routing table
learn; # Learn alien routes from the kernel
- persist; # Don't remove routes on bird shutdown
+ persist; # Do not remove routes on bird shutdown
scan time 10; # Scan kernel routing table every 10 seconds
ipv4 {
import all;
@@ -3535,6 +3931,140 @@ protocol kernel { # Secondary routing table
</code>
+<sect>L3VPN
+<label id="l3vpn">
+
+<sect1>Introduction
+<label id="l3vpn-intro">
+
+<p>The L3VPN protocol serves as a translator between IP routes and VPN
+routes. It is a component for BGP/MPLS IP VPNs (<rfc id="4364">) and implements
+policies defined there. In import direction (VPN -&gt; IP), VPN routes matching
+import target specification are stripped of route distinguisher and MPLS labels
+and announced as IP routes, In export direction (IP -&gt; VPN), IP routes are
+expanded with specific route distinguisher, export target communities and MPLS
+label and announced as labeled VPN routes. Unlike the Pipe protocol, the L3VPN
+protocol propagates just the best route for each network.
+
+<p>In BGP/MPLS IP VPNs, route distribution is controlled by Route Targets (RT).
+VRFs are associated with one or more RTs. Routes are also associated with one or
+more RTs, which are encoded as route target extended communities
+in <ref id="rta-bgp-ext-community" name="bgp_ext_community">. A route is then
+imported into each VRF that shares an associated Route Target. The L3VPN
+protocol implements this mechanism through mandatory <cf/import target/ and
+<cf/export target/ protocol options.
+
+<sect1>Configuration
+<label id="l3vpn-config">
+
+<p>L3VPN configuration consists of a few mandatory options and multiple channel
+definitions. For convenience, the default export filter in L3VPN channels is
+<cf/all/, as the primary way to control import and export of routes is through
+protocol options <cf/import target/ and <cf/export target/. If custom filters
+are used, note that the export filter of the input channel is applied before
+the route translation, while the import filter of the output channel is applied
+after that.
+
+<p>In contrast to the Pipe protocol, the L3VPN protocol can handle both IPv4 and
+IPv6 routes in one instance, also both IP side and VPN side are represented as
+separate channels, although that may change in the future. The L3VPN is always
+MPLS-aware protocol, therefore a MPLS channel is mandatory. Altogether, L3VPN
+could have up to 5 channels: <cf/ipv4/, <cf/ipv6/, <cf/vpn4/, <cf/vpn6/, and
+<cf/mpls/.
+
+<p><descrip>
+ <tag><label id="l3vpn-route-distinguisher">route distinguisher <m/vpnrd/</tag>
+ The route distinguisher that is attached to routes in the export
+ direction. Mandatory.
+
+ <tag><label id="l3vpn-rd">rd <m/vpnrd/</tag>
+ A shorthand for the option <cf/route distinguisher/.
+
+ <tag><label id="l3vpn-import-target">import target <m/ec/|<m/ec-set/</tag>
+ Route target extended communities specifying which routes should be
+ imported. Either one community or a set. A route is imported if there is
+ non-empty intersection between extended communities of the route and the
+ import target of the L3VPN protocol. Mandatory.
+
+ <tag><label id="l3vpn-export-target">export target <m/ec/|<m/ec-set/</tag>
+ Route target extended communities that are attached to the route in the
+ export direction. Either one community or a set. Other route target
+ extended communities are removed. Mandatory.
+
+ <tag><label id="l3vpn-route-target">route target <m/ec/|<m/ec-set/</tag>
+ A shorthand for both <cf/import target/ and <cf/export target/.
+</descrip>
+
+<sect1>Attributes
+<label id="l3vpn-attr">
+
+<p>The L3VPN protocol does not define any route attributes.
+
+<sect1>Example
+<label id="l3vpn-exam">
+
+<p>Here is an example of L3VPN setup with one VPN and BGP uplink. IP routes
+learned from a customer in the VPN are stored in <cf/vrf0vX/ tables, which are
+mapped to kernel VRF vrf0. Routes can also be exchanged through BGP with
+different sites hosting that VPN. Forwarding of VPN traffic through the network
+is handled by MPLS.
+
+<p>Omitted from the example are some routing protocol to exchange routes with
+the customer and some sort of MPLS-aware IGP to resolve next hops for BGP VPN
+routes.
+
+<code>
+# MPLS basics
+mpls domain mdom;
+mpls table mtab;
+
+protocol kernel krt_mpls {
+ mpls { table mtab; export all; };
+}
+
+vpn4 table vpntab4;
+vpn6 table vpntab6;
+
+# Exchange VPN routes through BGP
+protocol bgp {
+ vpn4 { table vpntab4; import all; export all; };
+ vpn6 { table vpntab6; import all; export all; };
+ mpls { label policy aggregate; };
+ local 10.0.0.1 as 10;
+ neighbor 10.0.0.2 as 10;
+}
+
+# VRF 0
+ipv4 table vrf0v4;
+ipv6 table vrf0v6;
+
+protocol kernel kernel0v4 {
+ vrf "vrf0";
+ ipv4 { table vrf0v4; export all; };
+ kernel table 100;
+}
+
+protocol kernel kernel0v6 {
+ vrf "vrf0";
+ ipv6 { table vrf0v6; export all; };
+ kernel table 100;
+}
+
+protocol l3vpn l3vpn0 {
+ vrf "vrf0";
+ ipv4 { table vrf0v4; };
+ ipv6 { table vrf0v6; };
+ vpn4 { table vpntab4; };
+ vpn6 { table vpntab6; };
+ mpls { label policy vrf; };
+
+ rd 10:12;
+ import target [(rt, 10, 32..40)];
+ export target [(rt, 10, 30), (rt, 10, 31)];
+}
+</code>
+
+
<sect>MRT
<label id="mrt">
@@ -4466,6 +4996,21 @@ definitions, prefix definitions and DNS definitions:
options and there is a short variant <cf>dnssl <m/domain/</cf> that just
specifies one DNS search domain.
+ <tag><label id="radv-custom-option">custom option type <m/number/ value <m/bytestring/</tag>
+ Custom option definitions allow to define an arbitrary option to
+ advertise. You need to specify the option type number and the binary
+ payload of the option. The length field is calculated automatically.
+ Like <cf/rdnss/ above, multiple definitions are cumulative, they can
+ be used also as interface-specific options.
+
+ The following example advertises PREF64 option (<rfc id="8781">) with
+ prefix <cf>2001:db8:a:b::/96</cf> and the lifetime of <cf/1 hour/:
+
+ <label id="radv-custom-option-exam">
+<p><code>
+custom option type 38 value hex:0e:10:20:01:0d:b8:00:0a:00:0b:00:00:00:00;
+</code>
+
<tag><label id="radv-trigger">trigger <m/prefix/</tag>
RAdv protocol could be configured to change its behavior based on
availability of routes. When this option is used, the protocol waits in
@@ -4595,6 +5140,10 @@ definitions, prefix definitions and DNS definitions:
<tag><label id="radv-iface-dnssl-local">dnssl local <m/switch/</tag>
Use only local DNSSL definitions for this interface. See <cf/rdnss local/
option above. Default: no.
+
+ <tag><label id="radv-iface-custom-local">custom option local <m/switch/</tag>
+ Use only local custom option definitions for this interface. See <cf/rdnss local/
+ option above. Default: no.
</descrip>
<p>Prefix specific options
@@ -5238,6 +5787,11 @@ but only routes of the same network type are allowed, as the static protocol
has just one channel. E.g., to have both IPv4 and IPv6 static routes, define two
static protocols, each with appropriate routes and channel.
+<p>The Static protocol can be configured as MPLS-aware (by defining both the
+primary channel and MPLS channel). In that case the Static protocol assigns
+labels to IP routes and automatically announces corresponding MPLS route for
+each labeled route.
+
<p>Global options:
<descrip>
@@ -5261,16 +5815,20 @@ static protocols, each with appropriate routes and channel.
<ref id="type-prefix" name="dependent on network type">.
<descrip>
- <tag>route <m/prefix/ via <m/ip/|<m/"interface"/ [<m/per-nexthop options/] [via ...]</tag>
- Regular routes may bear one or more <ref id="route-next-hop" name="next hops">.
- Every next hop is preceded by <cf/via/ and configured as shown.
+ <tag>route <m/prefix/ [mpls <m/number/] via <m/ip/|<m/"interface"/ [<m/per-nexthop options/] [via ...]</tag>
+ Regular routes may bear one or more <ref id="route-next-hop" name="next
+ hops">. Every next hop is preceded by <cf/via/ and configured as shown.
+
+ When the Static protocol is MPLS-aware, the optional <cf/mpls/ statement
+ after <m/prefix/ specifies a static label for the labeled route, instead
+ of using dynamically allocated label.
- <tag>route <m/prefix/ recursive <m/ip/ [mpls <m/num/[/<m/num/[/<m/num/[...]]]]</tag>
+ <tag>route <m/prefix/ [mpls <m/number/] recursive <m/ip/ [mpls <m/num/[/<m/num/[/<m/num/[...]]]]</tag>
Recursive nexthop resolves the given IP in the configured IGP table and
uses that route's next hop. The MPLS stacks are concatenated; on top is
the IGP's nexthop stack and on bottom is this route's stack.
- <tag>route <m/prefix/ blackhole|unreachable|prohibit</tag>
+ <tag>route <m/prefix/ [mpls <m/number/] blackhole|unreachable|prohibit</tag>
Special routes specifying to silently drop the packet, return it as
unreachable or return it as administratively prohibited. First two
targets are also known as <cf/drop/ and <cf/reject/.
@@ -5287,8 +5845,8 @@ as the destination becomes adjacent again.
(i.e., they can be used multiple times for a route, one time for each nexthop).
Syntactically, they are not separate options but just parts of <cf/route/
statement after each <cf/via/ statement, not separated by semicolons. E.g.,
-statement <cf/route 10.0.0.0/8 via 192.0.2.1 bfd weight 1 via 192.0.2.2 weight
-2;/ describes a route with two nexthops, the first nexthop has two per-nexthop
+statement <cf>route 10.0.0.0/8 via 192.0.2.1 bfd weight 1 via 192.0.2.2 weight
+2;</cf> describes a route with two nexthops, the first nexthop has two per-nexthop
options (<cf/bfd/ and <cf/weight 1/), the second nexthop has just <cf/weight 2/.
<descrip>
diff --git a/doc/reply_codes b/doc/reply_codes
index 02f4e656..71cdf341 100644
--- a/doc/reply_codes
+++ b/doc/reply_codes
@@ -61,6 +61,7 @@ Reply codes of BIRD command-line interface
1023 Show Babel interfaces
1024 Show Babel neighbors
1025 Show Babel entries
+1026 Show MPLS ranges
8000 Reply too long
8001 Route not found
diff --git a/doc/roadmap.md b/doc/roadmap.md
new file mode 100644
index 00000000..f6b9d475
--- /dev/null
+++ b/doc/roadmap.md
@@ -0,0 +1,307 @@
+# Project roadmap
+
+## Planned for 2023
+
+### SNMP AgentX plugin for BIRD status export
+Allow for easier status monitoring.
+
+### BGP Monitoring Protocol (BMP)
+BGP Monitoring Protocol (RFC 7854) is a protocol between a BGP speaker and
+a monitoring node, which is notified about route updates and neighbor state
+changes of the BGP speaker.
+
+### Better coverage of automatic tests
+Functionality tests should cover more possible configurations and
+combinations. Integration tests should run automatically between different OS
+versions and HW architectures. Experimental support for performance regression tests.
+
+### Release 3.0-alpha1
+Missing: MRT, merging
+
+### Show BFD sessions details
+CLI command showing detailed information about BFD sessions state
+
+### Review and merge Babel extended next hop patches (RFC 9229)
+Babel extension to allow IPv4 routes with IPv6 next hop. Patch on mailing list.
+
+### Consolidate protocol statistics
+Consolidate protocol statistics, make them useful for SNMP plugin and implement
+'show XX stats' command.
+
+### TCP-AO if it appears in Linux and BSD upstream
+Resolve whether we should or shouldn't control the kernel key management.
+Design and implement our side for both Linux and BSD.
+
+### Conditional routes (v3)
+Filters should be extended to allow conditional expressions based on a number of
+matching routes in a routing table. This would allow to specify aggregate routes
+using a static protocol and conditions like 'if there is at least 1000 routes
+from this BGP protocol, accept this default route'. This feature comes handy
+when a router needs to detect whether its BGP upstream is alive and working.
+Based of number of routes received, the router can then announce or retract a
+default route to OSPF, making multi-exit network routing simpler and more
+effective.
+
+### Aggregating routes
+Requested by customer: aggregating multiple routes by a common set of attributes.
+
+Implementation choice: the user specifies
+
+ EXPORT filter before aggregation AGGREGATE ON list of expressions to compare MERGE what to do with the remaining attributes
+
+Example usage:
+
+* aggregating information from multiple internal BGP routes into one external
+* creating a multipath route from multiple BGP routes (currently done by MERGE PATHS)
+* (in future) computing a minimal route set for kernel to make forwarding faster instead of writing the received full BGP set there
+
+### PREF64 option in RA (RFC 8781)
+Inform hosts about prefix used to synthesize NAT64 addresses. Requested in list:
+http://trubka.network.cz/pipermail/bird-users/2022-November/016401.html
+
+### Logging via UDP
+Got a patch, probably never merged. May be useful.
+http://trubka.network.cz/pipermail/bird-users/2022-January/015893.html
+
+### BGP Tunnel Encapsulation Attribute (RFC 9012)
+Packets sent to BGP next hop may be encapsulated using various tunnel
+technologies. Useful for L3VPN.
+
+### BGP AS Cones and ASPA support
+Extend the RPKI protocol with AS Cones and ASPA loading. Implement AS Cones
+and ASPA validation routines. There may be some pending patches from QRator.
+
+### DHCPv6 relay agent
+DHCPv6 relay agents (RFC 8415, RFC 8987) forward DHCPv6 messages between clients and
+servers. They also ensure that prefixes delegated by DHCPv6-PD are routable,
+i.e. they should generate routes for these prefixes.
+
+### Nexthop attributes and ECMP filtering
+Currently we have route attributes, but with ECMP routes it is necessary to
+store per-nexthop data (like weight or encapsulation). We also do not have
+proper way to manipulate with multiple nexthops from filters. Attributes should
+be extended to allow per-nexthop ones and filters should be extended to allow
+access multiple nexthops and their attributes.
+
+### Performance accounting
+Extended internal statistics about time spent in different modules of BIRD. If
+the route server admin checks why it takes 15 minutes to converge, this should
+give some basic info about performance. [MM: Internally needed by 3.0, already in progress]
+
+### MPLS support
+Finalize and merge improved MPLS infrastructure (including MPLS label allocator
+and supporting code), improve its reconfiguration support and support for
+segment routing.
+
+### BGP Segment Routing Extension (RFC 8669)
+Receive and announce Segment Identifiers (SIDs) for BGP next hops.
+
+## Backlog for following years
+
+*The order of these items is not significant.*
+
+### Flowspec attribute filtering
+Flowspec routes have many parameters, but these are not accessible from filters.
+Filters should be extended to access all these attributes, but first it is
+necessary to cleanup attribute handling in filters.
+
+### BGP Optimal Route Reflection (RFC 9107)
+Implement BGP best route selection on route reflectors to adhere to POV of
+client, not RR. Also requested by somebody, don't remember who and when.
+
+### OSPF Traffic engineering extensions (RFC 3630)
+Requested in list. May include lots of other RFC's as we have neglected this
+feature for a long time.
+http://trubka.network.cz/pipermail/bird-users/2022-January/015911.html
+
+### IPv6 preference in documentation (?)
+Address world's reluctance of legacy IPv4 deprecation by updating the
+documentation in such a way that IPv6 is preferred and first seen.
+
+### BGP local prefix leak prevention (?)
+Reject local prefixes on eBGP sessions by default to prevent leaks to public Internet.
+Unless explicitly enabled by config, of course.
+
+### Re-bogonization of 240/4 legacy range (?)
+We shouldn't believe that every operator does the
+filtering right and they could simply rely on pre-2.0.10 behavior which
+filtered this out by default.
+
+### IPv4 multicast
+Basic infrastructure for IPv4 multicast routing, including nettypes for
+multicast routes and multicast requests, multicast kernel protocol and IGMPv2
+protocol.
+
+### PIM-BIDIR
+Bidirectional PIM (RFC 5015) is a multicast routing protocol, variant of PIM-SM.
+It uses bidirectional shared trees rooted in Rendezvous Point (RP) to connect
+sources and receivers.
+
+There is an old branch containing this. We should have merged this years ago.
+
+### Improved VRF support
+BIRD has working VRF support, but it needs improvements. VRF entities should be
+first-class objects with explicit configuration, with a set of properties and
+default values (like default routing tables, or router ID) for associated
+protocols. Default kernel table ID should be autodetected. There should be
+better handling of VRF route leaking - when a route is propagated between VRFs,
+its nexthop should reflects that. Setup of VRFs in OS is out of scope.
+
+### Linux kernel nexthop abstraction
+Netlink allows setting nexthops as objects and using them in routes. It should
+be much faster than conventional route update.
+
+### Protocol attributes for filtering
+Filters can access route attributes, but sometimes it could be useful to access
+attributes of associated protocol (like neighbor-as or neighbor-ip for BGP
+protocol). But it would require to have internal object model (below) first,
+as we do not want to implement it independently for each protocol attribute.
+
+### Mutable static routes
+Extension to the static protocol that would allow to add/remove/change static
+routes from CLI.
+
+### Multipipe
+Pipe-like protocol: When a route is exported to this protocol, it runs its
+filter extended with capability to announce any number of new routes to any
+table from one filter run. Its primary purpose is to allow user-specified
+route aggregation and other non-linear operations.
+
+### BGP minimum route advertisement interval (MRAI)
+BGP specifies minimum interval between route advertisements for the same
+network. This is not implemented in BIRD. It should be implemented for 3.0 to
+avoid unnecessary re-routing spikes.
+
+### OSPF unnumbered interfaces
+The OSPFv2 protocol allows interfaces that do not have proper IP range but have
+peer IP addresses (like PtP links). It should be extended to also allow true
+unnumbered interfaces with no addresses (by using an IP address from some
+loopback device). This would require to have stricter separation between IP
+addresses and interfaces in OSPFv2.
+
+### OSPF Segment Routing Extension (RFC 8665)
+MPLS label distribution using segment routing and simple OSPF extension.
+
+### MPLS Label Distribution Protocol (LDP)
+Label Distribution Protocol (RFC 5036) is a protocol for establishing
+label-switched paths and distributing of MPLS labels between MPLS routers.
+These paths and labels are based on existing unlabeled routing information.
+
+### IPv6 multicast
+Basic infrastructure for IPv6 multicast routing, including nettypes for
+multicast routes and multicast requests, multicast kernel protocol and MLDv1
+protocol. Most of these (with the exception of MLDv1) is just a variant of
+IPv4 multicast.
+
+### IGMP/MLD multicast proxy
+A simple IGMP/MLD multicast proxy, which sends IGMP/MLD requests on a configured
+uplink interface based on received requests on downlink interfaces, and updates
+associated multicast routes.
+
+### Source-specific multicast (SSM)
+Infrastructure for multicasts should be extended to handle source-specific
+multicasts. Extend multicast nettypes to include source addresses, handle them
+in multicast kernel protocols and implement IGMPv3/MLDv2 protocols.
+
+### PIM-SSM
+PIM-SSM is a source-specific multicast routing protocol, a subset of PIM-SM
+protocol (RFC 7761). It is restricted to source-specific multicasts, which
+eliminates many problematic parts of PIM-SM.
+
+### Seamless BFD
+New version of BFD negotiation defined in RFC 7880-7886 enables faster
+continuity tests by dissemination discriminators by the governing protocols.
+
+### OSPF Graceful Link Shutdown
+To enable seamless maintenance of single links, OSPF can advertise such a link
+getting down in advance, allowing to re-route. Defined in RFC 8379.
+
+## Long-term
+
+### Internal object model
+We need to define explicit internal object model, where existing objects
+(protocols, channels, tables, routes, interfaces ...) and their properties are
+described in a way that allows introspection sufficient for implementing
+features (control protocol, CLI, filter access, perhaps reconfiguration) in a
+generic manner.
+
+### Generic configuration model
+Configuration options are implicitly defined by the configuration parsing code.
+We need to define explicit configuration model independent of the parsing code
+and generic parsing code using that model. This will allow uniform validation of
+configuration properties, generic access to configuration from control protocol
+and possibly independent configuration backends (like one for Netconf).
+
+### New control protocol
+BIRD should have a well-documented machine readable protocol. Requirements for
+such protocol are:
+
+* Generic machine readable abstract-tree representation (like CBOR)
+* Both request/reply and subscribe/notify access patterns
+* Access objects and properties using internal object model
+* In-band introspection based on internal object model
+
+From Maria's notes:
+
+* CBOR-based protocol for both control and route exports
+* Python3 library with example implementation of CLI
+* (maybe) Ansible modules
+* RFC 9164: CBOR tags for IP addresses and prefices
+* RFC 9254: YANG-CBOR mapping
+* RFC 9277: Stable storage of CBOR (files)
+
+## Perhaps
+
+### IS-IS
+IS-IS routing protocol is a nice-to-have alternative to OSPF.
+
+### BGPsec
+BGPsec (RFC 8205) is a new path security extension to BGP.
+
+### PIM-SM
+PIM-SM (RFC 7761) is a prevailing multicast routing protocol, but more
+complicated than planned PIM-BIDIR and PIM-SSM.
+
+### Netconf
+Network Configuration Protocol (RFC 6241) is a XML/JSON protocol for
+configuration management of network devices. It would benefit from generic
+configuration model (above).
+
+### NetConf overlay
+Machine-friendly config file editor daemon (standalone) with standard NetConf
+interface on one side and BIRD config file + reconfiguration requests on the
+other side. Python3 seems to be better choice than C for this kind of work.
+
+### Backend for 802.11r
+Let's assume a bunch of boxes, all having some public wifi APs and some (secure) uplinks.
+Design and implement an automatic backbone protocol to allow for simple almost-zeroconf
+setup of e.g. a conference room or train / bus public wifi or even a local home network,
+all with hostapd seamlessly transferring clients between APs via 802.11r.
+Possible collab with Turris.
+
+### BFD Multipoint Connectivity
+Checking whether multiple "receivers" can communicate with a single "sender".
+Possibly useful after merging PIM-BIDIR and implementing other PIMs. RFC 8562-8563.
+
+### BGP Link State extension
+BGP-LS allows to transport information about network topology across BGP links.
+This should help e.g. to run traffic-engineering between more confederated ASs.
+Also needed to implement Seamless BFD on BGP: RFC 9247
+
+### Locator/ID Separation Protocol
+LISP intends to break up addressing to Routing Locators and Endpoint
+Identifiers. This may help multihoming networks in future. RFC 9299-9306.
+
+### Backend for IPv6 Multihoming without BGP
+Implement and configure BIRD in such a way that local nodes are seamlessly
+connected to the Internet via multiple upstreams, using Network Prefix
+Translation and other techniques. Possible collab with Turris.
+
+## Minor
+
+* RFC 8510: OSPF LLS Extension for Local Interface ID Advertisement
+* RFC 8538: BGP Graceful Restart Hard Reset
+* RFC 8326: BGP Graceful Session Shutdown Community auto-apply
+* RFC 8962: Become part of the IETF Protocol Police
+* RFC 9072: Extended Optional Parameters Length for BGP OPEN Message
+* RFC 9339: OSPF Reverse Metric
diff --git a/filter/config.Y b/filter/config.Y
index a1e5e9f1..f3ed2dc5 100644
--- a/filter/config.Y
+++ b/filter/config.Y
@@ -19,8 +19,63 @@ static inline u32 pair(u32 a, u32 b) { return (a << 16) | b; }
static inline u32 pair_a(u32 p) { return p >> 16; }
static inline u32 pair_b(u32 p) { return p & 0xFFFF; }
-#define f_generate_complex(fi_code, da, arg) \
- f_new_inst(FI_EA_SET, f_new_inst(fi_code, f_new_inst(FI_EA_GET, da), arg), da)
+static struct symbol *this_function;
+static struct sym_scope *this_for_scope;
+
+static struct f_method_scope {
+ struct f_inst *object;
+ struct sym_scope *main;
+ struct sym_scope scope;
+} f_method_scope_stack[32];
+static int f_method_scope_pos = -1;
+
+#define FM (f_method_scope_stack[f_method_scope_pos])
+
+static inline void f_method_call_start(struct f_inst *object)
+{
+ if (object->type == T_VOID)
+ cf_error("Can't infer type to properly call a method, please assign the value to a variable");
+ if (++f_method_scope_pos >= (int) ARRAY_SIZE(f_method_scope_stack))
+ cf_error("Too many nested method calls");
+
+ struct sym_scope *scope = f_type_method_scope(object->type);
+ if (!scope)
+ cf_error("No methods defined for type %s", f_type_name(object->type));
+
+ FM = (struct f_method_scope) {
+ .object = object,
+ .main = new_config->current_scope,
+ .scope = {
+ .next = global_root_scope,
+ .hash = scope->hash,
+ .active = 1,
+ .block = 1,
+ .readonly = 1,
+ },
+ };
+ new_config->current_scope = &FM.scope;
+}
+
+static inline void f_method_call_args(void)
+{
+ ASSERT_DIE(FM.scope.active);
+ FM.scope.active = 0;
+
+ new_config->current_scope = FM.main;
+}
+
+static inline void f_method_call_end(void)
+{
+ ASSERT_DIE(f_method_scope_pos >= 0);
+ if (FM.scope.active) {
+ ASSERT_DIE(&FM.scope == new_config->current_scope);
+ new_config->current_scope = FM.main;
+
+ FM.scope.active = 0;
+ }
+
+ f_method_scope_pos--;
+}
static int
f_new_var(struct sym_scope *s)
@@ -191,28 +246,22 @@ f_new_lc_item(u32 f1, u32 t1, u32 f2, u32 t2, u32 f3, u32 t3)
}
static inline struct f_inst *
-f_generate_empty(struct f_dynamic_attr dyn)
+f_const_empty(enum f_type t)
{
- struct f_val empty;
-
- switch (dyn.type & EAF_TYPE_MASK) {
- case EAF_TYPE_AS_PATH:
- empty = f_const_empty_path;
- break;
- case EAF_TYPE_INT_SET:
- empty = f_const_empty_clist;
- break;
- case EAF_TYPE_EC_SET:
- empty = f_const_empty_eclist;
- break;
- case EAF_TYPE_LC_SET:
- empty = f_const_empty_lclist;
- break;
+ switch (t) {
+ case T_PATH:
+ case T_CLIST:
+ case T_ECLIST:
+ case T_LCLIST:
+ return f_new_inst(FI_CONSTANT, (struct f_val) {
+ .type = t,
+ .val.ad = &null_adata,
+ });
+ case T_ROUTE:
+ return f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE });
default:
- cf_error("Can't empty that attribute");
+ return f_new_inst(FI_CONSTANT, (struct f_val) {});
}
-
- return f_new_inst(FI_EA_SET, f_new_inst(FI_CONSTANT, empty), dyn);
}
/*
@@ -270,27 +319,34 @@ assert_done(struct f_inst *expr, const char *start, const char *end)
}
static struct f_inst *
-assert_assign(struct f_lval *lval, struct f_inst *expr, const char *start, const char *end)
+f_lval_getter(struct f_lval *lval)
{
- struct f_inst *setter, *getter, *checker;
switch (lval->type) {
- case F_LVAL_VARIABLE:
- setter = f_new_inst(FI_VAR_SET, expr, lval->sym);
- getter = f_new_inst(FI_VAR_GET, lval->sym);
- break;
- case F_LVAL_SA:
- setter = f_new_inst(FI_RTA_SET, expr, lval->sa);
- getter = f_new_inst(FI_RTA_GET, lval->sa);
- break;
- case F_LVAL_EA:
- setter = f_new_inst(FI_EA_SET, expr, lval->da);
- getter = f_new_inst(FI_EA_GET, lval->da);
- break;
- default:
- bug("Unknown lval type");
+ case F_LVAL_VARIABLE: return f_new_inst(FI_VAR_GET, lval->sym);
+ case F_LVAL_SA: return f_new_inst(FI_RTA_GET, lval->rte, lval->sa);
+ case F_LVAL_EA: return f_new_inst(FI_EA_GET, lval->rte, lval->da);
+ default: bug("Unknown lval type");
}
+}
- checker = f_new_inst(FI_EQ, expr, getter);
+static struct f_inst *
+f_lval_setter(struct f_lval *lval, struct f_inst *expr)
+{
+ switch (lval->type) {
+ case F_LVAL_VARIABLE: return f_new_inst(FI_VAR_SET, expr, lval->sym);
+ case F_LVAL_SA: return f_new_inst(FI_RTA_SET, expr, lval->sa);
+ case F_LVAL_EA: return f_new_inst(FI_EA_SET, expr, lval->da);
+ default: bug("Unknown lval type");
+ }
+}
+
+static struct f_inst *
+assert_assign(struct f_lval *lval, struct f_inst *expr, const char *start, const char *end)
+{
+ struct f_inst *setter = f_lval_setter(lval, expr),
+ *getter = f_lval_getter(lval);
+
+ struct f_inst *checker = f_new_inst(FI_EQ, expr, getter);
setter->next = checker;
return assert_done(setter, start, end);
@@ -298,43 +354,41 @@ assert_assign(struct f_lval *lval, struct f_inst *expr, const char *start, const
CF_DECLS
+CF_KEYWORDS_EXCLUSIVE(IN)
CF_KEYWORDS(FUNCTION, PRINT, PRINTN, UNSET, RETURN,
ACCEPT, REJECT, ERROR,
- INT, BOOL, IP, TYPE, PREFIX, RD, PAIR, QUAD, EC, LC,
- SET, STRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
+ INT, BOOL, IP, PREFIX, RD, PAIR, QUAD, EC, LC,
+ SET, STRING, BYTESTRING, BGPMASK, BGPPATH, CLIST, ECLIST, LCLIST,
IF, THEN, ELSE, CASE,
- FOR, IN, DO,
+ FOR, DO,
TRUE, FALSE, RT, RO, UNKNOWN, GENERIC,
- FROM, GW, NET, MASK, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS, ONLINK,
+ FROM, GW, NET, PROTO, SOURCE, SCOPE, DEST, IFNAME, IFINDEX, WEIGHT, GW_MPLS, ONLINK,
PREFERENCE,
- ROA_CHECK, ASN, SRC, DST,
- IS_V4, IS_V6,
- LEN, MAXLEN,
- DATA, DATA1, DATA2,
+ ROA_CHECK,
DEFINED,
- ADD, DELETE, CONTAINS, RESET,
- PREPEND, FIRST, LAST, LAST_NONAGGREGATED, MATCH,
- MIN, MAX,
+ ADD, DELETE, RESET,
+ PREPEND,
EMPTY,
FILTER, WHERE, EVAL, ATTRIBUTE,
+ FROM_HEX,
BT_ASSERT, BT_TEST_SUITE, BT_CHECK_ASSIGN, BT_TEST_SAME, FORMAT)
%nonassoc THEN
%nonassoc ELSE
%type <xp> cmds_int cmd_prep
-%type <x> term cmd cmd_var cmds cmds_scoped constant constructor print_list var var_init var_list function_call symbol_value bgp_path_expr bgp_path bgp_path_tail
+%type <x> term term_bs cmd cmd_var cmds cmds_scoped constant constructor var var_list var_list_r function_call symbol_value bgp_path_expr bgp_path bgp_path_tail term_dot_method method_name_cont
%type <fda> dynamic_attr
%type <fsa> static_attr
%type <f> filter where_filter
%type <fl> filter_body function_body
%type <flv> lvalue
-%type <i> type function_vars
+%type <i> type function_vars function_type
%type <fa> function_argsn function_args
%type <ecs> ec_kind
%type <fret> break_command
%type <i32> cnum
-%type <e> pair_item ec_item lc_item set_item switch_item set_items switch_items switch_body
+%type <e> pair_item ec_item lc_item set_item switch_item ec_items set_items switch_items switch_body
%type <trie> fprefix_set
%type <v> set_atom switch_atom fipa
%type <px> fprefix
@@ -345,29 +399,32 @@ CF_GRAMMAR
conf: filter_def ;
filter_def:
- FILTER symbol { $2 = cf_define_symbol($2, SYM_FILTER, filter, NULL); cf_push_scope( $2 ); }
- filter_body {
+ FILTER symbol {
+ $2 = cf_define_symbol(new_config, $2, SYM_FILTER, filter, NULL);
+ cf_push_scope( new_config, $2 );
+ this_function = NULL;
+ } filter_body {
struct filter *f = cfg_alloc(sizeof(struct filter));
*f = (struct filter) { .sym = $2, .root = $4 };
$2->filter = f;
- cf_pop_scope();
+ cf_pop_scope(new_config);
}
;
conf: filter_eval ;
filter_eval:
- EVAL term { f_eval_int(f_linearize($2, 1)); }
+ EVAL term { cf_eval_int($2); }
;
conf: custom_attr ;
custom_attr: ATTRIBUTE type symbol ';' {
- cf_define_symbol($3, SYM_ATTRIBUTE, attribute, ca_lookup(new_config->pool, $3->name, $2)->fda);
+ cf_define_symbol(new_config, $3, SYM_ATTRIBUTE, attribute, ca_lookup(new_config->pool, $3->name, $2)->fda);
};
conf: bt_test_suite ;
bt_test_suite:
- BT_TEST_SUITE '(' CF_SYM_KNOWN ',' text ')' {
+ BT_TEST_SUITE '(' symbol_known ',' text ')' {
cf_assert_symbol($3, SYM_FUNCTION);
struct f_bt_test_suite *t = cfg_allocz(sizeof(struct f_bt_test_suite));
t->fn = $3->function;
@@ -380,7 +437,7 @@ bt_test_suite:
conf: bt_test_same ;
bt_test_same:
- BT_TEST_SAME '(' CF_SYM_KNOWN ',' CF_SYM_KNOWN ',' NUM ')' {
+ BT_TEST_SAME '(' symbol_known ',' symbol_known ',' NUM ')' {
cf_assert_symbol($3, SYM_FUNCTION);
cf_assert_symbol($5, SYM_FUNCTION);
struct f_bt_test_suite *t = cfg_allocz(sizeof(struct f_bt_test_suite));
@@ -404,11 +461,13 @@ type:
| EC { $$ = T_EC; }
| LC { $$ = T_LC; }
| STRING { $$ = T_STRING; }
+ | BYTESTRING { $$ = T_BYTESTRING; }
| BGPMASK { $$ = T_PATH_MASK; }
| BGPPATH { $$ = T_PATH; }
| CLIST { $$ = T_CLIST; }
| ECLIST { $$ = T_ECLIST; }
| LCLIST { $$ = T_LCLIST; }
+ | ROUTE { $$ = T_ROUTE; }
| type SET {
switch ($1) {
case T_INT:
@@ -436,7 +495,7 @@ function_argsn:
| function_argsn type symbol ';' {
if ($3->scope->slots >= 0xfe) cf_error("Too many declarations, at most 255 allowed");
$$ = cfg_alloc(sizeof(struct f_arg));
- $$->arg = cf_define_symbol($3, SYM_VARIABLE | $2, offset, sym_->scope->slots++);
+ $$->arg = cf_define_symbol(new_config, $3, SYM_VARIABLE | $2, offset, sym_->scope->slots++);
$$->next = $1;
}
;
@@ -445,7 +504,7 @@ function_args:
'(' ')' { $$ = NULL; }
| '(' function_argsn type symbol ')' {
$$ = cfg_alloc(sizeof(struct f_arg));
- $$->arg = cf_define_symbol($4, SYM_VARIABLE | $3, offset, sym_->scope->slots++);
+ $$->arg = cf_define_symbol(new_config, $4, SYM_VARIABLE | $3, offset, sym_->scope->slots++);
$$->next = $2;
}
;
@@ -453,24 +512,32 @@ function_args:
function_vars:
/* EMPTY */ { $$ = 0; }
| function_vars type symbol ';' {
- cf_define_symbol($3, SYM_VARIABLE | $2, offset, f_new_var(sym_->scope));
+ cf_define_symbol(new_config, $3, SYM_VARIABLE | $2, offset, f_new_var(sym_->scope));
$$ = $1 + 1;
}
;
+function_type:
+ /* EMPTY */ { $$ = T_VOID; }
+ | IMP type { $$ = $2; }
+ ;
+
filter_body: function_body ;
filter:
- CF_SYM_KNOWN {
+ symbol_known {
cf_assert_symbol($1, SYM_FILTER);
$$ = $1->filter;
}
- | { cf_push_scope(NULL); } filter_body {
+ | {
+ cf_push_scope(new_config, NULL);
+ this_function = NULL;
+ } filter_body {
struct filter *f = cfg_alloc(sizeof(struct filter));
*f = (struct filter) { .root = $2 };
$$ = f;
- cf_pop_scope();
+ cf_pop_scope(new_config);
}
;
@@ -489,15 +556,18 @@ function_body:
;
conf: function_def ;
+
function_def:
FUNCTION symbol {
DBG( "Beginning of function %s\n", $2->name );
- $2 = cf_define_symbol($2, SYM_FUNCTION, function, NULL);
- cf_push_scope($2);
- } function_args {
+ this_function = cf_define_symbol(new_config, $2, SYM_FUNCTION, function, NULL);
+ cf_push_scope(new_config, this_function);
+ } function_args function_type {
/* Make dummy f_line for storing function prototype */
struct f_line *dummy = cfg_allocz(sizeof(struct f_line));
- $2->function = dummy;
+ this_function->function = dummy;
+
+ dummy->return_type = $5;
/* Revert the args */
while ($4) {
@@ -509,10 +579,11 @@ function_def:
dummy->args++;
}
} function_body {
- $6->args = $2->function->args;
- $6->arg_list = $2->function->arg_list;
- $2->function = $6;
- cf_pop_scope();
+ $7->args = this_function->function->args;
+ $7->arg_list = this_function->function->arg_list;
+ $7->return_type = this_function->function->return_type;
+ $2->function = $7;
+ cf_pop_scope(new_config);
}
;
@@ -522,7 +593,7 @@ cmds: /* EMPTY */ { $$ = NULL; }
| cmds_int { $$ = $1.begin; }
;
-cmds_scoped: { cf_push_soft_scope(); } cmds { cf_pop_soft_scope(); $$ = $2; } ;
+cmds_scoped: { cf_push_soft_scope(new_config); } cmds { cf_pop_soft_scope(new_config); $$ = $2; } ;
cmd_var: var | cmd ;
@@ -571,10 +642,10 @@ set_atom:
| VPN_RD { $$.type = T_RD; $$.val.ec = $1; }
| ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); }
| '(' term ')' {
- if (f_eval(f_linearize($2, 1), cfg_mem, &($$)) > F_RETURN) cf_error("Runtime error");
+ $$ = cf_eval($2, T_VOID);
if (!f_valid_set_type($$.type)) cf_error("Set-incompatible type");
}
- | CF_SYM_KNOWN {
+ | symbol_known {
cf_assert_symbol($1, SYM_CONSTANT);
if (!f_valid_set_type(SYM_TYPE($1))) cf_error("%s: set-incompatible type", $1->name);
$$ = *$1->val;
@@ -583,13 +654,13 @@ set_atom:
switch_atom:
NUM { $$.type = T_INT; $$.val.i = $1; }
- | '(' term ')' { $$.type = T_INT; $$.val.i = f_eval_int(f_linearize($2, 1)); }
+ | '(' term ')' { $$ = cf_eval($2, T_INT); }
| fipa { $$ = $1; }
| ENUM { $$.type = pair_a($1); $$.val.i = pair_b($1); }
;
cnum:
- term { $$ = f_eval_int(f_linearize($1, 1)); }
+ term { $$ = cf_eval_int($1); }
pair_item:
'(' cnum ',' cnum ')' { $$ = f_new_pair_item($2, $2, $4, $4); }
@@ -646,6 +717,11 @@ switch_item:
| switch_atom DDOT switch_atom { $$ = f_new_item($1, $3); }
;
+ec_items:
+ ec_item
+ | ec_items ',' ec_item { $$ = f_merge_items($1, $3); }
+ ;
+
set_items:
set_item
| set_items ',' set_item { $$ = f_merge_items($1, $3); }
@@ -714,13 +790,14 @@ bgp_path_tail:
;
constant:
- NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); }
- | TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); }
- | FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); }
- | TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); }
- | fipa { $$ = f_new_inst(FI_CONSTANT, $1); }
- | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); }
- | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); }
+ NUM { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_INT, .val.i = $1, }); }
+ | TRUE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }); }
+ | FALSE { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }); }
+ | TEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_STRING, .val.s = $1, }); }
+ | BYTETEXT { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BYTESTRING, .val.bs = $1, }); }
+ | fipa { $$ = f_new_inst(FI_CONSTANT, $1); }
+ | VPN_RD { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_RD, .val.ec = $1, }); }
+ | net_ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_NET, .val.net = $1, }); }
| '[' ']' { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_SET, .val.t = NULL, }); }
| '[' set_items ']' {
DBG( "We've got a set here..." );
@@ -739,32 +816,39 @@ constructor:
;
-/* This generates the function_call variable list backwards. */
-var_list: /* EMPTY */ { $$ = NULL; }
+/* This generates the function_call variable list backwards */
+var_list_r:
+ /* EMPTY */ { $$ = NULL; }
| term { $$ = $1; }
- | var_list ',' term { $$ = $3; $$->next = $1; }
+ | var_list_r ',' term { $$ = $3; $$->next = $1; }
+ ;
-function_call:
- CF_SYM_KNOWN '(' var_list ')'
+var_list: var_list_r
{
- if ($1->class != SYM_FUNCTION)
- cf_error("You can't call something which is not a function. Really.");
+ $$ = NULL;
- /* Revert the var_list */
- struct f_inst *args = NULL;
- while ($3) {
- struct f_inst *tmp = $3;
- $3 = $3->next;
+ /* Revert the var_list_r */
+ while ($1) {
+ struct f_inst *tmp = $1;
+ $1 = $1->next;
- tmp->next = args;
- args = tmp;
+ tmp->next = $$;
+ $$ = tmp;
}
+ }
+ ;
- $$ = f_new_inst(FI_CALL, args, $1);
+function_call:
+ symbol_known '(' var_list ')'
+ {
+ if ($1->class != SYM_FUNCTION)
+ cf_error("You can't call something which is not a function. Really.");
+
+ $$ = f_new_inst(FI_CALL, $3, $1);
}
;
-symbol_value: CF_SYM_KNOWN
+symbol_value: symbol_known
{
switch ($1->class) {
case SYM_CONSTANT_RANGE:
@@ -774,7 +858,7 @@ symbol_value: CF_SYM_KNOWN
$$ = f_new_inst(FI_VAR_GET, $1);
break;
case SYM_ATTRIBUTE:
- $$ = f_new_inst(FI_EA_GET, *$1->attribute);
+ $$ = f_new_inst(FI_EA_GET, f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL }), *$1->attribute);
break;
default:
cf_error("Can't get value of symbol %s", $1->name);
@@ -798,6 +882,28 @@ static_attr:
| ONLINK { $$ = f_new_static_attr(T_BOOL, SA_ONLINK, 0); }
;
+term_dot_method: term '.' { f_method_call_start($1); } method_name_cont { f_method_call_end(); $$ = $4; };
+method_name_cont:
+ CF_SYM_METHOD_BARE {
+ $$ = f_dispatch_method($1, FM.object, NULL, 1);
+ }
+ | CF_SYM_METHOD_ARGS {
+ f_method_call_args();
+ } '(' var_list ')' {
+ $$ = f_dispatch_method($1, FM.object, $4, 1);
+ }
+ | static_attr {
+ if (FM.object->type != T_ROUTE)
+ cf_error("Getting a route attribute from %s, need a route", f_type_name(FM.object->type));
+ $$ = f_new_inst(FI_RTA_GET, FM.object, $1);
+ }
+ | dynamic_attr {
+ if (FM.object->type != T_ROUTE)
+ cf_error("Getting a route attribute from %s, need a route", f_type_name(FM.object->type));
+ $$ = f_new_inst(FI_EA_GET, FM.object, $1);
+ }
+ ;
+
term:
'(' term ')' { $$ = $2; }
| term '+' term { $$ = f_new_inst(FI_ADD, $1, $3); }
@@ -821,86 +927,55 @@ term:
| constant { $$ = $1; }
| constructor { $$ = $1; }
- | static_attr { $$ = f_new_inst(FI_RTA_GET, $1); }
-
- | dynamic_attr { $$ = f_new_inst(FI_EA_GET, $1); }
-
- | term '.' IS_V4 { $$ = f_new_inst(FI_IS_V4, $1); }
- | term '.' TYPE { $$ = f_new_inst(FI_TYPE, $1); }
- | term '.' IP { $$ = f_new_inst(FI_IP, $1); }
- | term '.' RD { $$ = f_new_inst(FI_ROUTE_DISTINGUISHER, $1); }
- | term '.' LEN { $$ = f_new_inst(FI_LENGTH, $1); }
- | term '.' MAXLEN { $$ = f_new_inst(FI_ROA_MAXLEN, $1); }
- | term '.' ASN { $$ = f_new_inst(FI_ASN, $1); }
- | term '.' SRC { $$ = f_new_inst(FI_NET_SRC, $1); }
- | term '.' DST { $$ = f_new_inst(FI_NET_DST, $1); }
- | term '.' MASK '(' term ')' { $$ = f_new_inst(FI_IP_MASK, $1, $5); }
- | term '.' FIRST { $$ = f_new_inst(FI_AS_PATH_FIRST, $1); }
- | term '.' LAST { $$ = f_new_inst(FI_AS_PATH_LAST, $1); }
- | term '.' LAST_NONAGGREGATED { $$ = f_new_inst(FI_AS_PATH_LAST_NAG, $1); }
- | term '.' DATA { $$ = f_new_inst(FI_PAIR_DATA, $1); }
- | term '.' DATA1 { $$ = f_new_inst(FI_LC_DATA1, $1); }
- | term '.' DATA2 { $$ = f_new_inst(FI_LC_DATA2, $1); }
- | term '.' MIN { $$ = f_new_inst(FI_MIN, $1); }
- | term '.' MAX { $$ = f_new_inst(FI_MAX, $1); }
-
-/* Communities */
-/* This causes one shift/reduce conflict
- | dynamic_attr '.' ADD '(' term ')' { }
- | dynamic_attr '.' DELETE '(' term ')' { }
- | dynamic_attr '.' CONTAINS '(' term ')' { }
- | dynamic_attr '.' RESET{ }
-*/
-
- | '+' EMPTY '+' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_path); }
- | '-' EMPTY '-' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_clist); }
- | '-' '-' EMPTY '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_eclist); }
- | '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, f_const_empty_lclist); }
- | PREPEND '(' term ',' term ')' { $$ = f_new_inst(FI_PATH_PREPEND, $3, $5); }
- | ADD '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_ADD, $3, $5); }
- | DELETE '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_DEL, $3, $5); }
- | FILTER '(' term ',' term ')' { $$ = f_new_inst(FI_CLIST_FILTER, $3, $5); }
+ | static_attr { $$ = f_new_inst(FI_RTA_GET, f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL }), $1); }
+
+ | dynamic_attr { $$ = f_new_inst(FI_EA_GET, f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL }), $1); }
+
+ | term_dot_method
+
+ | '+' EMPTY '+' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_PATH)); }
+ | '-' EMPTY '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_CLIST)); }
+ | '-' '-' EMPTY '-' '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_ECLIST)); }
+ | '-' '-' '-' EMPTY '-' '-' '-' { $$ = f_new_inst(FI_CONSTANT, val_empty(T_LCLIST)); }
+
+| PREPEND '(' term ',' term ')' { $$ = f_dispatch_method_x("prepend", $3->type, $3, $5); }
+ | ADD '(' term ',' term ')' { $$ = f_dispatch_method_x("add", $3->type, $3, $5); }
+ | DELETE '(' term ',' term ')' { $$ = f_dispatch_method_x("delete", $3->type, $3, $5); }
+ | FILTER '(' term ',' term ')' { $$ = f_dispatch_method_x("filter", $3->type, $3, $5); }
| ROA_CHECK '(' rtable ')' { $$ = f_new_inst(FI_ROA_CHECK_IMPLICIT, $3); }
| ROA_CHECK '(' rtable ',' term ',' term ')' { $$ = f_new_inst(FI_ROA_CHECK_EXPLICIT, $5, $7, $3); }
| FORMAT '(' term ')' { $$ = f_new_inst(FI_FORMAT, $3); }
-/* | term '.' LEN { $$->code = P('P','l'); } */
-
+ | term_bs
| function_call
;
+term_bs:
+ FROM_HEX '(' term ')' { $$ = f_new_inst(FI_FROM_HEX, $3); }
+ ;
+
break_command:
ACCEPT { $$ = F_ACCEPT; }
| REJECT { $$ = F_REJECT; }
| ERROR { $$ = F_ERROR; }
;
-print_list: /* EMPTY */ { $$ = NULL; }
- | term { $$ = $1; }
- | term ',' print_list {
- ASSERT($1);
- ASSERT($1->next == NULL);
- $1->next = $3;
- $$ = $1;
- }
- ;
-
-var_init:
- /* empty */ { $$ = f_new_inst(FI_CONSTANT, (struct f_val) { }); }
- | '=' term { $$ = $2; }
- ;
-
var:
- type symbol var_init ';' {
- struct symbol *sym = cf_define_symbol($2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
- $$ = f_new_inst(FI_VAR_INIT, $3, sym);
+ type symbol '=' term ';' {
+ struct symbol *sym = cf_define_symbol(new_config, $2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
+ $$ = f_new_inst(FI_VAR_INIT, $4, sym);
+ }
+ | type symbol ';' {
+ struct symbol *sym = cf_define_symbol(new_config, $2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope));
+ $$ = f_new_inst(FI_VAR_INIT0, sym);
}
+ ;
for_var:
- type symbol { $$ = cf_define_symbol($2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope)); }
- | CF_SYM_KNOWN { $$ = $1; cf_assert_symbol($1, SYM_VARIABLE); }
+ type symbol { $$ = cf_define_symbol(new_config, $2, SYM_VARIABLE | $1, offset, f_new_var(sym_->scope)); }
+ | CF_SYM_KNOWN { cf_error("Use of a pre-defined variable in for loop is not allowed"); }
;
cmd:
@@ -915,17 +990,18 @@ cmd:
}
| FOR {
/* Reserve space for walk data on stack */
- cf_push_block_scope();
- conf_this_scope->slots += 2;
+ cf_push_block_scope(new_config);
+ new_config->current_scope->slots += 2;
} for_var IN
/* Parse term in the parent scope */
- { conf_this_scope->active = 0; } term { conf_this_scope->active = 1; }
+ { this_for_scope = new_config->current_scope; new_config->current_scope = this_for_scope->next; }
+ term
+ { new_config->current_scope = this_for_scope; this_for_scope = NULL; }
DO cmd {
- cf_pop_block_scope();
- $$ = f_new_inst(FI_FOR_INIT, $6, $3);
- $$->next = f_new_inst(FI_FOR_NEXT, $3, $9);
+ cf_pop_block_scope(new_config);
+ $$ = f_for_cycle($3, $6, $9);
}
- | CF_SYM_KNOWN '=' term ';' {
+ | symbol_known '=' term ';' {
switch ($1->class) {
case SYM_VARIABLE_RANGE:
$$ = f_new_inst(FI_VAR_SET, $3, $1);
@@ -939,6 +1015,18 @@ cmd:
}
| RETURN term ';' {
DBG( "Ook, we'll return the value\n" );
+ if (!this_function)
+ cf_error("Can't return from a non-function, use accept or reject instead.");
+ if (this_function->function->return_type == T_VOID)
+ {
+ if ($2->type != T_VOID)
+ cf_warn("Inferring function %s return type from its return value: %s", this_function->name, f_type_name($2->type));
+ ((struct f_line *) this_function->function)->return_type = $2->type;
+ }
+ else if (this_function->function->return_type != $2->type)
+ cf_error("Can't return type %s from function %s, expected %s",
+ f_type_name($2->type), this_function->name, f_type_name(this_function->function->return_type));
+
$$ = f_new_inst(FI_RETURN, $2);
}
| dynamic_attr '=' term ';' {
@@ -952,34 +1040,34 @@ cmd:
| UNSET '(' dynamic_attr ')' ';' {
$$ = f_new_inst(FI_EA_UNSET, $3);
}
- | break_command print_list ';' {
- struct f_inst *breaker = f_new_inst(FI_DIE, $1);
- if ($2) {
- struct f_inst *printer = f_new_inst(FI_PRINT, $2);
- struct f_inst *flusher = f_new_inst(FI_FLUSH);
- printer->next = flusher;
- flusher->next = breaker;
- $$ = printer;
- } else
- $$ = breaker;
+ | UNSET '(' symbol_known ')' ';' {
+ switch ($3->class) {
+ case SYM_ATTRIBUTE:
+ $$ = f_new_inst(FI_EA_UNSET, *$3->attribute);
+ break;
+ default:
+ cf_error("Can't unset symbol %s", $3->name);
+ }
+ }
+ | break_command var_list_r ';' {
+ $$ = f_print($2, !!$2, $1);
}
- | PRINT print_list ';' {
- $$ = f_new_inst(FI_PRINT, $2);
- $$->next = f_new_inst(FI_FLUSH);
+ | PRINT var_list_r ';' {
+ $$ = f_print($2, 1, F_NOP);
}
- | PRINTN print_list ';' {
- $$ = f_new_inst(FI_PRINT, $2);
+ | PRINTN var_list_r ';' {
+ $$ = f_print($2, 0, F_NOP);
}
| function_call ';' { $$ = f_new_inst(FI_DROP_RESULT, $1); }
| CASE term '{' switch_body '}' {
$$ = f_new_inst(FI_SWITCH, $2, $4);
}
-
- | dynamic_attr '.' EMPTY ';' { $$ = f_generate_empty($1); }
- | dynamic_attr '.' PREPEND '(' term ')' ';' { $$ = f_generate_complex( FI_PATH_PREPEND, $1, $5 ); }
- | dynamic_attr '.' ADD '(' term ')' ';' { $$ = f_generate_complex( FI_CLIST_ADD, $1, $5 ); }
- | dynamic_attr '.' DELETE '(' term ')' ';' { $$ = f_generate_complex( FI_CLIST_DEL, $1, $5 ); }
- | dynamic_attr '.' FILTER '(' term ')' ';' { $$ = f_generate_complex( FI_CLIST_FILTER, $1, $5 ); }
+ | lvalue '.' {
+ f_method_call_start(f_lval_getter(&$1));
+ } method_name_cont ';' {
+ f_method_call_end();
+ $$ = f_lval_setter(&$1, $4);
+ }
| BT_ASSERT '(' get_cf_position term get_cf_position ')' ';' { $$ = assert_done($4, $3 + 1, $5 - 1); }
| BT_CHECK_ASSIGN '(' get_cf_position lvalue get_cf_position ',' term ')' ';' { $$ = assert_assign(&$4, $7, $3 + 1, $5 - 1); }
;
@@ -990,8 +1078,21 @@ get_cf_position:
};
lvalue:
- CF_SYM_KNOWN { cf_assert_symbol($1, SYM_VARIABLE); $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1 }; }
- | static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1 }; }
- | dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1 }; };
+ CF_SYM_KNOWN {
+ switch ($1->class)
+ {
+ case SYM_VARIABLE_RANGE:
+ $$ = (struct f_lval) { .type = F_LVAL_VARIABLE, .sym = $1, .rte = f_const_empty(T_ROUTE) };
+ break;
+ case SYM_ATTRIBUTE:
+ $$ = (struct f_lval) { .type = F_LVAL_EA, .da = *($1->attribute), .rte = f_const_empty(T_ROUTE) };
+ break;
+ default:
+ cf_error("Variable name or custom attribute name required");
+ }
+ }
+ | static_attr { $$ = (struct f_lval) { .type = F_LVAL_SA, .sa = $1, .rte = f_const_empty(T_ROUTE) }; }
+ | dynamic_attr { $$ = (struct f_lval) { .type = F_LVAL_EA, .da = $1, .rte = f_const_empty(T_ROUTE) }; }
+ ;
CF_END
diff --git a/filter/data.c b/filter/data.c
index 56d746fd..e268a8ec 100644
--- a/filter/data.c
+++ b/filter/data.c
@@ -27,6 +27,7 @@
static const char * const f_type_str[] = {
[T_VOID] = "void",
+ [T_NONE] = "none",
[T_INT] = "int",
[T_BOOL] = "bool",
@@ -46,6 +47,7 @@ static const char * const f_type_str[] = {
[T_IP] = "ip",
[T_NET] = "prefix",
[T_STRING] = "string",
+ [T_BYTESTRING] = "bytestring",
[T_PATH_MASK] = "bgpmask",
[T_PATH] = "bgppath",
[T_CLIST] = "clist",
@@ -54,6 +56,9 @@ static const char * const f_type_str[] = {
[T_LC] = "lc",
[T_LCLIST] = "lclist",
[T_RD] = "rd",
+
+ [T_ROUTE] = "route",
+ [T_ROUTES_BLOCK] = "block of routes",
};
const char *
@@ -76,25 +81,13 @@ f_type_element_type(enum f_type t)
case T_CLIST: return T_PAIR;
case T_ECLIST: return T_EC;
case T_LCLIST: return T_LC;
+ case T_ROUTES_BLOCK: return T_ROUTE;
default: return T_VOID;
};
}
const struct f_trie f_const_empty_trie = { .ipv4 = -1, };
-
-const struct f_val f_const_empty_path = {
- .type = T_PATH,
- .val.ad = &null_adata,
-}, f_const_empty_clist = {
- .type = T_CLIST,
- .val.ad = &null_adata,
-}, f_const_empty_eclist = {
- .type = T_ECLIST,
- .val.ad = &null_adata,
-}, f_const_empty_lclist = {
- .type = T_LCLIST,
- .val.ad = &null_adata,
-}, f_const_empty_prefix_set = {
+const struct f_val f_const_empty_prefix_set = {
.type = T_PREFIX_SET,
.val.ti = &f_const_empty_trie,
};
@@ -217,12 +210,23 @@ val_compare(const struct f_val *v1, const struct f_val *v2)
return net_compare(v1->val.net, v2->val.net);
case T_STRING:
return strcmp(v1->val.s, v2->val.s);
+ case T_PATH:
+ return as_path_compare(v1->val.ad, v2->val.ad);
+ case T_ROUTE:
+ /* Fall through */
+ case T_ROUTES_BLOCK:
default:
return F_CMP_ERROR;
}
}
static inline int
+bs_same(const struct adata *bs1, const struct adata *bs2)
+{
+ return (bs1->length == bs2->length) && !memcmp(bs1->data, bs2->data, bs1->length);
+}
+
+static inline int
pmi_same(const struct f_path_mask_item *mi1, const struct f_path_mask_item *mi2)
{
if (mi1->kind != mi2->kind)
@@ -286,6 +290,8 @@ val_same(const struct f_val *v1, const struct f_val *v2)
return 0;
switch (v1->type) {
+ case T_BYTESTRING:
+ return bs_same(v1->val.bs, v2->val.bs);
case T_PATH_MASK:
return pm_same(v1->val.path_mask, v2->val.path_mask);
case T_PATH_MASK_ITEM:
@@ -299,6 +305,10 @@ val_same(const struct f_val *v1, const struct f_val *v2)
return same_tree(v1->val.t, v2->val.t);
case T_PREFIX_SET:
return trie_same(v1->val.ti, v2->val.ti);
+ case T_ROUTE:
+ return v1->val.rte == v2->val.rte;
+ case T_ROUTES_BLOCK:
+ return v1->val.ad == v2->val.ad;
default:
bug("Invalid type in val_same(): %x", v1->type);
}
@@ -573,6 +583,36 @@ val_in_range(const struct f_val *v1, const struct f_val *v2)
}
/*
+ * rte_format - format route information
+ */
+static void
+rte_format(const struct rte *rte, buffer *buf)
+{
+ if (rte)
+ buffer_print(buf, "Route [%d] to %N from %s.%s via %s",
+ rte->src->global_id, rte->net->n.addr,
+ rte->sender->proto->name, rte->sender->name,
+ rte->src->proto->name);
+ else
+ buffer_puts(buf, "[No route]");
+}
+
+static void
+rte_block_format(const struct rte *rte, buffer *buf)
+{
+ buffer_print(buf, "Block of routes:");
+
+ int i = 0;
+ while (rte)
+ {
+ buffer_print(buf, "%s%d: ", i ? "; " : " ", i);
+ rte_format(rte, buf);
+ rte = rte->next;
+ i++;
+ }
+}
+
+/*
* val_format - format filter value
*/
void
@@ -585,6 +625,7 @@ val_format(const struct f_val *v, buffer *buf)
case T_BOOL: buffer_puts(buf, v->val.i ? "TRUE" : "FALSE"); return;
case T_INT: buffer_print(buf, "%u", v->val.i); return;
case T_STRING: buffer_print(buf, "%s", v->val.s); return;
+ case T_BYTESTRING: bstrbintohex(v->val.bs->data, v->val.bs->length, buf2, 1000, ':'); buffer_print(buf, "%s", buf2); return;
case T_IP: buffer_print(buf, "%I", v->val.ip); return;
case T_NET: buffer_print(buf, "%N", v->val.net); return;
case T_PAIR: buffer_print(buf, "(%u,%u)", v->val.i >> 16, v->val.i & 0xffff); return;
@@ -600,6 +641,8 @@ val_format(const struct f_val *v, buffer *buf)
case T_ECLIST: ec_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(eclist %s)", buf2); return;
case T_LCLIST: lc_set_format(v->val.ad, -1, buf2, 1000); buffer_print(buf, "(lclist %s)", buf2); return;
case T_PATH_MASK: pm_format(v->val.path_mask, buf); return;
+ case T_ROUTE: rte_format(v->val.rte, buf); return;
+ case T_ROUTES_BLOCK: rte_block_format(v->val.rte, buf); return;
default: buffer_print(buf, "[unknown type %x]", v->type); return;
}
}
@@ -624,4 +667,3 @@ val_dump(const struct f_val *v) {
val_format(v, &b);
return val_dump_buffer;
}
-
diff --git a/filter/data.h b/filter/data.h
index b3767f7b..21a78bf6 100644
--- a/filter/data.h
+++ b/filter/data.h
@@ -11,6 +11,7 @@
#define _BIRD_FILTER_DATA_H_
#include "nest/bird.h"
+#include "nest/route.h"
/* Type numbers must be in 0..0xff range */
#define T_MASK 0xff
@@ -20,6 +21,8 @@ enum f_type {
/* Nothing. Simply nothing. */
T_VOID = 0,
+ T_NONE = 1, /* Special hack to represent missing arguments */
+
/* User visible types, which fit in int */
T_INT = 0x10,
T_BOOL = 0x11,
@@ -39,6 +42,7 @@ enum f_type {
T_ENUM_NETTYPE = 0x36,
T_ENUM_RA_PREFERENCE = 0x37,
T_ENUM_AF = 0x38,
+ T_ENUM_MPLS_POLICY = 0x39,
/* new enums go here */
T_ENUM_EMPTY = 0x3f, /* Special hack for atomic_aggr */
@@ -58,11 +62,22 @@ enum f_type {
T_LCLIST = 0x29, /* Large community list */
T_RD = 0x2a, /* Route distinguisher for VPN addresses */
T_PATH_MASK_ITEM = 0x2b, /* Path mask item for path mask constructors */
+ T_BYTESTRING = 0x2c,
+ T_ROUTE = 0x78,
+ T_ROUTES_BLOCK = 0x79,
T_SET = 0x80,
T_PREFIX_SET = 0x81,
} PACKED;
+struct f_method {
+ struct symbol *sym;
+ struct f_inst *(*new_inst)(struct f_inst *obj, struct f_inst *args);
+ const struct f_method *next;
+ uint arg_num;
+ enum f_type args_type[];
+};
+
/* Filter value; size of this affects filter memory consumption */
struct f_val {
enum f_type type; /* T_* */
@@ -73,11 +88,13 @@ struct f_val {
ip_addr ip;
const net_addr *net;
const char *s;
+ const struct adata *bs;
const struct f_tree *t;
const struct f_trie *ti;
const struct adata *ad;
const struct f_path_mask *path_mask;
struct f_path_mask_item pmi;
+ struct rte *rte;
} val;
};
@@ -87,6 +104,7 @@ struct f_dynamic_attr {
u8 bit; /* For bitfield accessors */
enum f_type f_type; /* Filter type */
uint ea_code; /* EA code */
+ uint flags;
};
enum f_sa_code {
@@ -123,6 +141,7 @@ enum f_lval_type {
/* Filter l-value */
struct f_lval {
enum f_lval_type type;
+ struct f_inst *rte;
union {
struct symbol *sym;
struct f_dynamic_attr da;
@@ -275,8 +294,8 @@ trie_match_next_longest_ip6(net_addr_ip6 *n, ip6_addr *found)
#define F_CMP_ERROR 999
const char *f_type_name(enum f_type t);
-
enum f_type f_type_element_type(enum f_type t);
+struct sym_scope *f_type_method_scope(enum f_type t);
int val_same(const struct f_val *v1, const struct f_val *v2);
int val_compare(const struct f_val *v1, const struct f_val *v2);
@@ -306,15 +325,33 @@ const struct adata *lclist_filter(struct linpool *pool, const struct adata *list
/* Special undef value for paths and clists */
+
static inline int
-undef_value(struct f_val v)
+val_is_undefined(struct f_val v)
{
return ((v.type == T_PATH) || (v.type == T_CLIST) ||
(v.type == T_ECLIST) || (v.type == T_LCLIST)) &&
(v.val.ad == &null_adata);
}
-extern const struct f_val f_const_empty_path, f_const_empty_clist, f_const_empty_eclist, f_const_empty_lclist, f_const_empty_prefix_set;
+static inline struct f_val
+val_empty(enum f_type t)
+{
+ switch (t)
+ {
+ case T_PATH:
+ case T_CLIST:
+ case T_ECLIST:
+ case T_LCLIST:
+ return (struct f_val) { .type = t, .val.ad = &null_adata };
+
+ default:
+ return (struct f_val) { };
+ }
+}
+
+
+extern const struct f_val f_const_empty_prefix_set;
enum filter_return f_eval(const struct f_line *expr, struct linpool *tmp_pool, struct f_val *pres);
diff --git a/filter/decl.m4 b/filter/decl.m4
index b6026867..57bf9454 100644
--- a/filter/decl.m4
+++ b/filter/decl.m4
@@ -34,6 +34,9 @@ m4_divert(-1)m4_dnl
# 102 constructor arguments
# 110 constructor attributes
# 103 constructor body
+# 111 method constructor body
+# 112 instruction constructor call from method constructor
+# 113 method constructor symbol registrator
# 104 dump line item content
# (there may be nothing in dump-line content and
# it must be handled specially in phase 2)
@@ -48,6 +51,9 @@ m4_define(FID_STRUCT_IN, `m4_divert(101)')
m4_define(FID_NEW_ARGS, `m4_divert(102)')
m4_define(FID_NEW_ATTRIBUTES, `m4_divert(110)')
m4_define(FID_NEW_BODY, `m4_divert(103)')
+m4_define(FID_NEW_METHOD, `m4_divert(111)')
+m4_define(FID_METHOD_CALL, `m4_divert(112)')
+m4_define(FID_TYPE_SIGNATURE, `m4_divert(113)')
m4_define(FID_DUMP_BODY, `m4_divert(104)m4_define([[FID_DUMP_BODY_EXISTS]])')
m4_define(FID_LINEARIZE_BODY, `m4_divert(105)')
m4_define(FID_SAME_BODY, `m4_divert(106)')
@@ -120,7 +126,13 @@ FID_IFCONST([[
constargs = 0;
]])
} while (child$1 = child$1->next);
-FID_LINEARIZE_BODY
+m4_define([[INST_METHOD_NUM_ARGS]],$1)m4_dnl
+m4_ifelse($1,1,,[[FID_NEW_METHOD()m4_dnl
+ struct f_inst *arg$1 = args;
+ if (args == NULL) cf_error("Not enough arguments"); /* INST_NAME */
+ args = args->next;
+FID_METHOD_CALL() , arg$1]])
+FID_LINEARIZE_BODY()m4_dnl
pos = linearize(dest, whati->f$1, pos);
FID_INTERPRET_BODY()')
@@ -170,28 +182,31 @@ FID_HIC(,[[
m4_define(ARG, `ARG_ANY($1) ARG_TYPE($1,$2)')
m4_define(ARG_TYPE, `ARG_TYPE_STATIC($1,$2) ARG_TYPE_DYNAMIC($1,$2)')
-m4_define(ARG_TYPE_STATIC, `
+m4_define(ARG_TYPE_STATIC, `m4_dnl
+m4_ifelse($1,1,[[m4_define([[INST_METHOD_OBJECT_TYPE]],$2)]],)m4_dnl
+FID_TYPE_SIGNATURE()m4_dnl
+ method->args_type[m4_eval($1-1)] = $2;
FID_NEW_BODY()m4_dnl
if (f$1->type && (f$1->type != ($2)) && !f_const_promotion(f$1, ($2)))
cf_error("Argument $1 of %s must be of type %s, got type %s",
f_instruction_name(what->fi_code), f_type_name($2), f_type_name(f$1->type));
FID_INTERPRET_BODY()')
-m4_define(ARG_TYPE_DYNAMIC, `
+m4_define(ARG_TYPE_DYNAMIC, `m4_dnl
FID_INTERPRET_EXEC()m4_dnl
if (v$1.type != ($2))
runtime("Argument $1 of %s must be of type %s, got type %s",
f_instruction_name(what->fi_code), f_type_name($2), f_type_name(v$1.type));
FID_INTERPRET_BODY()')
-m4_define(ARG_SAME_TYPE, `
+m4_define(ARG_SAME_TYPE, `m4_dnl
FID_NEW_BODY()m4_dnl
if (f$1->type && f$2->type && (f$1->type != f$2->type) &&
!f_const_promotion(f$2, f$1->type) && !f_const_promotion(f$1, f$2->type))
cf_error("Arguments $1 and $2 of %s must be of the same type", f_instruction_name(what->fi_code));
FID_INTERPRET_BODY()')
-m4_define(ARG_PREFER_SAME_TYPE, `
+m4_define(ARG_PREFER_SAME_TYPE, `m4_dnl
FID_NEW_BODY()m4_dnl
if (f$1->type && f$2->type && (f$1->type != f$2->type))
(void) (f_const_promotion(f$2, f$1->type) || f_const_promotion(f$1, f$2->type));
@@ -200,7 +215,7 @@ FID_INTERPRET_BODY()')
# Executing another filter line. This replaces the recursion
# that was needed in the former implementation.
m4_define(LINEX, `FID_INTERPRET_EXEC()LINEX_($1)FID_INTERPRET_NEW()return $1 FID_INTERPRET_BODY()')
-m4_define(LINEX_, `do {
+m4_define(LINEX_, `do if ($1) {
fstk->estk[fstk->ecnt].pos = 0;
fstk->estk[fstk->ecnt].line = $1;
fstk->estk[fstk->ecnt].ventry = fstk->vcnt;
@@ -218,6 +233,12 @@ FID_NEW_ARGS()m4_dnl
, struct f_inst * f$1
FID_NEW_BODY()m4_dnl
whati->f$1 = f$1;
+m4_define([[INST_METHOD_NUM_ARGS]],$1)m4_dnl
+FID_NEW_METHOD()m4_dnl
+ struct f_inst *arg$1 = args;
+ if (args == NULL) cf_error("Not enough arguments"); /* INST_NAME */
+ args = NULL; /* The rest is the line itself */
+FID_METHOD_CALL() , arg$1
FID_DUMP_BODY()m4_dnl
f_dump_line(item->fl$1, indent + 1);
FID_LINEARIZE_BODY()m4_dnl
@@ -227,9 +248,7 @@ if (!f_same(f1->fl$1, f2->fl$1)) return 0;
FID_ITERATE_BODY()m4_dnl
if (whati->fl$1) BUFFER_PUSH(fit->lines) = whati->fl$1;
FID_INTERPRET_EXEC()m4_dnl
-do { if (whati->fl$1) {
- LINEX_(whati->fl$1);
-} } while(0)
+LINEX_(whati->fl$1)
FID_INTERPRET_NEW()m4_dnl
return whati->f$1
FID_INTERPRET_BODY()')
@@ -268,6 +287,29 @@ m4_define(STATIC_ATTR, `FID_MEMBER(struct f_static_attr, sa, f1->sa.sa_code != f
m4_define(DYNAMIC_ATTR, `FID_MEMBER(struct f_dynamic_attr, da, f1->da.ea_code != f2->da.ea_code,,)')
m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access"); } while (0)]],NEVER_CONSTANT())')
+# Method constructor block
+m4_define(METHOD_CONSTRUCTOR, `m4_dnl
+FID_NEW_METHOD()m4_dnl
+ if (args) cf_error("Too many arguments");
+m4_define([[INST_IS_METHOD]])
+m4_define([[INST_METHOD_NAME]],$1)
+FID_INTERPRET_BODY()')
+
+# Short method constructor
+# $1 = type
+# $2 = name
+# $3 = method inputs
+# method outputs are always 1
+# $4 = code
+m4_define(METHOD, `m4_dnl
+INST([[FI_METHOD__]]$1[[__]]$2, m4_eval($3 + 1), 1) {
+ ARG(1, $1);
+ $4
+ METHOD_CONSTRUCTOR("$2");
+}')
+
+m4_define(METHOD_R, `METHOD($1, $2, 0, [[ RESULT($3, $4, $5) ]])')
+
# 2) Code wrapping
# The code produced in 1xx temporary diversions is a raw code without
# any auxiliary commands and syntactical structures around. When the
@@ -287,6 +329,7 @@ m4_define(ACCESS_RTE, `FID_HIC(,[[do { if (!fs->rte) runtime("No route to access
# 10 iterate
# 1 union in struct f_inst
# 3 constructors + interpreter
+# 11 method constructors
#
# These global diversions contain blocks of code that can be directly
# put into the final file, yet it still can't be written out now as
@@ -306,6 +349,9 @@ m4_define(FID_DUMP_CALLER, `FID_ZONE(7, Dump line caller)')
m4_define(FID_LINEARIZE, `FID_ZONE(8, Linearize)')
m4_define(FID_SAME, `FID_ZONE(9, Comparison)')
m4_define(FID_ITERATE, `FID_ZONE(10, Iteration)')
+m4_define(FID_METHOD, `FID_ZONE(11, Method constructor)')
+m4_define(FID_METHOD_SCOPE_INIT, `FID_ZONE(12, Method scope initializator)')
+m4_define(FID_METHOD_REGISTER, `FID_ZONE(13, Method registrator)')
# This macro does all the code wrapping. See inline comments.
m4_define(INST_FLUSH, `m4_ifdef([[INST_NAME]], [[
@@ -333,7 +379,8 @@ m4_undivert(102)m4_dnl
[[m4_dnl The one case in The Big Switch inside interpreter
case INST_NAME():
#define whati (&(what->i_]]INST_NAME()[[))
- m4_ifelse(m4_eval(INST_INVAL() > 0), 1, [[if (fstk->vcnt < INST_INVAL()) runtime("Stack underflow"); fstk->vcnt -= INST_INVAL(); ]])
+ m4_ifelse(m4_eval(INST_INVAL() > 0), 1, [[if (fstk->vcnt < INST_INVAL()) runtime("Stack underflow");
+ fstk->vcnt -= INST_INVAL();]])
m4_undivert(108)m4_dnl
#undef whati
break;
@@ -363,6 +410,29 @@ m4_undivert(102)m4_dnl
}
]])
+m4_ifdef([[INST_IS_METHOD]],m4_dnl
+FID_METHOD()m4_dnl
+[[struct f_inst * NONNULL(1)
+f_new_method_]]INST_NAME()[[(struct f_inst *obj, struct f_inst *args)
+ {
+ /* Unwind the arguments (INST_METHOD_NUM_ARGS) */
+ m4_undivert(111)m4_dnl
+ return f_new_inst(INST_NAME, obj
+m4_undivert(112)
+ );
+ }
+
+FID_METHOD_SCOPE_INIT()m4_dnl
+ [INST_METHOD_OBJECT_TYPE] = {},
+FID_METHOD_REGISTER()m4_dnl
+ method = lp_allocz(global_root_scope_linpool, sizeof(struct f_method) + INST_METHOD_NUM_ARGS * sizeof(enum f_type));
+ method->new_inst = f_new_method_]]INST_NAME()[[;
+ method->arg_num = INST_METHOD_NUM_ARGS;
+m4_undivert(113)
+ f_register_method(INST_METHOD_OBJECT_TYPE, INST_METHOD_NAME, method);
+
+]])m4_dnl
+
FID_DUMP_CALLER()m4_dnl Case in another big switch used in instruction dumping (debug)
case INST_NAME(): f_dump_line_item_]]INST_NAME()[[(item, indent + 1); break;
@@ -416,6 +486,8 @@ m4_define([[INST_INVAL]], [[$2]])m4_dnl instruction input value count,
m4_define([[INST_OUTVAL]], [[$3]])m4_dnl instruction output value count,
m4_undefine([[INST_NEVER_CONSTANT]])m4_dnl reset NEVER_CONSTANT trigger,
m4_undefine([[INST_RESULT_TYPE]])m4_dnl and reset RESULT_TYPE value.
+m4_undefine([[INST_IS_METHOD]])m4_dnl and reset method constructor request.
+m4_undefine([[INST_METHOD_OBJECT_TYPE]],)m4_dnl reset method object type,
FID_INTERPRET_BODY()m4_dnl By default, every code is interpreter code.
')
@@ -486,7 +558,7 @@ f_instruction_name_(enum f_instruction_code fi)
static inline struct f_inst *
fi_new(enum f_instruction_code fi_code)
{
- struct f_inst *what = cfg_allocz(sizeof(struct f_inst));
+ struct f_inst *what = tmp_allocz(sizeof(struct f_inst));
what->lineno = ifs->lino;
what->size = 1;
what->fi_code = fi_code;
@@ -501,8 +573,8 @@ fi_constant(struct f_inst *what, struct f_val val)
return what;
}
-static int
-f_const_promotion(struct f_inst *arg, enum f_type want)
+int
+f_const_promotion_(struct f_inst *arg, enum f_type want, int update)
{
if (arg->fi_code != FI_CONSTANT)
return 0;
@@ -510,15 +582,17 @@ f_const_promotion(struct f_inst *arg, enum f_type want)
struct f_val *c = &arg->i_FI_CONSTANT.val;
if ((c->type == T_IP) && ipa_is_ip4(c->val.ip) && (want == T_QUAD)) {
- *c = (struct f_val) {
- .type = T_QUAD,
- .val.i = ipa_to_u32(c->val.ip),
- };
+ if (update)
+ *c = (struct f_val) {
+ .type = T_QUAD,
+ .val.i = ipa_to_u32(c->val.ip),
+ };
return 1;
}
else if ((c->type == T_SET) && (!c->val.t) && (want == T_PREFIX_SET)) {
- *c = f_const_empty_prefix_set;
+ if (update)
+ *c = f_const_empty_prefix_set;
return 1;
}
@@ -539,6 +613,54 @@ FID_WR_PUT(3)
#undef v3
#undef vv
+/* Method constructor wrappers */
+FID_WR_PUT(11)
+
+#if defined(__GNUC__) && __GNUC__ >= 6
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Woverride-init"
+#endif
+
+static struct sym_scope f_type_method_scopes[] = {
+FID_WR_PUT(12)
+};
+
+#if defined(__GNUC__) && __GNUC__ >= 6
+#pragma GCC diagnostic pop
+#endif
+
+struct sym_scope *f_type_method_scope(enum f_type t)
+{
+ return (t < ARRAY_SIZE(f_type_method_scopes)) ? &f_type_method_scopes[t] : NULL;
+}
+
+static void
+f_register_method(enum f_type t, const byte *name, struct f_method *dsc)
+{
+ struct sym_scope *scope = &f_type_method_scopes[t];
+ struct symbol *sym = cf_find_symbol_scope(scope, name);
+
+ if (!sym)
+ {
+ sym = cf_new_symbol(scope, global_root_scope_pool, global_root_scope_linpool, name);
+ sym->class = SYM_METHOD;
+ }
+
+ dsc->sym = sym;
+ dsc->next = sym->method;
+ sym->method = dsc;
+}
+
+void f_type_methods_register(void)
+{
+ struct f_method *method;
+
+FID_WR_PUT(13)
+
+ for (uint i = 0; i < ARRAY_SIZE(f_type_method_scopes); i++)
+ f_type_method_scopes[i].readonly = 1;
+}
+
/* Line dumpers */
#define INDENT (((const char *) f_dump_line_indent_str) + sizeof(f_dump_line_indent_str) - (indent) - 1)
static const char f_dump_line_indent_str[] = " ";
diff --git a/filter/f-inst.c b/filter/f-inst.c
index e4b47ff4..9cc46aa0 100644
--- a/filter/f-inst.c
+++ b/filter/f-inst.c
@@ -72,6 +72,8 @@
* m4_dnl ACCESS_RTE; this instruction needs route
* m4_dnl ACCESS_EATTRS; this instruction needs extended attributes
*
+ * m4_dnl METHOD_CONSTRUCTOR(name); this instruction is in fact a method of the first argument's type; register it with the given name for that type
+ *
* m4_dnl FID_MEMBER( custom instruction member
* m4_dnl C type, for storage in structs
* m4_dnl name, how the member is named
@@ -99,6 +101,11 @@
*
* Other code is just copied into the interpreter part.
*
+ * It's also possible to declare type methods in a short way:
+ *
+ * m4_dnl METHOD(type, method name, argument count, code)
+ * m4_dnl METHOD_R(type, method name, argument count, result type, union-field, value)
+ *
* The filter language uses a simple type system, where values have types
* (constants T_*) and also terms (instructions) are statically typed. Our
* static typing is partial (some terms do not declare types of arguments
@@ -486,26 +493,13 @@
INST(FI_DEFINED, 1, 1) {
ARG_ANY(1);
- RESULT(T_BOOL, i, (v1.type != T_VOID) && !undef_value(v1));
+ RESULT(T_BOOL, i, (v1.type != T_VOID) && !val_is_undefined(v1));
}
- INST(FI_TYPE, 1, 1) {
- ARG_ANY(1); /* There may be more types supporting this operation */
- switch (v1.type)
- {
- case T_NET:
- RESULT(T_ENUM_NETTYPE, i, v1.val.net->type);
- break;
- default:
- runtime( "Can't determine type of this item" );
- }
- }
-
- INST(FI_IS_V4, 1, 1) {
- ARG(1, T_IP);
- RESULT(T_BOOL, i, ipa_is_ip4(v1.val.ip));
- }
+ METHOD_R(T_NET, type, T_ENUM_NETTYPE, i, v1.val.net->type);
+ METHOD_R(T_IP, is_v4, T_BOOL, i, ipa_is_ip4(v1.val.ip));
+ /* Add initialized variable */
INST(FI_VAR_INIT, 1, 0) {
NEVER_CONSTANT;
ARG_ANY(1);
@@ -518,6 +512,17 @@
fstk->vcnt = pos + 1;
}
+ /* Add uninitialized variable */
+ INST(FI_VAR_INIT0, 0, 0) {
+ NEVER_CONSTANT;
+ SYMBOL;
+
+ /* New variable is always the last on stack */
+ uint pos = curline.vbase + sym->offset;
+ fstk->vstk[pos] = val_empty(sym->class & 0xff);
+ fstk->vcnt = pos + 1;
+ }
+
/* Set to indirect value prepared in v1 */
INST(FI_VAR_SET, 1, 0) {
NEVER_CONSTANT;
@@ -548,92 +553,84 @@
RESULT_VAL(val);
}
- INST(FI_FOR_INIT, 1, 0) {
+ METHOD_R(T_PATH, empty, T_PATH, ad, &null_adata);
+ METHOD_R(T_CLIST, empty, T_CLIST, ad, &null_adata);
+ METHOD_R(T_ECLIST, empty, T_ECLIST, ad, &null_adata);
+ METHOD_R(T_LCLIST, empty, T_LCLIST, ad, &null_adata);
+
+ /* Common loop begin instruction, always created by f_for_cycle() */
+ INST(FI_FOR_LOOP_START, 0, 3) {
NEVER_CONSTANT;
- ARG_ANY(1);
SYMBOL;
- FID_NEW_BODY()
- ASSERT((sym->class & ~0xff) == SYM_VARIABLE);
+ /* Repeat the instruction which called us */
+ ASSERT_DIE(fstk->ecnt > 1);
+ prevline.pos--;
- /* Static type check */
- if (f1->type)
- {
- enum f_type t_var = (sym->class & 0xff);
- enum f_type t_arg = f_type_element_type(f1->type);
- if (!t_arg)
- cf_error("Value of expression in FOR must be iterable, got %s",
- f_type_name(f1->type));
- if (t_var != t_arg)
- cf_error("Loop variable '%s' in FOR must be %s, is %s",
- sym->name, f_type_name(t_arg), f_type_name(t_var));
- }
-
- FID_INTERPRET_BODY()
+ /* There should be exactly three items on the value stack to be taken care of */
+ fstk->vcnt += 3;
- /* Dynamic type check */
- if ((sym->class & 0xff) != f_type_element_type(v1.type))
- runtime("Mismatched argument and variable type");
+ /* And these should also stay there after we finish for the caller instruction */
+ curline.ventry += 3;
- /* Setup the index */
- v2 = (struct f_val) { .type = T_INT, .val.i = 0 };
+ /* Assert the iterator variable positioning */
+ ASSERT_DIE(curline.vbase + sym->offset == fstk->vcnt - 1);
- /* Keep v1 and v2 on the stack */
- fstk->vcnt += 2;
+ /* The result type declaration makes no sense here but is needed */
+ RESULT_TYPE(T_VOID);
}
- INST(FI_FOR_NEXT, 2, 0) {
+ /* Type-specific for_next iterators */
+ INST(FI_PATH_FOR_NEXT, 3, 0) {
NEVER_CONSTANT;
- SYMBOL;
-
- /* Type checks are done in FI_FOR_INIT */
-
- /* Loop variable */
- struct f_val *var = &fstk->vstk[curline.vbase + sym->offset];
- int step = 0;
+ ARG(1, T_PATH);
+ if (as_path_walk(v1.val.ad, &v2.val.i, &v3.val.i))
+ LINE(2,0);
- switch(v1.type)
- {
- case T_PATH:
- var->type = T_INT;
- step = as_path_walk(v1.val.ad, &v2.val.i, &var->val.i);
- break;
+ METHOD_CONSTRUCTOR("!for_next");
+ }
- case T_CLIST:
- var->type = T_PAIR;
- step = int_set_walk(v1.val.ad, &v2.val.i, &var->val.i);
- break;
+ INST(FI_CLIST_FOR_NEXT, 3, 0) {
+ NEVER_CONSTANT;
+ ARG(1, T_CLIST);
+ if (int_set_walk(v1.val.ad, &v2.val.i, &v3.val.i))
+ LINE(2,0);
- case T_ECLIST:
- var->type = T_EC;
- step = ec_set_walk(v1.val.ad, &v2.val.i, &var->val.ec);
- break;
+ METHOD_CONSTRUCTOR("!for_next");
+ }
- case T_LCLIST:
- var->type = T_LC;
- step = lc_set_walk(v1.val.ad, &v2.val.i, &var->val.lc);
- break;
+ INST(FI_ECLIST_FOR_NEXT, 3, 0) {
+ NEVER_CONSTANT;
+ ARG(1, T_ECLIST);
+ if (ec_set_walk(v1.val.ad, &v2.val.i, &v3.val.ec))
+ LINE(2,0);
- default:
- runtime( "Clist or lclist expected" );
- }
+ METHOD_CONSTRUCTOR("!for_next");
+ }
- if (step)
- {
- /* Keep v1 and v2 on the stack */
- fstk->vcnt += 2;
+ INST(FI_LCLIST_FOR_NEXT, 3, 0) {
+ NEVER_CONSTANT;
+ ARG(1, T_LCLIST);
+ if (lc_set_walk(v1.val.ad, &v2.val.i, &v3.val.lc))
+ LINE(2,0);
- /* Repeat this instruction */
- curline.pos--;
+ METHOD_CONSTRUCTOR("!for_next");
+ }
- /* Execute the loop body */
- LINE(1, 0);
+ INST(FI_ROUTES_BLOCK_FOR_NEXT, 3, 0) {
+ NEVER_CONSTANT;
+ ARG(1, T_ROUTES_BLOCK);
+ if (!v2.type)
+ v2 = v1;
- /* Space for loop variable, may be unused */
- fstk->vcnt += 1;
+ if (v2.val.rte)
+ {
+ v3.val.rte = v2.val.rte;
+ v2.val.rte = v2.val.rte->next;
+ LINE(2,0);
}
- else
- var->type = T_VOID;
+
+ METHOD_CONSTRUCTOR("!for_next");
}
INST(FI_CONDITION, 1, 0) {
@@ -644,13 +641,12 @@
LINE(3,0);
}
- INST(FI_PRINT, 0, 0) {
+ INST(FI_PRINT, 1, 0) {
NEVER_CONSTANT;
- VARARG;
+ ARG_ANY(1);
- if (whati->varcount && !(fs->flags & FF_SILENT))
- for (uint i=0; i<whati->varcount; i++)
- val_format(&(vv(i)), &fs->buf);
+ if (!(fs->flags & FF_SILENT))
+ val_format(&v1, &fs->buf);
}
INST(FI_FLUSH, 0, 0) {
@@ -674,11 +670,13 @@
}
}
- INST(FI_RTA_GET, 0, 1) {
+ INST(FI_RTA_GET, 1, 1) {
{
- STATIC_ATTR;
ACCESS_RTE;
- struct rta *rta = (*fs->rte)->attrs;
+ ARG(1, T_ROUTE);
+ STATIC_ATTR;
+
+ struct rta *rta = v1.val.rte ? v1.val.rte->attrs : (*fs->rte)->attrs;
switch (sa.sa_code)
{
@@ -817,41 +815,18 @@
}
}
- INST(FI_EA_GET, 0, 1) { /* Access to extended attributes */
- DYNAMIC_ATTR;
+ INST(FI_EA_GET, 1, 1) { /* Access to extended attributes */
ACCESS_RTE;
ACCESS_EATTRS;
+ ARG(1, T_ROUTE);
+ DYNAMIC_ATTR;
RESULT_TYPE(da.f_type);
{
- eattr *e = ea_find(*fs->eattrs, da.ea_code);
+ struct ea_list *eal = v1.val.rte ? v1.val.rte->attrs->eattrs : *fs->eattrs;
+ eattr *e = ea_find(eal, da.ea_code);
if (!e) {
- /* A special case: undefined as_path looks like empty as_path */
- if (da.type == EAF_TYPE_AS_PATH) {
- RESULT_(T_PATH, ad, &null_adata);
- break;
- }
-
- /* The same special case for int_set */
- if (da.type == EAF_TYPE_INT_SET) {
- RESULT_(T_CLIST, ad, &null_adata);
- break;
- }
-
- /* The same special case for ec_set */
- if (da.type == EAF_TYPE_EC_SET) {
- RESULT_(T_ECLIST, ad, &null_adata);
- break;
- }
-
- /* The same special case for lc_set */
- if (da.type == EAF_TYPE_LC_SET) {
- RESULT_(T_LCLIST, ad, &null_adata);
- break;
- }
-
- /* Undefined value */
- RESULT_VOID;
+ RESULT_VAL(val_empty(da.f_type));
break;
}
@@ -863,7 +838,10 @@
RESULT_(T_QUAD, i, e->u.data);
break;
case EAF_TYPE_OPAQUE:
- RESULT_(T_ENUM_EMPTY, i, 0);
+ if (da.f_type == T_ENUM_EMPTY)
+ RESULT_(T_ENUM_EMPTY, i, 0);
+ else
+ RESULT_(T_BYTESTRING, ad, e->u.ptr);
break;
case EAF_TYPE_IP_ADDRESS:
RESULT_(T_IP, ip, *((ip_addr *) e->u.ptr->data));
@@ -895,6 +873,12 @@
ARG_ANY(1);
DYNAMIC_ATTR;
ARG_TYPE(1, da.f_type);
+
+ FID_NEW_BODY;
+ if (da.f_type == T_ENUM_EMPTY)
+ cf_error("Setting opaque attribute is not allowed");
+
+ FID_INTERPRET_BODY;
{
struct ea_list *l = lp_alloc(fs->pool, sizeof(struct ea_list) + sizeof(eattr));
@@ -902,7 +886,7 @@
l->flags = EALF_SORTED;
l->count = 1;
l->attrs[0].id = da.ea_code;
- l->attrs[0].flags = 0;
+ l->attrs[0].flags = da.flags;
l->attrs[0].type = da.type;
l->attrs[0].originated = 1;
l->attrs[0].fresh = 1;
@@ -914,10 +898,6 @@
l->attrs[0].u.data = v1.val.i;
break;
- case EAF_TYPE_OPAQUE:
- runtime( "Setting opaque attribute is not allowed" );
- break;
-
case EAF_TYPE_IP_ADDRESS:;
int len = sizeof(ip_addr);
struct adata *ad = lp_alloc(fs->pool, sizeof(struct adata) + len);
@@ -926,6 +906,7 @@
l->attrs[0].u.ptr = ad;
break;
+ case EAF_TYPE_OPAQUE:
case EAF_TYPE_AS_PATH:
case EAF_TYPE_INT_SET:
case EAF_TYPE_EC_SET:
@@ -965,20 +946,16 @@
ea_unset_attr(fs->eattrs, fs->pool, 1, da.ea_code);
}
- INST(FI_LENGTH, 1, 1) { /* Get length of */
- ARG_ANY(1);
- switch(v1.type) {
- case T_NET: RESULT(T_INT, i, net_pxlen(v1.val.net)); break;
- case T_PATH: RESULT(T_INT, i, as_path_getlen(v1.val.ad)); break;
- case T_CLIST: RESULT(T_INT, i, int_set_get_size(v1.val.ad)); break;
- case T_ECLIST: RESULT(T_INT, i, ec_set_get_size(v1.val.ad)); break;
- case T_LCLIST: RESULT(T_INT, i, lc_set_get_size(v1.val.ad)); break;
- default: runtime( "Prefix, path, clist or eclist expected" );
- }
- }
+ /* Get length of */
+ METHOD_R(T_NET, len, T_INT, i, net_pxlen(v1.val.net));
+ METHOD_R(T_PATH, len, T_INT, i, as_path_getlen(v1.val.ad));
+ METHOD_R(T_CLIST, len, T_INT, i, int_set_get_size(v1.val.ad));
+ METHOD_R(T_ECLIST, len, T_INT, i, ec_set_get_size(v1.val.ad));
+ METHOD_R(T_LCLIST, len, T_INT, i, lc_set_get_size(v1.val.ad));
INST(FI_NET_SRC, 1, 1) { /* Get src prefix */
ARG(1, T_NET);
+ METHOD_CONSTRUCTOR("src");
net_addr_union *net = (void *) v1.val.net;
net_addr *src = falloc(sizeof(net_addr_ip6));
@@ -1014,6 +991,7 @@
INST(FI_NET_DST, 1, 1) { /* Get dst prefix */
ARG(1, T_NET);
+ METHOD_CONSTRUCTOR("dst");
net_addr_union *net = (void *) v1.val.net;
net_addr *dst = falloc(sizeof(net_addr_ip6));
@@ -1047,156 +1025,78 @@
RESULT(T_NET, net, dst);
}
- INST(FI_ROA_MAXLEN, 1, 1) { /* Get ROA max prefix length */
- ARG(1, T_NET);
+ /* Get ROA max prefix length */
+ METHOD(T_NET, maxlen, 0, [[
if (!net_is_roa(v1.val.net))
runtime( "ROA expected" );
RESULT(T_INT, i, (v1.val.net->type == NET_ROA4) ?
((net_addr_roa4 *) v1.val.net)->max_pxlen :
((net_addr_roa6 *) v1.val.net)->max_pxlen);
- }
+ ]]);
- INST(FI_ASN, 1, 1) { /* Get ROA ASN or community ASN part */
- ARG_ANY(1);
- RESULT_TYPE(T_INT);
- switch(v1.type)
- {
- case T_NET:
+ /* Get ROA ASN */
+ METHOD(T_NET, asn, 0, [[
if (!net_is_roa(v1.val.net))
runtime( "ROA expected" );
- RESULT_(T_INT, i, (v1.val.net->type == NET_ROA4) ?
+ RESULT(T_INT, i, (v1.val.net->type == NET_ROA4) ?
((net_addr_roa4 *) v1.val.net)->asn :
((net_addr_roa6 *) v1.val.net)->asn);
- break;
+ ]]);
- case T_PAIR:
- RESULT_(T_INT, i, v1.val.i >> 16);
- break;
-
- case T_LC:
- RESULT_(T_INT, i, v1.val.lc.asn);
- break;
-
- default:
- runtime( "Net, pair or lc expected" );
- }
- }
-
- INST(FI_IP, 1, 1) { /* Convert prefix to ... */
- ARG(1, T_NET);
- RESULT(T_IP, ip, net_prefix(v1.val.net));
- }
+ /* Convert prefix to IP */
+ METHOD_R(T_NET, ip, T_IP, ip, net_prefix(v1.val.net));
INST(FI_ROUTE_DISTINGUISHER, 1, 1) {
ARG(1, T_NET);
+ METHOD_CONSTRUCTOR("rd");
if (!net_is_vpn(v1.val.net))
runtime( "VPN address expected" );
RESULT(T_RD, ec, net_rd(v1.val.net));
}
- INST(FI_AS_PATH_FIRST, 1, 1) { /* Get first ASN from AS PATH */
- ARG(1, T_PATH);
- u32 as = 0;
- as_path_get_first(v1.val.ad, &as);
- RESULT(T_INT, i, as);
- }
+ /* Get first ASN from AS PATH */
+ METHOD_R(T_PATH, first, T_INT, i, ({ u32 as = 0; as_path_get_first(v1.val.ad, &as); as; }));
- INST(FI_AS_PATH_LAST, 1, 1) { /* Get last ASN from AS PATH */
- ARG(1, T_PATH);
- u32 as = 0;
- as_path_get_last(v1.val.ad, &as);
- RESULT(T_INT, i, as);
- }
+ /* Get last ASN from AS PATH */
+ METHOD_R(T_PATH, last, T_INT, i, ({ u32 as = 0; as_path_get_last(v1.val.ad, &as); as; }));
- INST(FI_AS_PATH_LAST_NAG, 1, 1) { /* Get last ASN from non-aggregated part of AS PATH */
- ARG(1, T_PATH);
- RESULT(T_INT, i, as_path_get_last_nonaggregated(v1.val.ad));
- }
+ /* Get last ASN from non-aggregated part of AS PATH */
+ METHOD_R(T_PATH, last_nonaggregated, T_INT, i, as_path_get_last_nonaggregated(v1.val.ad));
- INST(FI_PAIR_DATA, 1, 1) { /* Get data part from the standard community */
- ARG(1, T_PAIR);
- RESULT(T_INT, i, v1.val.i & 0xFFFF);
- }
+ /* Get ASN part from the standard community ASN */
+ METHOD_R(T_PAIR, asn, T_INT, i, v1.val.i >> 16);
- INST(FI_LC_DATA1, 1, 1) { /* Get data1 part from the large community */
- ARG(1, T_LC);
- RESULT(T_INT, i, v1.val.lc.ldp1);
- }
+ /* Get data part from the standard community */
+ METHOD_R(T_PAIR, data, T_INT, i, v1.val.i & 0xFFFF);
- INST(FI_LC_DATA2, 1, 1) { /* Get data2 part from the large community */
- ARG(1, T_LC);
- RESULT(T_INT, i, v1.val.lc.ldp2);
- }
+ /* Get ASN part from the large community */
+ METHOD_R(T_LC, asn, T_INT, i, v1.val.lc.asn);
- INST(FI_MIN, 1, 1) { /* Get minimum element from list */
- ARG_ANY(1);
- RESULT_TYPE(f_type_element_type(v1.type));
- switch(v1.type)
- {
- case T_CLIST:
- {
- u32 val = 0;
- int_set_min(v1.val.ad, &val);
- RESULT_(T_PAIR, i, val);
- }
- break;
+ /* Get data1 part from the large community */
+ METHOD_R(T_LC, data1, T_INT, i, v1.val.lc.ldp1);
- case T_ECLIST:
- {
- u64 val = 0;
- ec_set_min(v1.val.ad, &val);
- RESULT_(T_EC, ec, val);
- }
- break;
+ /* Get data2 part from the large community */
+ METHOD_R(T_LC, data2, T_INT, i, v1.val.lc.ldp2);
- case T_LCLIST:
- {
- lcomm val = { 0, 0, 0 };
- lc_set_min(v1.val.ad, &val);
- RESULT_(T_LC, lc, val);
- }
- break;
+ /* Get minimum element from clist */
+ METHOD_R(T_CLIST, min, T_PAIR, i, ({ u32 val = 0; int_set_min(v1.val.ad, &val); val; }));
- default:
- runtime( "Clist or lclist expected" );
- }
- }
+ /* Get maximum element from clist */
+ METHOD_R(T_CLIST, max, T_PAIR, i, ({ u32 val = 0; int_set_max(v1.val.ad, &val); val; }));
- INST(FI_MAX, 1, 1) { /* Get maximum element from list */
- ARG_ANY(1);
- RESULT_TYPE(f_type_element_type(v1.type));
- switch(v1.type)
- {
- case T_CLIST:
- {
- u32 val = 0;
- int_set_max(v1.val.ad, &val);
- RESULT_(T_PAIR, i, val);
- }
- break;
+ /* Get minimum element from eclist */
+ METHOD_R(T_ECLIST, min, T_EC, ec, ({ u64 val = 0; ec_set_min(v1.val.ad, &val); val; }));
- case T_ECLIST:
- {
- u64 val = 0;
- ec_set_max(v1.val.ad, &val);
- RESULT_(T_EC, ec, val);
- }
- break;
+ /* Get maximum element from eclist */
+ METHOD_R(T_ECLIST, max, T_EC, ec, ({ u64 val = 0; ec_set_max(v1.val.ad, &val); val; }));
- case T_LCLIST:
- {
- lcomm val = { 0, 0, 0 };
- lc_set_max(v1.val.ad, &val);
- RESULT_(T_LC, lc, val);
- }
- break;
+ /* Get minimum element from lclist */
+ METHOD_R(T_LCLIST, min, T_LC, lc, ({ lcomm val = {}; lc_set_min(v1.val.ad, &val); val; }));
- default:
- runtime( "Clist or lclist expected" );
- }
- }
+ /* Get maximum element from lclist */
+ METHOD_R(T_LCLIST, max, T_LC, lc, ({ lcomm val = {}; lc_set_max(v1.val.ad, &val); val; }));
INST(FI_RETURN, 1, 0) {
NEVER_CONSTANT;
@@ -1228,9 +1128,7 @@
NEVER_CONSTANT;
VARARG;
SYMBOL;
-
- /* Fake result type declaration */
- RESULT_TYPE(T_VOID);
+ RESULT_TYPE(sym->function->return_type);
FID_NEW_BODY()
ASSERT(sym->class == SYM_FUNCTION);
@@ -1327,7 +1225,6 @@
FID_HIC(,break,return NULL);
}
}
- /* It is actually possible to have t->data NULL */
LINEX(t->data);
}
@@ -1335,6 +1232,7 @@
INST(FI_IP_MASK, 2, 1) { /* IP.MASK(val) */
ARG(1, T_IP);
ARG(2, T_INT);
+ METHOD_CONSTRUCTOR("mask");
RESULT(T_IP, ip, [[ ipa_is_ip4(v1.val.ip) ?
ipa_from_ip4(ip4_and(ipa_to_ip4(v1.val.ip), ip4_mkmask(v2.val.i))) :
ipa_from_ip6(ip6_and(ipa_to_ip6(v1.val.ip), ip6_mkmask(v2.val.i))) ]]);
@@ -1343,165 +1241,252 @@
INST(FI_PATH_PREPEND, 2, 1) { /* Path prepend */
ARG(1, T_PATH);
ARG(2, T_INT);
+ METHOD_CONSTRUCTOR("prepend");
RESULT(T_PATH, ad, [[ as_path_prepend(fpool, v1.val.ad, v2.val.i) ]]);
}
- INST(FI_CLIST_ADD, 2, 1) { /* (Extended) Community list add */
- ARG_ANY(1);
- ARG_ANY(2);
- RESULT_TYPE(f1->type);
+ /* Community list add */
+ INST(FI_CLIST_ADD_PAIR, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_PAIR);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
+ }
- if (v1.type == T_PATH)
- runtime("Can't add to path");
+ INST(FI_CLIST_ADD_IP, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_IP);
+ METHOD_CONSTRUCTOR("add");
- else if (v1.type == T_CLIST)
- {
- /* Community (or cluster) list */
- struct f_val dummy;
-
- if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
- RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
- /* IP->Quad implicit conversion */
- else if (val_is_ip4(&v2))
- RESULT_(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
- else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy))
- runtime("Can't add set");
- else if (v2.type == T_CLIST)
- RESULT_(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
- else
- runtime("Can't add non-pair");
- }
+ FID_NEW_BODY();
+ /* IP->Quad implicit conversion, must be before FI_CLIST_ADD_QUAD */
+ cf_warn("Method add(clist, ip) is deprecated, please use add(clist, quad)");
- else if (v1.type == T_ECLIST)
- {
- /* v2.val is either EC or EC-set */
- if ((v2.type == T_SET) && eclist_set_type(v2.val.t))
- runtime("Can't add set");
- else if (v2.type == T_ECLIST)
- RESULT_(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
- else if (v2.type != T_EC)
- runtime("Can't add non-ec");
- else
- RESULT_(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
- }
+ FID_INTERPRET_BODY();
+ if (!val_is_ip4(&v2)) runtime("Mismatched IP type");
+ RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
+ }
- else if (v1.type == T_LCLIST)
- {
- /* v2.val is either LC or LC-set */
- if ((v2.type == T_SET) && lclist_set_type(v2.val.t))
- runtime("Can't add set");
- else if (v2.type == T_LCLIST)
- RESULT_(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
- else if (v2.type != T_LC)
- runtime("Can't add non-lc");
- else
- RESULT_(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
+ INST(FI_CLIST_ADD_QUAD, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_QUAD);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_CLIST, ad, [[ int_set_add(fpool, v1.val.ad, v2.val.i) ]]);
+ }
- }
+ INST(FI_CLIST_ADD_CLIST, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_CLIST);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_CLIST, ad, [[ int_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+ }
- else
- runtime("Can't add to non-[e|l]clist");
+ INST(FI_ECLIST_ADD_EC, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_EC);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_ECLIST, ad, [[ ec_set_add(fpool, v1.val.ad, v2.val.ec) ]]);
}
- INST(FI_CLIST_DEL, 2, 1) { /* (Extended) Community list add or delete */
- ARG_ANY(1);
- ARG_ANY(2);
- RESULT_TYPE(f1->type);
+ INST(FI_ECLIST_ADD_ECLIST, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_ECLIST);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_ECLIST, ad, [[ ec_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+ }
- if (v1.type == T_PATH)
- {
- if ((v2.type == T_SET) && path_set_type(v2.val.t) || (v2.type == T_INT))
- RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
- else
- runtime("Can't delete non-integer (set)");
- }
+ INST(FI_LCLIST_ADD_LC, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_LC);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_LCLIST, ad, [[ lc_set_add(fpool, v1.val.ad, v2.val.lc) ]]);
+ }
- else if (v1.type == T_CLIST)
- {
- /* Community (or cluster) list */
- struct f_val dummy;
-
- if ((v2.type == T_PAIR) || (v2.type == T_QUAD))
- RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
- /* IP->Quad implicit conversion */
- else if (val_is_ip4(&v2))
- RESULT_(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
- else if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
- RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
- else
- runtime("Can't delete non-pair");
- }
+ INST(FI_LCLIST_ADD_LCLIST, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_LCLIST);
+ METHOD_CONSTRUCTOR("add");
+ RESULT(T_LCLIST, ad, [[ lc_set_union(fpool, v1.val.ad, v2.val.ad) ]]);
+ }
- else if (v1.type == T_ECLIST)
- {
- /* v2.val is either EC or EC-set */
- if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
- RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
- else if (v2.type != T_EC)
- runtime("Can't delete non-ec");
- else
- RESULT_(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
- }
+ INST(FI_PATH_DELETE_INT, 2, 1) {
+ ARG(1, T_PATH);
+ ARG(2, T_INT);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
- else if (v1.type == T_LCLIST)
- {
- /* v2.val is either LC or LC-set */
- if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
- RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
- else if (v2.type != T_LC)
- runtime("Can't delete non-lc");
- else
- RESULT_(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
- }
+ INST(FI_PATH_DELETE_SET, 2, 1) {
+ ARG(1, T_PATH);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("delete");
- else
- runtime("Can't delete in non-[e|l]clist");
+ if (!path_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 0) ]]);
}
- INST(FI_CLIST_FILTER, 2, 1) { /* (Extended) Community list add or delete */
- ARG_ANY(1);
- ARG_ANY(2);
- RESULT_TYPE(f1->type);
+ /* Community list delete */
+ INST(FI_CLIST_DELETE_PAIR, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_PAIR);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
+ }
- if (v1.type == T_PATH)
- {
- if ((v2.type == T_SET) && path_set_type(v2.val.t))
- RESULT_(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 1) ]]);
- else
- runtime("Can't filter integer");
- }
+ INST(FI_CLIST_DELETE_IP, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_IP);
+ METHOD_CONSTRUCTOR("delete");
- else if (v1.type == T_CLIST)
- {
- /* Community (or cluster) list */
- struct f_val dummy;
+ FID_NEW_BODY();
+ /* IP->Quad implicit conversion, must be before FI_CLIST_DELETE_QUAD */
+ cf_warn("Method delete(clist, ip) is deprecated, please use delete(clist, quad)");
- if ((v2.type == T_SET) && clist_set_type(v2.val.t, &dummy) || (v2.type == T_CLIST))
- RESULT_(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
- else
- runtime("Can't filter pair");
- }
+ FID_INTERPRET_BODY();
+ if (!val_is_ip4(&v2)) runtime("Mismatched IP type");
+ RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, ipa_to_u32(v2.val.ip)) ]]);
+ }
- else if (v1.type == T_ECLIST)
- {
- /* v2.val is either EC or EC-set */
- if ((v2.type == T_SET) && eclist_set_type(v2.val.t) || (v2.type == T_ECLIST))
- RESULT_(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
- else
- runtime("Can't filter ec");
- }
+ INST(FI_CLIST_DELETE_QUAD, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_QUAD);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_CLIST, ad, [[ int_set_del(fpool, v1.val.ad, v2.val.i) ]]);
+ }
- else if (v1.type == T_LCLIST)
- {
- /* v2.val is either LC or LC-set */
- if ((v2.type == T_SET) && lclist_set_type(v2.val.t) || (v2.type == T_LCLIST))
- RESULT_(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
- else
- runtime("Can't filter lc");
- }
+ INST(FI_CLIST_DELETE_CLIST, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_CLIST);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
- else
- runtime("Can't filter non-[e|l]clist");
+ INST(FI_CLIST_DELETE_SET, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("delete");
+
+ if (!clist_set_type(v2.val.t, &(struct f_val){}))
+ runtime("Mismatched set type");
+
+ RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
+
+ INST(FI_ECLIST_DELETE_EC, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_EC);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_ECLIST, ad, [[ ec_set_del(fpool, v1.val.ad, v2.val.ec) ]]);
+ }
+
+ INST(FI_ECLIST_DELETE_ECLIST, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_ECLIST);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
+
+ INST(FI_ECLIST_DELETE_SET, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("delete");
+
+ if (!eclist_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
+
+ INST(FI_LCLIST_DELETE_LC, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_LC);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_LCLIST, ad, [[ lc_set_del(fpool, v1.val.ad, v2.val.lc) ]]);
+ }
+
+ INST(FI_LCLIST_DELETE_LCLIST, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_LCLIST);
+ METHOD_CONSTRUCTOR("delete");
+ RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
+
+ INST(FI_LCLIST_DELETE_SET, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("delete");
+
+ if (!lclist_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 0) ]]);
+ }
+
+ INST(FI_PATH_FILTER_SET, 2, 1) {
+ ARG(1, T_PATH);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("filter");
+
+ if (!path_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_PATH, ad, [[ as_path_filter(fpool, v1.val.ad, &v2, 1) ]]);
+ }
+
+ INST(FI_CLIST_FILTER_CLIST, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_CLIST);
+ METHOD_CONSTRUCTOR("filter");
+ RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+ }
+
+ INST(FI_CLIST_FILTER_SET, 2, 1) {
+ ARG(1, T_CLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("filter");
+
+ if (!clist_set_type(v2.val.t, &(struct f_val){}))
+ runtime("Mismatched set type");
+
+ RESULT(T_CLIST, ad, [[ clist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+ }
+
+ INST(FI_ECLIST_FILTER_ECLIST, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_ECLIST);
+ METHOD_CONSTRUCTOR("filter");
+ RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+ }
+
+ INST(FI_ECLIST_FILTER_SET, 2, 1) {
+ ARG(1, T_ECLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("filter");
+
+ if (!eclist_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_ECLIST, ad, [[ eclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+ }
+
+ INST(FI_LCLIST_FILTER_LCLIST, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_LCLIST);
+ METHOD_CONSTRUCTOR("filter");
+ RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
+ }
+
+ INST(FI_LCLIST_FILTER_SET, 2, 1) {
+ ARG(1, T_LCLIST);
+ ARG(2, T_SET);
+ METHOD_CONSTRUCTOR("filter");
+
+ if (!lclist_set_type(v2.val.t))
+ runtime("Mismatched set type");
+
+ RESULT(T_LCLIST, ad, [[ lclist_filter(fpool, v1.val.ad, &v2, 1) ]]);
}
INST(FI_ROA_CHECK_IMPLICIT, 0, 1) { /* ROA Check */
@@ -1556,6 +1541,21 @@
}
+ INST(FI_FROM_HEX, 1, 1) { /* Convert hex text to bytestring */
+ ARG(1, T_STRING);
+
+ int len = bstrhextobin(v1.val.s, NULL);
+ if (len < 0)
+ runtime("Invalid hex string");
+
+ struct adata *bs;
+ bs = falloc(sizeof(struct adata) + len);
+ bs->length = bstrhextobin(v1.val.s, bs->data);
+ ASSERT(bs->length == (size_t) len);
+
+ RESULT(T_BYTESTRING, bs, bs);
+ }
+
INST(FI_FORMAT, 1, 1) { /* Format */
ARG_ANY(1);
RESULT(T_STRING, s, val_format_str(fpool, &v1));
diff --git a/filter/f-inst.h b/filter/f-inst.h
index e35f71c6..955cfbdc 100644
--- a/filter/f-inst.h
+++ b/filter/f-inst.h
@@ -19,6 +19,7 @@
#include "filter/data.h"
#include "lib/buffer.h"
#include "lib/flowspec.h"
+#include "lib/string.h"
/* Flags for instructions */
enum f_instruction_flags {
@@ -35,6 +36,16 @@ const char *f_instruction_name_(enum f_instruction_code fi);
static inline const char *f_instruction_name(enum f_instruction_code fi)
{ return f_instruction_name_(fi) + 3; }
+
+int f_const_promotion_(struct f_inst *arg, enum f_type want, int update);
+
+static inline int f_const_promotion(struct f_inst *arg, enum f_type want)
+{ return f_const_promotion_(arg, want, 1); }
+
+static inline int f_try_const_promotion(struct f_inst *arg, enum f_type want)
+{ return f_const_promotion_(arg, want, 0); }
+
+
struct f_arg {
struct symbol *arg;
struct f_arg *next;
@@ -47,6 +58,7 @@ struct f_line {
u8 args; /* Function: Args required */
u8 vars;
u8 results; /* Results left on stack: cmd -> 0, term -> 1 */
+ u8 return_type; /* Type which the function returns */
struct f_arg *arg_list;
struct f_line_item items[0]; /* The items themselves */
};
@@ -94,14 +106,32 @@ void f_add_lines(const struct f_line_item *what, struct filter_iterator *fit);
struct filter *f_new_where(struct f_inst *);
+struct f_inst *f_dispatch_method(struct symbol *sym, struct f_inst *obj, struct f_inst *args, int skip);
+struct f_inst *f_dispatch_method_x(const char *name, enum f_type t, struct f_inst *obj, struct f_inst *args);
+struct f_inst *f_for_cycle(struct symbol *var, struct f_inst *term, struct f_inst *block);
+struct f_inst *f_print(struct f_inst *vars, int flush, enum filter_return fret);
+
static inline struct f_dynamic_attr f_new_dynamic_attr(u8 type, enum f_type f_type, uint code) /* Type as core knows it, type as filters know it, and code of dynamic attribute */
{ return (struct f_dynamic_attr) { .type = type, .f_type = f_type, .ea_code = code }; } /* f_type currently unused; will be handy for static type checking */
static inline struct f_dynamic_attr f_new_dynamic_attr_bit(u8 bit, enum f_type f_type, uint code) /* Type as core knows it, type as filters know it, and code of dynamic attribute */
{ return (struct f_dynamic_attr) { .type = EAF_TYPE_BITFIELD, .bit = bit, .f_type = f_type, .ea_code = code }; } /* f_type currently unused; will be handy for static type checking */
static inline struct f_static_attr f_new_static_attr(int f_type, int code, int readonly)
{ return (struct f_static_attr) { .f_type = f_type, .sa_code = code, .readonly = readonly }; }
-struct f_inst *f_generate_complex(enum f_instruction_code fi_code, struct f_dynamic_attr da, struct f_inst *argument);
-struct f_inst *f_generate_roa_check(struct rtable_config *table, struct f_inst *prefix, struct f_inst *asn);
+
+static inline int f_type_attr(int f_type) {
+ switch (f_type) {
+ case T_INT: return EAF_TYPE_INT;
+ case T_IP: return EAF_TYPE_IP_ADDRESS;
+ case T_QUAD: return EAF_TYPE_ROUTER_ID;
+ case T_PATH: return EAF_TYPE_AS_PATH;
+ case T_CLIST: return EAF_TYPE_INT_SET;
+ case T_ECLIST: return EAF_TYPE_EC_SET;
+ case T_LCLIST: return EAF_TYPE_LC_SET;
+ case T_BYTESTRING: return EAF_TYPE_OPAQUE;
+ default:
+ cf_error("Custom route attribute of unsupported type");
+ }
+}
/* Hook for call bt_assert() function in configuration */
extern void (*bt_assert_hook)(int result, const struct f_line_item *assert);
diff --git a/filter/f-util.c b/filter/f-util.c
index d814493e..a47a8747 100644
--- a/filter/f-util.c
+++ b/filter/f-util.c
@@ -30,7 +30,8 @@ filter_name(const struct filter *filter)
return filter->sym->name;
}
-struct filter *f_new_where(struct f_inst *where)
+struct filter *
+f_new_where(struct f_inst *where)
{
struct f_inst *cond = f_new_inst(FI_CONDITION, where,
f_new_inst(FI_DIE, F_ACCEPT),
@@ -41,6 +42,177 @@ struct filter *f_new_where(struct f_inst *where)
return f;
}
+static inline int
+f_match_signature(const struct f_method *dsc, struct f_inst *args)
+{
+ int i, arg_num = (int) dsc->arg_num;
+
+ for (i = 1; args && (i < arg_num); args = args->next, i++)
+ if (dsc->args_type[i] && (args->type != dsc->args_type[i]) &&
+ !f_try_const_promotion(args, dsc->args_type[i]))
+ return 0;
+
+ return !args && !(i < arg_num);
+}
+
+/* Variant of f_match_signature(), optimized for error reporting */
+static inline void
+f_match_signature_err(const struct f_method *dsc, struct f_inst *args, int *pos, int *want, int *got)
+{
+ int i, arg_num = (int) dsc->arg_num;
+
+ for (i = 1; args && (i < arg_num); args = args->next, i++)
+ if (dsc->args_type[i] && (args->type != dsc->args_type[i]) &&
+ !f_try_const_promotion(args, dsc->args_type[i]))
+ break;
+
+ *pos = i;
+ *want = (i < arg_num) ? dsc->args_type[i] : T_NONE;
+ *got = args ? args->type : T_NONE;
+}
+
+struct f_inst *
+f_dispatch_method(struct symbol *sym, struct f_inst *obj, struct f_inst *args, int skip)
+{
+ /* Find match */
+ for (const struct f_method *dsc = sym->method; dsc; dsc = dsc->next)
+ if (f_match_signature(dsc, args))
+ return dsc->new_inst(obj, args);
+
+
+ /* No valid match - format error message */
+
+ int best_pos = -1; /* Longest argument position with partial match */
+ int best_got = 0; /* Received type at best partial match position */
+ int best_count = 0; /* Number of partial matches at best position */
+ const int best_max = 8; /* Max number of reported types */
+ int best_want[best_max]; /* Expected types at best position */
+
+ for (const struct f_method *dsc = sym->method; dsc; dsc = dsc->next)
+ {
+ int pos, want, got;
+ f_match_signature_err(dsc, args, &pos, &want, &got);
+
+ /* Ignore shorter match */
+ if (pos < best_pos)
+ continue;
+
+ /* Found longer match, reset existing results */
+ if (pos > best_pos)
+ {
+ best_pos = pos;
+ best_got = got;
+ best_count = 0;
+ }
+
+ /* Skip duplicates */
+ for (int i = 0; i < best_count; i++)
+ if (best_want[i] == want)
+ goto next;
+
+ /* Skip if we have enough types */
+ if (best_count >= best_max)
+ continue;
+
+ /* Add new expected type */
+ best_want[best_count] = want;
+ best_count++;
+ next:;
+ }
+
+ /* There is at least one method */
+ ASSERT(best_pos >= 0 && best_count > 0);
+
+ /* Update best_pos for printing */
+ best_pos = best_pos - skip + 1;
+
+ if (!best_got)
+ cf_error("Cannot infer type of argument %d of '%s', please assign it to a variable", best_pos, sym->name);
+
+ /* Format list of expected types */
+ buffer tbuf;
+ STACK_BUFFER_INIT(tbuf, 128);
+ for (int i = 0; i < best_count; i++)
+ buffer_print(&tbuf, " / %s", best_want[i] ? f_type_name(best_want[i]) : "any");
+ char *types = tbuf.start + 3;
+ char *dots = (best_count >= best_max) || (tbuf.pos == tbuf.end) ? " / ..." : "";
+
+ cf_error("Argument %d of '%s' expected %s%s, got %s",
+ best_pos, sym->name, types, dots, f_type_name(best_got));
+}
+
+struct f_inst *
+f_dispatch_method_x(const char *name, enum f_type t, struct f_inst *obj, struct f_inst *args)
+{
+ struct sym_scope *scope = f_type_method_scope(t);
+ struct symbol *sym = cf_find_symbol_scope(scope, name);
+
+ if (!sym)
+ cf_error("Cannot dispatch method '%s'", name);
+
+ return f_dispatch_method(sym, obj, args, 0);
+}
+
+
+struct f_inst *
+f_for_cycle(struct symbol *var, struct f_inst *term, struct f_inst *block)
+{
+ ASSERT((var->class & ~0xff) == SYM_VARIABLE);
+ ASSERT(term->next == NULL);
+
+ /* Static type check */
+ if (term->type == T_VOID)
+ cf_error("Cannot infer type of FOR expression, please assign it to a variable");
+
+ enum f_type el_type = f_type_element_type(term->type);
+ struct sym_scope *scope = el_type ? f_type_method_scope(term->type) : NULL;
+ struct symbol *ms = scope ? cf_find_symbol_scope(scope, "!for_next") : NULL;
+
+ if (!ms)
+ cf_error("Type %s is not iterable, can't be used in FOR", f_type_name(term->type));
+
+ if (var->class != (SYM_VARIABLE | el_type))
+ cf_error("Loop variable '%s' in FOR must be of type %s, got %s",
+ var->name, f_type_name(el_type), f_type_name(var->class & 0xff));
+
+ /* Push the iterator auxiliary value onto stack */
+ struct f_inst *iter = term->next = f_new_inst(FI_CONSTANT, (struct f_val) {});
+
+ /* Initialize the iterator variable */
+ iter->next = f_new_inst(FI_CONSTANT, (struct f_val) { .type = el_type });
+
+ /* Prepend the loop block with loop beginning instruction */
+ struct f_inst *loop_start = f_new_inst(FI_FOR_LOOP_START, var);
+ loop_start->next = block;
+
+ return ms->method->new_inst(term, loop_start);
+}
+
+struct f_inst *
+f_print(struct f_inst *vars, int flush, enum filter_return fret)
+{
+#define AX(...) do { struct f_inst *_tmp = f_new_inst(__VA_ARGS__); _tmp->next = output; output = _tmp; } while (0)
+ struct f_inst *output = NULL;
+ if (fret != F_NOP)
+ AX(FI_DIE, fret);
+
+ if (flush)
+ AX(FI_FLUSH);
+
+ while (vars)
+ {
+ struct f_inst *tmp = vars;
+ vars = vars->next;
+ tmp->next = NULL;
+
+ AX(FI_PRINT, tmp);
+ }
+
+ return output;
+#undef AX
+}
+
+
#define CA_KEY(n) n->name, n->fda.type
#define CA_NEXT(n) n->next
#define CA_EQ(na,ta,nb,tb) (!strcmp(na,nb) && (ta == tb))
@@ -122,6 +294,9 @@ ca_lookup(pool *p, const char *name, int f_type)
case T_LCLIST:
ea_type = EAF_TYPE_LC_SET;
break;
+ case T_BYTESTRING:
+ ea_type = EAF_TYPE_OPAQUE;
+ break;
default:
cf_error("Custom route attribute of unsupported type");
}
diff --git a/filter/filter.c b/filter/filter.c
index 20a380dc..560778a8 100644
--- a/filter/filter.c
+++ b/filter/filter.c
@@ -90,6 +90,9 @@ struct filter_state {
/* Buffer for log output */
struct buffer buf;
+ /* Pointers to routes we are aggregating */
+ const struct f_val *val;
+
/* Filter execution flags */
int flags;
};
@@ -157,23 +160,26 @@ static struct tbf rl_runtime_err = TBF_DEFAULT_LOG_LIMITS;
* TWOARGS macro to get both of them evaluated.
*/
static enum filter_return
-interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
+interpret(struct filter_state *fs, const struct f_line *line, uint argc, const struct f_val *argv, struct f_val *val)
{
/* No arguments allowed */
- ASSERT(line->args == 0);
+ ASSERT_DIE(line->args == argc);
/* Initialize the filter stack */
struct filter_stack *fstk = fs->stack;
- fstk->vcnt = line->vars;
- memset(fstk->vstk, 0, sizeof(struct f_val) * line->vars);
+ /* Set the arguments and top-level variables */
+ fstk->vcnt = line->vars + line->args;
+ memcpy(fstk->vstk, argv, sizeof(struct f_val) * line->args);
+ memset(fstk->vstk + line->args, 0, sizeof(struct f_val) * line->vars);
- /* The same as with the value stack. Not resetting the stack for performance reasons. */
+ /* The same as with the value stack. Not resetting the stack completely for performance reasons. */
fstk->ecnt = 1;
fstk->estk[0].line = line;
fstk->estk[0].pos = 0;
#define curline fstk->estk[fstk->ecnt-1]
+#define prevline fstk->estk[fstk->ecnt-2]
#ifdef LOCAL_DEBUG
debug("Interpreting line.");
@@ -236,7 +242,6 @@ interpret(struct filter_state *fs, const struct f_line *line, struct f_val *val)
return F_ERROR;
}
-
/**
* f_run - run a filter for a route
* @filter: filter to run
@@ -270,6 +275,12 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
if (filter == FILTER_REJECT)
return F_REJECT;
+ return f_run_args(filter, rte, tmp_pool, 0, NULL, flags);
+}
+
+enum filter_return
+f_run_args(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, int flags)
+{
int rte_cow = ((*rte)->flags & REF_COW);
DBG( "Running filter `%s'...", filter->name );
@@ -284,7 +295,7 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
LOG_BUFFER_INIT(filter_state.buf);
/* Run the interpreter itself */
- enum filter_return fret = interpret(&filter_state, filter->root, NULL);
+ enum filter_return fret = interpret(&filter_state, filter->root, argc, argv, NULL);
if (filter_state.old_rta) {
/*
@@ -336,7 +347,7 @@ f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, i
*/
enum filter_return
-f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool)
+f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, struct f_val *pres)
{
filter_state = (struct filter_state) {
.stack = &filter_stack,
@@ -346,10 +357,7 @@ f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool
LOG_BUFFER_INIT(filter_state.buf);
- ASSERT(!((*rte)->flags & REF_COW));
- ASSERT(!rta_is_cached((*rte)->attrs));
-
- return interpret(&filter_state, expr, NULL);
+ return interpret(&filter_state, expr, argc, argv, pres);
}
/*
@@ -368,35 +376,27 @@ f_eval(const struct f_line *expr, struct linpool *tmp_pool, struct f_val *pres)
LOG_BUFFER_INIT(filter_state.buf);
- enum filter_return fret = interpret(&filter_state, expr, pres);
+ enum filter_return fret = interpret(&filter_state, expr, 0, NULL, pres);
return fret;
}
/*
- * f_eval_int - get an integer value of a term
+ * cf_eval - evaluate a value of a term and check its type
* Called internally from the config parser, uses its internal memory pool
* for allocations. Do not call in other cases.
*/
-uint
-f_eval_int(const struct f_line *expr)
+struct f_val
+cf_eval(const struct f_inst *inst, int type)
{
- /* Called independently in parse-time to eval expressions */
- filter_state = (struct filter_state) {
- .stack = &filter_stack,
- .pool = cfg_mem,
- };
-
struct f_val val;
- LOG_BUFFER_INIT(filter_state.buf);
-
- if (interpret(&filter_state, expr, &val) > F_RETURN)
+ if (f_eval(f_linearize(inst, 1), cfg_mem, &val) > F_RETURN)
cf_error("Runtime error while evaluating expression; see log for details");
- if (val.type != T_INT)
- cf_error("Integer expression expected");
+ if (type != T_VOID && val.type != type)
+ cf_error("Expression of type %s expected", f_type_name(type));
- return val.val.i;
+ return val;
}
/*
diff --git a/filter/filter.h b/filter/filter.h
index 26c1037b..18ff0874 100644
--- a/filter/filter.h
+++ b/filter/filter.h
@@ -15,6 +15,7 @@
#include "lib/macro.h"
#include "nest/route.h"
#include "nest/attrs.h"
+#include "filter/data.h"
/* Possible return values of filter execution */
enum filter_return {
@@ -40,9 +41,8 @@ static inline const char *filter_return_str(const enum filter_return fret) {
}
}
-struct f_val;
-
/* The filter encapsulating structure to be pointed-to from outside */
+struct f_inst;
struct f_line;
struct filter {
struct symbol *sym;
@@ -52,10 +52,13 @@ struct filter {
struct rte;
enum filter_return f_run(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, int flags);
-enum filter_return f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool);
-uint f_eval_int(const struct f_line *expr);
+enum filter_return f_run_args(const struct filter *filter, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, int flags);
+enum filter_return f_eval_rte(const struct f_line *expr, struct rte **rte, struct linpool *tmp_pool, uint argc, const struct f_val *argv, struct f_val *pres);
enum filter_return f_eval_buf(const struct f_line *expr, struct linpool *tmp_pool, buffer *buf);
+struct f_val cf_eval(const struct f_inst *inst, int type);
+static inline uint cf_eval_int(const struct f_inst *inst) { return cf_eval(inst, T_INT).val.i; };
+
const char *filter_name(const struct filter *filter);
int filter_same(const struct filter *new, const struct filter *old);
int f_same(const struct f_line *f1, const struct f_line *f2);
diff --git a/filter/filter_test.c b/filter/filter_test.c
index e8e8b747..782b3c02 100644
--- a/filter/filter_test.c
+++ b/filter/filter_test.c
@@ -25,9 +25,9 @@
static int
-t_reconfig(void)
+t_reconfig(const void *arg)
{
- if (!bt_config_file_parse(BT_CONFIG_FILE))
+ if (!bt_config_file_parse(arg))
return 0;
struct symbol *s;
@@ -78,7 +78,9 @@ main(int argc, char *argv[])
if (!bt_config_file_parse(BT_CONFIG_FILE))
abort();
- bt_test_suite(t_reconfig, "Testing reconfiguration");
+ bt_test_suite_arg(t_reconfig, BT_CONFIG_FILE ".overlay", "Testing reconfiguration to overlay");
+ bt_test_suite_arg(t_reconfig, BT_CONFIG_FILE, "Testing reconfiguration back");
+ bt_test_suite_arg(t_reconfig, BT_CONFIG_FILE, "Testing reconfiguration to the same file");
struct f_bt_test_suite *t;
WALK_LIST(t, config->tests)
diff --git a/filter/test.conf b/filter/test.conf
index 1d291c69..f9289189 100644
--- a/filter/test.conf
+++ b/filter/test.conf
@@ -9,6 +9,8 @@ router id 62.168.0.1;
/* We have to setup any protocol */
protocol device { }
+attribute bgppath mypath;
+attribute lclist mylclist;
/*
@@ -19,17 +21,17 @@ protocol device { }
define one = 1;
define ten = 10;
-function onef(int a)
+function onef(int a) -> int
{
return 1;
}
-function twof(int a)
+function twof(int a) -> int
{
return 2;
}
-function oneg(int a)
+function oneg(int a) -> int
{
return 1;
}
@@ -39,6 +41,18 @@ bt_test_same(onef, oneg, 1);
bt_test_same(onef, twof, 0);
/*
+ * Testing filter corner cases
+ * ---------------------------
+ */
+
+function t_nothing() {}
+bt_test_suite(t_nothing, "Testing nothing");
+
+function t_metanothing() { t_nothing(); }
+bt_test_suite(t_metanothing, "Testing meta nothing");
+
+
+/*
* Testing boolean expressions
* ---------------------------
*/
@@ -76,6 +90,14 @@ bt_test_suite(t_bool, "Testing boolean expressions");
* ----------------
*/
+function aux_t_int(int t; int u)
+{
+ case t {
+ 1: {}
+ else: {}
+ }
+}
+
define four = 4;
define xyzzy = (120+10);
define '1a-a1' = (xyzzy-100);
@@ -120,7 +142,10 @@ function t_int()
else: bt_assert(false);
}
-
+ aux_t_int(1, 2);
+ aux_t_int(1, 3);
+ aux_t_int(2, 3);
+ aux_t_int(2, 2);
}
bt_test_suite(t_int, "Testing integers");
@@ -215,11 +240,41 @@ bt_test_suite(t_string, "Testing string matching");
/*
+ * Testing bytestings
+ * ------------------
+ */
+
+function t_bytestring()
+{
+ bytestring bs1 = hex:;
+ bytestring bs2 = hex:0112233445566778899aabbccddeeff0;
+
+ bt_assert(format(bs1) = "");
+ bt_assert(format(bs2) = "01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0");
+ bt_assert(hex:01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0 = bs2);
+ bt_assert(01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0 = bs2);
+ bt_assert(0112233445566778899aabbccddeeff0 = bs2);
+ bt_assert(hex:01234567 = hex:01:23:45:67);
+ bt_assert(hex:0123456789abcdef != bs2);
+ bt_assert(hex:0123456789abcdef != hex:0123);
+ bt_assert(format(hex:0123456789abcdef) = "01:23:45:67:89:ab:cd:ef");
+ bt_assert(from_hex(" ") = bs1);
+ bt_assert(from_hex("01:12:23:34:45:56:67:78:89:9a:ab:bc:cd:de:ef:f0") = bs2);
+ bt_assert(from_hex(format(bs2)) = bs2);
+ bt_assert(from_hex(" 0112:23-34455667 78-89 - 9a-ab bc:cd : de:eff0 ") = bs2);
+}
+
+bt_test_suite(t_bytestring, "Testing bytestrings");
+
+
+
+
+/*
* Testing pairs
* -------------
*/
-function 'mkpair-a'(int a)
+function 'mkpair-a'(int a) -> pair
{
return (1, a);
}
@@ -542,10 +597,10 @@ prefix set pxs;
bt_assert(format([ 0.0.0.0/0 ]) = "[0.0.0.0/0]");
bt_assert(format([ 10.10.0.0/32 ]) = "[10.10.0.0/32{0.0.0.1}]");
bt_assert(format([ 10.10.0.0/17 ]) = "[10.10.0.0/17{0.0.128.0}]");
- bt_assert(format([ 10.10.0.0/17{17,19} ]) = "[10.10.0.0/17{0.0.224.0}]"); # 224 = 128+64+32
+ # bt_assert(format([ 10.10.0.0/17{17,19} ]) = "[10.10.0.0/17{0.0.224.0}]"); # 224 = 128+64+32
bt_assert(format([ 10.10.128.0/17{18,19} ]) = "[10.10.128.0/18{0.0.96.0}, 10.10.192.0/18{0.0.96.0}]"); # 96 = 64+32
- bt_assert(format([ 10.10.64.0/18- ]) = "[0.0.0.0/0, 0.0.0.0/1{128.0.0.0}, 0.0.0.0/2{64.0.0.0}, 0.0.0.0/3{32.0.0.0}, 10.10.0.0/16{255.255.0.0}, 10.10.0.0/17{0.0.128.0}, 10.10.64.0/18{0.0.64.0}]");
- bt_assert(format([ 10.10.64.0/18+ ]) = "[10.10.64.0/18{0.0.96.0}, 10.10.64.0/20{0.0.31.255}, 10.10.80.0/20{0.0.31.255}, 10.10.96.0/20{0.0.31.255}, 10.10.112.0/20{0.0.31.255}]");
+ # bt_assert(format([ 10.10.64.0/18- ]) = "[0.0.0.0/0, 0.0.0.0/1{128.0.0.0}, 0.0.0.0/2{64.0.0.0}, 0.0.0.0/3{32.0.0.0}, 10.10.0.0/16{255.255.0.0}, 10.10.0.0/17{0.0.128.0}, 10.10.64.0/18{0.0.64.0}]");
+ # bt_assert(format([ 10.10.64.0/18+ ]) = "[10.10.64.0/18{0.0.96.0}, 10.10.64.0/20{0.0.31.255}, 10.10.80.0/20{0.0.31.255}, 10.10.96.0/20{0.0.31.255}, 10.10.112.0/20{0.0.31.255}]");
bt_assert(format([ 10.10.160.0/19 ]) = "[10.10.160.0/19{0.0.32.0}]");
bt_assert(format([ 10.10.160.0/19{19,22} ]) = "[10.10.160.0/19{0.0.32.0}, 10.10.160.0/20{0.0.28.0}, 10.10.176.0/20{0.0.28.0}]"); # 28 = 16+8+4
@@ -557,10 +612,10 @@ prefix set pxs;
bt_assert(format([ 11:22:33:44::/64+ ]) = "[11:22:33:44::/64{::1:ffff:ffff:ffff:ffff}]");
bt_assert(format([ 11:22:33:44::/65 ]) = "[11:22:33:44::/65{::8000:0:0:0}]");
- bt_assert(format([ 11:22:33:44::/65{65,67} ]) = "[11:22:33:44::/65{::e000:0:0:0}]"); # e = 8+4+2
+ # bt_assert(format([ 11:22:33:44::/65{65,67} ]) = "[11:22:33:44::/65{::e000:0:0:0}]"); # e = 8+4+2
bt_assert(format([ 11:22:33:44:8000::/65{66,67} ]) = "[11:22:33:44:8000::/66{::6000:0:0:0}, 11:22:33:44:c000::/66{::6000:0:0:0}]"); # 6 = 4+2
- bt_assert(format([ 11:22:33:44:4000::/66- ]) = "[::/0, ::/1{8000::}, ::/2{4000::}, ::/3{2000::}, 11:22:33:44::/64{ffff:ffff:ffff:ffff::}, 11:22:33:44::/65{::8000:0:0:0}, 11:22:33:44:4000::/66{::4000:0:0:0}]");
- bt_assert(format([ 11:22:33:44:4000::/66+ ]) = "[11:22:33:44:4000::/66{::6000:0:0:0}, 11:22:33:44:4000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:5000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:6000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:7000::/68{::1fff:ffff:ffff:ffff}]");
+ # bt_assert(format([ 11:22:33:44:4000::/66- ]) = "[::/0, ::/1{8000::}, ::/2{4000::}, ::/3{2000::}, 11:22:33:44::/64{ffff:ffff:ffff:ffff::}, 11:22:33:44::/65{::8000:0:0:0}, 11:22:33:44:4000::/66{::4000:0:0:0}]");
+ # bt_assert(format([ 11:22:33:44:4000::/66+ ]) = "[11:22:33:44:4000::/66{::6000:0:0:0}, 11:22:33:44:4000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:5000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:6000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:7000::/68{::1fff:ffff:ffff:ffff}]");
bt_assert(format([ 11:22:33:44:c000::/67 ]) = "[11:22:33:44:c000::/67{::2000:0:0:0}]");
bt_assert(format([ 11:22:33:44:c000::/67{67,71} ]) = "[11:22:33:44:c000::/67{::2000:0:0:0}, 11:22:33:44:c000::/68{::1e00:0:0:0}, 11:22:33:44:d000::/68{::1e00:0:0:0}]");
bt_assert(format([ 11:22:33:44:c000::/67+ ]) = "[11:22:33:44:c000::/67{::2000:0:0:0}, 11:22:33:44:c000::/68{::1fff:ffff:ffff:ffff}, 11:22:33:44:d000::/68{::1fff:ffff:ffff:ffff}]");
@@ -577,8 +632,8 @@ bt_test_suite(t_prefix_set, "Testing prefix sets");
*/
function t_prefix6()
-prefix px;
{
+ prefix px;
px = 1020::/18;
bt_assert(format(px) = "1020::/18");
bt_assert(1020:3040:5060:: ~ 1020:3040:5000::/40);
@@ -694,14 +749,14 @@ bt_test_suite(t_flowspec, "Testing flowspec routes");
* -------------
*/
-function mkpath(int a; int b)
+function mkpath(int a; int b) -> bgpmask
{
return [= a b 3 2 1 =];
}
define set35 = [3 .. 5];
-function t_path()
+function t_path_old()
bgpmask pm1;
bgppath p2;
int set set12;
@@ -780,7 +835,91 @@ int set set12;
bt_assert(x = 18 && y = 50);
}
-bt_test_suite(t_path, "Testing paths");
+bt_test_suite(t_path_old, "Testing paths (old syntax)");
+
+
+function t_path_new()
+{
+ bgpmask pm1 = [= 4 3 2 1 =];
+ int set set12 = [1, 2];
+
+ bt_assert(format(pm1) = "[= 4 3 2 1 =]");
+
+ bt_assert(+empty+ = +empty+);
+ bt_assert(10 !~ +empty+);
+
+ bgppath p2;
+ bt_assert(p2 = +empty+);
+ p2.prepend(1);
+ p2.prepend(2);
+ p2.prepend(3);
+ p2.prepend(4);
+
+ bt_assert(p2.empty = +empty+);
+
+ bt_assert(format(p2) = "(path 4 3 2 1)");
+ bt_assert(p2.len = 4);
+ bt_assert(p2 ~ pm1);
+ bt_assert(3 ~ p2);
+ bt_assert(p2 ~ [2, 10..20]);
+ bt_assert(p2 ~ [4, 10..20]);
+ bt_assert(p2 !~ []);
+
+ p2.prepend(5);
+ bt_assert(p2 !~ pm1);
+ bt_assert(10 !~ p2);
+ bt_assert(p2 !~ [8, ten..(2*ten)]);
+ bt_assert(p2 ~ [= * 4 3 * 1 =]);
+ bt_assert(p2 ~ [= (3+2) (2*2) 3 2 1 =]);
+ bt_assert(p2 ~ [= 5 [2, 4, 6] 3 [1..2] 1 =]);
+ bt_assert(p2 ~ [= 5 set35 3 set12 set12 =]);
+ bt_assert(p2 ~ mkpath(5, 4));
+ bt_assert(p2 ~ [= * [3] * =]);
+ bt_assert(p2 !~ [= * [] * =]);
+
+ bt_assert(p2.len = 5);
+ bt_assert(p2.first = 5);
+ bt_assert(p2.last = 1);
+
+ bt_assert(p2.len = 5);
+ bt_assert(p2.delete(3) = +empty+.prepend(1).prepend(2).prepend(4).prepend(5));
+ bt_assert(p2.filter([1..3]) = +empty+.prepend(1).prepend(2).prepend(3));
+ bt_assert(p2.delete([]) = p2);
+ bt_assert(p2.filter([]) = +empty+);
+ bt_assert(+empty+.prepend(0).prepend(1).delete([]) = +empty+.prepend(0).prepend(1));
+ bt_assert(+empty+.prepend(0).prepend(1).filter([]) = +empty+);
+
+ p2 = +empty+;
+ p2.prepend(5);
+ p2.prepend(4);
+ p2.prepend(3);
+ p2.prepend(3);
+ p2.prepend(2);
+ p2.prepend(1);
+
+ bt_assert(p2 !~ [= 1 2 3 4 5 =]);
+ bt_assert(p2 ~ [= 1 2 * 4 5 =]);
+ bt_assert(p2 ~ [= 1 2 * 3 4 5 =]);
+ bt_assert(p2 ~ [= 1 2 3+ 4 5 =]);
+ bt_assert(p2 ~ [= 1 2 3+ 4+ 5 =]);
+ bt_assert(p2 !~ [= 1 2 3+ 5+ 4 5 =]);
+ bt_assert(p2 !~ [= 1 2 3 3 5+ 4 5 =]);
+ bt_assert(p2.delete(3) = +empty+.prepend(5).prepend(4).prepend(2).prepend(1));
+ bt_assert(p2.delete([4..5]) = +empty+.prepend(3).prepend(3).prepend(2).prepend(1));
+
+ bt_assert(format([= 1 2+ 3 =]) = "[= 1 2 + 3 =]");
+
+ # iteration over path
+ int x = 0;
+ int y = 0;
+ for int i in p2 do {
+ x = x + i;
+ y = y + x;
+ }
+ bt_assert(x = 18 && y = 50);
+}
+
+bt_test_suite(t_path_new, "Testing paths (new syntax)");
@@ -792,7 +931,7 @@ bt_test_suite(t_path, "Testing paths");
define p23 = (2, 3);
-function t_clist()
+function t_clist_old()
clist l;
clist l2;
clist r;
@@ -871,6 +1010,12 @@ clist r;
l = filter(l2, [(3,1..4)]);
l2 = filter(l2, [(3,3..6)]);
+ quad q = 2.0.1.0;
+ clist ql = add(add(add(-empty-, 1.0.0.1), q), 3.1.0.0);
+ bt_assert(delete(ql, 1.0.0.1) = add(add(-empty-, 2.0.1.0), 3.1.0.0));
+ bt_assert(delete(ql, [2.0.0.0 .. 4.0.0.0]) = add(-empty-, 1.0.0.1));
+ bt_assert(filter(ql, [3.0.0.0 .. 4.0.0.0]) = add(-empty-, 3.1.0.0));
+
# clist A (10,20,30)
bt_assert(l = add(add(add(add(-empty-, (3,1)), (3,2)), (3,3)), (3,4)));
bt_assert(format(l) = "(clist (3,1) (3,2) (3,3) (3,4))");
@@ -912,7 +1057,135 @@ clist r;
bt_assert(x = 36);
}
-bt_test_suite(t_clist, "Testing lists of communities");
+bt_test_suite(t_clist_old, "Testing lists of communities (old syntax)");
+
+function t_clist_new()
+{
+ bt_assert((10, 20).asn = 10);
+ bt_assert((10, 20).data = 20);
+ bt_assert(p23.asn = 2);
+ bt_assert(p23.data = 3);
+
+ clist l;
+ bt_assert(l = -empty-);
+ bt_assert(l !~ [(*,*)]);
+ bt_assert((l ~ [(*,*)]) != (l !~ [(*,*)]));
+
+ bt_assert(-empty- = -empty-);
+
+ l.add( (one,2) );
+ bt_assert(l ~ [(*,*)]);
+ l.add( (2,one+2) );
+ bt_assert(format(l) = "(clist (1,2) (2,3))");
+
+ bt_assert(l.empty = -empty-);
+
+ bt_assert((2,3) ~ l);
+ bt_assert(l ~ [(1,*)]);
+ bt_assert(l ~ [p23]);
+ bt_assert(l ~ [(2,2..3)]);
+ bt_assert(l ~ [(1,1..2)]);
+ bt_assert(l ~ [(1,1)..(1,2)]);
+ bt_assert(l !~ []);
+
+ l.add((2,5));
+ l.add((5,one));
+ l.add((6,one));
+ l.add((one,one));
+ l.delete([(5,1),(6,one),(one,1)]);
+ l.delete([(5,one),(6,one)]);
+ l.filter([(1,*)]);
+ bt_assert(l = -empty-.add((1,2)));
+
+ bt_assert((2,3) !~ l);
+ bt_assert(l !~ [(2,*)]);
+ bt_assert(l !~ [(one,3..6)]);
+ bt_assert(l ~ [(*,*)]);
+
+ l.add((3,one));
+ l.add((one+one+one,one+one));
+ l.add((3,3));
+ l.add((3,4));
+ l.add((3,5));
+ clist l2 = l.filter([(3,*)]);
+ l.delete([(3,2..4)]);
+ bt_assert(l = -empty-.add((1,2)).add((3,1)).add((3,5)));
+ bt_assert(l.len = 3);
+
+ l.add((3,2));
+ l.add((4,5));
+ bt_assert(l = -empty-.add((1,2)).add((3,1)).add((3,5)).add((3,2)).add((4,5)));
+
+ bt_assert(l.len = 5);
+ bt_assert(l ~ [(*,2)]);
+ bt_assert(l ~ [(*,5)]);
+ bt_assert(l ~ [(*, one)]);
+ bt_assert(l !~ [(*,3)]);
+ bt_assert(l !~ [(*,(one+6))]);
+ bt_assert(l !~ [(*, (one+one+one))]);
+
+ bt_assert(l.delete([]) = l);
+ bt_assert(l.filter([]) = -empty-);
+
+ l.delete([(*,(one+onef(3)))]);
+ l.delete([(*,(4+one))]);
+ bt_assert(l = -empty-.add((3,1)));
+
+ l.delete([(*,(onef(5)))]);
+ bt_assert(l = -empty-);
+
+ l2.add((3,6));
+ l = l2.filter([(3,1..4)]);
+ l2.filter([(3,3..6)]);
+
+ quad q = 2.0.1.0;
+ clist ql = -empty-.add(1.0.0.1).add(q).add(3.1.0.0);
+ bt_assert(delete(ql, 1.0.0.1) = -empty-.add(2.0.1.0).add(3.1.0.0));
+ bt_assert(delete(ql, [2.0.0.0 .. 4.0.0.0]) = -empty-.add(1.0.0.1));
+ bt_assert(filter(ql, [3.0.0.0 .. 4.0.0.0]) = -empty-.add(3.1.0.0));
+
+ # clist A (10,20,30)
+ bt_assert(l = -empty-.add((3,1)).add((3,2)).add((3,3)).add((3,4)));
+ bt_assert(format(l) = "(clist (3,1) (3,2) (3,3) (3,4))");
+
+ # clist B (30,40,50)
+ bt_assert(l2 = -empty-.add((3,3)).add((3,4)).add((3,5)).add((3,6)));
+ bt_assert(format(l2) = "(clist (3,3) (3,4) (3,5) (3,6))");
+
+ # clist A union B
+ clist r = l.add(l2);
+ bt_assert(r = -empty-.add((3,1)).add((3,2)).add((3,3)).add((3,4)).add((3,5)).add((3,6)));
+ bt_assert(format(r) = "(clist (3,1) (3,2) (3,3) (3,4) (3,5) (3,6))");
+
+ # clist A isect B
+ r = l.filter(l2);
+ bt_assert(r = -empty-.add((3,3)).add((3,4)));
+ bt_assert(format(r) = "(clist (3,3) (3,4))");
+
+ # clist A \ B
+ r = l.delete(l2);
+ bt_assert(r = -empty-.add((3,1)).add((3,2)));
+ bt_assert(format(r) = "(clist (3,1) (3,2))");
+
+ # clist in c set
+ r = l.filter([(3,1), (*,2)]);
+ bt_assert(r = -empty-.add((3,1)).add((3,2)));
+ bt_assert(format(r) = "(clist (3,1) (3,2))");
+
+ # minimim & maximum element
+ r = -empty-.add((2,1)).add((1,3)).add((2,2)).add((3,1)).add((2,3));
+ bt_assert(format(r) = "(clist (2,1) (1,3) (2,2) (3,1) (2,3))");
+ bt_assert(r.min = (1,3));
+ bt_assert(r.max = (3,1));
+
+ # iteration over clist
+ int x = 0;
+ for pair c in r do
+ x = x + c.asn * c.asn * c.data;
+ bt_assert(x = 36);
+}
+
+bt_test_suite(t_clist_new, "Testing lists of communities (new syntax)");
@@ -923,8 +1196,8 @@ bt_test_suite(t_clist, "Testing lists of communities");
*/
function t_ec()
-ec cc;
{
+ ec cc;
cc = (rt, 12345, 200000);
bt_assert(format(cc) = "(rt, 12345, 200000)");
@@ -947,11 +1220,12 @@ bt_test_suite(t_ec, "Testing extended communities");
* -------------------------------
*/
-function t_eclist()
+function t_eclist_old()
eclist el;
eclist el2;
eclist r;
{
+ # Deprecated syntax
el = -- empty --;
el = add(el, (rt, 10, 20));
el = add(el, (ro, 10.20.30.40, 100));
@@ -1034,7 +1308,99 @@ eclist r;
bt_assert(x = 3);
}
-bt_test_suite(t_eclist, "Testing lists of extended communities");
+bt_test_suite(t_eclist_old, "Testing lists of extended communities");
+
+
+function t_eclist_new()
+{
+ # New syntax
+ eclist el;
+ bt_assert(el = --empty--);
+ el.add((rt, 10, 20));
+ el.add((ro, 10.20.30.40, 100));
+ el.add((ro, 11.21.31.41.mask(16), 200));
+
+ bt_assert(--empty-- = --empty--);
+ bt_assert(((rt, 10, 20)) !~ --empty--);
+
+ bt_assert(format(el) = "(eclist (rt, 10, 20) (ro, 10.20.30.40, 100) (ro, 11.21.0.0, 200))");
+ bt_assert(el.len = 3);
+ el.delete((rt, 10, 20));
+ el.delete((rt, 10, 30));
+ bt_assert(el = (--empty--).add((ro, 10.20.30.40, 100)).add((ro, 11.21.0.0, 200)));
+
+ bt_assert(el.empty = --empty--);
+
+ el.add((unknown 2, ten, 1));
+ el.add((unknown 5, ten, 1));
+ el.add((rt, ten, one+one));
+ el.add((rt, 10, 3));
+ el.add((rt, 10, 4));
+ el.add((rt, 10, 5));
+ el.add((generic, 0x2000a, 3*ten));
+ el.delete([(rt, 10, 2..ten)]);
+ bt_assert(el = (--empty--).add((ro, 10.20.30.40, 100)).add((ro, 11.21.0.0, 200)).add((rt, 10, 1)).add((unknown 5, 10, 1)).add((rt, 10, 30)));
+
+ el.filter([(rt, 10, *)]);
+ bt_assert(el = (--empty--).add((rt, 10, 1)).add((rt, 10, 30)));
+ bt_assert((rt, 10, 1) ~ el);
+ bt_assert(el ~ [(rt, 10, ten..40)]);
+ bt_assert((rt, 10, 20) !~ el);
+ bt_assert((ro, 10.20.30.40, 100) !~ el);
+ bt_assert(el !~ [(rt, 10, 35..40)]);
+ bt_assert(el !~ [(ro, 10, *)]);
+ bt_assert(el !~ []);
+
+ el.add((rt, 10, 40));
+ eclist el2 = el.filter([(rt, 10, 20..40)] );
+ el2.add((rt, 10, 50));
+
+ bt_assert(el.delete([]) = el);
+ bt_assert(el.filter([]) = --empty--);
+
+ # eclist A (1,30,40)
+ bt_assert(el = --empty--.add((rt, 10, 1)).add((rt, 10, 30)).add((rt, 10, 40)));
+ bt_assert(format(el) = "(eclist (rt, 10, 1) (rt, 10, 30) (rt, 10, 40))");
+
+ # eclist B (30,40,50)
+ bt_assert(el2 = --empty--.add((rt, 10, 30)).add((rt, 10, 40)).add((rt, 10, 50)));
+ bt_assert(format(el2) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50))");
+
+ # eclist A union B
+ eclist r = el2.add(el);
+ bt_assert(r = --empty--.add((rt, 10, 30)).add((rt, 10, 40)).add((rt, 10, 50)).add((rt, 10, 1)));
+ bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40) (rt, 10, 50) (rt, 10, 1))");
+
+ # eclist A isect B
+ r = el.filter(el2);
+ bt_assert(r = --empty--.add((rt, 10, 30)).add((rt, 10, 40)));
+ bt_assert(format(r) = "(eclist (rt, 10, 30) (rt, 10, 40))");
+
+ # eclist A \ B
+ r = el.delete(el2);
+ bt_assert(r = --empty--.add((rt, 10, 1)));
+ bt_assert(format(r) = "(eclist (rt, 10, 1))");
+
+ # eclist in ec set
+ r = el.filter([(rt, 10, 1), (rt, 10, 25..30), (ro, 10, 40)]);
+ bt_assert(r = --empty--.add((rt, 10, 1)).add((rt, 10, 30)));
+ bt_assert(format(r) = "(eclist (rt, 10, 1) (rt, 10, 30))");
+
+ # minimim & maximum element
+ r = --empty--.add((rt, 2, 1)).add((rt, 1, 3)).add((rt, 2, 2)).add((rt, 3, 1)).add((rt, 2, 3));
+ bt_assert(format(r) = "(eclist (rt, 2, 1) (rt, 1, 3) (rt, 2, 2) (rt, 3, 1) (rt, 2, 3))");
+ bt_assert(r.min = (rt, 1, 3));
+ bt_assert(r.max = (rt, 3, 1));
+
+ # iteration over eclist
+ int x = 0;
+ for ec c in r do
+ if c > (rt, 2, 0) && c < (rt, 3, 0) then
+ x = x + 1;
+ bt_assert(x = 3);
+}
+
+bt_test_suite(t_eclist_new, "Testing lists of extended communities");
@@ -1078,12 +1444,12 @@ bt_test_suite(t_ec_set, "Testing sets of extended communities");
* -------------------------
*/
-function mktrip(int a)
+function mktrip(int a) -> lc
{
return (a, 2*a, 3*a);
}
-function t_lclist()
+function t_lclist_old()
lclist ll;
lclist ll2;
lclist r;
@@ -1165,7 +1531,92 @@ lclist r;
bt_assert(mx = r.max);
}
-bt_test_suite(t_lclist, "Testing lists of large communities");
+bt_test_suite(t_lclist_old, "Testing lists of large communities");
+
+
+function t_lclist_new()
+{
+ bt_assert(---empty--- = ---empty---);
+ bt_assert((10, 20, 30) !~ ---empty---);
+
+ bt_assert((10, 20, 30).asn = 10);
+ bt_assert((10, 20, 30).data1 = 20);
+ bt_assert((10, 20, 30).data2 = 30);
+
+ lclist ll;
+ bt_assert(ll = ---empty---);
+ ll.add((ten, 20, 30));
+ ll.add((1000, 2000, 3000));
+ ll.add(mktrip(100000));
+
+ bt_assert(ll.empty = ---empty---);
+ bt_assert(format(ll) = "(lclist (10, 20, 30) (1000, 2000, 3000) (100000, 200000, 300000))");
+ bt_assert(ll.len = 3);
+ bt_assert(ll = ---empty---.add((10, 20, 30)).add((1000, 2000, 3000)).add((100000, 200000, 300000)));
+
+ bt_assert(mktrip(1000) ~ ll);
+ bt_assert(mktrip(100) !~ ll);
+
+ ll.empty;
+ ll.add((10, 10, 10));
+ ll.add((20, 20, 20));
+ ll.add((30, 30, 30));
+
+ lclist ll2;
+ ll2.add((20, 20, 20));
+ ll2.add((30, 30, 30));
+ ll2.add((40, 40, 40));
+
+ bt_assert(ll.delete([]) = ll);
+ bt_assert(ll.filter([]) = ---empty---);
+
+ # lclist A (10, 20, 30)
+ bt_assert(format(ll) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30))");
+
+ # lclist B (20, 30, 40)
+ bt_assert(format(ll2) = "(lclist (20, 20, 20) (30, 30, 30) (40, 40, 40))");
+
+ # lclist A union B
+ lclist r = ll.add(ll2);
+ bt_assert(r = ---empty---.add((10,10,10)).add((20,20,20)).add((30,30,30)).add((40,40,40)));
+ bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20) (30, 30, 30) (40, 40, 40))");
+
+ # lclist A isect B
+ r = ll.filter(ll2);
+ bt_assert(r = ---empty---.add((20, 20, 20)).add((30, 30, 30)));
+ bt_assert(format(r) = "(lclist (20, 20, 20) (30, 30, 30))");
+
+ # lclist A \ B
+ r = ll.delete(ll2);
+ bt_assert(r = ---empty---.add((10, 10, 10)));
+ bt_assert(format(r) = "(lclist (10, 10, 10))");
+
+ # lclist in lc set
+ r = ll.filter([(5..15, *, *), (20, 15..25, *)]);
+ bt_assert(r = ---empty---.add((10, 10, 10)).add((20, 20, 20)));
+ bt_assert(format(r) = "(lclist (10, 10, 10) (20, 20, 20))");
+
+ # minimim & maximum element
+ r = ---empty---.add((2, 3, 3)).add((1, 2, 3)).add((2, 3, 1)).add((3, 1, 2)).add((2, 1, 3));
+ bt_assert(format(r) = "(lclist (2, 3, 3) (1, 2, 3) (2, 3, 1) (3, 1, 2) (2, 1, 3))");
+ bt_assert(r.min = (1, 2, 3));
+ bt_assert(r.max = (3, 1, 2));
+
+ # iteration over lclist
+ int x = 0;
+ int y = 0;
+ lc mx = (0, 0, 0);
+ for lc c in r do {
+ int asn2 = c.asn * c.asn;
+ x = x + asn2 * c.data1;
+ y = y + asn2 * c.data2;
+ if c > mx then mx = c;
+ }
+ bt_assert(x = 39 && y = 49);
+ bt_assert(mx = r.max);
+}
+
+bt_test_suite(t_lclist_new, "Testing lists of large communities");
@@ -1308,7 +1759,7 @@ bt_test_suite(t_define, "Testing defined() function");
* -------------------------
*/
-function callme(int arg1; int arg2)
+function callme(int arg1; int arg2) -> int
int i;
{
case arg1 {
@@ -1319,12 +1770,12 @@ int i;
return 0;
}
-function callmeagain(int a; int b; int c)
+function callmeagain(int a; int b; int c) -> int
{
return a + b + c;
}
-function fifteen()
+function fifteen() -> int
{
return 15;
}
@@ -1357,28 +1808,28 @@ function local_vars(int j)
bt_assert(j = 35 && k = 20 && m = 100);
}
-function factorial(int x)
+function factorial(int x) -> int
{
if x = 0 then return 0;
if x = 1 then return 1;
else return x * factorial(x - 1);
}
-function fibonacci(int x)
+function fibonacci(int x) -> int
{
if x = 0 then return 0;
if x = 1 then return 1;
else return fibonacci(x - 1) + fibonacci(x - 2);
}
-function hanoi_init(int a; int b)
+function hanoi_init(int a; int b) -> bgppath
{
if b = 0
then return +empty+;
else return prepend(hanoi_init(a + 1, b - 1), a);
}
-function hanoi_solve(int n; bgppath h_src; bgppath h_dst; bgppath h_aux; bool x; bool y)
+function hanoi_solve(int n; bgppath h_src; bgppath h_dst; bgppath h_aux; bool x; bool y) -> bgppath
{
# x -> return src or dst
# y -> print state
@@ -1488,6 +1939,63 @@ bt_test_suite(t_if_else, "Testing if-else statement");
/*
+ * Test for-in statement
+ * ---------------------
+ */
+
+function t_for_in()
+{
+ bgppath p = +empty+.prepend(5).prepend(4).prepend(3).prepend(2).prepend(1);
+ bgppath q = +empty+.prepend(30).prepend(20).prepend(10);
+ int a;
+
+ # basic for loop
+ a = 0;
+ for int i in p do {
+ a = 2 * a + i;
+ }
+ bt_assert(a = 57);
+
+ # basic for loop, no braces
+ a = 0;
+ for int i in p do a = 2 * a + i;
+ bt_assert(a = 57);
+
+ # for loop with empty body
+ a = 0;
+ for int i in p do { }
+ bt_assert(a = 0);
+
+ # for loop over empty path
+ a = 0;
+ for int i in +empty+ do a = 1;
+ bt_assert(a = 0);
+
+ # for loop with var name shadowing
+ a = 0;
+ for int p in p do {
+ a = 2 * a + p;
+ }
+ bt_assert(a = 57);
+
+ # nested for loops
+ a = 0;
+ int n = 1;
+ for int i in p do {
+ for int j in q do {
+ a = a + i * j * n;
+ n = n + 1;
+ }
+ }
+ bt_assert(a = 9300);
+}
+
+bt_test_suite(t_for_in, "Testing for-in statement");
+
+
+
+
+/*
* Unused functions -- testing only parsing
* ----------------------------------------
*/
@@ -1641,6 +2149,15 @@ filter vpn_filter
bgp_ext_community.add((ro, 135, 999));
bgp_large_community.add((6464156, 89646354, 8675643));
+ mypath.prepend(65533);
+ mylclist.add((1234, 5678, 90123));
+
+ bgppath locpath;
+ lclist loclclist;
+
+ locpath.prepend(65533);
+ loclclist.add((1234, 5678, 90123));
+
accept;
}
diff --git a/filter/test.conf.overlay b/filter/test.conf.overlay
new file mode 100644
index 00000000..5967ba90
--- /dev/null
+++ b/filter/test.conf.overlay
@@ -0,0 +1,3 @@
+attribute int peek_a_boo;
+
+include "test.conf";
diff --git a/filter/trie_test.c b/filter/trie_test.c
index dc791280..5724e49f 100644
--- a/filter/trie_test.c
+++ b/filter/trie_test.c
@@ -28,12 +28,6 @@ struct f_prefix_node {
struct f_prefix prefix;
};
-static u32
-xrandom(u32 max)
-{
- return (bt_random() % max);
-}
-
static inline uint
get_exp_random(void)
{
@@ -96,26 +90,9 @@ is_prefix_included(list *prefixes, const net_addr *needle)
}
static void
-get_random_net(net_addr *net, int v6)
-{
- if (!v6)
- {
- uint pxlen = xrandom(24)+8;
- ip4_addr ip4 = ip4_from_u32((u32) bt_random());
- net_fill_ip4(net, ip4_and(ip4, ip4_mkmask(pxlen)), pxlen);
- }
- else
- {
- uint pxlen = xrandom(120)+8;
- ip6_addr ip6 = ip6_build(bt_random(), bt_random(), bt_random(), bt_random());
- net_fill_ip6(net, ip6_and(ip6, ip6_mkmask(pxlen)), pxlen);
- }
-}
-
-static void
get_random_prefix(struct f_prefix *px, int v6, int tight)
{
- get_random_net(&px->net, v6);
+ bt_random_net(&px->net, !v6 ? NET_IP4 : NET_IP6);
if (tight)
{
@@ -379,7 +356,7 @@ select_random_prefix_subset(list *src[], net_addr dst[], int sn, int dn)
struct f_prefix_node *px;
WALK_LIST(px, *src[i])
{
- if (xrandom(rnd) != 0)
+ if (bt_random_n(rnd) != 0)
continue;
net_copy(&dst[n], &px->prefix.net);
@@ -395,7 +372,7 @@ done:
/* Shuffle networks */
for (int i = 0; i < dn; i++)
{
- int j = xrandom(dn);
+ int j = bt_random_n(dn);
if (i == j)
continue;
@@ -444,7 +421,7 @@ t_match_random_net(void)
for (int i = 0; i < PREFIX_TESTS_NUM; i++)
{
net_addr net;
- get_random_net(&net, v6);
+ bt_random_net(&net, !v6 ? NET_IP4 : NET_IP6);
test_match_net(prefixes, trie, &net);
}
@@ -828,7 +805,7 @@ t_trie_walk_to_root(void)
for (i = 0; i < (PREFIX_TESTS_NUM / 10); i++)
{
net_addr from;
- get_random_net(&from, v6);
+ bt_random_net(&from, !v6 ? NET_IP4 : NET_IP6);
net_addr found[129];
int found_num = find_covering_nets(pxset, num, &from, found);
diff --git a/lib/birdlib.h b/lib/birdlib.h
index e03bd0b2..b7226411 100644
--- a/lib/birdlib.h
+++ b/lib/birdlib.h
@@ -162,6 +162,7 @@ void bug(const char *msg, ...) NORET;
void debug(const char *msg, ...); /* Printf to debug output */
void debug_safe(const char *msg); /* Printf to debug output, async-safe */
+
/* Debugging */
#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG)
@@ -196,10 +197,36 @@ asm(
);
#endif
+
/* Pseudorandom numbers */
u32 random_u32(void);
void random_init(void);
void random_bytes(void *buf, size_t size);
+
+/* Hashing */
+
+/* Constant parameter for non-parametrized hashes */
+#define HASH_PARAM 2902958171u
+
+/* Precomputed powers of HASH_PARAM */
+#define HASH_PARAM1 ((u64) HASH_PARAM)
+#define HASH_PARAM2 (HASH_PARAM1 * HASH_PARAM)
+#define HASH_PARAM3 (HASH_PARAM2 * HASH_PARAM)
+#define HASH_PARAM4 (HASH_PARAM3 * HASH_PARAM)
+
+/* Reduce intermediate 64-bit value to final 32-bit value */
+static inline u32 hash_value(u64 a)
+{ return ((u32) a) ^ ((u32) (a >> 32)); }
+
+static inline u64 u32_hash0(u32 v, u32 p, u64 acc)
+{ return (acc + v) * p; }
+
+static inline u64 u64_hash0(u64 v, u32 p, u64 acc)
+{ return u32_hash0(v >> 32, p, u32_hash0(v, p, acc)); }
+
+static inline u32 u64_hash(u64 v)
+{ return hash_value(u64_hash0(v, HASH_PARAM, 0)); }
+
#endif
diff --git a/lib/bitmap.c b/lib/bitmap.c
index b6ea5a38..c036f80d 100644
--- a/lib/bitmap.c
+++ b/lib/bitmap.c
@@ -195,3 +195,292 @@ hmap_check(struct hmap *b)
}
}
}
+
+
+/*
+ * Indirect bitmap for MPLS labels (20 bit range)
+ */
+
+void
+lmap_init(struct lmap *b, pool *p)
+{
+ b->slab = sl_new(p, 128);
+ b->size = 8;
+ b->data = mb_allocz(p, b->size * sizeof(u32 *));
+ b->root = sl_allocz(b->slab);
+}
+
+static void
+lmap_grow(struct lmap *b, uint need)
+{
+ uint old_size = b->size;
+
+ while (b->size < need)
+ b->size *= 2;
+
+ b->data = mb_realloc(b->data, b->size * sizeof(u32 *));
+
+ memset(b->data + old_size, 0, (b->size - old_size) * sizeof(u32 *));
+}
+
+void
+lmap_free(struct lmap *b)
+{
+ rfree(b->slab);
+ mb_free(b->data);
+ memset(b, 0, sizeof(struct lmap));
+}
+
+static inline int
+b1024_and(u32 *p)
+{
+ for (int i = 0; i < 32; i++)
+ if (~p[i])
+ return 0;
+
+ return 1;
+}
+
+static inline int
+b1024_or(u32 *p)
+{
+ for (int i = 0; i < 32; i++)
+ if (p[i])
+ return 1;
+
+ return 0;
+}
+
+int
+lmap_test(struct lmap *b, uint n)
+{
+ uint n0 = n >> 10;
+ uint n1 = n & 0x3ff;
+
+ return (n0 < b->size) && b->data[n0] && BIT32_TEST(b->data[n0], n1);
+}
+
+void
+lmap_set(struct lmap *b, uint n)
+{
+ uint n0 = n >> 10;
+ uint n1 = n & 0x3ff;
+
+ if (n0 >= b->size)
+ lmap_grow(b, n0 + 1);
+
+ if (! b->data[n0])
+ b->data[n0] = sl_allocz(b->slab);
+
+ BIT32_SET(b->data[n0], n1);
+
+ if (b1024_and(b->data[n0]))
+ BIT32_SET(b->root, n0);
+}
+
+void
+lmap_clear(struct lmap *b, uint n)
+{
+ uint n0 = n >> 10;
+ uint n1 = n & 0x3ff;
+
+ if (n0 >= b->size)
+ return;
+
+ if (! b->data[n0])
+ return;
+
+ BIT32_CLR(b->data[n0], n1);
+ BIT32_CLR(b->root, n0);
+
+ if (!b1024_or(b->data[n0]))
+ {
+ sl_free(b->data[n0]);
+ b->data[n0] = NULL;
+ }
+}
+
+static inline int
+b1024_first_zero(u32 *p)
+{
+ for (int i = 0; i < 32; i++)
+ if (~p[i])
+ return 32*i + u32_ctz(~p[i]);
+
+ return 1024;
+}
+
+uint
+lmap_first_zero(struct lmap *b)
+{
+ uint n0 = b1024_first_zero(b->root);
+ uint n1 = ((n0 < b->size) && b->data[n0]) ?
+ b1024_first_zero(b->data[n0]) : 0;
+
+ return (n0 << 10) + n1;
+}
+
+static uint
+b1024_first_zero_in_range(u32 *p, uint lo, uint hi)
+{
+ uint lo0 = lo >> 5;
+ uint lo1 = lo & 0x1f;
+ uint hi0 = hi >> 5;
+ uint hi1 = hi & 0x1f;
+ u32 mask = (1 << lo1) - 1;
+ u32 val;
+
+ for (uint i = lo0; i < hi0; i++)
+ {
+ val = p[i] | mask;
+ mask = 0;
+
+ if (~val)
+ return 32*i + u32_ctz(~val);
+ }
+
+ if (hi1)
+ {
+ mask |= ~((1u << hi1) - 1);
+ val = p[hi0] | mask;
+
+ if (~val)
+ return 32*hi0 + u32_ctz(~val);
+ }
+
+ return hi;
+}
+
+uint
+lmap_first_zero_in_range(struct lmap *b, uint lo, uint hi)
+{
+ uint lo0 = lo >> 10;
+ uint lo1 = lo & 0x3ff;
+ uint hi0 = hi >> 10;
+ uint hi1 = hi & 0x3ff;
+
+ if (lo1)
+ {
+ uint max = (lo0 == hi0) ? hi1 : 1024;
+ uint n0 = lo0;
+ uint n1 = ((n0 < b->size) && b->data[n0]) ?
+ b1024_first_zero_in_range(b->data[n0], lo1, max) : lo1;
+
+ if (n1 < 1024)
+ return (n0 << 10) + n1;
+
+ lo0++;
+ lo1 = 0;
+ }
+
+ if (lo0 < hi0)
+ {
+ uint n0 = b1024_first_zero_in_range(b->root, lo0, hi0);
+
+ if (n0 < hi0)
+ {
+ uint n1 = ((n0 < b->size) && b->data[n0]) ?
+ b1024_first_zero(b->data[n0]) : 0;
+
+ return (n0 << 10) + n1;
+ }
+ }
+
+ if (hi1)
+ {
+ uint n0 = hi0;
+ uint n1 = ((n0 < b->size) && b->data[n0]) ?
+ b1024_first_zero_in_range(b->data[n0], 0, hi1) : 0;
+
+ return (n0 << 10) + n1;
+ }
+
+ return hi;
+}
+
+static inline int
+b1024_last_one(u32 *p)
+{
+ for (int i = 31; i >= 0; i--)
+ if (p[i])
+ return 32*i + (31 - u32_clz(p[i]));
+
+ return 1024;
+}
+
+static uint
+b1024_last_one_in_range(u32 *p, uint lo, uint hi)
+{
+ uint lo0 = lo >> 5;
+ uint lo1 = lo & 0x1f;
+ uint hi0 = hi >> 5;
+ uint hi1 = hi & 0x1f;
+ u32 mask = (1u << hi1) - 1;
+ u32 val;
+
+ for (int i = hi0; i > (int) lo0; i--)
+ {
+ val = p[i] & mask;
+ mask = ~0;
+
+ if (val)
+ return 32*i + (31 - u32_clz(val));
+ }
+
+ {
+ mask &= ~((1u << lo1) - 1);
+ val = p[lo0] & mask;
+
+ if (val)
+ return 32*lo0 + (31 - u32_clz(val));
+ }
+
+ return hi;
+}
+
+uint
+lmap_last_one_in_range(struct lmap *b, uint lo, uint hi)
+{
+ uint lo0 = lo >> 10;
+ uint lo1 = lo & 0x3ff;
+ uint hi0 = hi >> 10;
+ uint hi1 = hi & 0x3ff;
+
+ if (hi1 && (hi0 < b->size) && b->data[hi0])
+ {
+ uint min = (lo0 == hi0) ? lo1 : 0;
+ uint n0 = hi0;
+ uint n1 = b1024_last_one_in_range(b->data[n0], min, hi1);
+
+ if (n1 < hi1)
+ return (n0 << 10) + n1;
+ }
+
+ for (int i = (int)MIN(hi0, b->size) - 1; i >= (int) lo0; i--)
+ {
+ if (! b->data[i])
+ continue;
+
+ uint n0 = i;
+ uint n1 = b1024_last_one(b->data[n0]);
+
+ if ((n0 == lo0) && (n1 < lo1))
+ return hi;
+
+ return (n0 << 10) + n1;
+ }
+
+ return hi;
+}
+
+void
+lmap_check(struct lmap *b)
+{
+ for (int i = 0; i < (int) b->size; i++)
+ {
+ int x = b->data[i] && b1024_and(b->data[i]);
+ int y = !!BIT32_TEST(b->root, i);
+ if (x != y)
+ bug("Inconsistent data on %d (%d vs %d)", i, x, y);
+ }
+}
diff --git a/lib/bitmap.h b/lib/bitmap.h
index 0093cd18..e3351ab1 100644
--- a/lib/bitmap.h
+++ b/lib/bitmap.h
@@ -60,4 +60,26 @@ void hmap_clear(struct hmap *b, uint n);
u32 hmap_first_zero(struct hmap *b);
void hmap_check(struct hmap *b);
+
+struct lmap
+{
+ slab *slab;
+ uint size;
+ u32 **data;
+ u32 *root;
+};
+
+static inline uint lmap_max(struct lmap *b)
+{ return b->size << 10; }
+
+void lmap_init(struct lmap *b, pool *p);
+void lmap_free(struct lmap *b);
+int lmap_test(struct lmap *b, uint n);
+void lmap_set(struct lmap *b, uint n);
+void lmap_clear(struct lmap *b, uint n);
+uint lmap_first_zero(struct lmap *b);
+uint lmap_first_zero_in_range(struct lmap *b, uint lo, uint hi);
+uint lmap_last_one_in_range(struct lmap *b, uint lo, uint hi);
+void lmap_check(struct lmap *b);
+
#endif
diff --git a/lib/bitmap_test.c b/lib/bitmap_test.c
index 07860c94..39fbd0ed 100644
--- a/lib/bitmap_test.c
+++ b/lib/bitmap_test.c
@@ -170,6 +170,82 @@ t_hmap_set_clear_fill(void)
return 1;
}
+static int
+t_lmap_set_clear_fill(void)
+{
+ struct lmap b;
+
+ lmap_init(&b, &root_pool);
+
+ char expected[MAX_NUM] = {};
+ uint i, j, n;
+
+ for (i = 0; i < STEP_NUM; i++)
+ {
+ uint last = 0;
+ uint lo = bt_random() % (1 << 19);
+ uint hi = lo + 2 * STEP_SET;
+ uint step_set = bt_random() % STEP_SET;
+ uint step_clr = bt_random() % STEP_CLR;
+
+ for (j = 0; j < step_set; j++)
+ {
+ n = lmap_first_zero_in_range(&b, lo, hi);
+ bt_assert(n >= lo);
+ bt_assert(n <= hi);
+
+ for (last = lo; last < n; last++)
+ bt_assert(expected[last]);
+
+ if (n >= hi)
+ break;
+
+ bt_assert(!expected[n]);
+
+ lmap_set(&b, n);
+ expected[n] = 1;
+ }
+
+ for (j = 0; j < step_clr; j++)
+ {
+ n = lo + bt_random() % (step_set + 1);
+
+ if (!expected[n])
+ continue;
+
+ lmap_clear(&b, n);
+ expected[n] = 0;
+ }
+
+ {
+ n = lmap_last_one_in_range(&b, lo, hi);
+ bt_assert(n >= lo);
+ bt_assert(n <= hi);
+
+ for (last = n + 1; last < hi; last++)
+ bt_assert(!expected[last]);
+
+ if (n < hi)
+ bt_assert(expected[n]);
+ }
+ }
+
+ uint cnt = 0;
+ for (i = 0; i < MAX_NUM; i++)
+ {
+ if (lmap_test(&b, i) != expected[i])
+ bt_abort_msg("Bitmap mismatch on %d (should be %d %d)", i, lmap_test(&b, i), expected[i]);
+
+ if (expected[i])
+ cnt++;
+ }
+ // bt_log("Total %u", cnt);
+
+ lmap_check(&b);
+
+ return 1;
+}
+
int
main(int argc, char *argv[])
{
@@ -178,6 +254,7 @@ main(int argc, char *argv[])
bt_test_suite(t_bmap_set_clear_random, "BMap - random sequence of sets / clears");
bt_test_suite(t_hmap_set_clear_random, "HMap - random sequence of sets / clears");
bt_test_suite(t_hmap_set_clear_fill, "HMap - linear sets and random clears");
+ bt_test_suite(t_lmap_set_clear_fill, "LMap - linear sets and random clears");
return bt_exit_value();
}
diff --git a/lib/hash.h b/lib/hash.h
index 8febb33f..3c173958 100644
--- a/lib/hash.h
+++ b/lib/hash.h
@@ -216,6 +216,14 @@ mem_hash_mix(u64 *h, const void *p, uint s)
}
static inline void
+mem_hash_mix_str(u64 *h, const char *s)
+{
+ const u64 multiplier = 0xb38bc09a61202731ULL;
+ while (s)
+ *h = *h * multiplier + *s++;
+}
+
+static inline void
mem_hash_mix_num(u64 *h, u64 val)
{
mem_hash_mix(h, &val, sizeof(val));
@@ -237,7 +245,7 @@ mem_hash(const void *p, uint s)
}
static inline uint
-ptr_hash(void *ptr)
+ptr_hash(const void *ptr)
{
uintptr_t p = (uintptr_t) ptr;
return p ^ (p << 8) ^ (p >> 16);
diff --git a/lib/ip.h b/lib/ip.h
index 9eef2e16..f2650d3f 100644
--- a/lib/ip.h
+++ b/lib/ip.h
@@ -194,14 +194,37 @@ static inline int ipa_nonzero2(ip_addr a)
* Hash and compare functions
*/
+static inline u64 ip4_hash0(ip4_addr a, u32 p, u64 acc)
+{ return (acc + _I(a)) * p; }
+
static inline u32 ip4_hash(ip4_addr a)
-{ return u32_hash(_I(a)); }
+{
+ // return hash_value(ip4_hash0(a, HASH_PARAM, 0));
+
+ /* For some reason, the old hash works slightly better */
+ return u32_hash(_I(a));
+}
+
+static inline u64 ip6_hash0(ip6_addr a, u32 p, u64 acc)
+{
+ acc += _I0(a); acc *= p;
+ acc += _I1(a); acc *= p;
+ acc += _I2(a); acc *= p;
+ acc += _I3(a); acc *= p;
+ return acc;
+}
static inline u32 ip6_hash(ip6_addr a)
{
- /* Returns a 32-bit hash key, although low-order bits are not mixed */
- u32 x = _I0(a) ^ _I1(a) ^ _I2(a) ^ _I3(a);
- return x ^ (x << 16) ^ (x << 24);
+ // return hash_value(ip6_hash0(a, HASH_PARAM, 0));
+
+ /* Just use the expanded form */
+ u64 acc =
+ _I0(a) * HASH_PARAM4 +
+ _I1(a) * HASH_PARAM3 +
+ _I2(a) * HASH_PARAM2 +
+ _I3(a) * HASH_PARAM1;
+ return hash_value(acc);
}
static inline int ip4_compare(ip4_addr a, ip4_addr b)
@@ -362,6 +385,8 @@ static inline ip6_addr ip6_hton(ip6_addr a)
static inline ip6_addr ip6_ntoh(ip6_addr a)
{ return _MI6(ntohl(_I0(a)), ntohl(_I1(a)), ntohl(_I2(a)), ntohl(_I3(a))); }
+#define MPLS_MAX_LABEL 0x100000
+
#define MPLS_MAX_LABEL_STACK 8
typedef struct mpls_label_stack {
uint len;
diff --git a/lib/mempool.c b/lib/mempool.c
index 325b1ecf..9d4404f7 100644
--- a/lib/mempool.c
+++ b/lib/mempool.c
@@ -187,11 +187,18 @@ lp_flush(linpool *m)
{
struct lp_chunk *c;
- /* Move ptr to the first chunk and free all large chunks */
+ /* Move ptr to the first chunk and free all other chunks */
m->current = c = m->first;
m->ptr = c ? c->data : NULL;
m->end = c ? c->data + LP_DATA_SIZE : NULL;
+ while (c && c->next)
+ {
+ struct lp_chunk *d = c->next;
+ c->next = d->next;
+ free_page(d);
+ }
+
while (c = m->first_large)
{
m->first_large = c->next;
@@ -233,9 +240,9 @@ lp_restore(linpool *m, lp_state *p)
struct lp_chunk *c;
/* Move ptr to the saved pos and free all newer large chunks */
- m->current = c = p->current;
- m->ptr = p->ptr;
- m->end = c ? c->data + LP_DATA_SIZE : NULL;
+ m->current = c = p->current ?: m->first;
+ m->ptr = p->ptr ?: (c ? c->data : NULL);
+ m->end = c ? (c->data + LP_DATA_SIZE) : NULL;
m->total_large = p->total_large;
while ((c = m->first_large) && (c != p->large))
diff --git a/lib/net.c b/lib/net.c
index 976ddbcc..289a4297 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -57,6 +57,19 @@ const u16 net_max_text_length[] = {
[NET_MPLS] = 7, /* "1048575" */
};
+/* There should be no implicit padding in net_addr structures */
+STATIC_ASSERT(sizeof(net_addr) == 24);
+STATIC_ASSERT(sizeof(net_addr_ip4) == 8);
+STATIC_ASSERT(sizeof(net_addr_ip6) == 20);
+STATIC_ASSERT(sizeof(net_addr_vpn4) == 16);
+STATIC_ASSERT(sizeof(net_addr_vpn6) == 32);
+STATIC_ASSERT(sizeof(net_addr_roa4) == 16);
+STATIC_ASSERT(sizeof(net_addr_roa6) == 28);
+STATIC_ASSERT(sizeof(net_addr_flow4) == 8);
+STATIC_ASSERT(sizeof(net_addr_flow6) == 20);
+STATIC_ASSERT(sizeof(net_addr_ip6_sadr) == 40);
+STATIC_ASSERT(sizeof(net_addr_mpls) == 8);
+
int
rd_format(const u64 rd, char *buf, int buflen)
@@ -310,22 +323,3 @@ net_in_netX(const net_addr *a, const net_addr *n)
return (net_pxlen(n) <= net_pxlen(a)) && ipa_in_netX(net_prefix(a), n);
}
-
-#define CHECK_NET(T,S) \
- ({ if (sizeof(T) != S) die("sizeof %s is %d/%d", #T, (int) sizeof(T), S); })
-
-void
-net_init(void)
-{
- CHECK_NET(net_addr, 24);
- CHECK_NET(net_addr_ip4, 8);
- CHECK_NET(net_addr_ip6, 20);
- CHECK_NET(net_addr_vpn4, 16);
- CHECK_NET(net_addr_vpn6, 32);
- CHECK_NET(net_addr_roa4, 16);
- CHECK_NET(net_addr_roa6, 28);
- CHECK_NET(net_addr_flow4, 8);
- CHECK_NET(net_addr_flow6, 20);
- CHECK_NET(net_addr_ip6_sadr, 40);
- CHECK_NET(net_addr_mpls, 8);
-}
diff --git a/lib/net.h b/lib/net.h
index 9f4a00ad..e9828557 100644
--- a/lib/net.h
+++ b/lib/net.h
@@ -479,39 +479,47 @@ static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src)
{ memcpy(dst, src, sizeof(net_addr_mpls)); }
-/* XXXX */
-static inline u32 u64_hash(u64 a)
-{ return u32_hash(a); }
+static inline u32 px4_hash(ip4_addr prefix, u32 pxlen)
+{ return ip4_hash(prefix) ^ (pxlen << 26); }
+
+static inline u32 px6_hash(ip6_addr prefix, u32 pxlen)
+{ return ip6_hash(prefix) ^ (pxlen << 26); }
static inline u32 net_hash_ip4(const net_addr_ip4 *n)
-{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
+{ return px4_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_ip6(const net_addr_ip6 *n)
-{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
+{ return px6_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_vpn4(const net_addr_vpn4 *n)
-{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26) ^ u64_hash(n->rd); }
+{
+ u64 acc = ip4_hash0(n->prefix, HASH_PARAM, 0) ^ (n->pxlen << 26);
+ return hash_value(u64_hash0(n->rd, HASH_PARAM, acc));
+}
static inline u32 net_hash_vpn6(const net_addr_vpn6 *n)
-{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26) ^ u64_hash(n->rd); }
+{
+ u64 acc = ip6_hash0(n->prefix, HASH_PARAM, 0) ^ (n->pxlen << 26);
+ return hash_value(u64_hash0(n->rd, HASH_PARAM, acc));
+}
static inline u32 net_hash_roa4(const net_addr_roa4 *n)
-{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
+{ return px4_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_roa6(const net_addr_roa6 *n)
-{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
+{ return px6_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_flow4(const net_addr_flow4 *n)
-{ return ip4_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
+{ return px4_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_flow6(const net_addr_flow6 *n)
-{ return ip6_hash(n->prefix) ^ ((u32) n->pxlen << 26); }
+{ return px6_hash(n->prefix, n->pxlen); }
static inline u32 net_hash_ip6_sadr(const net_addr_ip6_sadr *n)
-{ return net_hash_ip6((net_addr_ip6 *) n); }
+{ return px6_hash(n->dst_prefix, n->dst_pxlen); }
static inline u32 net_hash_mpls(const net_addr_mpls *n)
-{ return n->label; }
+{ return u32_hash(n->label); }
u32 net_hash(const net_addr *a);
@@ -620,6 +628,4 @@ static inline int net_in_net_src_ip6_sadr(const net_addr_ip6_sadr *a, const net_
int ipa_in_netX(const ip_addr A, const net_addr *N);
int net_in_netX(const net_addr *A, const net_addr *N);
-void net_init(void);
-
#endif
diff --git a/lib/printf_test.c b/lib/printf_test.c
index 47ea905d..88ecf05e 100644
--- a/lib/printf_test.c
+++ b/lib/printf_test.c
@@ -32,11 +32,14 @@ t_simple(void)
BSPRINTF(1, "@", buf, "@", 64);
BSPRINTF(1, "\xff", buf, "%c", 0xff);
- errno = 5;
- BSPRINTF(18, "Input/output error", buf, "%m");
+ const char *io_error_str = lp_strdup(tmp_linpool, strerror(EIO));
+ const int io_error_len = strlen(io_error_str);
+
+ errno = EIO;
+ BSPRINTF(io_error_len, io_error_str, buf, "%m");
errno = 0;
- BSPRINTF(18, "Input/output error", buf, "%M", 5);
+ BSPRINTF(io_error_len, io_error_str, buf, "%M", EIO);
BSPRINTF(11, "TeSt%StRiNg", buf, "%s", "TeSt%StRiNg");
diff --git a/lib/string.h b/lib/string.h
index 2829943d..161b7651 100644
--- a/lib/string.h
+++ b/lib/string.h
@@ -33,6 +33,9 @@ u64 bstrtoul10(const char *str, char **end);
u64 bstrtoul16(const char *str, char **end);
byte bstrtobyte16(const char *str);
+int bstrhextobin(const char *s, byte *b);
+int bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim);
+
int patmatch(const byte *pat, const byte *str);
static inline char *xbasename(const char *str)
diff --git a/lib/strtoul.c b/lib/strtoul.c
index a5b11f68..420931a4 100644
--- a/lib/strtoul.c
+++ b/lib/strtoul.c
@@ -25,7 +25,7 @@ bstrtoul10(const char *str, char **end)
errno = ERANGE;
return UINT64_MAX;
}
-
+
out *= 10;
out += (**end) - '0';
}
@@ -60,29 +60,91 @@ bstrtoul16(const char *str, char **end)
return UINT64_MAX;
}
-byte
-bstrtobyte16(const char *str)
+static int
+fromxdigit(char c)
{
- byte out = 0;
- for (int i=0; i<2; i++) {
- switch (str[i]) {
- case '0' ... '9':
- out *= 16;
- out += str[i] - '0';
- break;
- case 'a' ... 'f':
- out *= 16;
- out += str[i] + 10 - 'a';
- break;
- case 'A' ... 'F':
- out *= 16;
- out += str[i] + 10 - 'A';
- break;
- default:
- errno = ERANGE;
+ switch (c)
+ {
+ case '0' ... '9':
+ return c - '0';
+ case 'a' ... 'f':
+ return c + 10 - 'a';
+ case 'A' ... 'F':
+ return c + 10 - 'A';
+ default:
+ return -1;
+ }
+}
+
+int
+bstrhextobin(const char *s, byte *b)
+{
+ int len = 0;
+ int hi = 0;
+
+ for (; *s; s++)
+ {
+ int v = fromxdigit(*s);
+ if (v < 0)
+ {
+ if (strchr(" -.:", *s) && !hi)
+ continue;
+ else
return -1;
}
+
+ if (len == INT32_MAX)
+ return -1;
+
+ if (b)
+ {
+ if (!hi)
+ b[len] = (v << 4);
+ else
+ b[len] |= v;
+ }
+
+ len += hi;
+ hi = !hi;
}
- return out;
+ return !hi ? len : -1;
+}
+
+static char
+toxdigit(uint b)
+{
+ if (b < 10)
+ return ('0' + b);
+ else if (b < 16)
+ return ('a' + b - 10);
+ else
+ return 0;
+}
+
+int
+bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim)
+{
+ ASSERT(size >= 6);
+ char *bound = buf + size - 3;
+
+ size_t i;
+ for (i = 0; i < len; i++)
+ {
+ if (buf > bound)
+ {
+ strcpy(buf - 4, "...");
+ return -1;
+ }
+
+ uint x = b[i];
+ buf[0] = toxdigit(x >> 4);
+ buf[1] = toxdigit(x & 0xF);
+ buf[2] = delim;
+ buf += 3;
+ }
+
+ buf[i ? -1 : 0] = 0;
+
+ return 0;
}
diff --git a/lib/timer.h b/lib/timer.h
index c5ea430c..0f87852b 100644
--- a/lib/timer.h
+++ b/lib/timer.h
@@ -46,6 +46,9 @@ extern struct timeloop main_timeloop;
btime current_time(void);
btime current_real_time(void);
+/* In sysdep code */
+btime current_time_now(void);
+
//#define now (current_time() TO_S)
//#define now_real (current_real_time() TO_S)
extern btime boot_time;
diff --git a/nest/Doc b/nest/Doc
index 38af0feb..3be43a71 100644
--- a/nest/Doc
+++ b/nest/Doc
@@ -6,6 +6,7 @@ D proto.sgml
S proto.c
S proto-hooks.c
S iface.c
+S mpls.c
S neighbor.c
S cli.c
S locks.c
diff --git a/nest/Makefile b/nest/Makefile
index a2e30ee2..ef0fdf67 100644
--- a/nest/Makefile
+++ b/nest/Makefile
@@ -1,12 +1,15 @@
-src := a-path.c a-set.c cli.c cmds.c iface.c locks.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c
+src := a-path.c a-set.c cli.c cmds.c iface.c locks.c mpls.c neighbor.c password.c proto.c proto-build.c rt-attr.c rt-dev.c rt-fib.c rt-show.c rt-table.c
obj := $(src-o-files)
$(all-daemon)
$(cf-local)
+$(conf-y-targets): $(s)mpls.Y
-$(objdir)/nest/proto-build.c: $(lastword $(MAKEFILE_LIST))
+$(o)proto-build.c: Makefile $(lastword $(MAKEFILE_LIST)) $(objdir)/.dir-stamp
$(E)echo GEN $@
$(Q)echo "$(patsubst %,void %_build(void); ,$(PROTO_BUILD)) void protos_build_gen(void) { $(patsubst %, %_build(); ,$(PROTO_BUILD))}" > $@
-tests_src := a-set_test.c a-path_test.c
+prepare: $(o)proto-build.c
+
+tests_src := a-set_test.c a-path_test.c rt-fib_test.c
tests_targets := $(tests_targets) $(tests-target-files)
tests_objs := $(tests_objs) $(src-o-files)
diff --git a/nest/a-path.c b/nest/a-path.c
index c421b41f..aba2c86d 100644
--- a/nest/a-path.c
+++ b/nest/a-path.c
@@ -670,6 +670,29 @@ as_path_filter(struct linpool *pool, const struct adata *path, const struct f_va
}
int
+as_path_compare(const struct adata *path1, const struct adata *path2)
+{
+ uint pos1 = 0;
+ uint pos2 = 0;
+ uint val1 = 0;
+ uint val2 = 0;
+
+ while (1)
+ {
+ int res1 = as_path_walk(path1, &pos1, &val1);
+ int res2 = as_path_walk(path2, &pos2, &val2);
+
+ if (res1 == 0 && res2 == 0)
+ return 0;
+
+ if (val1 == val2)
+ continue;
+
+ return val1 < val2 ? -1 : 1;
+ }
+}
+
+int
as_path_walk(const struct adata *path, uint *pos, uint *val)
{
if (!path)
diff --git a/nest/attrs.h b/nest/attrs.h
index fcd5ac16..aee3468b 100644
--- a/nest/attrs.h
+++ b/nest/attrs.h
@@ -51,6 +51,7 @@ u32 as_path_get_last_nonaggregated(const struct adata *path);
int as_path_contains(const struct adata *path, u32 as, int min);
int as_path_match_set(const struct adata *path, const struct f_tree *set);
const struct adata *as_path_filter(struct linpool *pool, const struct adata *path, const struct f_val *set, int pos);
+int as_path_compare(const struct adata *path1, const struct adata *path2);
int as_path_walk(const struct adata *path, uint *pos, uint *val);
static inline struct adata *as_path_prepend(struct linpool *pool, const struct adata *path, u32 as)
@@ -238,6 +239,7 @@ int lc_set_max(const struct adata *list, lcomm *val);
int int_set_walk(const struct adata *list, uint *pos, u32 *val);
int ec_set_walk(const struct adata *list, uint *pos, u64 *val);
int lc_set_walk(const struct adata *list, uint *pos, lcomm *val);
+int rte_set_walk(const struct adata *list, u32 *pos, struct rte **val);
void ec_set_sort_x(struct adata *set); /* Sort in place */
diff --git a/nest/cmds.c b/nest/cmds.c
index bcc8d6c2..d49bbc53 100644
--- a/nest/cmds.c
+++ b/nest/cmds.c
@@ -51,17 +51,18 @@ cmd_show_symbols(struct sym_show_data *sd)
cli_msg(1010, "%-8s\t%s", sd->sym->name, cf_symbol_class_name(sd->sym));
else
{
- HASH_WALK(config->sym_hash, next, sym)
- {
- if (!sym->scope->active)
- continue;
-
- if (sd->type && (sym->class != sd->type))
- continue;
-
- cli_msg(-1010, "%-8s\t%s", sym->name, cf_symbol_class_name(sym));
- }
- HASH_WALK_END;
+ for (const struct sym_scope *scope = config->root_scope; scope; scope = scope->next)
+ HASH_WALK(scope->hash, next, sym)
+ {
+ if (!sym->scope->active)
+ continue;
+
+ if (sd->type && (sym->class != sd->type))
+ continue;
+
+ cli_msg(-1010, "%-8s\t%s", sym->name, cf_symbol_class_name(sym));
+ }
+ HASH_WALK_END;
cli_msg(0, "");
}
diff --git a/nest/config.Y b/nest/config.Y
index b2aa0906..6bd08481 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -12,6 +12,7 @@ CF_HDR
#include "nest/rt-dev.h"
#include "nest/password.h"
#include "nest/cmds.h"
+#include "nest/mpls.h"
#include "lib/lists.h"
#include "lib/mac.h"
@@ -118,24 +119,27 @@ CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS)
CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI)
CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CHANNELS, INTERFACES)
CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512)
-CF_KEYWORDS(PRIMARY, STATS, COUNT, BY, FOR, IN, COMMANDS, PREEXPORT, NOEXPORT, EXPORTED, GENERATE)
+CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, IN, COMMANDS, PREEXPORT, NOEXPORT, EXPORTED, GENERATE)
CF_KEYWORDS(BGP, PASSWORDS, DESCRIPTION)
CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, IGP_METRIC, CLASS, DSCP)
CF_KEYWORDS(TIMEFORMAT, ISO, SHORT, LONG, ROUTE, PROTOCOL, BASE, LOG, S, MS, US)
-CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, FLUSH, AS)
+CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, AS)
CF_KEYWORDS(MIN, IDLE, RX, TX, INTERVAL, MULTIPLIER, PASSIVE)
CF_KEYWORDS(CHECK, LINK)
CF_KEYWORDS(SORTED, TRIE, MIN, MAX, SETTLE, TIME, GC, THRESHOLD, PERIOD)
+CF_KEYWORDS(MPLS_LABEL, MPLS_POLICY, MPLS_CLASS)
/* For r_args_channel */
CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC)
CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
- RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL)
+ RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL, RPKI, L3VPN,
+ AGGREGATED)
CF_ENUM(T_ENUM_SCOPE, SCOPE_, HOST, LINK, SITE, ORGANIZATION, UNIVERSE, UNDEFINED)
CF_ENUM(T_ENUM_RTD, RTD_, UNICAST, BLACKHOLE, UNREACHABLE, PROHIBIT)
CF_ENUM(T_ENUM_ROA, ROA_, UNKNOWN, VALID, INVALID)
CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)
+CF_ENUM(T_ENUM_MPLS_POLICY, MPLS_POLICY_, NONE, STATIC, PREFIX, AGGREGATE, VRF)
%type <i32> idval
%type <f> imexport
@@ -143,7 +147,7 @@ CF_ENUM_PX(T_ENUM_AF, AF_, AFI_, IPV4, IPV6)
%type <s> optproto
%type <ra> r_args
%type <sd> sym_args
-%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type tos password_algorithm
+%type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type net_type_base tos password_algorithm
%type <ps> proto_patt proto_patt2
%type <cc> channel_start proto_channel
%type <cl> limit_spec
@@ -165,7 +169,7 @@ rtrid:
idval:
NUM { $$ = $1; }
- | '(' term ')' { $$ = f_eval_int(f_linearize($2, 1)); }
+ | '(' term ')' { $$ = cf_eval_int($2); }
| IP4 { $$ = ip4_to_u32($1); }
| CF_SYM_KNOWN {
if ($1->class == (SYM_CONSTANT | T_INT) || $1->class == (SYM_CONSTANT | T_QUAD))
@@ -188,7 +192,7 @@ gr_opts: GRACEFUL RESTART WAIT expr ';' { new_config->gr_wait = $4; } ;
/* Network types (for tables, channels) */
-net_type:
+net_type_base:
IPV4 { $$ = NET_IP4; }
| IPV6 { $$ = NET_IP6; }
| IPV6 SADR { $$ = NET_IP6_SADR; }
@@ -198,6 +202,10 @@ net_type:
| ROA6 { $$ = NET_ROA6; }
| FLOW4{ $$ = NET_FLOW4; }
| FLOW6{ $$ = NET_FLOW6; }
+ ;
+
+net_type:
+ net_type_base
| MPLS { $$ = NET_MPLS; }
;
@@ -255,19 +263,19 @@ proto_start:
proto_name:
/* EMPTY */ {
- struct symbol *s = cf_default_name(this_proto->protocol->template, &this_proto->protocol->name_counter);
+ struct symbol *s = cf_default_name(new_config, this_proto->protocol->template, &this_proto->protocol->name_counter);
s->class = this_proto->class;
s->proto = this_proto;
this_proto->name = s->name;
}
| symbol {
- cf_define_symbol($1, this_proto->class, proto, this_proto);
+ cf_define_symbol(new_config, $1, this_proto->class, proto, this_proto);
this_proto->name = $1->name;
}
| FROM CF_SYM_KNOWN {
if (($2->class != SYM_TEMPLATE) && ($2->class != SYM_PROTO)) cf_error("Template or protocol name expected");
- struct symbol *s = cf_default_name(this_proto->protocol->template, &this_proto->protocol->name_counter);
+ struct symbol *s = cf_default_name(new_config, this_proto->protocol->template, &this_proto->protocol->name_counter);
s->class = this_proto->class;
s->proto = this_proto;
this_proto->name = s->name;
@@ -277,7 +285,7 @@ proto_name:
| symbol FROM CF_SYM_KNOWN {
if (($3->class != SYM_TEMPLATE) && ($3->class != SYM_PROTO)) cf_error("Template or protocol name expected");
- cf_define_symbol($1, this_proto->class, proto, this_proto);
+ cf_define_symbol(new_config, $1, this_proto->class, proto, this_proto);
this_proto->name = $1->name;
proto_copy_config(this_proto, $3->proto);
@@ -296,7 +304,7 @@ proto_item:
;
-channel_start: net_type
+channel_start: net_type_base
{
$$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto);
};
@@ -544,10 +552,15 @@ password_item:
pass_key: PASSWORD | KEY;
-password_item_begin:
- pass_key text { init_password_list(); init_password($2, strlen($2), password_id++); }
- | pass_key BYTESTRING { init_password_list(); init_password($2->data, $2->length, password_id++); }
-;
+password_item_begin: pass_key bytestring_text
+{
+ init_password_list();
+ if ($2.type == T_BYTESTRING)
+ init_password($2.val.bs->data, $2.val.bs->length, password_id++);
+ else if ($2.type == T_STRING)
+ init_password($2.val.s, strlen($2.val.s), password_id++);
+ else bug("Bad bytestring_text");
+};
password_item_params:
/* empty */ { }
@@ -632,7 +645,7 @@ CF_CLI(SHOW INTERFACES SUMMARY,,, [[Show summary of network interfaces]])
{ if_show_summary(); } ;
CF_CLI_HELP(SHOW ROUTE, ..., [[Show routing table]])
-CF_CLI(SHOW ROUTE, r_args, [[[<prefix>|for <prefix>|for <ip>] [table <t>] [(import|export) table <p>.<c>] [filter <f>|where <cond>] [all] [primary] [filtered] [(export|preexport|noexport) <p>] [protocol <p>] [stats|count]]], [[Show routing table]])
+CF_CLI(SHOW ROUTE, r_args, [[[<prefix>|for <prefix>|for <ip>|in <prefix>] [table <t>] [(import|export) table <p>.<c>] [filter <f>|where <cond>] [all] [primary] [filtered] [(export|preexport|noexport) <p>] [protocol <p>] [stats|count]]], [[Show routing table]])
{ rt_show($3); } ;
r_args:
@@ -640,7 +653,7 @@ r_args:
$$ = cfg_allocz(sizeof(struct rt_show_data));
init_list(&($$->tables));
$$->filter = FILTER_ACCEPT;
- $$->running_on_config = new_config->fallback;
+ $$->running_on_config = config;
}
| r_args net_any {
$$ = $1;
@@ -661,7 +674,7 @@ r_args:
$$->addr = $3;
$$->addr_mode = RSD_ADDR_IN;
}
-| r_args TABLE CF_SYM_KNOWN {
+| r_args TABLE symbol_known {
cf_assert_symbol($3, SYM_TABLE);
$$ = $1;
rt_show_add_table($$, $3->table->table);
@@ -706,7 +719,7 @@ r_args:
$$ = $1;
$$->filtered = 1;
}
- | r_args export_mode CF_SYM_KNOWN {
+ | r_args export_mode symbol_known {
cf_assert_symbol($3, SYM_PROTO);
struct proto_config *c = (struct proto_config *) $3->proto;
$$ = $1;
@@ -723,7 +736,7 @@ r_args:
$$->export_channel = $3;
$$->tables_defined_by = RSD_TDB_INDIRECT;
}
- | r_args PROTOCOL CF_SYM_KNOWN {
+ | r_args PROTOCOL symbol_known {
cf_assert_symbol($3, SYM_PROTO);
struct proto_config *c = (struct proto_config *) $3->proto;
$$ = $1;
@@ -835,7 +848,7 @@ sym_args:
| sym_args FILTER { $$ = $1; $$->type = SYM_FILTER; }
| sym_args PROTOCOL { $$ = $1; $$->type = SYM_PROTO; }
| sym_args TEMPLATE { $$ = $1; $$->type = SYM_TEMPLATE; }
- | sym_args symbol { $$ = $1; $$->sym = $2; }
+ | sym_args CF_SYM_KNOWN { $$ = $1; $$->sym = $2; }
;
@@ -925,6 +938,10 @@ proto_patt2:
dynamic_attr: IGP_METRIC { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_GEN_IGP_METRIC); } ;
+dynamic_attr: MPLS_LABEL { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_MPLS_LABEL); } ;
+dynamic_attr: MPLS_POLICY { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_ENUM_MPLS_POLICY, EA_MPLS_POLICY); } ;
+dynamic_attr: MPLS_CLASS { $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_MPLS_CLASS); } ;
+
CF_CODE
diff --git a/nest/iface.c b/nest/iface.c
index 682340c5..6c84cbcf 100644
--- a/nest/iface.c
+++ b/nest/iface.c
@@ -147,7 +147,7 @@ ifa_send_notify(struct proto *p, unsigned c, struct ifa *a)
{
if (p->ifa_notify &&
(p->proto_state != PS_DOWN) &&
- (!p->vrf_set || p->vrf == a->iface->master))
+ (!p->vrf_set || if_in_vrf(a->iface, p->vrf)))
{
if (p->debug & D_IFACES)
log(L_TRACE "%s < address %N on interface %s %s",
@@ -185,7 +185,7 @@ if_send_notify(struct proto *p, unsigned c, struct iface *i)
{
if (p->if_notify &&
(p->proto_state != PS_DOWN) &&
- (!p->vrf_set || p->vrf == i->master))
+ (!p->vrf_set || if_in_vrf(i, p->vrf)))
{
if (p->debug & D_IFACES)
log(L_TRACE "%s < interface %s %s", p->name, i->name,
diff --git a/nest/iface.h b/nest/iface.h
index 1189cdd4..f8e92850 100644
--- a/nest/iface.h
+++ b/nest/iface.h
@@ -53,6 +53,7 @@ struct iface {
#define IF_IGNORE 0x40 /* Not to be used by routing protocols (loopbacks etc.) */
#define IF_ADMIN_UP 0x80 /* Administrative up (e.g. IFF_UP in Linux) */
#define IF_LINK_UP 0x100 /* Link available (e.g. IFF_LOWER_UP in Linux) */
+#define IF_VRF 0x200 /* Iface is VRF master */
#define IA_PRIMARY 0x10000 /* This address is primary */
#define IA_SECONDARY 0x20000 /* This address has been reported as secondary by the kernel */
@@ -119,6 +120,9 @@ struct iface *if_find_by_name(const char *);
struct iface *if_get_by_name(const char *);
void if_recalc_all_preferred_addresses(void);
+static inline int if_in_vrf(struct iface *i, struct iface *vrf)
+{ return (i->flags & IF_VRF) ? (i == vrf) : (i->master == vrf); }
+
/* The Neighbor Cache */
diff --git a/nest/mpls.Y b/nest/mpls.Y
new file mode 100644
index 00000000..726a834a
--- /dev/null
+++ b/nest/mpls.Y
@@ -0,0 +1,200 @@
+/*
+ * BIRD Internet Routing Daemon -- MPLS Structures
+ *
+ * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "nest/mpls.h"
+
+CF_DEFINES
+
+static struct mpls_domain_config *this_mpls_domain;
+static struct mpls_range_config *this_mpls_range;
+
+#define MPLS_CC ((struct mpls_channel_config *) this_channel)
+
+CF_DECLS
+
+CF_KEYWORDS(MPLS, DOMAIN, LABEL, RANGE, STATIC, DYNAMIC, START, LENGTH, POLICY, PREFIX, AGGREGATE, VRF)
+
+%type <i> mpls_label_policy
+%type <cc> mpls_channel_start mpls_channel
+%type <msrc> show_mpls_ranges_args
+
+CF_GRAMMAR
+
+conf: mpls_domain;
+
+mpls_domain: mpls_domain_start mpls_domain_opt_list mpls_domain_end;
+
+mpls_domain_start: MPLS DOMAIN symbol { this_mpls_domain = mpls_domain_config_new($3); };
+
+mpls_domain_opt:
+ mpls_range
+ ;
+
+mpls_domain_opts:
+ /* empty */
+ | mpls_domain_opts mpls_domain_opt ';'
+ ;
+
+mpls_domain_opt_list:
+ /* empty */
+ | '{' mpls_domain_opts '}'
+ ;
+
+mpls_domain_end: { mpls_domain_postconfig(this_mpls_domain); this_mpls_domain = NULL; };
+
+
+mpls_range: mpls_range_start mpls_range_opt_list mpls_range_end;
+
+mpls_range_start: LABEL RANGE symbol
+{
+ if (($3->class == SYM_KEYWORD) && ($3->keyword->value == STATIC))
+ this_mpls_range = this_mpls_domain->static_range;
+ else if (($3->class == SYM_KEYWORD) && ($3->keyword->value == DYNAMIC))
+ this_mpls_range = this_mpls_domain->dynamic_range;
+ else
+ this_mpls_range = mpls_range_config_new(this_mpls_domain, $3);
+};
+
+mpls_range_opt:
+ START expr { this_mpls_range->start = $2; if ($2 >= MPLS_MAX_LABEL) cf_error("MPLS label range start must be less than 2^20"); }
+ | LENGTH expr { this_mpls_range->length = $2; if ($2 >= MPLS_MAX_LABEL) cf_error("MPLS label range length must be less than 2^20"); if (!$2) cf_error("MPLS label range length must be nonzero"); }
+ ;
+
+mpls_range_opts:
+ /* empty */
+ | mpls_range_opts mpls_range_opt ';'
+ ;
+
+mpls_range_opt_list:
+ /* empty */
+ | '{' mpls_range_opts '}'
+ ;
+
+mpls_range_end:
+{
+ struct mpls_range_config *r = this_mpls_range;
+
+ if ((r->start == (uint) -1) || (r->length == (uint) -1))
+ cf_error("MPLS label range start and length must be specified");
+
+ if (r->start + r->length > MPLS_MAX_LABEL)
+ cf_error("MPLS label range end must be less than 2^20");
+
+ this_mpls_range = NULL;
+};
+
+
+mpls_channel: mpls_channel_start mpls_channel_opt_list mpls_channel_end;
+
+mpls_channel_start: MPLS
+{
+ $$ = this_channel = channel_config_get(&channel_mpls, net_label[NET_MPLS], NET_MPLS, this_proto);
+
+ if (EMPTY_LIST(new_config->mpls_domains))
+ {
+ int counter = 0;
+ mpls_domain_config_new(cf_default_name(new_config, "mpls%d", &counter));
+ cf_warn("No MPLS domain defined");
+ }
+
+ /* Default values for new channel */
+ if (!MPLS_CC->domain)
+ {
+ MPLS_CC->domain = cf_default_mpls_domain(new_config);
+ MPLS_CC->label_policy = MPLS_POLICY_PREFIX;
+ }
+};
+
+mpls_label_policy:
+ STATIC { $$ = MPLS_POLICY_STATIC; }
+ | PREFIX { $$ = MPLS_POLICY_PREFIX; }
+ | AGGREGATE { $$ = MPLS_POLICY_AGGREGATE; }
+ | VRF { $$ = MPLS_POLICY_VRF; }
+ ;
+
+mpls_channel_opt:
+ channel_item
+ | DOMAIN symbol_known { cf_assert_symbol($2, SYM_MPLS_DOMAIN); MPLS_CC->domain = $2->mpls_domain; }
+ | LABEL RANGE symbol_known { cf_assert_symbol($3, SYM_MPLS_RANGE); MPLS_CC->range = $3->mpls_range; }
+ | LABEL RANGE STATIC { MPLS_CC->range = MPLS_CC->domain->static_range; }
+ | LABEL RANGE DYNAMIC { MPLS_CC->range = MPLS_CC->domain->dynamic_range; }
+ | LABEL POLICY mpls_label_policy { MPLS_CC->label_policy = $3; }
+ ;
+
+mpls_channel_opts:
+ /* empty */
+ | mpls_channel_opts mpls_channel_opt ';'
+ ;
+
+mpls_channel_opt_list:
+ /* empty */
+ | '{' mpls_channel_opts '}'
+ ;
+
+mpls_channel_end: { mpls_channel_postconfig(this_channel); } channel_end;
+
+
+show_mpls_ranges_args:
+ /* empty */
+ {
+ if (EMPTY_LIST(config->mpls_domains))
+ cf_error("No MPLS domain defined");
+
+ $$ = cfg_allocz(sizeof(struct mpls_show_ranges_cmd));
+ }
+ | show_mpls_ranges_args symbol_known
+ {
+ if ($2->class == SYM_MPLS_DOMAIN)
+ {
+ if ($$->domain)
+ cf_error("Only one MPLS domain expected");
+
+ $$->domain = $2->mpls_domain;
+ }
+ else if ($2->class == SYM_MPLS_RANGE)
+ {
+ if ($$->range)
+ cf_error("Only one MPLS label range expected");
+
+ if ($$->domain != $2->mpls_range->domain)
+ cf_error("MPLS label range from different MPLS domain");
+
+ $$->domain = $2->mpls_range->domain;
+ $$->range = $2->mpls_range;
+ }
+ else
+ cf_error("MPLS domain or label range expected");
+ }
+ | show_mpls_ranges_args STATIC
+ {
+ if ($$->range)
+ cf_error("Only one MPLS label range expected");
+
+ $$->domain = $$->domain ?: cf_default_mpls_domain(config);
+ $$->range = $$->domain->static_range;
+ }
+ | show_mpls_ranges_args DYNAMIC
+ {
+ if ($$->range)
+ cf_error("Only one MPLS label range expected");
+
+ $$->domain = $$->domain ?: cf_default_mpls_domain(config);
+ $$->range = $$->domain->dynamic_range;
+ }
+ ;
+
+CF_CLI(SHOW MPLS RANGES, show_mpls_ranges_args, [<MPLS domain> | <MPLS range>], [[Show MPLS ranges]])
+{ mpls_show_ranges($4); } ;
+
+
+CF_CODE
+
+CF_END
diff --git a/nest/mpls.c b/nest/mpls.c
new file mode 100644
index 00000000..e37535c5
--- /dev/null
+++ b/nest/mpls.c
@@ -0,0 +1,1296 @@
+/*
+ * BIRD Internet Routing Daemon -- MPLS Structures
+ *
+ * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: MPLS
+ *
+ * The MPLS subsystem manages MPLS labels and handles their allocation to
+ * MPLS-aware routing protocols. These labels are then attached to IP or VPN
+ * routes representing label switched paths -- LSPs. MPLS labels are also used
+ * in special MPLS routes (which use labels as network address) that are
+ * exported to MPLS routing table in kernel. The MPLS subsystem consists of MPLS
+ * domains (struct &mpls_domain), MPLS channels (struct &mpls_channel) and FEC
+ * maps (struct &mpls_fec_map).
+ *
+ * The MPLS domain represents one MPLS label address space, implements the label
+ * allocator, and handles associated configuration and management. The domain is
+ * declared in the configuration (struct &mpls_domain_config). There might be
+ * multiple MPLS domains representing separate label spaces, but in most cases
+ * one domain is enough. MPLS-aware protocols and routing tables are associated
+ * with a specific MPLS domain.
+ *
+ * The MPLS domain has configurable label ranges (struct &mpls_range), by
+ * default it has two ranges: static (16-1000) and dynamic (1000-10000). When
+ * a protocol wants to allocate labels, it first acquires a handle (struct
+ * &mpls_handle) for a specific range using mpls_new_handle(), and then it
+ * allocates labels from that with mpls_new_label(). When not needed, labels are
+ * freed by mpls_free_label() and the handle is released by mpls_free_handle().
+ * Note that all labels and handles must be freed manually.
+ *
+ * Both MPLS domain and MPLS range are reference counted, so when deconfigured
+ * they could be freed just after all labels and ranges are freed. Users are
+ * expected to hold a reference to a MPLS domain for whole time they use
+ * something from that domain (e.g. &mpls_handle), but releasing reference to
+ * a range while holding associated handle is OK.
+ *
+ * The MPLS channel is subclass of a generic protocol channel. It has two
+ * distinct purposes - to handle per-protocol MPLS configuration (e.g. which
+ * MPLS domain is associated with the protocol, which label range is used by the
+ * protocol), and to announce MPLS routes to a routing table (as a regular
+ * protocol channel).
+ *
+ * The FEC map is a helper structure that maps forwarding equivalent classes
+ * (FECs) to MPLS labels. It is an internal matter of a routing protocol how to
+ * assign meaning to allocated labels, announce LSP routes and associated MPLS
+ * routes (i.e. ILM entries). But the common behavior is implemented in the FEC
+ * map, which can be used by the protocols that work with IP-prefix-based FECs.
+ *
+ * The FEC map keeps hash tables of FECs (struct &mpls_fec) based on network
+ * prefix, next hop eattr and assigned label. It has three general labeling policies:
+ * static assignment (%MPLS_POLICY_STATIC), per-prefix policy (%MPLS_POLICY_PREFIX),
+ * and aggregating policy (%MPLS_POLICY_AGGREGATE). In per-prefix policy, each
+ * distinct LSP is a separate FEC and uses a separate label, which is kept even
+ * if the next hop of the LSP changes. In aggregating policy, LSPs with a same
+ * next hop form one FEC and use one label, but when a next hop (or remote
+ * label) of such LSP changes then the LSP must be moved to a different FEC and
+ * assigned a different label. There is also a special VRF policy (%MPLS_POLICY_VRF)
+ * applicable for L3VPN protocols, which uses one label for all routes from a VRF,
+ * while replacing the original next hop with lookup in the VRF.
+ *
+ * The overall process works this way: A protocol wants to announce a LSP route,
+ * it does that by announcing e.g. IP route with %EA_MPLS_POLICY attribute.
+ * After the route is accepted by filters (which may also change the policy
+ * attribute or set a static label), the mpls_handle_rte() is called from
+ * rte_update2(), which applies selected labeling policy, finds existing FEC or
+ * creates a new FEC (which includes allocating new label and announcing related
+ * MPLS route by mpls_announce_fec()), and attach FEC label to the LSP route.
+ * After that, the LSP route is stored in routing table by rte_recalculate().
+ * Changes in routing tables trigger mpls_rte_insert() and mpls_rte_remove()
+ * hooks, which refcount FEC structures and possibly trigger removal of FECs
+ * and withdrawal of MPLS routes.
+ *
+ * TODO:
+ * - special handling of reserved labels
+ */
+
+#include <stdlib.h>
+
+#include "nest/bird.h"
+#include "nest/route.h"
+#include "nest/mpls.h"
+#include "nest/cli.h"
+
+static struct mpls_range *mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf);
+static struct mpls_range *mpls_find_range_(list *l, const char *name);
+static int mpls_reconfigure_range(struct mpls_domain *m, struct mpls_range *r, struct mpls_range_config *cf);
+static void mpls_remove_range(struct mpls_range *r);
+
+
+/*
+ * MPLS domain
+ */
+
+list mpls_domains;
+
+void
+mpls_init(void)
+{
+ init_list(&mpls_domains);
+}
+
+struct mpls_domain_config *
+mpls_domain_config_new(struct symbol *s)
+{
+ struct mpls_domain_config *mc = cfg_allocz(sizeof(struct mpls_domain_config));
+ struct mpls_range_config *rc;
+
+ cf_define_symbol(new_config, s, SYM_MPLS_DOMAIN, mpls_domain, mc);
+ mc->name = s->name;
+ init_list(&mc->ranges);
+
+ /* Predefined static range */
+ rc = mpls_range_config_new(mc, NULL);
+ rc->name = "static";
+ rc->start = 16;
+ rc->length = 984;
+ rc->implicit = 1;
+ mc->static_range = rc;
+
+ /* Predefined dynamic range */
+ rc = mpls_range_config_new(mc, NULL);
+ rc->name = "dynamic";
+ rc->start = 1000;
+ rc->length = 9000;
+ rc->implicit = 1;
+ mc->dynamic_range = rc;
+
+ add_tail(&new_config->mpls_domains, &mc->n);
+
+ return mc;
+}
+
+static int
+mpls_compare_range_configs(const void *r1_, const void *r2_)
+{
+ const struct mpls_range_config * const *r1 = r1_;
+ const struct mpls_range_config * const *r2 = r2_;
+
+ return uint_cmp((*r1)->start, (*r2)->start);
+}
+
+void
+mpls_domain_postconfig(struct mpls_domain_config *cf UNUSED)
+{
+ /* Label range non-intersection check */
+
+ int num_ranges = list_length(&cf->ranges);
+ struct mpls_range_config **ranges = tmp_alloc(num_ranges * sizeof(struct mpls_range_config *));
+
+ {
+ int i = 0;
+ struct mpls_range_config *r;
+ WALK_LIST(r, cf->ranges)
+ ranges[i++] = r;
+ }
+
+ qsort(ranges, num_ranges, sizeof(struct mpls_range_config *), mpls_compare_range_configs);
+
+ struct mpls_range_config *max_range = NULL;
+ uint max_hi = 0;
+
+ for (int i = 0; i < num_ranges; i++)
+ {
+ struct mpls_range_config *r = ranges[i];
+ uint hi = r->start + r->length;
+
+ if (r->implicit)
+ continue;
+
+ if (r->start < max_hi)
+ cf_warn("MPLS label ranges %s and %s intersect", max_range->name, r->name);
+
+ if (hi > max_hi)
+ {
+ max_range = r;
+ max_hi = hi;
+ }
+ }
+}
+
+static struct mpls_domain *
+mpls_new_domain(struct mpls_domain_config *cf)
+{
+ struct pool *p = rp_new(&root_pool, "MPLS domain");
+ struct mpls_domain *m = mb_allocz(p, sizeof(struct mpls_domain));
+
+ m->cf = cf;
+ m->name = cf->name;
+ m->pool = p;
+
+ lmap_init(&m->labels, p);
+ lmap_set(&m->labels, 0);
+
+ init_list(&m->ranges);
+ init_list(&m->handles);
+
+ struct mpls_range_config *rc;
+ WALK_LIST(rc, cf->ranges)
+ mpls_new_range(m, rc);
+
+ add_tail(&mpls_domains, &m->n);
+ cf->domain = m;
+
+ return m;
+}
+
+static struct mpls_domain *
+mpls_find_domain_(list *l, const char *name)
+{
+ struct mpls_domain *m;
+
+ WALK_LIST(m, *l)
+ if (!strcmp(m->name, name))
+ return m;
+
+ return NULL;
+}
+
+static int
+mpls_reconfigure_domain(struct mpls_domain *m, struct mpls_domain_config *cf)
+{
+ cf->domain = m;
+ m->cf->domain = NULL;
+ m->cf = cf;
+ m->name = cf->name;
+
+ /* Reconfigure label ranges */
+ list old_ranges;
+ init_list(&old_ranges);
+ add_tail_list(&old_ranges, &m->ranges);
+ init_list(&m->ranges);
+
+ struct mpls_range_config *rc;
+ WALK_LIST(rc, cf->ranges)
+ {
+ struct mpls_range *r = mpls_find_range_(&old_ranges, rc->name);
+
+ if (r && mpls_reconfigure_range(m, r, rc))
+ {
+ rem_node(&r->n);
+ add_tail(&m->ranges, &r->n);
+ continue;
+ }
+
+ mpls_new_range(m, rc);
+ }
+
+ struct mpls_range *r, *r2;
+ WALK_LIST_DELSAFE(r, r2, old_ranges)
+ if (!r->removed)
+ mpls_remove_range(r);
+
+ add_tail_list(&m->ranges, &old_ranges);
+
+ return 1;
+}
+
+static void
+mpls_free_domain(struct mpls_domain *m)
+{
+ ASSERT(m->use_count == 0);
+ ASSERT(m->label_count == 0);
+ ASSERT(EMPTY_LIST(m->handles));
+
+ struct config *cfg = m->removed;
+
+ m->cf->domain = NULL;
+ rem_node(&m->n);
+ rfree(m->pool);
+
+ config_del_obstacle(cfg);
+}
+
+static void
+mpls_remove_domain(struct mpls_domain *m, struct config *cfg)
+{
+ m->removed = cfg;
+ config_add_obstacle(cfg);
+
+ if (!m->use_count)
+ mpls_free_domain(m);
+}
+
+void
+mpls_lock_domain(struct mpls_domain *m)
+{
+ m->use_count++;
+}
+
+void
+mpls_unlock_domain(struct mpls_domain *m)
+{
+ ASSERT(m->use_count > 0);
+
+ m->use_count--;
+ if (!m->use_count && m->removed)
+ mpls_free_domain(m);
+}
+
+void
+mpls_preconfig(struct config *c)
+{
+ init_list(&c->mpls_domains);
+}
+
+void
+mpls_commit(struct config *new, struct config *old)
+{
+ list old_domains;
+ init_list(&old_domains);
+ add_tail_list(&old_domains, &mpls_domains);
+ init_list(&mpls_domains);
+
+ struct mpls_domain_config *mc;
+ WALK_LIST(mc, new->mpls_domains)
+ {
+ struct mpls_domain *m = mpls_find_domain_(&old_domains, mc->name);
+
+ if (m && mpls_reconfigure_domain(m, mc))
+ {
+ rem_node(&m->n);
+ add_tail(&mpls_domains, &m->n);
+ continue;
+ }
+
+ mpls_new_domain(mc);
+ }
+
+ struct mpls_domain *m, *m2;
+ WALK_LIST_DELSAFE(m, m2, old_domains)
+ mpls_remove_domain(m, old);
+
+ add_tail_list(&mpls_domains, &old_domains);
+}
+
+
+/*
+ * MPLS range
+ */
+
+struct mpls_range_config *
+mpls_range_config_new(struct mpls_domain_config *mc, struct symbol *s)
+{
+ struct mpls_range_config *rc = cfg_allocz(sizeof(struct mpls_range_config));
+
+ if (s)
+ cf_define_symbol(new_config, s, SYM_MPLS_RANGE, mpls_range, rc);
+
+ rc->domain = mc;
+ rc->name = s ? s->name : NULL;
+ rc->start = (uint) -1;
+ rc->length = (uint) -1;
+
+ add_tail(&mc->ranges, &rc->n);
+
+ return rc;
+}
+
+static struct mpls_range *
+mpls_new_range(struct mpls_domain *m, struct mpls_range_config *cf)
+{
+ struct mpls_range *r = mb_allocz(m->pool, sizeof(struct mpls_range));
+
+ r->cf = cf;
+ r->name = cf->name;
+ r->lo = cf->start;
+ r->hi = cf->start + cf->length;
+
+ add_tail(&m->ranges, &r->n);
+ cf->range = r;
+
+ return r;
+}
+
+static struct mpls_range *
+mpls_find_range_(list *l, const char *name)
+{
+ struct mpls_range *r;
+
+ WALK_LIST(r, *l)
+ if (!strcmp(r->name, name) && !r->removed)
+ return r;
+
+ return NULL;
+}
+
+static int
+mpls_reconfigure_range(struct mpls_domain *m, struct mpls_range *r, struct mpls_range_config *cf)
+{
+ uint last = lmap_last_one_in_range(&m->labels, r->lo, r->hi);
+ if (last == r->hi) last = 0;
+
+ if ((cf->start > r->lo) || (cf->start + cf->length <= last))
+ return 0;
+
+ cf->range = r;
+ r->cf->range = NULL;
+ r->cf = cf;
+ r->name = cf->name;
+ r->lo = cf->start;
+ r->hi = cf->start + cf->length;
+
+ return 1;
+}
+
+static void
+mpls_free_range(struct mpls_range *r)
+{
+ ASSERT(r->use_count == 0);
+ ASSERT(r->label_count == 0);
+
+ rem_node(&r->n);
+ mb_free(r);
+}
+
+static void
+mpls_remove_range(struct mpls_range *r)
+{
+ ASSERT(!r->removed);
+
+ r->removed = 1;
+ r->cf->range = NULL;
+ r->cf = NULL;
+
+ if (!r->use_count)
+ mpls_free_range(r);
+}
+
+void
+mpls_lock_range(struct mpls_range *r)
+{
+ r->use_count++;
+}
+
+void
+mpls_unlock_range(struct mpls_range *r)
+{
+ ASSERT(r->use_count > 0);
+
+ r->use_count--;
+ if (!r->use_count && r->removed)
+ mpls_free_range(r);
+}
+
+
+/*
+ * MPLS handle
+ */
+
+struct mpls_handle *
+mpls_new_handle(struct mpls_domain *m, struct mpls_range *r)
+{
+ struct mpls_handle *h = mb_allocz(m->pool, sizeof(struct mpls_handle));
+
+ h->range = r;
+ mpls_lock_range(h->range);
+
+ add_tail(&m->handles, &h->n);
+
+ return h;
+}
+
+void
+mpls_free_handle(struct mpls_domain *m UNUSED, struct mpls_handle *h)
+{
+ ASSERT(h->label_count == 0);
+
+ mpls_unlock_range(h->range);
+ rem_node(&h->n);
+ mb_free(h);
+}
+
+
+/*
+ * MPLS label
+ */
+
+uint
+mpls_new_label(struct mpls_domain *m, struct mpls_handle *h, uint n)
+{
+ struct mpls_range *r = h->range;
+
+ if (!n)
+ n = lmap_first_zero_in_range(&m->labels, r->lo, r->hi);
+
+ if ((n < r->lo) || (n >= r->hi) || lmap_test(&m->labels, n))
+ return 0;
+
+ m->label_count++;
+ r->label_count++;
+ h->label_count++;
+
+ lmap_set(&m->labels, n);
+ return n;
+}
+
+void
+mpls_free_label(struct mpls_domain *m, struct mpls_handle *h, uint n)
+{
+ struct mpls_range *r = h->range;
+
+ ASSERT(lmap_test(&m->labels, n));
+ lmap_clear(&m->labels, n);
+
+ ASSERT(m->label_count);
+ m->label_count--;
+
+ ASSERT(r->label_count);
+ r->label_count--;
+
+ ASSERT(h->label_count);
+ h->label_count--;
+}
+
+void
+mpls_move_label(struct mpls_domain *m, struct mpls_handle *fh, struct mpls_handle *th, uint n)
+{
+ struct mpls_range *fr = fh->range;
+ struct mpls_range *tr = th->range;
+
+ ASSERT(lmap_test(&m->labels, n));
+ ASSERT((n >= fr->lo) && (n < fr->hi));
+ ASSERT((n >= tr->lo) && (n < tr->hi));
+
+ ASSERT(fr->label_count);
+ fr->label_count--;
+
+ ASSERT(fh->label_count);
+ fh->label_count--;
+
+ tr->label_count++;
+ th->label_count++;
+}
+
+
+/*
+ * MPLS channel
+ */
+
+static void
+mpls_channel_init(struct channel *C, struct channel_config *CC)
+{
+ struct mpls_channel *c = (void *) C;
+ struct mpls_channel_config *cc = (void *) CC;
+
+ c->domain = cc->domain->domain;
+ c->range = cc->range->range;
+ c->label_policy = cc->label_policy;
+}
+
+static int
+mpls_channel_start(struct channel *C)
+{
+ struct mpls_channel *c = (void *) C;
+
+ mpls_lock_domain(c->domain);
+ mpls_lock_range(c->range);
+
+ return 0;
+}
+
+/*
+static void
+mpls_channel_shutdown(struct channel *C)
+{
+ struct mpls_channel *c = (void *) C;
+
+}
+*/
+
+static void
+mpls_channel_cleanup(struct channel *C)
+{
+ struct mpls_channel *c = (void *) C;
+
+ mpls_unlock_range(c->range);
+ mpls_unlock_domain(c->domain);
+}
+
+static int
+mpls_channel_reconfigure(struct channel *C, struct channel_config *CC, int *import_changed, int *export_changed UNUSED)
+{
+ struct mpls_channel *c = (void *) C;
+ struct mpls_channel_config *new = (void *) CC;
+
+ if (new->domain->domain != c->domain)
+ return 0;
+
+ if (new->range->range != c->range)
+ {
+ if (c->c.channel_state != CS_DOWN)
+ mpls_unlock_range(c->range);
+
+ c->range = new->range->range;
+ *import_changed = 1;
+
+ if (c->c.channel_state != CS_DOWN)
+ mpls_lock_range(c->range);
+ }
+
+ if (new->label_policy != c->label_policy)
+ {
+ c->label_policy = new->label_policy;
+ *import_changed = 1;
+ }
+
+ return 1;
+}
+
+void
+mpls_channel_postconfig(struct channel_config *CC)
+{
+ struct mpls_channel_config *cc = (void *) CC;
+
+ if (!cc->domain)
+ cf_error("MPLS domain not specified");
+
+ if (!cc->range)
+ cc->range = cc->domain->dynamic_range;
+
+ if (cc->range->domain != cc->domain)
+ cf_error("MPLS label range from different MPLS domain");
+
+ if (!cc->c.table)
+ cf_error("Routing table not specified");
+}
+
+struct channel_class channel_mpls = {
+ .channel_size = sizeof(struct mpls_channel),
+ .config_size = sizeof(struct mpls_channel_config),
+ .init = mpls_channel_init,
+ .start = mpls_channel_start,
+// .shutdown = mpls_channel_shutdown,
+ .cleanup = mpls_channel_cleanup,
+ .reconfigure = mpls_channel_reconfigure,
+};
+
+
+/*
+ * MPLS FEC map
+ */
+
+#define NET_KEY(fec) fec->net, fec->path_id, fec->hash
+#define NET_NEXT(fec) fec->next_k
+#define NET_EQ(n1,i1,h1,n2,i2,h2) h1 == h2 && i1 == i2 && net_equal(n1, n2)
+#define NET_FN(n,i,h) h
+
+#define NET_REHASH mpls_net_rehash
+#define NET_PARAMS /8, *2, 2, 2, 8, 24
+
+
+#define RTA_KEY(fec) fec->rta, fec->class_id, fec->hash
+#define RTA_NEXT(fec) fec->next_k
+#define RTA_EQ(r1,i1,h1,r2,i2,h2) h1 == h2 && r1 == r2 && i1 == i2
+#define RTA_FN(r,i,h) h
+
+#define RTA_REHASH mpls_rta_rehash
+#define RTA_PARAMS /8, *2, 2, 2, 8, 24
+
+
+#define LABEL_KEY(fec) fec->label
+#define LABEL_NEXT(fec) fec->next_l
+#define LABEL_EQ(l1,l2) l1 == l2
+#define LABEL_FN(l) u32_hash(l)
+
+#define LABEL_REHASH mpls_label_rehash
+#define LABEL_PARAMS /8, *2, 2, 2, 8, 24
+
+
+HASH_DEFINE_REHASH_FN(NET, struct mpls_fec)
+HASH_DEFINE_REHASH_FN(RTA, struct mpls_fec)
+HASH_DEFINE_REHASH_FN(LABEL, struct mpls_fec)
+
+
+static void mpls_unlink_fec(struct mpls_fec_map *m, struct mpls_fec *fec);
+static void mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec);
+static rta * mpls_get_key_rta(struct mpls_fec_map *m, const rta *src);
+
+struct mpls_fec_map *
+mpls_fec_map_new(pool *pp, struct channel *C, uint rts)
+{
+ struct pool *p = rp_new(pp, "MPLS FEC map");
+ struct mpls_fec_map *m = mb_allocz(p, sizeof(struct mpls_fec_map));
+ struct mpls_channel *c = (void *) C;
+
+ m->pool = p;
+ m->channel = C;
+
+ m->domain = c->domain;
+ mpls_lock_domain(m->domain);
+
+ m->handle = mpls_new_handle(c->domain, c->range);
+
+ /* net_hash and rta_hash are initialized on-demand */
+ HASH_INIT(m->label_hash, m->pool, 4);
+
+ m->mpls_rts = rts;
+ m->mpls_scope = SCOPE_UNIVERSE;
+
+ return m;
+}
+
+void
+mpls_fec_map_reconfigure(struct mpls_fec_map *m, struct channel *C)
+{
+ struct mpls_channel *c = (void *) C;
+
+ struct mpls_handle *old_d = NULL;
+ struct mpls_handle *old_s = NULL;
+
+ /* Reallocate dynamic handle */
+ if (m->handle->range != c->range)
+ {
+ old_d = m->handle;
+ m->handle = mpls_new_handle(m->domain, c->range);
+ }
+
+ /* Reallocate static handle */
+ if (m->static_handle && (m->static_handle->range != m->domain->cf->static_range->range))
+ {
+ old_s = m->static_handle;
+ m->static_handle = mpls_new_handle(m->domain, m->domain->cf->static_range->range);
+ }
+
+ /* Skip rest if there is no change */
+ if (!old_d && !old_s)
+ return;
+
+ /* Process existing FECs */
+ HASH_WALK(m->label_hash, next_l, fec)
+ {
+ /* Skip already dead FECs */
+ if (fec->policy == MPLS_POLICY_NONE)
+ continue;
+
+ /* Skip FECs with valid handle */
+ if ((fec->handle == m->handle) || (fec->handle == m->static_handle))
+ continue;
+
+ /* Try new handle for the FEC */
+ struct mpls_handle *new = (fec->policy != MPLS_POLICY_STATIC) ? m->handle : m->static_handle;
+ if ((fec->label >= new->range->lo) && (fec->label < new->range->hi))
+ {
+ mpls_move_label(m->domain, fec->handle, new, fec->label);
+ fec->handle = new;
+ continue;
+ }
+
+ /* Unlink the FEC while keep it in the label hash */
+ mpls_unlink_fec(m, fec);
+ fec->policy = MPLS_POLICY_NONE;
+ }
+ HASH_WALK_END;
+
+ /* Remove old unused handles */
+
+ if (old_d && !old_d->label_count)
+ mpls_free_handle(m->domain, old_d);
+
+ if (old_s && !old_s->label_count)
+ mpls_free_handle(m->domain, old_s);
+}
+
+void
+mpls_fec_map_free(struct mpls_fec_map *m)
+{
+ /* Free stored rtas */
+ if (m->rta_hash.data)
+ {
+ HASH_WALK(m->rta_hash, next_k, fec)
+ {
+ rta_free(fec->rta);
+ fec->rta = NULL;
+ }
+ HASH_WALK_END;
+ }
+
+ /* Free allocated labels */
+ HASH_WALK(m->label_hash, next_l, fec)
+ {
+ mpls_free_label(m->domain, fec->handle, fec->label);
+
+ if (!fec->policy && !fec->handle->label_count)
+ mpls_free_handle(m->domain, fec->handle);
+ }
+ HASH_WALK_END;
+
+ if (m->static_handle)
+ mpls_free_handle(m->domain, m->static_handle);
+
+ mpls_free_handle(m->domain, m->handle);
+ mpls_unlock_domain(m->domain);
+
+ rfree(m->pool);
+}
+
+static slab *
+mpls_slab(struct mpls_fec_map *m, uint type)
+{
+ ASSERT(type <= NET_VPN6);
+ int pos = type ? (type - 1) : 0;
+
+ if (!m->slabs[pos])
+ m->slabs[pos] = sl_new(m->pool, sizeof(struct mpls_fec) + net_addr_length[pos + 1]);
+
+ return m->slabs[pos];
+}
+
+struct mpls_fec *
+mpls_find_fec_by_label(struct mpls_fec_map *m, u32 label)
+{
+ return HASH_FIND(m->label_hash, LABEL, label);
+}
+
+struct mpls_fec *
+mpls_get_fec_by_label(struct mpls_fec_map *m, u32 label)
+{
+ struct mpls_fec *fec = HASH_FIND(m->label_hash, LABEL, label);
+
+ if (fec)
+ return (fec->policy == MPLS_POLICY_STATIC) ? fec : NULL;
+
+ if (!m->static_handle)
+ m->static_handle = mpls_new_handle(m->domain, m->domain->cf->static_range->range);
+
+ label = mpls_new_label(m->domain, m->static_handle, label);
+
+ if (!label)
+ return NULL;
+
+ fec = sl_allocz(mpls_slab(m, 0));
+
+ fec->label = label;
+ fec->policy = MPLS_POLICY_STATIC;
+ fec->handle = m->static_handle;
+
+ DBG("New FEC lab %u\n", fec->label);
+
+ HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+
+ return fec;
+}
+
+struct mpls_fec *
+mpls_get_fec_by_net(struct mpls_fec_map *m, const net_addr *net, u64 path_id)
+{
+ if (!m->net_hash.data)
+ HASH_INIT(m->net_hash, m->pool, 4);
+
+ u32 hash = net_hash(net) ^ u64_hash(path_id);
+ struct mpls_fec *fec = HASH_FIND(m->net_hash, NET, net, path_id, hash);
+
+ if (fec)
+ return fec;
+
+ u32 label = mpls_new_label(m->domain, m->handle, 0);
+
+ if (!label)
+ return NULL;
+
+ fec = sl_allocz(mpls_slab(m, net->type));
+
+ fec->hash = hash;
+ fec->path_id = path_id;
+ net_copy(fec->net, net);
+
+ fec->label = label;
+ fec->policy = MPLS_POLICY_PREFIX;
+ fec->handle = m->handle;
+
+ DBG("New FEC net %u\n", fec->label);
+
+ HASH_INSERT2(m->net_hash, NET, m->pool, fec);
+ HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+
+ return fec;
+}
+
+struct mpls_fec *
+mpls_get_fec_by_rta(struct mpls_fec_map *m, const rta *src, u32 class_id)
+{
+ if (!m->rta_hash.data)
+ HASH_INIT(m->rta_hash, m->pool, 4);
+
+ rta *rta = mpls_get_key_rta(m, src);
+ u32 hash = rta->hash_key ^ u32_hash(class_id);
+ struct mpls_fec *fec = HASH_FIND(m->rta_hash, RTA, rta, class_id, hash);
+
+ if (fec)
+ {
+ rta_free(rta);
+ return fec;
+ }
+
+ u32 label = mpls_new_label(m->domain, m->handle, 0);
+
+ if (!label)
+ {
+ rta_free(rta);
+ return NULL;
+ }
+
+ fec = sl_allocz(mpls_slab(m, 0));
+
+ fec->hash = hash;
+ fec->class_id = class_id;
+ fec->rta = rta;
+
+ fec->label = label;
+ fec->policy = MPLS_POLICY_AGGREGATE;
+ fec->handle = m->handle;
+
+ DBG("New FEC rta %u\n", fec->label);
+
+ HASH_INSERT2(m->rta_hash, RTA, m->pool, fec);
+ HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+
+ return fec;
+}
+
+struct mpls_fec *
+mpls_get_fec_for_vrf(struct mpls_fec_map *m)
+{
+ struct mpls_fec *fec = m->vrf_fec;
+
+ if (fec)
+ return fec;
+
+ u32 label = mpls_new_label(m->domain, m->handle, 0);
+
+ if (!label)
+ return NULL;
+
+ fec = sl_allocz(mpls_slab(m, 0));
+
+ fec->label = label;
+ fec->policy = MPLS_POLICY_VRF;
+ fec->handle = m->handle;
+ fec->iface = m->vrf_iface;
+
+ DBG("New FEC vrf %u\n", fec->label);
+
+ m->vrf_fec = fec;
+ HASH_INSERT2(m->label_hash, LABEL, m->pool, fec);
+
+ return fec;
+}
+
+static void
+mpls_unlink_fec(struct mpls_fec_map *m, struct mpls_fec *fec)
+{
+ switch (fec->policy)
+ {
+ case MPLS_POLICY_NONE:
+ case MPLS_POLICY_STATIC:
+ break;
+
+ case MPLS_POLICY_PREFIX:
+ HASH_REMOVE2(m->net_hash, NET, m->pool, fec);
+ break;
+
+ case MPLS_POLICY_AGGREGATE:
+ rta_free(fec->rta);
+ HASH_REMOVE2(m->rta_hash, RTA, m->pool, fec);
+ break;
+
+ case MPLS_POLICY_VRF:
+ ASSERT(m->vrf_fec == fec);
+ m->vrf_fec = NULL;
+ break;
+
+ default:
+ bug("Unknown fec type");
+ }
+}
+
+void
+mpls_free_fec(struct mpls_fec_map *m, struct mpls_fec *fec)
+{
+ if (fec->state != MPLS_FEC_DOWN)
+ mpls_withdraw_fec(m, fec);
+
+ DBG("Free FEC %u\n", fec->label);
+
+ mpls_free_label(m->domain, fec->handle, fec->label);
+
+ if (!fec->policy && !fec->handle->label_count)
+ mpls_free_handle(m->domain, fec->handle);
+
+ HASH_REMOVE2(m->label_hash, LABEL, m->pool, fec);
+
+ mpls_unlink_fec(m, fec);
+
+ sl_free(fec);
+}
+
+static inline void mpls_lock_fec(struct mpls_fec_map *x UNUSED, struct mpls_fec *fec)
+{ if (fec) fec->uc++; }
+
+static inline void mpls_unlock_fec(struct mpls_fec_map *x, struct mpls_fec *fec)
+{ if (fec && !--fec->uc) mpls_free_fec(x, fec); }
+
+static inline void
+mpls_damage_fec(struct mpls_fec_map *m UNUSED, struct mpls_fec *fec)
+{
+ if (fec && (fec->state == MPLS_FEC_CLEAN))
+ fec->state = MPLS_FEC_DIRTY;
+}
+
+static rta *
+mpls_get_key_rta(struct mpls_fec_map *m, const rta *src)
+{
+ rta *a = allocz(RTA_MAX_SIZE);
+
+ a->source = m->mpls_rts;
+ a->scope = m->mpls_scope;
+
+ if (!src->hostentry)
+ {
+ /* Just copy the nexthop */
+ a->dest = src->dest;
+ nexthop_link(a, &src->nh);
+ }
+ else
+ {
+ /* Keep the hostentry */
+ a->hostentry = src->hostentry;
+
+ /* Keep the original labelstack */
+ const u32 *labels = &src->nh.label[src->nh.labels - src->nh.labels_orig];
+ a->nh.labels = a->nh.labels_orig = src->nh.labels_orig;
+ memcpy(a->nh.label, labels, src->nh.labels_orig * sizeof(u32));
+ }
+
+ return rta_lookup(a);
+}
+
+static void
+mpls_announce_fec(struct mpls_fec_map *m, struct mpls_fec *fec, const rta *src)
+{
+ rta *a = allocz(RTA_MAX_SIZE);
+
+ a->source = m->mpls_rts;
+ a->scope = m->mpls_scope;
+
+ if (!src->hostentry)
+ {
+ /* Just copy the nexthop */
+ a->dest = src->dest;
+ nexthop_link(a, &src->nh);
+ }
+ else
+ {
+ const u32 *labels = &src->nh.label[src->nh.labels - src->nh.labels_orig];
+ mpls_label_stack ms;
+
+ /* Apply the hostentry with the original labelstack */
+ ms.len = src->nh.labels_orig;
+ memcpy(ms.stack, labels, src->nh.labels_orig * sizeof(u32));
+ rta_apply_hostentry(a, src->hostentry, &ms);
+ }
+
+ net_addr_mpls n = NET_ADDR_MPLS(fec->label);
+
+ rte *e = rte_get_temp(rta_lookup(a), m->channel->proto->main_source);
+ e->pflags = 0;
+
+ fec->state = MPLS_FEC_CLEAN;
+ rte_update2(m->channel, (net_addr *) &n, e, m->channel->proto->main_source);
+}
+
+static void
+mpls_withdraw_fec(struct mpls_fec_map *m, struct mpls_fec *fec)
+{
+ net_addr_mpls n = NET_ADDR_MPLS(fec->label);
+
+ fec->state = MPLS_FEC_DOWN;
+ rte_update2(m->channel, (net_addr *) &n, NULL, m->channel->proto->main_source);
+}
+
+static void
+mpls_apply_fec(rte *r, struct mpls_fec *fec, linpool *lp)
+{
+ struct ea_list *ea = lp_allocz(lp, sizeof(struct ea_list) + 2 * sizeof(eattr));
+
+ rta *old_attrs = r->attrs;
+
+ if (rta_is_cached(old_attrs))
+ r->attrs = rta_do_cow(r->attrs, lp);
+
+ *ea = (struct ea_list) {
+ .next = r->attrs->eattrs,
+ .flags = EALF_SORTED,
+ .count = 2,
+ };
+
+ ea->attrs[0] = (struct eattr) {
+ .id = EA_MPLS_LABEL,
+ .type = EAF_TYPE_INT,
+ .u.data = fec->label,
+ };
+
+ ea->attrs[1] = (struct eattr) {
+ .id = EA_MPLS_POLICY,
+ .type = EAF_TYPE_INT,
+ .u.data = fec->policy,
+ };
+
+ r->attrs->eattrs = ea;
+
+ if (fec->policy == MPLS_POLICY_VRF)
+ {
+ r->attrs->hostentry = NULL;
+ r->attrs->dest = RTD_UNICAST;
+ r->attrs->nh = (struct nexthop) { .iface = fec->iface };
+ }
+
+ if (rta_is_cached(old_attrs))
+ {
+ r->attrs = rta_lookup(r->attrs);
+ rta_free(old_attrs);
+ }
+}
+
+
+int
+mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r, linpool *lp, struct mpls_fec **locked_fec)
+{
+ ASSERT(!(r->flags & REF_COW));
+
+ struct mpls_fec *fec = NULL;
+
+ /* Select FEC for route */
+ uint policy = ea_get_int(r->attrs->eattrs, EA_MPLS_POLICY, 0);
+ switch (policy)
+ {
+ case MPLS_POLICY_NONE:
+ return 0;
+
+ case MPLS_POLICY_STATIC:;
+ uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0);
+
+ if (label < 16)
+ return 0;
+
+ fec = mpls_get_fec_by_label(m, label);
+ if (!fec)
+ {
+ log(L_WARN "Static label %u failed for %N from %s",
+ label, n, r->sender->proto->name);
+ return -1;
+ }
+
+ mpls_damage_fec(m, fec);
+ break;
+
+ case MPLS_POLICY_PREFIX:
+ fec = mpls_get_fec_by_net(m, n, r->src->private_id);
+ mpls_damage_fec(m, fec);
+ break;
+
+ case MPLS_POLICY_AGGREGATE:;
+ uint class = ea_get_int(r->attrs->eattrs, EA_MPLS_CLASS, 0);
+ fec = mpls_get_fec_by_rta(m, r->attrs, class);
+ break;
+
+ case MPLS_POLICY_VRF:
+ if (!m->vrf_iface)
+ return 0;
+
+ fec = mpls_get_fec_for_vrf(m);
+ break;
+
+ default:
+ log(L_WARN "Route %N has invalid MPLS policy %u", n, policy);
+ return -1;
+ }
+
+ /* Label allocation failure */
+ if (!fec)
+ {
+ log(L_WARN "Label allocation in range %s failed for %N from %s",
+ m->handle->range->name, n, r->sender->proto->name);
+ return -1;
+ }
+
+ /* Temporarily lock FEC */
+ mpls_lock_fec(m, fec);
+ *locked_fec = fec;
+
+ /* Apply FEC label to route */
+ mpls_apply_fec(r, fec, lp);
+
+ /* Announce MPLS rule for new/updated FEC */
+ if (fec->state != MPLS_FEC_CLEAN)
+ mpls_announce_fec(m, fec, r->attrs);
+
+ return 0;
+}
+
+void
+mpls_handle_rte_cleanup(struct mpls_fec_map *m, struct mpls_fec **locked_fec)
+{
+ /* Unlock temporarily locked FEC from mpls_handle_rte() */
+ if (*locked_fec)
+ {
+ mpls_unlock_fec(m, *locked_fec);
+ *locked_fec = NULL;
+ }
+}
+
+void
+mpls_rte_insert(net *n UNUSED, rte *r)
+{
+ struct proto *p = r->src->proto;
+ struct mpls_fec_map *m = p->mpls_map;
+
+ uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0);
+ if (label < 16)
+ return;
+
+ struct mpls_fec *fec = mpls_find_fec_by_label(m, label);
+ if (!fec)
+ return;
+
+ mpls_lock_fec(m, fec);
+}
+
+void
+mpls_rte_remove(net *n UNUSED, rte *r)
+{
+ struct proto *p = r->src->proto;
+ struct mpls_fec_map *m = p->mpls_map;
+
+ uint label = ea_get_int(r->attrs->eattrs, EA_MPLS_LABEL, 0);
+ if (label < 16)
+ return;
+
+ struct mpls_fec *fec = mpls_find_fec_by_label(m, label);
+ if (!fec)
+ return;
+
+ mpls_unlock_fec(m, fec);
+}
+
+static void
+mpls_show_ranges_rng(struct mpls_show_ranges_cmd *cmd, struct mpls_range *r)
+{
+ uint last = lmap_last_one_in_range(&cmd->dom->labels, r->lo, r->hi);
+ if (last == r->hi) last = 0;
+
+ cli_msg(-1026, "%-11s %7u %7u %7u %7u %7u",
+ r->name, r->lo, r->hi - r->lo, r->hi, r->label_count, last);
+}
+
+void
+mpls_show_ranges_dom(struct mpls_show_ranges_cmd *cmd, struct mpls_domain *m)
+{
+ if (cmd->dom)
+ cli_msg(-1026, "");
+
+ cmd->dom = m;
+ cli_msg(-1026, "MPLS domain %s:", m->name);
+ cli_msg(-1026, "%-11s %7s %7s %7s %7s %7s",
+ "Range", "Start", "Length", "End", "Labels", "Last");
+
+ if (cmd->range)
+ mpls_show_ranges_rng(cmd, cmd->range->range);
+ else
+ {
+ struct mpls_range *r;
+ WALK_LIST(r, m->ranges)
+ if (!r->removed)
+ mpls_show_ranges_rng(cmd, r);
+ }
+}
+
+void
+mpls_show_ranges(struct mpls_show_ranges_cmd *cmd)
+{
+ if (cmd->domain)
+ mpls_show_ranges_dom(cmd, cmd->domain->domain);
+ else
+ {
+ struct mpls_domain *m;
+ WALK_LIST(m, mpls_domains)
+ mpls_show_ranges_dom(cmd, m);
+ }
+
+ cli_msg(0, "");
+}
diff --git a/nest/mpls.h b/nest/mpls.h
new file mode 100644
index 00000000..c2bdf5ef
--- /dev/null
+++ b/nest/mpls.h
@@ -0,0 +1,191 @@
+/*
+ * BIRD Internet Routing Daemon -- MPLS Structures
+ *
+ * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_MPLS_H_
+#define _BIRD_MPLS_H_
+
+#include "nest/bird.h"
+#include "lib/bitmap.h"
+#include "lib/hash.h"
+#include "nest/route.h"
+#include "nest/protocol.h"
+
+
+#define MPLS_POLICY_NONE 0
+#define MPLS_POLICY_STATIC 1
+#define MPLS_POLICY_PREFIX 2
+#define MPLS_POLICY_AGGREGATE 3
+#define MPLS_POLICY_VRF 4
+
+#define MPLS_FEC_DOWN 0
+#define MPLS_FEC_CLEAN 1
+#define MPLS_FEC_DIRTY 2
+
+
+struct mpls_domain_config {
+ node n; /* Node in config.mpls_domains */
+ struct mpls_domain *domain; /* Our instance */
+ const char *name;
+
+ list ranges; /* List of label ranges (struct mpls_range_config) */
+ struct mpls_range_config *static_range; /* Default static label range */
+ struct mpls_range_config *dynamic_range; /* Default dynamic label range */
+};
+
+struct mpls_domain {
+ node n; /* Node in global list of MPLS domains (mpls_domains) */
+ struct mpls_domain_config *cf; /* Our config */
+ const char *name;
+ pool *pool; /* Pool for the domain and associated objects */
+
+ struct lmap labels; /* Bitmap of allocated labels */
+ uint label_count; /* Number of allocated labels */
+ uint use_count; /* Reference counter */
+
+ struct config *removed; /* Deconfigured, waiting for zero use_count,
+ while keeping config obstacle */
+
+ list ranges; /* List of label ranges (struct mpls_range) */
+ list handles; /* List of label handles (struct mpls_handle) */
+};
+
+struct mpls_range_config {
+ node n; /* Node in mpls_domain_config.ranges */
+ struct mpls_range *range; /* Our instance */
+ struct mpls_domain_config *domain; /* Parent MPLS domain */
+ const char *name;
+
+ uint start; /* Label range start, (uint) -1 for undefined */
+ uint length; /* Label range length, (uint) -1 for undefined */
+ u8 implicit; /* Implicitly defined range */
+};
+
+struct mpls_range {
+ node n; /* Node in mpls_domain.ranges */
+ struct mpls_range_config *cf; /* Our config */
+ const char *name;
+
+ uint lo, hi; /* Label range interval */
+ uint label_count; /* Number of allocated labels */
+ uint use_count; /* Reference counter */
+ u8 removed; /* Deconfigured, waiting for zero use_count */
+};
+
+struct mpls_handle {
+ node n; /* Node in mpls_domain.handles */
+
+ struct mpls_range *range; /* Associated range, keeping reference */
+ uint label_count; /* Number of allocated labels */
+};
+
+
+void mpls_init(void);
+struct mpls_domain_config * mpls_domain_config_new(struct symbol *s);
+void mpls_domain_postconfig(struct mpls_domain_config *cf);
+struct mpls_range_config * mpls_range_config_new(struct mpls_domain_config *m, struct symbol *s);
+void mpls_preconfig(struct config *c);
+void mpls_commit(struct config *new, struct config *old);
+uint mpls_new_label(struct mpls_domain *m, struct mpls_handle *h, uint n);
+void mpls_free_label(struct mpls_domain *m, struct mpls_handle *h, uint n);
+void mpls_move_label(struct mpls_domain *m, struct mpls_handle *fh, struct mpls_handle *th, uint n);
+
+static inline struct mpls_domain_config *cf_default_mpls_domain(struct config *cfg)
+{ return EMPTY_LIST(cfg->mpls_domains) ? NULL : HEAD(cfg->mpls_domains); }
+
+
+struct mpls_channel_config {
+ struct channel_config c;
+
+ struct mpls_domain_config *domain;
+ struct mpls_range_config *range;
+
+ uint label_policy;
+};
+
+struct mpls_channel {
+ struct channel c;
+
+ struct mpls_domain *domain;
+ struct mpls_range *range;
+
+ uint label_policy;
+};
+
+
+void mpls_channel_postconfig(struct channel_config *CF);
+extern struct channel_class channel_mpls;
+
+
+struct mpls_fec {
+ u32 label; /* Label for FEC */
+ u32 hash; /* Hash for primary key (net / rta) */
+ u32 uc; /* Number of LSPs for FEC */
+ u8 state; /* FEC state (MPLS_FEC_*) */
+ u8 policy; /* Label policy (MPLS_POLICY_*) */
+
+ struct mpls_handle *handle; /* Handle holding the label */
+
+ struct mpls_fec *next_k; /* Next in mpls_fec.net_hash/rta_hash */
+ struct mpls_fec *next_l; /* Next in mpls_fec.label_hash */
+
+ union { /* Extension part of key */
+ u64 path_id; /* Source path_id */
+ u32 class_id; /* Aaggregation class */
+ };
+ union { /* Primary key */
+ struct rta *rta;
+ struct iface *iface;
+ net_addr net[0];
+ };
+};
+
+struct mpls_fec_map {
+ pool *pool; /* Pool for FEC map */
+ slab *slabs[4]; /* Slabs for FEC allocation */
+ HASH(struct mpls_fec) net_hash; /* Hash table for MPLS_POLICY_PREFIX FECs */
+ HASH(struct mpls_fec) rta_hash; /* Hash table for MPLS_POLICY_AGGREGATE FECs */
+ HASH(struct mpls_fec) label_hash; /* Hash table for FEC lookup by label */
+ struct mpls_fec *vrf_fec; /* Single FEC for MPLS_POLICY_VRF */
+
+ struct channel *channel; /* MPLS channel for FEC announcement */
+ struct mpls_domain *domain; /* MPLS domain, keeping reference */
+ struct mpls_handle *handle; /* Handle for dynamic allocation of labels */
+ struct mpls_handle *static_handle; /* Handle for static label allocations, optional */
+ struct iface *vrf_iface;
+
+ u8 mpls_rts; /* Source value used for MPLS routes (RTS_*) */
+ u8 mpls_scope; /* Scope value used for MPLS routes (SCOPE_*) */
+};
+
+
+struct mpls_fec_map *mpls_fec_map_new(pool *p, struct channel *c, uint rts);
+void mpls_fec_map_reconfigure(struct mpls_fec_map *m, struct channel *C);
+void mpls_fec_map_free(struct mpls_fec_map *m);
+struct mpls_fec *mpls_find_fec_by_label(struct mpls_fec_map *x, u32 label);
+struct mpls_fec *mpls_get_fec_by_label(struct mpls_fec_map *m, u32 label);
+struct mpls_fec *mpls_get_fec_by_net(struct mpls_fec_map *m, const net_addr *net, u64 path_id);
+struct mpls_fec *mpls_get_fec_by_rta(struct mpls_fec_map *m, const rta *src, u32 class_id);
+void mpls_free_fec(struct mpls_fec_map *x, struct mpls_fec *fec);
+int mpls_handle_rte(struct mpls_fec_map *m, const net_addr *n, rte *r, linpool *lp, struct mpls_fec **locked_fec);
+void mpls_handle_rte_cleanup(struct mpls_fec_map *m, struct mpls_fec **locked_fec);
+void mpls_rte_insert(net *n UNUSED, rte *r);
+void mpls_rte_remove(net *n UNUSED, rte *r);
+
+
+struct mpls_show_ranges_cmd {
+ struct mpls_domain_config *domain;
+ struct mpls_range_config *range;
+
+ /* Runtime */
+ struct mpls_domain *dom;
+};
+
+void mpls_show_ranges(struct mpls_show_ranges_cmd *cmd);
+
+#endif
diff --git a/nest/neighbor.c b/nest/neighbor.c
index 7cf9c85d..2c2d3adf 100644
--- a/nest/neighbor.c
+++ b/nest/neighbor.c
@@ -153,7 +153,7 @@ if_connected_any(ip_addr a, struct iface *vrf, uint vrf_set, struct iface **ifac
/* Prefer SCOPE_HOST or longer prefix */
WALK_LIST(i, iface_list)
- if ((!vrf_set || vrf == i->master) && ((s = if_connected(a, i, &b, flags)) >= 0))
+ if ((!vrf_set || if_in_vrf(i, vrf)) && ((s = if_connected(a, i, &b, flags)) >= 0))
if (scope_better(s, scope) || (scope_remote(s, scope) && ifa_better(b, *addr)))
{
*iface = i;
@@ -369,7 +369,7 @@ neigh_update(neighbor *n, struct iface *iface)
return;
/* VRF-bound neighbors ignore changes in other VRFs */
- if (p->vrf_set && (p->vrf != iface->master))
+ if (p->vrf_set && !if_in_vrf(iface, p->vrf))
return;
scope = if_connected(n->addr, iface, &ifa, n->flags);
diff --git a/nest/proto.c b/nest/proto.c
index 885a0b75..88f4813e 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -18,6 +18,7 @@
#include "conf/conf.h"
#include "nest/route.h"
#include "nest/iface.h"
+#include "nest/mpls.h"
#include "nest/cli.h"
#include "filter/filter.h"
#include "filter/f-inst.h"
@@ -90,7 +91,6 @@ proto_log_state_change(struct proto *p)
p->last_state_name_announced = NULL;
}
-
struct channel_config *
proto_cf_find_channel(struct proto_config *pc, uint net_type)
{
@@ -179,6 +179,7 @@ proto_add_channel(struct proto *p, struct channel_config *cf)
c->merge_limit = cf->merge_limit;
c->in_keep_filtered = cf->in_keep_filtered;
c->rpki_reload = cf->rpki_reload;
+ c->bmp_hack = cf->bmp_hack;
c->channel_state = CS_DOWN;
c->export_state = ES_DOWN;
@@ -523,7 +524,7 @@ channel_setup_in_table(struct channel *c)
cf->addr_type = c->net_type;
cf->internal = 1;
- c->in_table = rt_setup(c->proto->pool, cf);
+ c->in_table = cf->table = rt_setup(c->proto->pool, cf);
c->reload_event = ev_new_init(c->proto->pool, channel_reload_loop, c);
}
@@ -574,7 +575,8 @@ channel_do_up(struct channel *c)
static void
channel_do_flush(struct channel *c)
{
- rt_schedule_prune(c->table);
+ if (!c->bmp_hack)
+ rt_schedule_prune(c->table);
c->gr_wait = 0;
if (c->gr_lock)
@@ -763,7 +765,7 @@ channel_config_new(const struct channel_class *cc, const char *name, uint net_ty
if (!net_val_match(net_type, proto->protocol->channel_mask))
cf_error("Unsupported channel type");
- if (proto->net_type && (net_type != proto->net_type))
+ if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS))
cf_error("Different channel type");
tab = new_config->def_tables[net_type];
@@ -954,6 +956,81 @@ proto_configure_channel(struct proto *p, struct channel **pc, struct channel_con
return 1;
}
+/**
+ * proto_setup_mpls_map - automatically setup FEC map for protocol
+ * @p: affected protocol
+ * @rts: RTS_* value for generated MPLS routes
+ * @hooks: whether to update rte_insert / rte_remove hooks
+ *
+ * Add, remove or reconfigure MPLS FEC map of the protocol @p, depends on
+ * whether MPLS channel exists, and setup rte_insert / rte_remove hooks with
+ * default MPLS handlers. It is a convenience function supposed to be called
+ * from the protocol start and configure hooks, after reconfiguration of
+ * channels. For shutdown, use proto_shutdown_mpls_map(). If caller uses its own
+ * rte_insert / rte_remove hooks, it is possible to disable updating hooks and
+ * doing that manually.
+ */
+void
+proto_setup_mpls_map(struct proto *p, uint rts, int hooks)
+{
+ struct mpls_fec_map *m = p->mpls_map;
+ struct channel *c = p->mpls_channel;
+
+ if (!m && c)
+ {
+ /*
+ * Note that when called from a protocol start hook, it is called before
+ * mpls_channel_start(). But FEC map locks MPLS domain internally so it does
+ * not depend on lock from MPLS channel.
+ */
+ p->mpls_map = mpls_fec_map_new(p->pool, c, rts);
+ }
+ else if (m && !c)
+ {
+ /*
+ * Note that for reconfiguration, it is called after the MPLS channel has
+ * been already removed. But removal of active MPLS channel would trigger
+ * protocol restart anyways.
+ */
+ mpls_fec_map_free(m);
+ p->mpls_map = NULL;
+ }
+ else if (m && c)
+ {
+ mpls_fec_map_reconfigure(m, c);
+ }
+
+ if (hooks)
+ {
+ p->rte_insert = p->mpls_map ? mpls_rte_insert : NULL;
+ p->rte_remove = p->mpls_map ? mpls_rte_remove : NULL;
+ }
+}
+
+/**
+ * proto_shutdown_mpls_map - automatically shutdown FEC map for protocol
+ * @p: affected protocol
+ * @hooks: whether to update rte_insert / rte_remove hooks
+ *
+ * Remove MPLS FEC map of the protocol @p during protocol shutdown.
+ */
+void
+proto_shutdown_mpls_map(struct proto *p, int hooks)
+{
+ struct mpls_fec_map *m = p->mpls_map;
+
+ if (!m)
+ return;
+
+ mpls_fec_map_free(m);
+ p->mpls_map = NULL;
+
+ if (hooks)
+ {
+ p->rte_insert = NULL;
+ p->rte_remove = NULL;
+ }
+}
static void
proto_event(void *ptr)
@@ -1267,8 +1344,8 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty
/* This is hack, we would like to share config, but we need to copy it now */
new_config = new;
cfg_mem = new->mem;
- conf_this_scope = new->root_scope;
- sym = cf_get_symbol(oc->name);
+ new->current_scope = new->root_scope;
+ sym = cf_get_symbol(new, oc->name);
proto_clone_config(sym, parsym->proto);
new_config = NULL;
cfg_mem = NULL;
diff --git a/nest/protocol.h b/nest/protocol.h
index fcbf0539..c87d3814 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -31,6 +31,7 @@ struct channel;
struct ea_list;
struct eattr;
struct symbol;
+struct mpls_fec_map;
/*
@@ -39,12 +40,15 @@ struct symbol;
enum protocol_class {
PROTOCOL_NONE,
+ PROTOCOL_AGGREGATOR,
PROTOCOL_BABEL,
PROTOCOL_BFD,
PROTOCOL_BGP,
+ PROTOCOL_BMP,
PROTOCOL_DEVICE,
PROTOCOL_DIRECT,
PROTOCOL_KERNEL,
+ PROTOCOL_L3VPN,
PROTOCOL_OSPF,
PROTOCOL_MRT,
PROTOCOL_PERF,
@@ -102,8 +106,8 @@ void protos_dump_all(void);
extern struct protocol
proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
- proto_ospf, proto_perf,
- proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki;
+ proto_ospf, proto_perf, proto_l3vpn, proto_aggregator,
+ proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
/*
* Routing Protocol Instance
@@ -170,6 +174,8 @@ struct proto {
struct channel *main_channel; /* Primary channel */
struct rte_src *main_source; /* Primary route source */
struct iface *vrf; /* Related VRF instance, NULL if global */
+ struct channel *mpls_channel; /* MPLS channel, when used */
+ struct mpls_fec_map *mpls_map; /* Maps protocol routes to FECs / labels */
const char *name; /* Name of this instance (== cf->name) */
u32 debug; /* Debugging flags */
@@ -475,7 +481,8 @@ struct channel_class {
#endif
};
-extern struct channel_class channel_bgp;
+extern const struct channel_class channel_basic;
+extern const struct channel_class channel_bgp;
struct channel_config {
node n;
@@ -498,6 +505,7 @@ struct channel_config {
u8 merge_limit; /* Maximal number of nexthops for RA_MERGED */
u8 in_keep_filtered; /* Routes rejected in import filter are kept */
u8 rpki_reload; /* RPKI changes trigger channel reload */
+ u8 bmp_hack; /* No flush */
};
struct channel {
@@ -550,6 +558,7 @@ struct channel {
u8 reload_pending; /* Reloading and another reload is scheduled */
u8 refeed_pending; /* Refeeding and another refeed is scheduled */
u8 rpki_reload; /* RPKI changes trigger channel reload */
+ u8 bmp_hack; /* No flush */
struct rtable *out_table; /* Internal table for exported routes */
@@ -614,11 +623,16 @@ struct channel {
struct channel_config *proto_cf_find_channel(struct proto_config *p, uint net_type);
static inline struct channel_config *proto_cf_main_channel(struct proto_config *pc)
{ return proto_cf_find_channel(pc, pc->net_type); }
+static inline struct channel_config *proto_cf_mpls_channel(struct proto_config *pc)
+{ return (pc->net_type != NET_MPLS) ? proto_cf_find_channel(pc, NET_MPLS) : NULL; }
struct channel *proto_find_channel_by_table(struct proto *p, struct rtable *t);
struct channel *proto_find_channel_by_name(struct proto *p, const char *n);
struct channel *proto_add_channel(struct proto *p, struct channel_config *cf);
+void proto_remove_channel(struct proto *p, struct channel *c);
int proto_configure_channel(struct proto *p, struct channel **c, struct channel_config *cf);
+void proto_setup_mpls_map(struct proto *p, uint rts, int hooks);
+void proto_shutdown_mpls_map(struct proto *p, int hooks);
void channel_set_state(struct channel *c, uint state);
void channel_setup_in_table(struct channel *c);
diff --git a/nest/route.h b/nest/route.h
index 7aec7117..f83a5b33 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -200,6 +200,7 @@ typedef struct rtable {
struct timer *settle_timer; /* Settle time for notifications */
list flowspec_links; /* List of flowspec links, src for NET_IPx and dst for NET_FLOWx */
struct f_trie *flowspec_trie; /* Trie for evaluation of flowspec notifications */
+ // struct mpls_domain *mpls_domain; /* Label allocator for MPLS */
} rtable;
struct rt_subscription {
@@ -342,6 +343,8 @@ void rt_prune_sync(rtable *t, int all);
int rte_update_out(struct channel *c, const net_addr *n, rte *new, rte *old0, int refeed);
struct rtable_config *rt_new_table(struct symbol *s, uint addr_type);
+int rte_same(rte *x, rte *y);
+
static inline int rt_is_ip(rtable *tab)
{ return (tab->addr_type == NET_IP4) || (tab->addr_type == NET_IP6); }
@@ -437,7 +440,7 @@ struct nexthop {
struct rte_src {
struct rte_src *next; /* Hash chain */
struct proto *proto; /* Protocol the source is based on */
- u32 private_id; /* Private ID, assigned by the protocol */
+ u64 private_id; /* Private ID, assigned by the protocol */
u32 global_id; /* Globally unique ID of the source */
unsigned uc; /* Use count */
};
@@ -474,7 +477,9 @@ typedef struct rta {
#define RTS_BABEL 13 /* Babel route */
#define RTS_RPKI 14 /* Route Origin Authorization */
#define RTS_PERF 15 /* Perf checker */
-#define RTS_MAX 16
+#define RTS_L3VPN 16 /* MPLS L3VPN */
+#define RTS_AGGREGATED 17 /* Aggregated route */
+#define RTS_MAX 18
#define RTD_NONE 0 /* Undefined next hop */
#define RTD_UNICAST 1 /* Next hop is neighbor router */
@@ -524,7 +529,10 @@ typedef struct eattr {
const char *ea_custom_name(uint ea);
-#define EA_GEN_IGP_METRIC EA_CODE(PROTOCOL_NONE, 0)
+#define EA_GEN_IGP_METRIC EA_CODE(PROTOCOL_NONE, 0)
+#define EA_MPLS_LABEL EA_CODE(PROTOCOL_NONE, 1)
+#define EA_MPLS_POLICY EA_CODE(PROTOCOL_NONE, 2)
+#define EA_MPLS_CLASS EA_CODE(PROTOCOL_NONE, 3)
#define EA_CODE_MASK 0xffff
#define EA_CUSTOM_BIT 0x8000
@@ -684,7 +692,7 @@ static inline int nexthop_same(struct nexthop *x, struct nexthop *y)
{ return (x == y) || nexthop__same(x, y); }
struct nexthop *nexthop_merge(struct nexthop *x, struct nexthop *y, int rx, int ry, int max, linpool *lp);
struct nexthop *nexthop_sort(struct nexthop *x);
-static inline void nexthop_link(struct rta *a, struct nexthop *from)
+static inline void nexthop_link(struct rta *a, const struct nexthop *from)
{ memcpy(&a->nh, from, nexthop_size(from)); }
void nexthop_insert(struct nexthop **n, struct nexthop *y);
int nexthop_is_sorted(struct nexthop *x);
@@ -752,6 +760,8 @@ int rt_flowspec_check(rtable *tab_ip, rtable *tab_flow, const net_addr *n, rta *
#define DEF_PREF_RIP 120 /* RIP */
#define DEF_PREF_BGP 100 /* BGP */
#define DEF_PREF_RPKI 100 /* RPKI */
+#define DEF_PREF_L3VPN_IMPORT 80 /* L3VPN import -> lower than BGP */
+#define DEF_PREF_L3VPN_EXPORT 120 /* L3VPN export -> higher than BGP */
#define DEF_PREF_INHERITED 10 /* Routes inherited from other routing daemons */
/*
diff --git a/nest/rt-attr.c b/nest/rt-attr.c
index d793c72e..7beb119b 100644
--- a/nest/rt-attr.c
+++ b/nest/rt-attr.c
@@ -75,6 +75,9 @@ const char * const rta_src_names[RTS_MAX] = {
[RTS_PIPE] = "pipe",
[RTS_BABEL] = "Babel",
[RTS_RPKI] = "RPKI",
+ [RTS_PERF] = "Perf",
+ [RTS_L3VPN] = "L3VPN",
+ [RTS_AGGREGATED] = "aggregated",
};
const char * rta_dest_names[RTD_MAX] = {
@@ -99,7 +102,7 @@ static struct idm src_ids;
#define RSH_KEY(n) n->proto, n->private_id
#define RSH_NEXT(n) n->next
#define RSH_EQ(p1,n1,p2,n2) p1 == p2 && n1 == n2
-#define RSH_FN(p,n) p->hash_key ^ u32_hash(n)
+#define RSH_FN(p,n) p->hash_key ^ u64_hash(n)
#define RSH_REHASH rte_src_rehash
#define RSH_PARAMS /2, *2, 1, 1, 8, 20
@@ -801,13 +804,27 @@ ea_free(ea_list *o)
static int
get_generic_attr(const eattr *a, byte **buf, int buflen UNUSED)
{
- if (a->id == EA_GEN_IGP_METRIC)
- {
- *buf += bsprintf(*buf, "igp_metric");
- return GA_NAME;
- }
+ switch (a->id)
+ {
+ case EA_GEN_IGP_METRIC:
+ *buf += bsprintf(*buf, "igp_metric");
+ return GA_NAME;
+
+ case EA_MPLS_LABEL:
+ *buf += bsprintf(*buf, "mpls_label");
+ return GA_NAME;
- return GA_UNKNOWN;
+ case EA_MPLS_POLICY:
+ *buf += bsprintf(*buf, "mpls_policy");
+ return GA_NAME;
+
+ case EA_MPLS_CLASS:
+ *buf += bsprintf(*buf, "mpls_class");
+ return GA_NAME;
+
+ default:
+ return GA_UNKNOWN;
+ }
}
void
@@ -1272,7 +1289,8 @@ rta_dump(rta *a)
static char *rts[] = { "", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
"RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
"RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
- "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL" };
+ "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL",
+ "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", };
static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
debug("pref=%d uc=%d %s %s%s h=%04x",
diff --git a/nest/rt-fib_test.c b/nest/rt-fib_test.c
new file mode 100644
index 00000000..2dd7ce8a
--- /dev/null
+++ b/nest/rt-fib_test.c
@@ -0,0 +1,246 @@
+/*
+ * BIRD -- Forwarding Information Base -- Tests
+ *
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "test/birdtest.h"
+#include "test/bt-utils.h"
+
+#include "nest/route.h"
+
+
+#define TESTS_NUM 10
+#define PREFIXES_NUM 400000
+#define PREFIX_TESTS_NUM 200000
+#define PREFIX_BENCH_MAX 1000000
+#define PREFIX_BENCH_NUM 10000000
+
+struct test_node
+{
+ int pos;
+ struct fib_node n;
+};
+
+static inline int net_match(struct test_node *tn, net_addr *query, net_addr *data)
+{ return (tn->pos < PREFIXES_NUM) && net_equal(query, &data[tn->pos]); }
+
+static int
+t_match_random_net(void)
+{
+ bt_bird_init();
+ bt_config_parse(BT_CONFIG_SIMPLE);
+
+ for (int round = 0; round < TESTS_NUM; round++)
+ {
+ int type = !(round & 1) ? NET_IP4 : NET_IP6;
+
+ pool *p = rp_new(&root_pool, "FIB pool");
+ net_addr *nets = bt_random_nets(type, PREFIXES_NUM);
+
+ /* Make FIB structure */
+ struct fib f;
+ fib_init(&f, &root_pool, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 4, NULL);
+
+ for (int i = 0; i < PREFIXES_NUM; i++)
+ {
+ struct test_node *tn = fib_get(&f, &nets[i]);
+ bt_assert(!tn->pos || net_match(tn, &nets[i], nets));
+ tn->pos = i;
+ }
+
+ /* Test (mostly) negative matches */
+ for (int i = 0; i < PREFIX_TESTS_NUM; i++)
+ {
+ net_addr net;
+ bt_random_net(&net, type);
+
+ struct test_node *tn = fib_find(&f, &net);
+ bt_assert(!tn || net_match(tn, &net, nets));
+ }
+
+ /* Test positive matches */
+ for (int i = 0; i < PREFIX_TESTS_NUM; i++)
+ {
+ int j = bt_random_n(PREFIXES_NUM);
+
+ struct test_node *tn = fib_find(&f, &nets[j]);
+ bt_assert(tn && net_match(tn, &nets[j], nets));
+ }
+
+ rfree(p);
+ tmp_flush();
+ }
+
+ bt_bird_cleanup();
+ return 1;
+}
+
+static int
+t_fib_walk(void)
+{
+ bt_bird_init();
+ bt_config_parse(BT_CONFIG_SIMPLE);
+
+ for (int round = 0; round < TESTS_NUM; round++)
+ {
+ int type = !(round & 1) ? NET_IP4 : NET_IP6;
+
+ pool *p = rp_new(&root_pool, "FIB pool");
+ net_addr *nets = bt_random_nets(type, PREFIXES_NUM);
+ byte *marks = tmp_allocz(PREFIXES_NUM);
+
+ /* Make FIB structure */
+ struct fib f;
+ fib_init(&f, p, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 4, NULL);
+
+ for (int i = 1; i < PREFIXES_NUM; i++)
+ {
+ struct test_node *tn = fib_get(&f, &nets[i]);
+ bt_assert(!tn->pos || net_match(tn, &nets[i], nets));
+ if (tn->pos)
+ {
+ /* Mark dupicate nets */
+ bt_assert(!marks[tn->pos]);
+ marks[tn->pos] = 1;
+ }
+ tn->pos = i;
+ }
+
+ /* Walk FIB and mark nets */
+ FIB_WALK(&f, struct test_node, tn)
+ {
+ bt_assert(!marks[tn->pos]);
+ marks[tn->pos] = 1;
+ }
+ FIB_WALK_END;
+
+ /* Check in all nets are marked */
+ for (int i = 1; i < PREFIXES_NUM; i++)
+ bt_assert(marks[i]);
+
+ rfree(p);
+ tmp_flush();
+ }
+
+ bt_bird_cleanup();
+ return 1;
+}
+
+static int
+benchmark_fib_dataset(const char *filename, int type)
+{
+ net_addr *nets, *test_r, *test_s;
+ uint n = PREFIX_BENCH_MAX;
+ int tn = PREFIX_BENCH_NUM;
+ int match;
+
+ bt_reset_suite_case_timer();
+ bt_log_suite_case_result(1, "Reading %s", filename, n);
+ nets = bt_read_net_file(filename, type, &n);
+ bt_log_suite_case_result(1, "Read net data, %u nets", n);
+ bt_reset_suite_case_timer();
+
+ pool *p = rp_new(&root_pool, "FIB pool");
+
+ /* Make FIB structure */
+ struct fib f;
+ fib_init(&f, p, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 0, NULL);
+
+ for (int i = 0; i < (int) n; i++)
+ {
+ struct test_node *tn = fib_get(&f, &nets[i]);
+ tn->pos = i;
+ }
+
+ bt_log_suite_case_result(1, "Fill FIB structure, %u nets, order %u", n, f.hash_order);
+ bt_reset_suite_case_timer();
+
+ /* Compute FIB size */
+ size_t fib_size = rmemsize(p).effective * 1000 / (1024*1024);
+ bt_log_suite_case_result(1, "FIB size: %u.%03u MB", (uint) (fib_size / 1000), (uint) (fib_size % 1000));
+
+ /* Compute FIB histogram */
+ uint hist[16] = {};
+ uint sum = 0;
+ for (uint i = 0; i < f.hash_size; i++)
+ {
+ int len = 0;
+ for (struct fib_node *fn = f.hash_table[i]; fn; fn = fn->next)
+ len++;
+
+ sum += len;
+ len = MIN(len, 15);
+ hist[len]++;
+ }
+ bt_log_suite_case_result(1, "FIB histogram:");
+ for (uint i = 0; i < 16; i++)
+ if (hist[i])
+ bt_log_suite_case_result(1, "%02u: %8u", i, hist[i]);
+
+ uint avg = (sum * 1000) / (f.hash_size - hist[0]);
+ bt_log_suite_case_result(1, "FIB chain length: %u.%03u", (uint) (avg / 1000), (uint) (avg % 1000));
+ bt_reset_suite_case_timer();
+
+ /* Make test data */
+ test_r = bt_random_nets(type, tn);
+ test_s = bt_random_net_subset(nets, n, tn);
+
+ bt_log_suite_case_result(1, "Make test data, 2x %u nets", tn);
+ bt_reset_suite_case_timer();
+
+ /* Test (mostly negative) random matches */
+ match = 0;
+ for (int i = 0; i < tn; i++)
+ if (fib_find(&f, &test_r[i]))
+ match++;
+
+ bt_log_suite_case_result(1, "Random match, %d / %d matches", match, tn);
+ bt_reset_suite_case_timer();
+
+ /* Test (positive) subset matches */
+ match = 0;
+ for (int i = 0; i < tn; i++)
+ if (fib_find(&f, &test_s[i]))
+ match++;
+
+ bt_log_suite_case_result(1, "Subset match, %d / %d matches", match, tn);
+ bt_log_suite_case_result(1, "");
+ bt_reset_suite_case_timer();
+
+ rfree(p);
+ tmp_flush();
+ return 1;
+}
+
+static int UNUSED
+t_bench_fib_datasets(void)
+{
+ bt_bird_init();
+ bt_config_parse(BT_CONFIG_SIMPLE);
+
+ /* Specific datasets, not included */
+ benchmark_fib_dataset("fib-data-bgp-v4-1", NET_IP4);
+ benchmark_fib_dataset("fib-data-bgp-v4-10", NET_IP4);
+ benchmark_fib_dataset("fib-data-bgp-v6-1", NET_IP6);
+ benchmark_fib_dataset("fib-data-bgp-v6-10", NET_IP6);
+
+ bt_bird_cleanup();
+
+ return 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+ bt_init(argc, argv);
+
+ bt_test_suite(t_match_random_net, "Testing random prefix matching");
+ bt_test_suite(t_fib_walk, "Testing FIB_WALK() on random FIB");
+
+ // bt_test_suite(t_bench_fib_datasets, "Benchmark FIB from datasets by random subset of nets");
+
+ return bt_exit_value();
+}
diff --git a/nest/rt-show.c b/nest/rt-show.c
index 183d023c..265d5c44 100644
--- a/nest/rt-show.c
+++ b/nest/rt-show.c
@@ -103,7 +103,7 @@ static void
rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
{
rte *e, *ee;
- byte ia[NET_MAX_TEXT_LENGTH+1];
+ byte ia[NET_MAX_TEXT_LENGTH+16+1];
struct channel *ec = d->tab->export_channel;
/* The Clang static analyzer complains that ec may be NULL.
@@ -112,6 +112,7 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
int first = 1;
int first_show = 1;
+ int last_label = 0;
int pass = 0;
for (e = n->routes; e; e = e->next)
@@ -187,13 +188,21 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
if (d->stats < 2)
{
- if (first_show)
- net_format(n->n.addr, ia, sizeof(ia));
+ int label = (int) ea_get_int(e->attrs->eattrs, EA_MPLS_LABEL, (uint) -1);
+
+ if (first_show || (last_label != label))
+ {
+ if (label < 0)
+ net_format(n->n.addr, ia, sizeof(ia));
+ else
+ bsnprintf(ia, sizeof(ia), "%N mpls %d", n->n.addr, label);
+ }
else
ia[0] = 0;
rt_show_rte(c, ia, e, d, (e->net->routes == ee));
first_show = 0;
+ last_label = label;
}
d->show_counter++;
diff --git a/nest/rt-table.c b/nest/rt-table.c
index e4b27814..e497524f 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -98,6 +98,7 @@
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
+#include "nest/mpls.h"
#include "lib/resource.h"
#include "lib/event.h"
#include "lib/timer.h"
@@ -117,7 +118,7 @@
pool *rt_table_pool;
static slab *rte_slab;
-static linpool *rte_update_pool;
+linpool *rte_update_pool;
list routing_tables;
@@ -675,7 +676,7 @@ rte_mergable(rte *pri, rte *sec)
static void
rte_trace(struct channel *c, rte *e, int dir, char *msg)
{
- log(L_TRACE "%s.%s %c %s %N %uL %uG %s",
+ log(L_TRACE "%s.%s %c %s %N %luL %uG %s",
c->proto->name, c->name ?: "?", dir, msg, e->net->n.addr, e->src->private_id, e->src->global_id,
rta_dest_name(e->attrs->dest));
}
@@ -975,7 +976,6 @@ rt_export_merged(struct channel *c, net *net, rte **rt_free, linpool *pool, int
return best;
}
-
static void
rt_notify_merged(struct channel *c, net *net, rte *new_changed, rte *old_changed,
rte *new_best, rte *old_best, int refeed)
@@ -1206,7 +1206,7 @@ rte_free_quick(rte *e)
sl_free(e);
}
-static int
+int
rte_same(rte *x, rte *y)
{
/* rte.flags / rte.pflags are not checked, as they are internal to rtable */
@@ -1560,9 +1560,10 @@ rte_update_unlock(void)
void
rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
{
- // struct proto *p = c->proto;
+ struct proto *p = c->proto;
struct proto_stats *stats = &c->stats;
const struct filter *filter = c->in_filter;
+ struct mpls_fec *fec = NULL;
net *nn;
ASSERT(c->channel_state == CS_UP);
@@ -1611,6 +1612,17 @@ rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
new->flags |= REF_FILTERED;
}
}
+
+ if (p->mpls_map)
+ {
+ if (mpls_handle_rte(p->mpls_map, n, new, rte_update_pool, &fec) < 0)
+ {
+ rte_trace_in(D_FILTERS, c, new, "invalid");
+ stats->imp_updates_invalid++;
+ goto drop;
+ }
+ }
+
if (!rta_is_cached(new->attrs)) /* Need to copy attributes */
new->attrs = rta_lookup(new->attrs);
new->flags |= REF_COW;
@@ -1635,6 +1647,9 @@ rte_update2(struct channel *c, const net_addr *n, rte *new, struct rte_src *src)
/* And recalculate the best route */
rte_recalculate(c, nn, new, src);
+ if (p->mpls_map)
+ mpls_handle_rte_cleanup(p->mpls_map, &fec);
+
rte_update_unlock();
return;
@@ -2148,11 +2163,11 @@ rt_setup(pool *pp, struct rtable_config *cf)
init_list(&t->flowspec_links);
init_list(&t->subscribers);
+ hmap_init(&t->id_map, p, 1024);
+ hmap_set(&t->id_map, 0);
+
if (!(t->internal = cf->internal))
{
- hmap_init(&t->id_map, p, 1024);
- hmap_set(&t->id_map, 0);
-
t->rt_event = ev_new_init(p, rt_event, t);
t->prune_timer = tm_new_init(p, rt_prune_timer, t, 0, 0);
t->last_rt_change = t->gc_time = current_time();
@@ -2409,8 +2424,8 @@ rt_preconfig(struct config *c)
{
init_list(&c->tables);
- rt_new_table(cf_get_symbol("master4"), NET_IP4);
- rt_new_table(cf_get_symbol("master6"), NET_IP6);
+ rt_new_table(cf_get_symbol(c, "master4"), NET_IP4);
+ rt_new_table(cf_get_symbol(c, "master6"), NET_IP6);
}
void
@@ -2838,7 +2853,7 @@ rt_new_table(struct symbol *s, uint addr_type)
struct rtable_config *c = cfg_allocz(sizeof(struct rtable_config));
- cf_define_symbol(s, SYM_TABLE, table, c);
+ cf_define_symbol(new_config, s, SYM_TABLE, table, c);
c->name = s->name;
c->addr_type = addr_type;
c->gc_threshold = 1000;
@@ -3095,6 +3110,7 @@ rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *sr
if (old->flags & (REF_STALE | REF_DISCARD | REF_MODIFY))
{
old->flags &= ~(REF_STALE | REF_DISCARD | REF_MODIFY);
+
return 1;
}
@@ -3107,25 +3123,15 @@ rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *sr
/* Remove the old rte */
*pos = old->next;
- rte_free_quick(old);
tab->rt_count--;
-
break;
}
- if (!new)
- {
- if (!old)
- goto drop_withdraw;
-
- if (!net->routes)
- fib_delete(&tab->fib, net);
-
- return 1;
- }
+ if (!old && !new)
+ goto drop_withdraw;
struct channel_limit *l = &c->rx_limit;
- if (l->action && !old)
+ if (l->action && !old && new)
{
if (tab->rt_count >= l->limit)
channel_notify_limit(c, l, PLD_RX, tab->rt_count);
@@ -3140,15 +3146,40 @@ rte_update_in(struct channel *c, const net_addr *n, rte *new, struct rte_src *sr
}
}
- /* Insert the new rte */
- rte *e = rte_do_cow(new);
- e->flags |= REF_COW;
- e->net = net;
- e->sender = c;
- e->lastmod = current_time();
- e->next = *pos;
- *pos = e;
- tab->rt_count++;
+ if (new)
+ {
+ /* Insert the new rte */
+ rte *e = rte_do_cow(new);
+ e->flags |= REF_COW;
+ e->net = net;
+ e->sender = c;
+ e->lastmod = current_time();
+ e->next = *pos;
+ *pos = new = e;
+ tab->rt_count++;
+
+ if (!old)
+ {
+ new->id = hmap_first_zero(&tab->id_map);
+ hmap_set(&tab->id_map, new->id);
+ }
+ else
+ new->id = old->id;
+ }
+
+ rte_announce(tab, RA_ANY, net, new, old, NULL, NULL);
+
+ if (old)
+ {
+ if (!new)
+ hmap_clear(&tab->id_map, old->id);
+
+ rte_free_quick(old);
+ }
+
+ if (!net->routes)
+ fib_delete(&tab->fib, net);
+
return 1;
drop_update:
diff --git a/proto/Doc b/proto/Doc
index ef573d2a..9de9eeec 100644
--- a/proto/Doc
+++ b/proto/Doc
@@ -2,6 +2,7 @@ H Protocols
C babel
C bfd
C bgp
+C bmp
C ospf
C pipe
C radv
diff --git a/proto/aggregator/Doc b/proto/aggregator/Doc
new file mode 100644
index 00000000..6111f2f1
--- /dev/null
+++ b/proto/aggregator/Doc
@@ -0,0 +1 @@
+S aggregator.c
diff --git a/proto/aggregator/Makefile b/proto/aggregator/Makefile
new file mode 100644
index 00000000..d1dae8dd
--- /dev/null
+++ b/proto/aggregator/Makefile
@@ -0,0 +1,6 @@
+src := aggregator.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/aggregator/aggregator.c b/proto/aggregator/aggregator.c
new file mode 100644
index 00000000..e5c2a176
--- /dev/null
+++ b/proto/aggregator/aggregator.c
@@ -0,0 +1,804 @@
+/*
+ * BIRD Internet Routing Daemon -- Route aggregation
+ *
+ * (c) 2023--2023 Igor Putovny <igor.putovny@nic.cz>
+ * (c) 2023 CZ.NIC, z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Route aggregation
+ *
+ * This is an implementation of route aggregation functionality.
+ * It enables user to specify a set of route attributes in the configuarion file
+ * and then, for a given destination (net), aggregate routes with the same
+ * values of these attributes into a single multi-path route.
+ *
+ * Structure &channel contains pointer to aggregation list which is represented
+ * by &aggr_list_linearized. In rt_notify_aggregated(), attributes from this
+ * list are evaluated for every route of a given net and results are stored
+ * in &rte_val_list which contains pointer to this route and array of &f_val.
+ * Array of pointers to &rte_val_list entries is sorted using
+ * sort_rte_val_list(). For comparison of &f_val structures, val_compare()
+ * is used. Comparator function is written so that sorting is stable. If all
+ * attributes have the same values, routes are compared by their global IDs.
+ *
+ * After sorting, &rte_val_list entries containing equivalent routes will be
+ * adjacent to each other. Function process_rte_list() iterates through these
+ * entries to identify sequences of equivalent routes. New route will be
+ * created for each such sequence, even if only from a single route.
+ * Only attributes from the aggreagation list will be set for the new route.
+ * New &rta is created and prepare_rta() is used to copy static and dynamic
+ * attributes to new &rta from &rta of the original route. New route is created
+ * by create_merged_rte() from new &rta and exported to the routing table.
+ */
+
+#undef LOCAL_DEBUG
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "filter/filter.h"
+#include "aggregator.h"
+
+#include <stdlib.h>
+/*
+#include "nest/route.h"
+#include "nest/iface.h"
+#include "lib/resource.h"
+#include "lib/event.h"
+#include "lib/timer.h"
+#include "lib/string.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "filter/data.h"
+#include "lib/hash.h"
+#include "lib/string.h"
+#include "lib/alloca.h"
+#include "lib/flowspec.h"
+*/
+
+extern linpool *rte_update_pool;
+
+/*
+ * Set static attribute in @rta from static attribute in @old according to @sa.
+ */
+static void
+rta_set_static_attr(struct rta *rta, const struct rta *old, struct f_static_attr sa)
+{
+ switch (sa.sa_code)
+ {
+ case SA_NET:
+ break;
+
+ case SA_FROM:
+ rta->from = old->from;
+ break;
+
+ case SA_GW:
+ rta->dest = RTD_UNICAST;
+ rta->nh.gw = old->nh.gw;
+ rta->nh.iface = old->nh.iface;
+ rta->nh.next = NULL;
+ rta->hostentry = NULL;
+ rta->nh.labels = 0;
+ break;
+
+ case SA_SCOPE:
+ rta->scope = old->scope;
+ break;
+
+ case SA_DEST:
+ rta->dest = old->dest;
+ rta->nh.gw = IPA_NONE;
+ rta->nh.iface = NULL;
+ rta->nh.next = NULL;
+ rta->hostentry = NULL;
+ rta->nh.labels = 0;
+ break;
+
+ case SA_IFNAME:
+ rta->dest = RTD_UNICAST;
+ rta->nh.gw = IPA_NONE;
+ rta->nh.iface = old->nh.iface;
+ rta->nh.next = NULL;
+ rta->hostentry = NULL;
+ rta->nh.labels = 0;
+ break;
+
+ case SA_GW_MPLS:
+ rta->nh.labels = old->nh.labels;
+ memcpy(&rta->nh.label, &old->nh.label, sizeof(u32) * old->nh.labels);
+ break;
+
+ case SA_WEIGHT:
+ rta->nh.weight = old->nh.weight;
+ break;
+
+ case SA_PREF:
+ rta->pref = old->pref;
+ break;
+
+ default:
+ bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
+ }
+}
+
+/*
+ * Compare list of &f_val entries.
+ * @count: number of &f_val entries
+ */
+static int
+same_val_list(const struct f_val *v1, const struct f_val *v2, uint len)
+{
+ for (uint i = 0; i < len; i++)
+ if (!val_same(&v1[i], &v2[i]))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Create and export new merged route.
+ * @old: first route in a sequence of equivalent routes that are to be merged
+ * @rte_val: first element in a sequence of equivalent rte_val_list entries
+ * @length: number of equivalent routes that are to be merged (at least 1)
+ * @ail: aggregation list
+ */
+static void
+aggregator_bucket_update(struct aggregator_proto *p, struct aggregator_bucket *bucket, struct network *net)
+{
+ /* Empty bucket */
+ if (!bucket->rte)
+ {
+ rte_update2(p->dst, net->n.addr, NULL, bucket->last_src);
+ bucket->last_src = NULL;
+ return;
+ }
+
+ /* Allocate RTA and EA list */
+ struct rta *rta = allocz(rta_size(bucket->rte->attrs));
+ rta->dest = RTD_UNREACHABLE;
+ rta->source = RTS_AGGREGATED;
+ rta->scope = SCOPE_UNIVERSE;
+
+ struct ea_list *eal = allocz(sizeof(struct ea_list) + sizeof(struct eattr) * p->aggr_on_da_count);
+ eal->next = NULL;
+ eal->count = 0;
+ rta->eattrs = eal;
+
+ /* Seed the attributes from aggregator rule */
+ for (uint i = 0; i < p->aggr_on_count; i++)
+ {
+ if (p->aggr_on[i].type == AGGR_ITEM_DYNAMIC_ATTR)
+ {
+ u32 ea_code = p->aggr_on[i].da.ea_code;
+ const struct eattr *e = ea_find(bucket->rte->attrs->eattrs, ea_code);
+
+ if (e)
+ eal->attrs[eal->count++] = *e;
+ }
+ else if (p->aggr_on[i].type == AGGR_ITEM_STATIC_ATTR)
+ rta_set_static_attr(rta, bucket->rte->attrs, p->aggr_on[i].sa);
+ }
+
+ struct rte *new = rte_get_temp(rta, bucket->rte->src);
+ new->net = net;
+
+ /*
+ log("=============== CREATE MERGED ROUTE ===============");
+ log("New route created: id = %d, protocol: %s", new->src->global_id, new->src->proto->name);
+ log("===================================================");
+ */
+
+ /* merge filter needs one argument called "routes" */
+ struct f_val val = {
+ .type = T_ROUTES_BLOCK,
+ .val.rte = bucket->rte,
+ };
+
+ /* Actually run the filter */
+ enum filter_return fret = f_eval_rte(p->merge_by, &new, rte_update_pool, 1, &val, 0);
+
+ /* Src must be stored now, rte_update2() may return new */
+ struct rte_src *new_src = new ? new->src : NULL;
+
+ /* Finally import the route */
+ switch (fret)
+ {
+ /* Pass the route to the protocol */
+ case F_ACCEPT:
+ rte_update2(p->dst, net->n.addr, new, bucket->last_src ?: new->src);
+ break;
+
+ /* Something bad happened */
+ default:
+ ASSERT_DIE(fret == F_ERROR);
+ /* fall through */
+
+ /* We actually don't want this route */
+ case F_REJECT:
+ if (bucket->last_src)
+ rte_update2(p->dst, net->n.addr, NULL, bucket->last_src);
+ break;
+ }
+
+ /* Switch source lock for bucket->last_src */
+ if (bucket->last_src != new_src)
+ {
+ if (new_src)
+ rt_lock_source(new_src);
+ if (bucket->last_src)
+ rt_unlock_source(bucket->last_src);
+
+ bucket->last_src = new_src;
+ }
+}
+
+/*
+ * Reload all the buckets on reconfiguration if merge filter has changed.
+ * TODO: make this splitted
+ */
+static void
+aggregator_reload_buckets(void *data)
+{
+ struct aggregator_proto *p = data;
+
+ HASH_WALK(p->buckets, next_hash, b)
+ if (b->rte)
+ {
+ aggregator_bucket_update(p, b, b->rte->net);
+ lp_flush(rte_update_pool);
+ }
+ HASH_WALK_END;
+}
+
+
+/*
+ * Evaluate static attribute of @rt1 according to @sa
+ * and store result in @pos.
+ */
+static void
+eval_static_attr(const struct rte *rt1, struct f_static_attr sa, struct f_val *pos)
+{
+ const struct rta *rta = rt1->attrs;
+
+#define RESULT(_type, value, result) \
+ do { \
+ pos->type = _type; \
+ pos->val.value = result; \
+ } while (0)
+
+ switch (sa.sa_code)
+ {
+ case SA_NET: RESULT(sa.f_type, net, rt1->net->n.addr); break;
+ case SA_FROM: RESULT(sa.f_type, ip, rta->from); break;
+ case SA_GW: RESULT(sa.f_type, ip, rta->nh.gw); break;
+ case SA_PROTO: RESULT(sa.f_type, s, rt1->src->proto->name); break;
+ case SA_SOURCE: RESULT(sa.f_type, i, rta->source); break;
+ case SA_SCOPE: RESULT(sa.f_type, i, rta->scope); break;
+ case SA_DEST: RESULT(sa.f_type, i, rta->dest); break;
+ case SA_IFNAME: RESULT(sa.f_type, s, rta->nh.iface ? rta->nh.iface->name : ""); break;
+ case SA_IFINDEX: RESULT(sa.f_type, i, rta->nh.iface ? rta->nh.iface->index : 0); break;
+ case SA_WEIGHT: RESULT(sa.f_type, i, rta->nh.weight + 1); break;
+ case SA_PREF: RESULT(sa.f_type, i, rta->pref); break;
+ case SA_GW_MPLS: RESULT(sa.f_type, i, rta->nh.labels ? rta->nh.label[0] : MPLS_NULL); break;
+ default:
+ bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
+ }
+
+#undef RESULT
+}
+
+/*
+ * Evaluate dynamic attribute of @rt1 according to @da
+ * and store result in @pos.
+ */
+static void
+eval_dynamic_attr(const struct rte *rt1, struct f_dynamic_attr da, struct f_val *pos)
+{
+ const struct rta *rta = rt1->attrs;
+ const struct eattr *e = ea_find(rta->eattrs, da.ea_code);
+
+#define RESULT(_type, value, result) \
+ do { \
+ pos->type = _type; \
+ pos->val.value = result; \
+ } while (0)
+
+#define RESULT_VOID \
+ do { \
+ pos->type = T_VOID; \
+ } while (0)
+
+ if (!e)
+ {
+ /* A special case: undefined as_path looks like empty as_path */
+ if (da.type == EAF_TYPE_AS_PATH)
+ {
+ RESULT(T_PATH, ad, &null_adata);
+ return;
+ }
+
+ /* The same special case for int_set */
+ if (da.type == EAF_TYPE_INT_SET)
+ {
+ RESULT(T_CLIST, ad, &null_adata);
+ return;
+ }
+
+ /* The same special case for ec_set */
+ if (da.type == EAF_TYPE_EC_SET)
+ {
+ RESULT(T_ECLIST, ad, &null_adata);
+ return;
+ }
+
+ /* The same special case for lc_set */
+ if (da.type == EAF_TYPE_LC_SET)
+ {
+ RESULT(T_LCLIST, ad, &null_adata);
+ return;
+ }
+
+ /* Undefined value */
+ RESULT_VOID;
+ return;
+ }
+
+ switch (e->type & EAF_TYPE_MASK)
+ {
+ case EAF_TYPE_INT:
+ RESULT(da.f_type, i, e->u.data);
+ break;
+ case EAF_TYPE_ROUTER_ID:
+ RESULT(T_QUAD, i, e->u.data);
+ break;
+ case EAF_TYPE_OPAQUE:
+ RESULT(T_ENUM_EMPTY, i, 0);
+ break;
+ case EAF_TYPE_IP_ADDRESS:
+ RESULT(T_IP, ip, *((ip_addr *) e->u.ptr->data));
+ break;
+ case EAF_TYPE_AS_PATH:
+ RESULT(T_PATH, ad, e->u.ptr);
+ break;
+ case EAF_TYPE_BITFIELD:
+ RESULT(T_BOOL, i, !!(e->u.data & (1u << da.bit)));
+ break;
+ case EAF_TYPE_INT_SET:
+ RESULT(T_CLIST, ad, e->u.ptr);
+ break;
+ case EAF_TYPE_EC_SET:
+ RESULT(T_ECLIST, ad, e->u.ptr);
+ break;
+ case EAF_TYPE_LC_SET:
+ RESULT(T_LCLIST, ad, e->u.ptr);
+ break;
+ default:
+ bug("Unknown dynamic attribute type");
+ }
+
+#undef RESULT
+#undef RESULT_VOID
+}
+
+static inline u32 aggr_route_hash(const rte *e)
+{
+ struct {
+ net *net;
+ struct rte_src *src;
+ } obj = {
+ .net = e->net,
+ .src = e->src,
+ };
+
+ return mem_hash(&obj, sizeof obj);
+}
+
+#define AGGR_RTE_KEY(n) (&(n)->rte)
+#define AGGR_RTE_NEXT(n) ((n)->next_hash)
+#define AGGR_RTE_EQ(a,b) (((a)->src == (b)->src) && ((a)->net == (b)->net))
+#define AGGR_RTE_FN(_n) aggr_route_hash(_n)
+#define AGGR_RTE_ORDER 4 /* Initial */
+
+#define AGGR_RTE_REHASH aggr_rte_rehash
+#define AGGR_RTE_PARAMS /8, *2, 2, 2, 4, 24
+
+HASH_DEFINE_REHASH_FN(AGGR_RTE, struct aggregator_route);
+
+
+#define AGGR_BUCK_KEY(n) (n)
+#define AGGR_BUCK_NEXT(n) ((n)->next_hash)
+#define AGGR_BUCK_EQ(a,b) (((a)->hash == (b)->hash) && (same_val_list((a)->aggr_data, (b)->aggr_data, p->aggr_on_count)))
+#define AGGR_BUCK_FN(n) ((n)->hash)
+#define AGGR_BUCK_ORDER 4 /* Initial */
+
+#define AGGR_BUCK_REHASH aggr_buck_rehash
+#define AGGR_BUCK_PARAMS /8, *2, 2, 2, 4, 24
+
+HASH_DEFINE_REHASH_FN(AGGR_BUCK, struct aggregator_bucket);
+
+
+#define AGGR_DATA_MEMSIZE (sizeof(struct f_val) * p->aggr_on_count)
+
+static void
+aggregator_rt_notify(struct proto *P, struct channel *src_ch, net *net, rte *new, rte *old)
+{
+ struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+ ASSERT_DIE(src_ch == p->src);
+ struct aggregator_bucket *new_bucket = NULL, *old_bucket = NULL;
+ struct aggregator_route *old_route = NULL;
+
+ /* Find the objects for the old route */
+ if (old)
+ old_route = HASH_FIND(p->routes, AGGR_RTE, old);
+
+ if (old_route)
+ old_bucket = old_route->bucket;
+
+ /* Find the bucket for the new route */
+ if (new)
+ {
+ /* Routes are identical, do nothing */
+ if (old_route && rte_same(&old_route->rte, new))
+ return;
+
+ /* Evaluate route attributes. */
+ struct aggregator_bucket *tmp_bucket = sl_allocz(p->bucket_slab);
+
+ for (uint val_idx = 0; val_idx < p->aggr_on_count; val_idx++)
+ {
+ int type = p->aggr_on[val_idx].type;
+ struct f_val *pos = &tmp_bucket->aggr_data[val_idx];
+
+ switch (type)
+ {
+ case AGGR_ITEM_TERM: {
+ const struct f_line *line = p->aggr_on[val_idx].line;
+ struct rte *rt1 = new;
+ enum filter_return fret = f_eval_rte(line, &new, rte_update_pool, 0, NULL, pos);
+
+ if (rt1 != new)
+ {
+ rte_free(rt1);
+ log(L_WARN "Aggregator rule modifies the route, reverting");
+ }
+
+ if (fret > F_RETURN)
+ log(L_WARN "%s.%s: Wrong number of items left on stack after evaluation of aggregation list", rt1->src->proto->name, rt1->sender->name);
+
+ switch (pos->type) {
+ case T_VOID:
+ case T_INT:
+ case T_BOOL:
+ case T_PAIR:
+ case T_QUAD:
+ case T_ENUM:
+ case T_IP:
+ case T_EC:
+ case T_LC:
+ case T_RD:
+ /* Fits, OK */
+ break;
+
+ default:
+ log(L_WARN "%s.%s: Expression evaluated to type %s unsupported by aggregator. Store this value as a custom attribute instead", new->src->proto->name, new->sender->name, f_type_name(pos->type));
+ *pos = (struct f_val) { .type = T_INT, .val.i = 0 };
+ }
+
+ break;
+ }
+
+ case AGGR_ITEM_STATIC_ATTR: {
+ eval_static_attr(new, p->aggr_on[val_idx].sa, pos);
+ break;
+ }
+
+ case AGGR_ITEM_DYNAMIC_ATTR: {
+ eval_dynamic_attr(new, p->aggr_on[val_idx].da, pos);
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ /* Compute the hash */
+ u64 haux;
+ mem_hash_init(&haux);
+ for (uint i = 0; i < p->aggr_on_count; i++)
+ {
+ mem_hash_mix_num(&haux, tmp_bucket->aggr_data[i].type);
+
+#define MX(k) mem_hash_mix(&haux, &IT(k), sizeof IT(k));
+#define IT(k) tmp_bucket->aggr_data[i].val.k
+
+ switch (tmp_bucket->aggr_data[i].type)
+ {
+ case T_VOID:
+ break;
+ case T_INT:
+ case T_BOOL:
+ case T_PAIR:
+ case T_QUAD:
+ case T_ENUM:
+ MX(i);
+ break;
+ case T_EC:
+ case T_RD:
+ MX(ec);
+ break;
+ case T_LC:
+ MX(lc);
+ break;
+ case T_IP:
+ MX(ip);
+ break;
+ case T_NET:
+ mem_hash_mix_num(&haux, net_hash(IT(net)));
+ break;
+ case T_STRING:
+ mem_hash_mix_str(&haux, IT(s));
+ break;
+ case T_PATH_MASK:
+ mem_hash_mix(&haux, IT(path_mask), sizeof(*IT(path_mask)) + IT(path_mask)->len * sizeof (IT(path_mask)->item));
+ break;
+ case T_PATH:
+ case T_CLIST:
+ case T_ECLIST:
+ case T_LCLIST:
+ case T_BYTESTRING:
+ mem_hash_mix(&haux, IT(ad)->data, IT(ad)->length);
+ break;
+ case T_NONE:
+ case T_PATH_MASK_ITEM:
+ case T_ROUTE:
+ case T_ROUTES_BLOCK:
+ bug("Invalid type %s in hashing", f_type_name(tmp_bucket->aggr_data[i].type));
+ case T_SET:
+ MX(t);
+ break;
+ case T_PREFIX_SET:
+ MX(ti);
+ break;
+ }
+ }
+
+ tmp_bucket->hash = mem_hash_value(&haux);
+
+ /* Find the existing bucket */
+ if (new_bucket = HASH_FIND(p->buckets, AGGR_BUCK, tmp_bucket))
+ sl_free(tmp_bucket);
+ else
+ {
+ new_bucket = tmp_bucket;
+ HASH_INSERT2(p->buckets, AGGR_BUCK, p->p.pool, new_bucket);
+ }
+
+ /* Store the route attributes */
+ if (rta_is_cached(new->attrs))
+ rta_clone(new->attrs);
+ else
+ new->attrs = rta_lookup(new->attrs);
+
+ /* Insert the new route into the bucket */
+ struct aggregator_route *arte = sl_alloc(p->route_slab);
+ *arte = (struct aggregator_route) {
+ .bucket = new_bucket,
+ .rte = *new,
+ };
+ arte->rte.next = new_bucket->rte,
+ new_bucket->rte = &arte->rte;
+ new_bucket->count++;
+ HASH_INSERT2(p->routes, AGGR_RTE, p->p.pool, arte);
+ }
+
+ /* Remove the old route from its bucket */
+ if (old_bucket)
+ {
+ for (struct rte **k = &old_bucket->rte; *k; k = &(*k)->next)
+ if (*k == &old_route->rte)
+ {
+ *k = (*k)->next;
+ break;
+ }
+
+ old_bucket->count--;
+ HASH_REMOVE2(p->routes, AGGR_RTE, p->p.pool, old_route);
+ rta_free(old_route->rte.attrs);
+ sl_free(old_route);
+ }
+
+ /* Announce changes */
+ if (old_bucket)
+ aggregator_bucket_update(p, old_bucket, net);
+
+ if (new_bucket && (new_bucket != old_bucket))
+ aggregator_bucket_update(p, new_bucket, net);
+
+ /* Cleanup the old bucket if empty */
+ if (old_bucket && (!old_bucket->rte || !old_bucket->count))
+ {
+ ASSERT_DIE(!old_bucket->rte && !old_bucket->count);
+ HASH_REMOVE2(p->buckets, AGGR_BUCK, p->p.pool, old_bucket);
+ sl_free(old_bucket);
+ }
+}
+
+static int
+aggregator_preexport(struct channel *C, struct rte *new)
+{
+ struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, C->proto);
+ /* Reject our own routes */
+ if (new->sender == p->dst)
+ return -1;
+
+ /* Disallow aggregating already aggregated routes */
+ if (new->attrs->source == RTS_AGGREGATED)
+ {
+ log(L_ERR "Multiple aggregations of the same route not supported in BIRD 2.");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+aggregator_postconfig(struct proto_config *CF)
+{
+ struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
+
+ if (!cf->dst->table)
+ cf_error("Source table not specified");
+
+ if (!cf->src->table)
+ cf_error("Destination table not specified");
+
+ if (cf->dst->table->addr_type != cf->src->table->addr_type)
+ cf_error("Both tables must be of the same type");
+
+ cf->dst->in_filter = cf->src->in_filter;
+
+ cf->src->in_filter = FILTER_REJECT;
+ cf->dst->out_filter = FILTER_REJECT;
+
+ cf->dst->debug = cf->src->debug;
+}
+
+static struct proto *
+aggregator_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+ struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
+
+ proto_configure_channel(P, &p->src, cf->src);
+ proto_configure_channel(P, &p->dst, cf->dst);
+
+ p->aggr_on_count = cf->aggr_on_count;
+ p->aggr_on_da_count = cf->aggr_on_da_count;
+ p->aggr_on = cf->aggr_on;
+ p->merge_by = cf->merge_by;
+
+ P->rt_notify = aggregator_rt_notify;
+ P->preexport = aggregator_preexport;
+
+ return P;
+}
+
+static int
+aggregator_start(struct proto *P)
+{
+ struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+
+ p->bucket_slab = sl_new(P->pool, sizeof(struct aggregator_bucket) + AGGR_DATA_MEMSIZE);
+ HASH_INIT(p->buckets, P->pool, AGGR_BUCK_ORDER);
+
+ p->route_slab = sl_new(P->pool, sizeof(struct aggregator_route));
+ HASH_INIT(p->routes, P->pool, AGGR_RTE_ORDER);
+
+ p->reload_buckets = (event) {
+ .hook = aggregator_reload_buckets,
+ .data = p,
+ };
+
+ return PS_UP;
+}
+
+static int
+aggregator_shutdown(struct proto *P)
+{
+ struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+
+ HASH_WALK_DELSAFE(p->buckets, next_hash, b)
+ {
+ while (b->rte)
+ {
+ struct aggregator_route *arte = SKIP_BACK(struct aggregator_route, rte, b->rte);
+ b->rte = arte->rte.next;
+ b->count--;
+ HASH_REMOVE(p->routes, AGGR_RTE, arte);
+ rta_free(arte->rte.attrs);
+ sl_free(arte);
+ }
+
+ ASSERT_DIE(b->count == 0);
+ HASH_REMOVE(p->buckets, AGGR_BUCK, b);
+ sl_free(b);
+ }
+ HASH_WALK_END;
+
+ return PS_DOWN;
+}
+
+static int
+aggregator_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct aggregator_proto *p = SKIP_BACK(struct aggregator_proto, p, P);
+ struct aggregator_config *cf = SKIP_BACK(struct aggregator_config, c, CF);
+
+ TRACE(D_EVENTS, "Reconfiguring");
+
+ /* Compare numeric values (shortcut) */
+ if (cf->aggr_on_count != p->aggr_on_count)
+ return 0;
+
+ if (cf->aggr_on_da_count != p->aggr_on_da_count)
+ return 0;
+
+ /* Compare aggregator rule */
+ for (uint i = 0; i < p->aggr_on_count; i++)
+ switch (cf->aggr_on[i].type)
+ {
+ case AGGR_ITEM_TERM:
+ if (!f_same(cf->aggr_on[i].line, p->aggr_on[i].line))
+ return 0;
+ break;
+ case AGGR_ITEM_STATIC_ATTR:
+ if (memcmp(&cf->aggr_on[i].sa, &p->aggr_on[i].sa, sizeof(struct f_static_attr)) != 0)
+ return 0;
+ break;
+ case AGGR_ITEM_DYNAMIC_ATTR:
+ if (memcmp(&cf->aggr_on[i].da, &p->aggr_on[i].da, sizeof(struct f_dynamic_attr)) != 0)
+ return 0;
+ break;
+ default:
+ bug("Broken aggregator rule");
+ }
+
+ /* Compare merge filter */
+ if (!f_same(cf->merge_by, p->merge_by))
+ ev_schedule(&p->reload_buckets);
+
+ p->aggr_on = cf->aggr_on;
+ p->merge_by = cf->merge_by;
+
+ return 1;
+}
+
+struct protocol proto_aggregator = {
+ .name = "Aggregator",
+ .template = "aggregator%d",
+ .class = PROTOCOL_AGGREGATOR,
+ .preference = 1,
+ .channel_mask = NB_ANY,
+ .proto_size = sizeof(struct aggregator_proto),
+ .config_size = sizeof(struct aggregator_config),
+ .postconfig = aggregator_postconfig,
+ .init = aggregator_init,
+ .start = aggregator_start,
+ .shutdown = aggregator_shutdown,
+ .reconfigure = aggregator_reconfigure,
+};
+
+void
+aggregator_build(void)
+{
+ proto_build(&proto_aggregator);
+}
diff --git a/proto/aggregator/aggregator.h b/proto/aggregator/aggregator.h
new file mode 100644
index 00000000..19459b1d
--- /dev/null
+++ b/proto/aggregator/aggregator.h
@@ -0,0 +1,86 @@
+/*
+ * BIRD -- Aggregator Pseudoprotocol
+ *
+ * (c) 2023 Igor Putovny <igor.putovny@nic.cz>
+ * (c) 2023 Maria Matejka <mq@ucw.cz>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ *
+ * This file contains the data structures used by Babel.
+ */
+
+#ifndef _BIRD_AGGREGATOR_H_
+#define _BIRD_AGGREGATOR_H_
+
+#include "nest/bird.h"
+#include "nest/protocol.h"
+#include "lib/hash.h"
+
+struct aggregator_config {
+ struct proto_config c;
+ struct channel_config *src, *dst;
+ uint aggr_on_count;
+ uint aggr_on_da_count;
+ struct aggr_item *aggr_on;
+ const struct f_line *merge_by;
+};
+
+struct aggregator_route {
+ struct aggregator_route *next_hash;
+ struct aggregator_bucket *bucket;
+ struct rte rte;
+};
+
+struct aggregator_bucket {
+ struct aggregator_bucket *next_hash;
+ struct rte *rte; /* Pointer to struct aggregator_route.rte */
+ struct rte_src *last_src; /* Which src we announced the bucket last with */
+ u32 count;
+ u32 hash;
+ struct f_val aggr_data[0];
+};
+
+struct aggregator_proto {
+ struct proto p;
+ struct channel *src, *dst;
+
+ /* Buckets by aggregator rule */
+ HASH(struct aggregator_bucket) buckets;
+ slab *bucket_slab;
+
+ /* Routes by net and src */
+ HASH(struct aggregator_route) routes;
+ slab *route_slab;
+
+ /* Aggregator rule */
+ uint aggr_on_count;
+ uint aggr_on_da_count;
+ struct aggr_item *aggr_on;
+
+ /* Merge filter */
+ const struct f_line *merge_by;
+ event reload_buckets;
+};
+
+enum aggr_item_type {
+ AGGR_ITEM_TERM,
+ AGGR_ITEM_STATIC_ATTR,
+ AGGR_ITEM_DYNAMIC_ATTR,
+};
+
+struct aggr_item {
+ enum aggr_item_type type;
+ union {
+ struct f_static_attr sa;
+ struct f_dynamic_attr da;
+ const struct f_line *line;
+ };
+};
+
+struct aggr_item_node {
+ const struct aggr_item_node *next;
+ struct aggr_item i;
+};
+
+#endif
diff --git a/proto/aggregator/config.Y b/proto/aggregator/config.Y
new file mode 100644
index 00000000..44b7752f
--- /dev/null
+++ b/proto/aggregator/config.Y
@@ -0,0 +1,134 @@
+/*
+ * BIRD -- Aggregator configuration
+ *
+ * (c) 2023 Igor Putovny <igor.putovny@nic.cz>
+ * (c) 2023 Maria Matejka <mq@ucw.cz>
+ * (c) 2023 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/aggregator/aggregator.h"
+
+CF_DEFINES
+
+#define AGGREGATOR_CFG ((struct aggregator_config *) this_proto)
+#define AGGR_ITEM_ALLOC ((struct aggr_item_node *) cfg_allocz(sizeof(struct aggr_item_node)))
+
+
+CF_DECLS
+
+CF_KEYWORDS(AGGREGATOR, AGGREGATE, ON, MERGE, BY)
+
+%type <ai> aggr_item aggr_list
+
+CF_GRAMMAR
+
+proto: aggregator_proto ;
+
+aggregator_proto_start: proto_start AGGREGATOR
+{
+ this_proto = proto_config_new(&proto_aggregator, $1);
+ this_channel = AGGREGATOR_CFG->src = channel_config_new(NULL, "source", 0, this_proto);
+ AGGREGATOR_CFG->dst = channel_config_new(NULL, "destination", 0, this_proto);
+
+ AGGREGATOR_CFG->src->ra_mode = AGGREGATOR_CFG->dst->ra_mode = RA_ANY;
+};
+
+aggregator_proto_item:
+ proto_item
+ | channel_item_
+ | PEER TABLE rtable { AGGREGATOR_CFG->dst->table = $3; }
+ | AGGREGATE ON aggr_list {
+ if (AGGREGATOR_CFG->aggr_on)
+ cf_error("Only one aggregate on clause allowed");
+
+ _Bool net_present = 0;
+ int count = 0;
+
+ for (const struct aggr_item_node *item = $3; item; item = item->next) {
+// log(L_WARN "type %d sacode %d", item->i.type, item->i.sa.sa_code);
+ if (item->i.type == AGGR_ITEM_STATIC_ATTR && item->i.sa.sa_code == SA_NET)
+ net_present = 1;
+
+ count++;
+ }
+
+ if (!net_present)
+ cf_error("'NET' must be present");
+
+ AGGREGATOR_CFG->aggr_on = cfg_alloc(sizeof(struct aggr_item) * count);
+
+ int pos = 0;
+ for (const struct aggr_item_node *item = $3; item; item = item->next) {
+ if (item->i.type == AGGR_ITEM_DYNAMIC_ATTR)
+ AGGREGATOR_CFG->aggr_on_da_count++;
+
+ AGGREGATOR_CFG->aggr_on[pos++] = item->i;
+ }
+
+ AGGREGATOR_CFG->aggr_on_count = pos;
+ }
+ | MERGE BY {
+ cf_push_block_scope(new_config);
+ cf_create_symbol(new_config, "routes", SYM_VARIABLE | T_ROUTES_BLOCK, offset, f_new_var(sym_->scope));
+ } function_body {
+ cf_pop_block_scope(new_config);
+ $4->args++;
+ AGGREGATOR_CFG->merge_by = $4;
+ }
+;
+
+aggregator_proto_opts: /* empty */ | aggregator_proto_opts aggregator_proto_item ';' ;
+aggregator_proto: aggregator_proto_start proto_name '{' aggregator_proto_opts '}' ;
+
+
+aggr_list:
+ aggr_item
+ | aggr_list ',' aggr_item {
+ if ($3 == NULL) {
+ $$ = $1;
+ } else {
+ $$ = $3;
+ $$->next = $1;
+ }
+ }
+ ;
+
+aggr_item:
+ '(' term ')' {
+ $$ = AGGR_ITEM_ALLOC;
+ $$->i.type = AGGR_ITEM_TERM;
+ $$->i.line = f_linearize($2, 1);
+ }
+ | CF_SYM_KNOWN {
+ switch ($1->class) {
+ case SYM_ATTRIBUTE:
+ $$ = AGGR_ITEM_ALLOC;
+ $$->i.type = AGGR_ITEM_DYNAMIC_ATTR;
+ $$->i.da = *$1->attribute;
+ break;
+ case SYM_CONSTANT_RANGE:
+ $$ = NULL;
+ break;
+ default:
+ cf_error("Can't aggregate on symbol type %s.", cf_symbol_class_name($1));
+ }
+ }
+ | dynamic_attr {
+ $$ = AGGR_ITEM_ALLOC;
+ $$->i.type = AGGR_ITEM_DYNAMIC_ATTR;
+ $$->i.da = $1;
+ }
+ | static_attr {
+ $$ = AGGR_ITEM_ALLOC;
+ $$->i.type = AGGR_ITEM_STATIC_ATTR;
+ $$->i.sa = $1;
+ }
+ ;
+
+CF_CODE
+
+CF_END
diff --git a/proto/aggregator/test.conf b/proto/aggregator/test.conf
new file mode 100644
index 00000000..e5e1e267
--- /dev/null
+++ b/proto/aggregator/test.conf
@@ -0,0 +1,116 @@
+log "bird.log" all;
+
+protocol device {}
+
+protocol static {
+ ipv6;
+ route 2001:db8:0::/48 unreachable { bgp_path.prepend(65432); bgp_path.prepend(4200000000); };
+ route 2001:db8:1::/48 unreachable;
+ route 2001:db8:2::/48 unreachable;
+ route 2001:db8:3::/48 unreachable;
+ route 2001:db8:4::/48 unreachable;
+ route 2001:db8:5::/48 unreachable;
+ route 2001:db8:6::/48 unreachable;
+ route 2001:db8:7::/48 unreachable;
+ route 2001:db8:8::/48 unreachable;
+ route 2001:db8:9::/48 unreachable;
+ route 2001:db8:a::/48 unreachable;
+ route 2001:db8:b::/48 unreachable;
+ route 2001:db8:c::/48 unreachable;
+ route 2001:db8:d::/48 unreachable;
+ route 2001:db8:e::/48 unreachable;
+ route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+ ipv6 {
+ import filter {
+ bgp_med = 1;
+ bgp_community = -empty-.add((65533,1)).add((65500,0xe));
+ accept;
+ };
+ };
+ route 2001:db8:1::/48 unreachable;
+ route 2001:db8:3::/48 unreachable;
+ route 2001:db8:5::/48 unreachable;
+ route 2001:db8:7::/48 unreachable;
+ route 2001:db8:9::/48 unreachable;
+ route 2001:db8:b::/48 unreachable;
+ route 2001:db8:d::/48 unreachable;
+ route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+ ipv6 {
+ import filter {
+ bgp_med = 2;
+ bgp_community = -empty-.add((65533,2)).add((65500,0xd));
+ accept;
+ };
+ };
+ route 2001:db8:2::/48 unreachable;
+ route 2001:db8:3::/48 unreachable;
+ route 2001:db8:6::/48 unreachable;
+ route 2001:db8:7::/48 unreachable;
+ route 2001:db8:a::/48 unreachable;
+ route 2001:db8:b::/48 unreachable;
+ route 2001:db8:e::/48 unreachable;
+ route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+ ipv6 {
+ import filter {
+ bgp_med = 4;
+ bgp_community = -empty-.add((65533,4)).add((65500,0xb));
+ accept;
+ };
+ };
+ route 2001:db8:4::/48 unreachable;
+ route 2001:db8:5::/48 unreachable;
+ route 2001:db8:6::/48 unreachable;
+ route 2001:db8:7::/48 unreachable;
+ route 2001:db8:c::/48 unreachable;
+ route 2001:db8:d::/48 unreachable;
+ route 2001:db8:e::/48 unreachable;
+ route 2001:db8:f::/48 unreachable;
+}
+
+protocol static {
+ ipv6 {
+ import filter {
+ bgp_med = 8;
+ bgp_community = -empty-.add((65533,8)).add((65500,0x7));
+ accept;
+ };
+ };
+ route 2001:db8:8::/48 unreachable;
+ route 2001:db8:9::/48 unreachable;
+ route 2001:db8:a::/48 unreachable;
+ route 2001:db8:b::/48 unreachable;
+ route 2001:db8:c::/48 unreachable;
+ route 2001:db8:d::/48 unreachable;
+ route 2001:db8:e::/48 unreachable;
+ route 2001:db8:f::/48 unreachable;
+}
+
+ipv6 table agr_result;
+
+protocol aggregator {
+ table master6;
+ peer table agr_result;
+ export all;
+ aggregate on net,(defined(bgp_med));
+ merge by {
+ print "Merging all these: ", routes;
+ bgp_med = 0;
+ for route r in routes do {
+ if ! defined(r.bgp_med) then { unset(bgp_med); accept; }
+
+ print r, " bgp_med: ", r.bgp_med;
+ bgp_med = bgp_med + r.bgp_med;
+ bgp_community = bgp_community.add(r.bgp_community);
+ }
+ accept;
+ };
+}
diff --git a/proto/babel/babel.c b/proto/babel/babel.c
index ff8b6b52..4187d258 100644
--- a/proto/babel/babel.c
+++ b/proto/babel/babel.c
@@ -14,9 +14,8 @@
/**
* DOC: The Babel protocol
*
- * Babel (RFC6126) is a loop-avoiding distance-vector routing protocol that is
- * robust and efficient both in ordinary wired networks and in wireless mesh
- * networks.
+ * The Babel is a loop-avoiding distance-vector routing protocol that is robust
+ * and efficient both in ordinary wired networks and in wireless mesh networks.
*
* The Babel protocol keeps state for each neighbour in a &babel_neighbor
* struct, tracking received Hello and I Heard You (IHU) messages. A
@@ -33,6 +32,12 @@
* an entry is updated by receiving updates from the network or when modified by
* internal timers. The function selects from feasible and reachable routes the
* one with the lowest metric to be announced to the core.
+ *
+ * Supported standards:
+ * RFC 8966 - The Babel Routing Protocol
+ * RFC 8967 - MAC Authentication for Babel
+ * RFC 9079 - Source Specific Routing for Babel
+ * RFC 9229 - IPv4 Routes with IPv6 Next Hop for Babel
*/
#include <stdlib.h>
@@ -49,6 +54,10 @@
static inline int ge_mod64k(uint a, uint b)
{ return (u16)(a - b) < 0x8000; }
+/* Strict inequality version of the above */
+static inline int gt_mod64k(uint a, uint b)
+{ return ge_mod64k(a, b) && a != b; }
+
static void babel_expire_requests(struct babel_proto *p, struct babel_entry *e);
static void babel_select_route(struct babel_proto *p, struct babel_entry *e, struct babel_route *mod);
static inline void babel_announce_retraction(struct babel_proto *p, struct babel_entry *e);
@@ -101,7 +110,8 @@ babel_find_source(struct babel_entry *e, u64 router_id)
}
static struct babel_source *
-babel_get_source(struct babel_proto *p, struct babel_entry *e, u64 router_id)
+babel_get_source(struct babel_proto *p, struct babel_entry *e, u64 router_id,
+ u16 initial_seqno)
{
struct babel_source *s = babel_find_source(e, router_id);
@@ -111,7 +121,7 @@ babel_get_source(struct babel_proto *p, struct babel_entry *e, u64 router_id)
s = sl_allocz(p->source_slab);
s->router_id = router_id;
s->expires = current_time() + BABEL_GARBAGE_INTERVAL;
- s->seqno = 0;
+ s->seqno = initial_seqno;
s->metric = BABEL_INFINITY;
add_tail(&e->sources, NODE s);
@@ -289,7 +299,7 @@ babel_expire_routes(struct babel_proto *p)
}
/*
- * Add seqno request to the table of pending requests (RFC 6216 3.2.6) and send
+ * Add seqno request to the table of pending requests (RFC 8966 3.2.6) and send
* it to network. Do nothing if it is already in the table.
*/
@@ -559,7 +569,7 @@ babel_is_feasible(struct babel_source *s, u16 seqno, u16 metric)
{
return !s ||
(metric == BABEL_INFINITY) ||
- (seqno > s->seqno) ||
+ gt_mod64k(seqno, s->seqno) ||
((seqno == s->seqno) && (metric < s->metric));
}
@@ -586,6 +596,7 @@ babel_update_cost(struct babel_neighbor *nbr)
switch (cf->type)
{
case BABEL_IFACE_TYPE_WIRED:
+ case BABEL_IFACE_TYPE_TUNNEL:
/* k-out-of-j selection - Appendix 2.1 in the RFC. */
/* Link is bad if less than cf->limit/16 of expected hellos were received */
@@ -614,6 +625,24 @@ babel_update_cost(struct babel_neighbor *nbr)
break;
}
+ if (cf->rtt_cost && nbr->srtt > cf->rtt_min)
+ {
+ uint rtt_cost = cf->rtt_cost;
+
+ if (nbr->srtt < cf->rtt_max)
+ {
+ uint rtt_interval = cf->rtt_max TO_US - cf->rtt_min TO_US;
+ uint rtt_diff = (nbr->srtt TO_US - cf->rtt_min TO_US);
+
+ rtt_cost = (rtt_cost * rtt_diff) / rtt_interval;
+ }
+
+ txcost = MIN(txcost + rtt_cost, BABEL_INFINITY);
+
+ TRACE(D_EVENTS, "Added RTT cost %u to nbr %I on %s with srtt %t ms",
+ rtt_cost, nbr->addr, nbr->ifa->iface->name, nbr->srtt * 1000);
+ }
+
done:
/* If RX cost changed, send IHU with next Hello */
if (rxcost != nbr->rxcost)
@@ -844,6 +873,12 @@ babel_build_ihu(union babel_msg *msg, struct babel_iface *ifa, struct babel_neig
msg->ihu.rxcost = n->rxcost;
msg->ihu.interval = ifa->cf->ihu_interval;
+ if (n->last_tstamp_rcvd && ifa->cf->rtt_send)
+ {
+ msg->ihu.tstamp = n->last_tstamp;
+ msg->ihu.tstamp_rcvd = n->last_tstamp_rcvd TO_US;
+ }
+
TRACE(D_PACKETS, "Sending IHU for %I with rxcost %d interval %t",
msg->ihu.addr, msg->ihu.rxcost, (btime) msg->ihu.interval);
}
@@ -883,6 +918,9 @@ babel_send_hello(struct babel_iface *ifa, uint interval)
msg.hello.seqno = ifa->hello_seqno++;
msg.hello.interval = interval ?: ifa->cf->hello_interval;
+ if (ifa->cf->rtt_send)
+ msg.hello.tstamp = 1; /* real timestamp will be set on TLV write */
+
TRACE(D_PACKETS, "Sending hello on %s with seqno %d interval %t",
ifa->ifname, msg.hello.seqno, (btime) msg.hello.interval);
@@ -998,8 +1036,18 @@ babel_send_update_(struct babel_iface *ifa, btime changed, struct fib *rtable)
msg.update.router_id = e->router_id;
net_copy(&msg.update.net, e->n.addr);
- msg.update.next_hop = ((e->n.addr->type == NET_IP4) ?
- ifa->next_hop_ip4 : ifa->next_hop_ip6);
+ if (e->n.addr->type == NET_IP4)
+ {
+ /* Always prefer IPv4 nexthop if set */
+ if (ipa_nonzero(ifa->next_hop_ip4))
+ msg.update.next_hop = ifa->next_hop_ip4;
+
+ /* Only send IPv6 nexthop if enabled */
+ else if (ifa->cf->ext_next_hop)
+ msg.update.next_hop = ifa->next_hop_ip6;
+ }
+ else
+ msg.update.next_hop = ifa->next_hop_ip6;
/* Do not send route if next hop is unknown, e.g. no configured IPv4 address */
if (ipa_zero(msg.update.next_hop))
@@ -1007,13 +1055,13 @@ babel_send_update_(struct babel_iface *ifa, btime changed, struct fib *rtable)
babel_enqueue(&msg, ifa);
- /* Update feasibility distance for redistributed routes */
+ /* RFC 8966 3.7.3 - update feasibility distance for redistributed routes */
if (e->router_id != p->router_id)
{
- struct babel_source *s = babel_get_source(p, e, e->router_id);
+ struct babel_source *s = babel_get_source(p, e, e->router_id, msg.update.seqno);
s->expires = current_time() + BABEL_GARBAGE_INTERVAL;
- if ((msg.update.seqno > s->seqno) ||
+ if (gt_mod64k(msg.update.seqno, s->seqno) ||
((msg.update.seqno == s->seqno) && (msg.update.metric < s->metric)))
{
s->seqno = msg.update.seqno;
@@ -1179,14 +1227,26 @@ babel_handle_hello(union babel_msg *m, struct babel_iface *ifa)
msg->seqno, (btime) msg->interval);
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
+ struct babel_iface_config *cf = n->ifa->cf;
int first_hello = !n->hello_cnt;
+ if (msg->tstamp)
+ {
+ n->last_tstamp = msg->tstamp;
+ n->last_tstamp_rcvd = msg->pkt_received;
+ }
babel_update_hello_history(n, msg->seqno, msg->interval);
babel_update_cost(n);
/* Speed up session establishment by sending IHU immediately */
if (first_hello)
- babel_send_ihu(ifa, n);
+ {
+ /* if using RTT, all IHUs must be paired with hellos */
+ if(cf->rtt_send)
+ babel_send_hello(ifa, 0);
+ else
+ babel_send_ihu(ifa, n);
+ }
}
void
@@ -1205,6 +1265,39 @@ babel_handle_ihu(union babel_msg *m, struct babel_iface *ifa)
struct babel_neighbor *n = babel_get_neighbor(ifa, msg->sender);
n->txcost = msg->rxcost;
n->ihu_expiry = current_time() + BABEL_IHU_EXPIRY_FACTOR(msg->interval);
+
+ if (msg->tstamp)
+ {
+ u32 rtt_sample = 0, pkt_received = msg->pkt_received TO_US;
+ int remote_time, full_time;
+
+ /* processing time reported by peer */
+ remote_time = (n->last_tstamp - msg->tstamp_rcvd);
+ /* time since we sent the last timestamp - RTT including remote time */
+ full_time = (pkt_received - msg->tstamp);
+
+ /* sanity checks */
+ if (remote_time < 0 || full_time < 0 ||
+ remote_time US_ > BABEL_RTT_MAX_VALUE || full_time US_ > BABEL_RTT_MAX_VALUE)
+ goto out;
+
+ if (remote_time < full_time)
+ rtt_sample = full_time - remote_time;
+
+ if (n->srtt)
+ {
+ uint decay = n->ifa->cf->rtt_decay;
+
+ n->srtt = (decay * rtt_sample + (256 - decay) * n->srtt) / 256;
+ }
+ else
+ n->srtt = rtt_sample;
+
+ TRACE(D_EVENTS, "RTT sample for neighbour %I on %s: %u us (srtt %t ms)",
+ n->addr, ifa->ifname, rtt_sample, n->srtt * 1000);
+ }
+
+out:
babel_update_cost(n);
}
@@ -1258,6 +1351,13 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa)
return;
}
+ /* Reject IPv4 via IPv6 routes if disabled */
+ if ((msg->net.type == NET_IP4) && ipa_is_ip6(msg->next_hop) && !ifa->cf->ext_next_hop)
+ {
+ DBG("Babel: Ignoring disabled IPv4 via IPv6 route.\n");
+ return;
+ }
+
/* Retraction */
if (msg->metric == BABEL_INFINITY)
{
@@ -1303,10 +1403,11 @@ babel_handle_update(union babel_msg *m, struct babel_iface *ifa)
best = e->selected;
/*
- * RFC section 3.8.2.2 - Dealing with unfeasible updates. Generate a one-off
- * (not retransmitted) unicast seqno request to the originator of this update
+ * RFC 8966 3.8.2.2 - dealing with unfeasible updates. Generate a one-off
+ * (not retransmitted) unicast seqno request to the originator of this update.
+ * Note: !feasible -> s exists, check for 's' is just for clarity / safety.
*/
- if (!feasible && (metric != BABEL_INFINITY) &&
+ if (!feasible && s && (metric != BABEL_INFINITY) &&
(!best || (r == best) || (metric < best->metric)))
babel_generate_seqno_request(p, e, s->router_id, s->seqno + 1, nbr);
@@ -1347,7 +1448,7 @@ babel_handle_route_request(union babel_msg *m, struct babel_iface *ifa)
struct babel_proto *p = ifa->proto;
struct babel_msg_route_request *msg = &m->route_request;
- /* RFC 6126 3.8.1.1 */
+ /* RFC 8966 3.8.1.1 */
/* Wildcard request - full update on the interface */
if (msg->full)
@@ -1405,7 +1506,7 @@ babel_handle_seqno_request(union babel_msg *m, struct babel_iface *ifa)
struct babel_proto *p = ifa->proto;
struct babel_msg_seqno_request *msg = &m->seqno_request;
- /* RFC 6126 3.8.1.2 */
+ /* RFC 8966 3.8.1.2 */
TRACE(D_PACKETS, "Handling seqno request for %N router-id %lR seqno %d hop count %d",
&msg->net, msg->router_id, msg->seqno, msg->hop_count);
@@ -1543,7 +1644,8 @@ babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg)
n->auth_index_len = msg->index_len;
memcpy(n->auth_index, msg->index, msg->index_len);
- n->auth_pc = msg->pc;
+ n->auth_pc_unicast = msg->pc;
+ n->auth_pc_multicast = msg->pc;
n->auth_passed = 1;
return 1;
@@ -1562,16 +1664,30 @@ babel_auth_check_pc(struct babel_iface *ifa, struct babel_msg_auth *msg)
return 0;
}
- /* (6) Index matches; only accept if PC is greater than last */
- if (n->auth_pc >= msg->pc)
+ /*
+ * (6) Index matches; only accept if PC is greater than last. We keep separate
+ * counters for unicast and multicast because multicast packets can be delayed
+ * significantly on wireless networks (enough to be received out of order).
+ * Separate counters are safe because the packet destination address is part
+ * of the MAC pseudo-header (so unicast packets can't be replayed as multicast
+ * and vice versa).
+ */
+ u32 auth_pc = msg->unicast ? n->auth_pc_unicast : n->auth_pc_multicast;
+ if (auth_pc >= msg->pc)
{
LOG_PKT_AUTH("Authentication failed for %I on %s - "
- "lower packet counter (rcv %u, old %u)",
- msg->sender, ifa->ifname, msg->pc, n->auth_pc);
+ "lower %s packet counter (rcv %u, old %u)",
+ msg->sender, ifa->ifname,
+ msg->unicast ? "unicast" : "multicast",
+ msg->pc, auth_pc);
return 0;
}
- n->auth_pc = msg->pc;
+ if (msg->unicast)
+ n->auth_pc_unicast = msg->pc;
+ else
+ n->auth_pc_multicast = msg->pc;
+
n->auth_passed = 1;
return 1;
@@ -1723,7 +1839,7 @@ babel_iface_update_addr4(struct babel_iface *ifa)
ip_addr addr4 = ifa->iface->addr4 ? ifa->iface->addr4->ip : IPA_NONE;
ifa->next_hop_ip4 = ipa_nonzero(ifa->cf->next_hop_ip4) ? ifa->cf->next_hop_ip4 : addr4;
- if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
+ if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !ifa->cf->ext_next_hop)
log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
if (ifa->up)
@@ -1800,8 +1916,8 @@ babel_add_iface(struct babel_proto *p, struct iface *new, struct babel_iface_con
ifa->next_hop_ip4 = ipa_nonzero(ic->next_hop_ip4) ? ic->next_hop_ip4 : addr4;
ifa->next_hop_ip6 = ipa_nonzero(ic->next_hop_ip6) ? ic->next_hop_ip6 : ifa->addr;
- if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
- log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, new->name);
+ if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !ic->ext_next_hop)
+ log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
init_list(&ifa->neigh_list);
ifa->hello_seqno = 1;
@@ -1921,7 +2037,7 @@ babel_reconfigure_iface(struct babel_proto *p, struct babel_iface *ifa, struct b
if ((new->auth_type != BABEL_AUTH_NONE) && (new->auth_type != old->auth_type))
babel_auth_reset_index(ifa);
- if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel)
+ if (ipa_zero(ifa->next_hop_ip4) && p->ip4_channel && !new->ext_next_hop)
log(L_WARN "%s: Missing IPv4 next hop address for %s", p->p.name, ifa->ifname);
if (ifa->next_hello > (current_time() + new->hello_interval))
@@ -1946,7 +2062,7 @@ babel_reconfigure_ifaces(struct babel_proto *p, struct babel_config *cf)
WALK_LIST(iface, iface_list)
{
- if (p->p.vrf_set && p->p.vrf != iface->master)
+ if (p->p.vrf_set && !if_in_vrf(iface, p->p.vrf))
continue;
if (!(iface->flags & IF_UP))
@@ -2156,8 +2272,8 @@ babel_show_neighbors(struct proto *P, const char *iff)
}
cli_msg(-1024, "%s:", p->p.name);
- cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s",
- "IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth");
+ cli_msg(-1024, "%-25s %-10s %6s %6s %6s %7s %4s %9s",
+ "IP address", "Interface", "Metric", "Routes", "Hellos", "Expires", "Auth", "RTT (ms)");
WALK_LIST(ifa, p->interfaces)
{
@@ -2172,9 +2288,10 @@ babel_show_neighbors(struct proto *P, const char *iff)
uint hellos = u32_popcount(n->hello_map);
btime timer = (n->hello_expiry ?: n->init_expiry) - current_time();
- cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s",
+ cli_msg(-1024, "%-25I %-10s %6u %6u %6u %7t %-4s %9t",
n->addr, ifa->iface->name, n->cost, rts, hellos, MAX(timer, 0),
- n->auth_passed ? "Yes" : "No");
+ n->auth_passed ? "Yes" : "No",
+ n->srtt * 1000);
}
}
}
diff --git a/proto/babel/babel.h b/proto/babel/babel.h
index da8386b3..edde4cab 100644
--- a/proto/babel/babel.h
+++ b/proto/babel/babel.h
@@ -53,10 +53,16 @@
#define BABEL_GARBAGE_INTERVAL (300 S_)
#define BABEL_RXCOST_WIRED 96
#define BABEL_RXCOST_WIRELESS 256
+#define BABEL_RXCOST_RTT 96
#define BABEL_INITIAL_HOP_COUNT 255
#define BABEL_MAX_SEND_INTERVAL 5 /* Unused ? */
#define BABEL_INITIAL_NEIGHBOR_TIMEOUT (60 S_)
+#define BABEL_RTT_MAX_VALUE (600 S_)
+#define BABEL_RTT_MIN (10 MS_)
+#define BABEL_RTT_MAX (120 MS_)
+#define BABEL_RTT_DECAY 42
+
/* Max interval that will not overflow when carried as 16-bit centiseconds */
#define BABEL_TIME_UNITS 10000 /* On-wire times are counted in centiseconds */
#define BABEL_MIN_INTERVAL (0x0001 * BABEL_TIME_UNITS)
@@ -96,6 +102,8 @@ enum babel_tlv_type {
enum babel_subtlv_type {
BABEL_SUBTLV_PAD1 = 0,
BABEL_SUBTLV_PADN = 1,
+ BABEL_SUBTLV_DIVERSITY = 2, /* we don't support this */
+ BABEL_SUBTLV_TIMESTAMP = 3,
/* Mandatory subtlvs */
BABEL_SUBTLV_SOURCE_PREFIX = 128,
@@ -106,6 +114,7 @@ enum babel_iface_type {
BABEL_IFACE_TYPE_UNDEF = 0,
BABEL_IFACE_TYPE_WIRED = 1,
BABEL_IFACE_TYPE_WIRELESS = 2,
+ BABEL_IFACE_TYPE_TUNNEL = 3,
BABEL_IFACE_TYPE_MAX
};
@@ -114,6 +123,7 @@ enum babel_ae_type {
BABEL_AE_IP4 = 1,
BABEL_AE_IP6 = 2,
BABEL_AE_IP6_LL = 3,
+ BABEL_AE_IP4_VIA_IP6 = 4,
BABEL_AE_MAX
};
@@ -140,6 +150,12 @@ struct babel_iface_config {
uint ihu_interval; /* IHU interval, in us */
uint update_interval; /* Update interval, in us */
+ btime rtt_min; /* rtt above which to start penalising metric */
+ btime rtt_max; /* max rtt metric penalty applied above this */
+ u16 rtt_cost; /* metric penalty to apply at rtt_max */
+ u16 rtt_decay; /* decay of neighbour RTT (units of 1/256) */
+ u8 rtt_send; /* whether to send timestamps on this interface */
+
u16 rx_buffer; /* RX buffer size, 0 for MTU */
u16 tx_length; /* TX packet length limit (including headers), 0 for MTU */
int tx_tos;
@@ -147,8 +163,9 @@ struct babel_iface_config {
ip_addr next_hop_ip4;
ip_addr next_hop_ip6;
+ u8 ext_next_hop; /* Enable IPv4 via IPv6 */
- u8 auth_type; /* Authentication type (BABEL_AUTH_*) */
+ u8 auth_type; /* Authentication type (BABEL_AUTH_*) */
u8 auth_permissive; /* Don't drop packets failing auth check */
uint mac_num_keys; /* Number of configured HMAC keys */
uint mac_total_len; /* Total digest length for all configured keys */
@@ -227,7 +244,12 @@ struct babel_neighbor {
u16 next_hello_seqno;
uint last_hello_int;
- u32 auth_pc;
+ u32 last_tstamp;
+ btime last_tstamp_rcvd;
+ btime srtt;
+
+ u32 auth_pc_unicast;
+ u32 auth_pc_multicast;
u8 auth_passed;
u8 auth_index_len;
u8 auth_index[BABEL_AUTH_INDEX_LEN];
@@ -323,6 +345,8 @@ struct babel_msg_hello {
u16 seqno;
uint interval;
ip_addr sender;
+ u32 tstamp;
+ btime pkt_received;
};
struct babel_msg_ihu {
@@ -332,6 +356,9 @@ struct babel_msg_ihu {
uint interval;
ip_addr addr;
ip_addr sender;
+ u32 tstamp;
+ u32 tstamp_rcvd;
+ btime pkt_received;
};
struct babel_msg_update {
@@ -405,6 +432,7 @@ struct babel_msg_auth {
u8 challenge_seen;
u8 challenge_len;
u8 challenge[BABEL_AUTH_MAX_NONCE_LEN];
+ u8 unicast;
};
static inline int babel_sadr_enabled(struct babel_proto *p)
diff --git a/proto/babel/config.Y b/proto/babel/config.Y
index 05210fa4..b8af0267 100644
--- a/proto/babel/config.Y
+++ b/proto/babel/config.Y
@@ -25,7 +25,8 @@ CF_DECLS
CF_KEYWORDS(BABEL, INTERFACE, METRIC, RXCOST, HELLO, UPDATE, INTERVAL, PORT,
TYPE, WIRED, WIRELESS, RX, TX, BUFFER, PRIORITY, LENGTH, CHECK, LINK,
NEXT, HOP, IPV4, IPV6, BABEL_METRIC, SHOW, INTERFACES, NEIGHBORS,
- ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE)
+ ENTRIES, RANDOMIZE, ROUTER, ID, AUTHENTICATION, NONE, MAC, PERMISSIVE,
+ EXTENDED, TUNNEL, RTT, MIN, MAX, DECAY, SEND, TIMESTAMPS)
CF_GRAMMAR
@@ -66,7 +67,12 @@ babel_iface_start:
BABEL_IFACE->limit = BABEL_HELLO_LIMIT;
BABEL_IFACE->tx_tos = IP_PREC_INTERNET_CONTROL;
BABEL_IFACE->tx_priority = sk_priority_control;
+ BABEL_IFACE->rtt_min = BABEL_RTT_MIN;
+ BABEL_IFACE->rtt_max = BABEL_RTT_MAX;
+ BABEL_IFACE->rtt_decay = BABEL_RTT_DECAY;
+ BABEL_IFACE->rtt_send = 1;
BABEL_IFACE->check_link = 1;
+ BABEL_IFACE->ext_next_hop = 1;
};
@@ -85,8 +91,16 @@ babel_iface_finish:
BABEL_IFACE->hello_interval = BABEL_HELLO_INTERVAL_WIRED;
if (!BABEL_IFACE->rxcost)
BABEL_IFACE->rxcost = BABEL_RXCOST_WIRED;
+ if (BABEL_IFACE->type == BABEL_IFACE_TYPE_TUNNEL && !BABEL_IFACE->rtt_cost)
+ BABEL_IFACE->rtt_cost = BABEL_RXCOST_RTT;
}
+ if (BABEL_IFACE->rtt_cost && !BABEL_IFACE->rtt_send)
+ cf_error("Can't set RTT cost when sending timestamps is disabled");
+
+ if (BABEL_IFACE->rtt_min >= BABEL_IFACE->rtt_max)
+ cf_error("Min RTT must be smaller than max RTT");
+
/* Make sure we do not overflow the 16-bit centisec fields */
if (!BABEL_IFACE->update_interval)
BABEL_IFACE->update_interval = MIN_(BABEL_IFACE->hello_interval*BABEL_UPDATE_INTERVAL_FACTOR, BABEL_MAX_INTERVAL);
@@ -134,6 +148,7 @@ babel_iface_item:
| LIMIT expr { BABEL_IFACE->limit = $2; if (($2<1) || ($2>16)) cf_error("Limit must be in range 1-16"); }
| TYPE WIRED { BABEL_IFACE->type = BABEL_IFACE_TYPE_WIRED; }
| TYPE WIRELESS { BABEL_IFACE->type = BABEL_IFACE_TYPE_WIRELESS; }
+ | TYPE TUNNEL { BABEL_IFACE->type = BABEL_IFACE_TYPE_TUNNEL; }
| HELLO INTERVAL expr_us { BABEL_IFACE->hello_interval = $3; if (($3<BABEL_MIN_INTERVAL) || ($3>BABEL_MAX_INTERVAL)) cf_error("Hello interval must be in range 10 ms - 655 s"); }
| UPDATE INTERVAL expr_us { BABEL_IFACE->update_interval = $3; if (($3<BABEL_MIN_INTERVAL) || ($3>BABEL_MAX_INTERVAL)) cf_error("Update interval must be in range 10 ms - 655 s"); }
| RX BUFFER expr { BABEL_IFACE->rx_buffer = $3; if (($3<256) || ($3>65535)) cf_error("RX buffer must be in range 256-65535"); }
@@ -143,9 +158,15 @@ babel_iface_item:
| CHECK LINK bool { BABEL_IFACE->check_link = $3; }
| NEXT HOP IPV4 ipa { BABEL_IFACE->next_hop_ip4 = $4; if (!ipa_is_ip4($4)) cf_error("Must be an IPv4 address"); }
| NEXT HOP IPV6 ipa { BABEL_IFACE->next_hop_ip6 = $4; if (!ipa_is_ip6($4)) cf_error("Must be an IPv6 address"); }
+ | EXTENDED NEXT HOP bool { BABEL_IFACE->ext_next_hop = $4; }
| AUTHENTICATION NONE { BABEL_IFACE->auth_type = BABEL_AUTH_NONE; }
| AUTHENTICATION MAC { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 0; }
| AUTHENTICATION MAC PERMISSIVE { BABEL_IFACE->auth_type = BABEL_AUTH_MAC; BABEL_IFACE->auth_permissive = 1; }
+ | RTT MIN expr_us { BABEL_IFACE->rtt_min = $3; }
+ | RTT MAX expr_us { BABEL_IFACE->rtt_max = $3; }
+ | RTT COST expr { BABEL_IFACE->rtt_cost = $3; if ($3 >= BABEL_INFINITY) cf_error("RTT cost must be < 65535"); }
+ | RTT DECAY expr { BABEL_IFACE->rtt_decay = $3; if (($3 < 1) || ($3 > 256)) cf_error("RTT decay must be between 1-256"); }
+ | SEND TIMESTAMPS bool { BABEL_IFACE->rtt_send = $3; }
| password_list
;
diff --git a/proto/babel/packets.c b/proto/babel/packets.c
index d4acc170..f1895655 100644
--- a/proto/babel/packets.c
+++ b/proto/babel/packets.c
@@ -58,6 +58,13 @@ struct babel_tlv_ihu {
u8 addr[0];
} PACKED;
+struct babel_subtlv_timestamp {
+ u8 type;
+ u8 length;
+ u32 tstamp;
+ u32 tstamp_rcvd; /* only used in IHU */
+} PACKED;
+
struct babel_tlv_router_id {
u8 type;
u8 length;
@@ -161,15 +168,19 @@ struct babel_parse_state {
const struct babel_tlv_data* (*get_subtlv_data)(u8 type);
struct babel_proto *proto;
struct babel_iface *ifa;
+ btime received_time;
ip_addr saddr;
ip_addr next_hop_ip4;
ip_addr next_hop_ip6;
u64 router_id; /* Router ID used in subsequent updates */
u8 def_ip6_prefix[16]; /* Implicit IPv6 prefix in network order */
- u8 def_ip4_prefix[4]; /* Implicit IPv4 prefix in network order */
+ u8 def_ip4_prefix[4]; /* Implicit IPv4 prefix (AE 1) in network order */
+ u8 def_ip4_via_ip6_prefix[4]; /* Implicit IPv4 prefix (AE 4) in network order */
u8 router_id_seen; /* router_id field is valid */
u8 def_ip6_prefix_seen; /* def_ip6_prefix is valid */
u8 def_ip4_prefix_seen; /* def_ip4_prefix is valid */
+ u8 def_ip4_via_ip6_prefix_seen; /* def_ip4_via_ip6_prefix is valid */
+ u8 hello_tstamp_seen; /* pkt contains a hello timestamp */
u8 current_tlv_endpos; /* End of self-terminating TLVs (offset from start) */
u8 sadr_enabled;
u8 is_unicast;
@@ -334,6 +345,7 @@ static int babel_read_update(struct babel_tlv *hdr, union babel_msg *msg, struct
static int babel_read_route_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static int babel_read_seqno_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static int babel_read_source_prefix(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
+static int babel_read_timestamp(struct babel_tlv *hdr, union babel_msg *msg, struct babel_parse_state *state);
static uint babel_write_ack(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
static uint babel_write_hello(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
@@ -342,6 +354,7 @@ static uint babel_write_update(struct babel_tlv *hdr, union babel_msg *msg, stru
static uint babel_write_route_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
static uint babel_write_seqno_request(struct babel_tlv *hdr, union babel_msg *msg, struct babel_write_state *state, uint max_len);
static int babel_write_source_prefix(struct babel_tlv *hdr, net_addr *net, uint max_len);
+static int babel_write_timestamp(struct babel_tlv *hdr, u32 tstamp, u32 tstamp_rcvd, uint max_len);
static const struct babel_tlv_data tlv_data[BABEL_TLV_MAX] = {
[BABEL_TLV_ACK_REQ] = {
@@ -417,6 +430,13 @@ static const struct babel_tlv_data *get_packet_tlv_data(u8 type)
return type < sizeof(tlv_data) / sizeof(*tlv_data) ? &tlv_data[type] : NULL;
}
+static const struct babel_tlv_data timestamp_tlv_data = {
+ sizeof(struct babel_subtlv_timestamp),
+ babel_read_timestamp,
+ NULL,
+ NULL
+};
+
static const struct babel_tlv_data source_prefix_tlv_data = {
sizeof(struct babel_subtlv_source_prefix),
babel_read_source_prefix,
@@ -428,6 +448,8 @@ static const struct babel_tlv_data *get_packet_subtlv_data(u8 type)
{
switch (type)
{
+ case BABEL_SUBTLV_TIMESTAMP:
+ return &timestamp_tlv_data;
case BABEL_SUBTLV_SOURCE_PREFIX:
return &source_prefix_tlv_data;
@@ -489,16 +511,34 @@ babel_read_hello(struct babel_tlv *hdr, union babel_msg *m,
static uint
babel_write_hello(struct babel_tlv *hdr, union babel_msg *m,
- struct babel_write_state *state UNUSED, uint max_len UNUSED)
+ struct babel_write_state *state UNUSED, uint max_len)
{
struct babel_tlv_hello *tlv = (void *) hdr;
struct babel_msg_hello *msg = &m->hello;
+ uint len = sizeof(struct babel_tlv_hello);
TLV_HDR0(tlv, BABEL_TLV_HELLO);
put_u16(&tlv->seqno, msg->seqno);
put_time16(&tlv->interval, msg->interval);
- return sizeof(struct babel_tlv_hello);
+ if (msg->tstamp)
+ {
+ /*
+ * There can be a substantial delay between when the babel_msg was created
+ * and when it is serialised. We don't want this included in the RTT
+ * measurement, so replace the timestamp with the current time to get as
+ * close as possible to on-wire time for the packet.
+ */
+ u32 tstamp = current_time_now() TO_US;
+
+ int l = babel_write_timestamp(hdr, tstamp, 0, max_len);
+ if (l < 0)
+ return 0;
+
+ len += l;
+ }
+
+ return len;
}
static int
@@ -515,9 +555,6 @@ babel_read_ihu(struct babel_tlv *hdr, union babel_msg *m,
msg->addr = IPA_NONE;
msg->sender = state->saddr;
- if (msg->ae >= BABEL_AE_MAX)
- return PARSE_IGNORE;
-
/*
* We only actually read link-local IPs. In every other case, the addr field
* will be 0 but validation will succeed. The handler takes care of these
@@ -526,17 +563,20 @@ babel_read_ihu(struct babel_tlv *hdr, union babel_msg *m,
*/
switch (msg->ae)
{
+ case BABEL_AE_WILDCARD:
+ return PARSE_SUCCESS;
+
case BABEL_AE_IP4:
if (TLV_OPT_LENGTH(tlv) < 4)
return PARSE_ERROR;
state->current_tlv_endpos += 4;
- break;
+ return PARSE_SUCCESS;
case BABEL_AE_IP6:
if (TLV_OPT_LENGTH(tlv) < 16)
return PARSE_ERROR;
state->current_tlv_endpos += 16;
- break;
+ return PARSE_SUCCESS;
case BABEL_AE_IP6_LL:
if (TLV_OPT_LENGTH(tlv) < 8)
@@ -544,10 +584,17 @@ babel_read_ihu(struct babel_tlv *hdr, union babel_msg *m,
msg->addr = ipa_from_ip6(get_ip6_ll(&tlv->addr));
state->current_tlv_endpos += 8;
- break;
+ return PARSE_SUCCESS;
+
+ /* RFC 9229 2.4 - IHU TLV MUST NOT carry the AE 4 (IPv4-via-IPv6) */
+ case BABEL_AE_IP4_VIA_IP6:
+ return PARSE_ERROR;
+
+ default:
+ return PARSE_IGNORE;
}
- return PARSE_SUCCESS;
+ return PARSE_IGNORE;
}
static uint
@@ -556,6 +603,7 @@ babel_write_ihu(struct babel_tlv *hdr, union babel_msg *m,
{
struct babel_tlv_ihu *tlv = (void *) hdr;
struct babel_msg_ihu *msg = &m->ihu;
+ uint len = sizeof(*tlv);
if (ipa_is_link_local(msg->addr) && max_len < sizeof(struct babel_tlv_ihu) + 8)
return 0;
@@ -567,12 +615,24 @@ babel_write_ihu(struct babel_tlv *hdr, union babel_msg *m,
if (!ipa_is_link_local(msg->addr))
{
tlv->ae = BABEL_AE_WILDCARD;
- return sizeof(struct babel_tlv_ihu);
+ goto out;
}
put_ip6_ll(&tlv->addr, msg->addr);
tlv->ae = BABEL_AE_IP6_LL;
hdr->length += 8;
- return sizeof(struct babel_tlv_ihu) + 8;
+ len += 8;
+
+out:
+ if (msg->tstamp)
+ {
+ int l = babel_write_timestamp(hdr, msg->tstamp, msg->tstamp_rcvd, max_len);
+ if (l < 0)
+ return 0;
+
+ len += l;
+ }
+
+ return len;
}
static int
@@ -640,6 +700,10 @@ babel_read_next_hop(struct babel_tlv *hdr, union babel_msg *m UNUSED,
state->current_tlv_endpos += 8;
return PARSE_IGNORE;
+ /* RFC 9229 2.4 - Next Hop TLV MUST NOT carry the AE 4 (IPv4-via-IPv6) */
+ case BABEL_AE_IP4_VIA_IP6:
+ return PARSE_ERROR;
+
default:
return PARSE_IGNORE;
}
@@ -692,6 +756,42 @@ babel_write_next_hop(struct babel_tlv *hdr, ip_addr addr,
return 0;
}
+/* This is called directly from babel_read_update() to handle
+ both BABEL_AE_IP4 and BABEL_AE_IP4_VIA_IP6 encodings */
+static int
+babel_read_ip4_prefix(struct babel_tlv_update *tlv, struct babel_msg_update *msg,
+ u8 *def_prefix, u8 *def_prefix_seen, ip_addr next_hop, int len)
+{
+ if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
+ return PARSE_ERROR;
+
+ /* Cannot omit data if there is no saved prefix */
+ if (tlv->omitted && !*def_prefix_seen)
+ return PARSE_ERROR;
+
+ /* Update must have next hop, unless it is retraction */
+ if (ipa_zero(next_hop) && msg->metric != BABEL_INFINITY)
+ return PARSE_ERROR;
+
+ /* Merge saved prefix and received prefix parts */
+ u8 buf[4] = {};
+ memcpy(buf, def_prefix, tlv->omitted);
+ memcpy(buf + tlv->omitted, tlv->addr, len);
+
+ ip4_addr prefix4 = get_ip4(buf);
+ net_fill_ip4(&msg->net, prefix4, tlv->plen);
+
+ if (tlv->flags & BABEL_UF_DEF_PREFIX)
+ {
+ put_ip4(def_prefix, prefix4);
+ *def_prefix_seen = 1;
+ }
+
+ msg->next_hop = next_hop;
+
+ return PARSE_SUCCESS;
+}
+
static int
babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
struct babel_parse_state *state)
@@ -706,11 +806,11 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
/* Length of received prefix data without omitted part */
int len = BYTES(tlv->plen) - (int) tlv->omitted;
- u8 buf[16] = {};
if ((len < 0) || ((uint) len > TLV_OPT_LENGTH(tlv)))
return PARSE_ERROR;
+ int rc;
switch (tlv->ae)
{
case BABEL_AE_WILDCARD:
@@ -724,31 +824,20 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
break;
case BABEL_AE_IP4:
- if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
- return PARSE_ERROR;
-
- /* Cannot omit data if there is no saved prefix */
- if (tlv->omitted && !state->def_ip4_prefix_seen)
- return PARSE_ERROR;
+ rc = babel_read_ip4_prefix(tlv, msg, state->def_ip4_prefix,
+ &state->def_ip4_prefix_seen,
+ state->next_hop_ip4, len);
+ if (rc != PARSE_SUCCESS)
+ return rc;
- /* Update must have next hop, unless it is retraction */
- if (ipa_zero(state->next_hop_ip4) && (msg->metric != BABEL_INFINITY))
- return PARSE_IGNORE;
-
- /* Merge saved prefix and received prefix parts */
- memcpy(buf, state->def_ip4_prefix, tlv->omitted);
- memcpy(buf + tlv->omitted, tlv->addr, len);
-
- ip4_addr prefix4 = get_ip4(buf);
- net_fill_ip4(&msg->net, prefix4, tlv->plen);
-
- if (tlv->flags & BABEL_UF_DEF_PREFIX)
- {
- put_ip4(state->def_ip4_prefix, prefix4);
- state->def_ip4_prefix_seen = 1;
- }
+ break;
- msg->next_hop = state->next_hop_ip4;
+ case BABEL_AE_IP4_VIA_IP6:
+ rc = babel_read_ip4_prefix(tlv, msg, state->def_ip4_via_ip6_prefix,
+ &state->def_ip4_via_ip6_prefix_seen,
+ state->next_hop_ip6, len);
+ if (rc != PARSE_SUCCESS)
+ return rc;
break;
@@ -761,6 +850,7 @@ babel_read_update(struct babel_tlv *hdr, union babel_msg *m,
return PARSE_ERROR;
/* Merge saved prefix and received prefix parts */
+ u8 buf[16] = {};
memcpy(buf, state->def_ip6_prefix, tlv->omitted);
memcpy(buf + tlv->omitted, tlv->addr, len);
@@ -863,7 +953,7 @@ babel_write_update(struct babel_tlv *hdr, union babel_msg *m,
}
else if (msg->net.type == NET_IP4)
{
- tlv->ae = BABEL_AE_IP4;
+ tlv->ae = ipa_is_ip4(msg->next_hop) ? BABEL_AE_IP4 : BABEL_AE_IP4_VIA_IP6;
tlv->plen = net4_pxlen(&msg->net);
put_ip4_px(tlv->addr, &msg->net);
}
@@ -931,7 +1021,12 @@ babel_read_route_request(struct babel_tlv *hdr, union babel_msg *m,
msg->full = 1;
return PARSE_SUCCESS;
+ /*
+ * RFC 9229 2.3 - When receiving requests, AE 1 (IPv4) and AE 4
+ * (IPv4-via-IPv6) MUST be treated in the same manner.
+ */
case BABEL_AE_IP4:
+ case BABEL_AE_IP4_VIA_IP6:
if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
return PARSE_ERROR;
@@ -1032,7 +1127,12 @@ babel_read_seqno_request(struct babel_tlv *hdr, union babel_msg *m,
case BABEL_AE_WILDCARD:
return PARSE_ERROR;
+ /*
+ * RFC 9229 2.3 - When receiving requests, AE 1 (IPv4) and AE 4
+ * (IPv4-via-IPv6) MUST be treated in the same manner.
+ */
case BABEL_AE_IP4:
+ case BABEL_AE_IP4_VIA_IP6:
if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
return PARSE_ERROR;
@@ -1200,6 +1300,66 @@ babel_write_source_prefix(struct babel_tlv *hdr, net_addr *n, uint max_len)
return len;
}
+static int
+babel_read_timestamp(struct babel_tlv *hdr, union babel_msg *msg,
+ struct babel_parse_state *state)
+{
+ struct babel_subtlv_timestamp *tlv = (void *) hdr;
+
+ switch (msg->type)
+ {
+ case BABEL_TLV_HELLO:
+ if (tlv->length < 4)
+ return PARSE_ERROR;
+
+ msg->hello.tstamp = get_u32(&tlv->tstamp);
+ msg->hello.pkt_received = state->received_time;
+ state->hello_tstamp_seen = 1;
+ break;
+
+ case BABEL_TLV_IHU:
+ if (tlv->length < 8)
+ return PARSE_ERROR;
+
+ /* RTT calculation relies on a Hello always being present with an IHU */
+ if (!state->hello_tstamp_seen)
+ break;
+
+ msg->ihu.tstamp = get_u32(&tlv->tstamp);
+ msg->ihu.tstamp_rcvd = get_u32(&tlv->tstamp_rcvd);
+ msg->ihu.pkt_received = state->received_time;
+ break;
+
+ default:
+ return PARSE_ERROR;
+ }
+
+ return PARSE_SUCCESS;
+}
+
+static int
+babel_write_timestamp(struct babel_tlv *hdr, u32 tstamp, u32 tstamp_rcvd, uint max_len)
+{
+ struct babel_subtlv_timestamp *tlv = (void *) NEXT_TLV(hdr);
+ uint len = sizeof(*tlv);
+
+ if (hdr->type == BABEL_TLV_HELLO)
+ len -= 4;
+
+ if (len > max_len)
+ return -1;
+
+ TLV_HDR(tlv, BABEL_SUBTLV_TIMESTAMP, len);
+ hdr->length += len;
+
+ put_u32(&tlv->tstamp, tstamp);
+
+ if (hdr->type == BABEL_TLV_IHU)
+ put_u32(&tlv->tstamp_rcvd, tstamp_rcvd);
+
+ return len;
+}
+
static inline int
babel_read_subtlvs(struct babel_tlv *hdr,
union babel_msg *msg,
@@ -1469,6 +1629,13 @@ babel_process_packet(struct babel_iface *ifa,
.saddr = saddr,
.next_hop_ip6 = saddr,
.sadr_enabled = babel_sadr_enabled(p),
+
+ /*
+ * The core updates current_time() after returning from poll(), so this is
+ * actually the time the packet was received, even though there may have
+ * been a bit of delay before we got to process it
+ */
+ .received_time = current_time(),
};
if ((pkt->magic != BABEL_MAGIC) || (pkt->version != BABEL_VERSION))
@@ -1655,6 +1822,7 @@ babel_read_pc(struct babel_tlv *hdr, union babel_msg *m UNUSED,
state->auth.pc_seen = 1;
state->auth.index_len = index_len;
state->auth.index = tlv->index;
+ state->auth.unicast = state->is_unicast;
state->current_tlv_endpos += index_len;
return PARSE_SUCCESS;
diff --git a/proto/bfd/bfd.c b/proto/bfd/bfd.c
index 873e2ed5..97eb2d9b 100644
--- a/proto/bfd/bfd.c
+++ b/proto/bfd/bfd.c
@@ -638,7 +638,7 @@ bfd_reconfigure_iface(struct bfd_proto *p, struct bfd_iface *ifa, struct bfd_con
*/
static void
-bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
+bfd_request_notify(struct bfd_request *req, u8 state, u8 remote, u8 diag)
{
u8 old_state = req->state;
@@ -648,7 +648,7 @@ bfd_request_notify(struct bfd_request *req, u8 state, u8 diag)
req->state = state;
req->diag = diag;
req->old_state = old_state;
- req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN);
+ req->down = (old_state == BFD_STATE_UP) && (state == BFD_STATE_DOWN) && (remote != BFD_STATE_ADMIN_DOWN);
if (req->hook)
req->hook(req);
@@ -670,7 +670,7 @@ bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
uint ifindex = req->iface ? req->iface->index : 0;
struct bfd_session *s = bfd_find_session_by_addr(p, req->addr, ifindex);
- u8 state, diag;
+ u8 loc_state, rem_state, diag;
if (!s)
s = bfd_add_session(p, req->addr, req->local, req->iface, &req->opts);
@@ -680,11 +680,12 @@ bfd_add_request(struct bfd_proto *p, struct bfd_request *req)
req->session = s;
bfd_lock_sessions(p);
- state = s->loc_state;
+ loc_state = s->loc_state;
+ rem_state = s->rem_state;
diag = s->loc_diag;
bfd_unlock_sessions(p);
- bfd_request_notify(req, state, diag);
+ bfd_request_notify(req, loc_state, rem_state, diag);
return 1;
}
@@ -701,7 +702,7 @@ bfd_submit_request(struct bfd_request *req)
rem_node(&req->n);
add_tail(&bfd_wait_list, &req->n);
req->session = NULL;
- bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, 0);
+ bfd_request_notify(req, BFD_STATE_ADMIN_DOWN, BFD_STATE_ADMIN_DOWN, 0);
}
static void
@@ -926,7 +927,7 @@ bfd_notify_hook(sock *sk, uint len UNUSED)
struct bfd_proto *p = sk->data;
struct bfd_session *s;
list tmp_list;
- u8 state, diag;
+ u8 loc_state, rem_state, diag;
node *n, *nn;
pipe_drain(sk->fd);
@@ -941,13 +942,14 @@ bfd_notify_hook(sock *sk, uint len UNUSED)
{
bfd_lock_sessions(p);
rem_node(&s->n);
- state = s->loc_state;
+ loc_state = s->loc_state;
+ rem_state = s->rem_state;
diag = s->loc_diag;
bfd_unlock_sessions(p);
s->notify_running = 1;
WALK_LIST_DELSAFE(n, nn, s->request_list)
- bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), state, diag);
+ bfd_request_notify(SKIP_BACK(struct bfd_request, n, n), loc_state, rem_state, diag);
s->notify_running = 0;
/* Remove the session if all requests were removed in notify hooks */
diff --git a/proto/bfd/config.Y b/proto/bfd/config.Y
index 70461872..d9a154b2 100644
--- a/proto/bfd/config.Y
+++ b/proto/bfd/config.Y
@@ -95,7 +95,7 @@ bfd_iface_finish:
BFD_IFACE->passwords = get_passwords();
if (!BFD_IFACE->auth_type != !BFD_IFACE->passwords)
- log(L_WARN "Authentication and password options should be used together");
+ cf_warn("Authentication and password options should be used together");
if (BFD_IFACE->passwords)
{
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index 1e234b16..4346cd5d 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -17,6 +17,7 @@
#include "nest/protocol.h"
#include "nest/route.h"
#include "nest/attrs.h"
+#include "nest/mpls.h"
#include "conf/conf.h"
#include "lib/resource.h"
#include "lib/string.h"
@@ -1151,6 +1152,19 @@ bgp_attr_known(uint code)
return (code < ARRAY_SIZE(bgp_attr_table)) && bgp_attr_table[code].name;
}
+const char *
+bgp_attr_name(uint code)
+{
+ return (code < ARRAY_SIZE(bgp_attr_table)) ? bgp_attr_table[code].name : NULL;
+}
+
+void bgp_fix_attr_flags(ea_list *attrs)
+{
+ for (u8 i = 0; i < attrs->count; i++)
+ {
+ attrs->attrs[i].flags = bgp_attr_table[EA_ID(attrs->attrs[i].id)].flags;
+ }
+}
/*
* Attribute export
@@ -1494,6 +1508,13 @@ bgp_finish_attrs(struct bgp_parse_state *s, rta *a)
p->cf->local_role == BGP_ROLE_RS_CLIENT))
bgp_set_attr_u32(&a->eattrs, s->pool, BA_ONLY_TO_CUSTOMER, 0, p->cf->remote_as);
}
+
+ /* Apply MPLS policy for labeled SAFIs */
+ if (s->mpls && s->proto->p.mpls_channel)
+ {
+ struct mpls_channel *mc = (void *) s->proto->p.mpls_channel;
+ ea_set_attr_u32(&a->eattrs, s->pool, EA_MPLS_POLICY, 0, EAF_TYPE_INT, mc->label_policy);
+ }
}
@@ -1834,7 +1855,7 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at
/* MULTI_EXIT_DESC attribute - accept only if set in export filter */
a = bgp_find_attr(attrs0, BA_MULTI_EXIT_DISC);
- if (a && !(a->fresh))
+ if (a && !a->fresh && !p->cf->allow_med)
bgp_unset_attr(&attrs, pool, BA_MULTI_EXIT_DISC);
}
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 2e442e16..f8146bdf 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -86,7 +86,6 @@
* RFC 5065 - AS confederations for BGP
* RFC 5082 - Generalized TTL Security Mechanism
* RFC 5492 - Capabilities Advertisement with BGP
- * RFC 5549 - Advertising IPv4 NLRI with an IPv6 Next Hop
* RFC 5575 - Dissemination of Flow Specification Rules
* RFC 5668 - 4-Octet AS Specific BGP Extended Community
* RFC 6286 - AS-Wide Unique BGP Identifier
@@ -101,9 +100,10 @@
* RFC 8203 - BGP Administrative Shutdown Communication
* RFC 8212 - Default EBGP Route Propagation Behavior without Policies
* RFC 8654 - Extended Message Support for BGP
+ * RFC 8950 - Advertising IPv4 NLRI with an IPv6 Next Hop
+ * RFC 9072 - Extended Optional Parameters Length for BGP OPEN Message
* RFC 9117 - Revised Validation Procedure for BGP Flow Specifications
* RFC 9234 - Route Leak Prevention and Detection Using Roles
- * draft-ietf-idr-ext-opt-param-07
* draft-uttaro-idr-bgp-persistence-04
* draft-walton-bgp-hostname-capability-02
*/
@@ -125,6 +125,7 @@
#include "lib/string.h"
#include "bgp.h"
+#include "proto/bmp/bmp.h"
static list STATIC_LIST_INIT(bgp_sockets); /* Global list of listening sockets */
@@ -379,10 +380,20 @@ bgp_close_conn(struct bgp_conn *conn)
rfree(conn->sk);
conn->sk = NULL;
+ mb_free(conn->local_open_msg);
+ conn->local_open_msg = NULL;
+ mb_free(conn->remote_open_msg);
+ conn->remote_open_msg = NULL;
+ conn->local_open_length = 0;
+ conn->remote_open_length = 0;
+
mb_free(conn->local_caps);
conn->local_caps = NULL;
mb_free(conn->remote_caps);
conn->remote_caps = NULL;
+
+ conn->notify_data = NULL;
+ conn->notify_size = 0;
}
@@ -496,8 +507,8 @@ bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip)
/* This is hack, we would like to share config, but we need to copy it now */
new_config = config;
cfg_mem = config->mem;
- conf_this_scope = config->root_scope;
- sym = cf_default_name(fmt, &(pp->dynamic_name_counter));
+ config->current_scope = config->root_scope;
+ sym = cf_default_name(config, fmt, &(pp->dynamic_name_counter));
proto_clone_config(sym, pp->p.cf);
new_config = NULL;
cfg_mem = NULL;
@@ -511,6 +522,8 @@ bgp_spawn(struct bgp_proto *pp, ip_addr remote_ip)
void
bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len)
{
+ proto_shutdown_mpls_map(&p->p, 1);
+
proto_notify_state(&p->p, PS_STOP);
bgp_graceful_close_conn(&p->outgoing_conn, subcode, data, len);
bgp_graceful_close_conn(&p->incoming_conn, subcode, data, len);
@@ -681,10 +694,12 @@ bgp_conn_enter_established_state(struct bgp_conn *conn)
bgp_conn_set_state(conn, BS_ESTABLISHED);
proto_notify_state(&p->p, PS_UP);
+ bmp_peer_up(p, conn->local_open_msg, conn->local_open_length,
+ conn->remote_open_msg, conn->remote_open_length);
}
static void
-bgp_conn_leave_established_state(struct bgp_proto *p)
+bgp_conn_leave_established_state(struct bgp_conn *conn, struct bgp_proto *p)
{
BGP_TRACE(D_EVENTS, "BGP session closed");
p->last_established = current_time();
@@ -692,6 +707,10 @@ bgp_conn_leave_established_state(struct bgp_proto *p)
if (p->p.proto_state == PS_UP)
bgp_stop(p, 0, NULL, 0);
+
+ bmp_peer_down(p, p->last_error_class,
+ conn->notify_code, conn->notify_subcode,
+ conn->notify_data, conn->notify_size);
}
void
@@ -708,7 +727,7 @@ bgp_conn_enter_close_state(struct bgp_conn *conn)
bgp_start_timer(conn->hold_timer, 10);
if (os == BS_ESTABLISHED)
- bgp_conn_leave_established_state(p);
+ bgp_conn_leave_established_state(conn, p);
}
void
@@ -722,7 +741,7 @@ bgp_conn_enter_idle_state(struct bgp_conn *conn)
ev_schedule(p->event);
if (os == BS_ESTABLISHED)
- bgp_conn_leave_established_state(p);
+ bgp_conn_leave_established_state(conn, p);
}
/**
@@ -1109,6 +1128,7 @@ bgp_connect(struct bgp_proto *p) /* Enter Connect state and start establishing c
s->tos = IP_PREC_INTERNET_CONTROL;
s->password = p->cf->password;
s->tx_hook = bgp_connected;
+ s->flags = p->cf->free_bind ? SKF_FREEBIND : 0;
BGP_TRACE(D_EVENTS, "Connecting to %I%J from local address %I%J",
s->daddr, ipa_is_link_local(s->daddr) ? p->cf->iface : NULL,
s->saddr, ipa_is_link_local(s->saddr) ? s->iface : NULL);
@@ -1394,6 +1414,16 @@ bgp_reload_routes(struct channel *C)
struct bgp_proto *p = (void *) C->proto;
struct bgp_channel *c = (void *) C;
+ /* For MPLS channel, reload all MPLS-aware channels */
+ if (C == p->p.mpls_channel)
+ {
+ BGP_WALK_CHANNELS(p, c)
+ if ((c->desc->mpls) && (p->route_refresh || c->c.in_table))
+ bgp_reload_routes(&c->c);
+
+ return;
+ }
+
/* Ignore non-BGP channels */
if (C->channel != &channel_bgp)
return;
@@ -1555,6 +1585,8 @@ bgp_start(struct proto *P)
p->remote_id = 0;
p->link_addr = IPA_NONE;
+ proto_setup_mpls_map(P, RTS_BGP, 1);
+
/* Lock all channels when in GR recovery mode */
if (p->p.gr_recovery && p->cf->gr_mode)
{
@@ -1711,11 +1743,14 @@ bgp_init(struct proto_config *CF)
if (cf->c.parent)
cf->remote_ip = IPA_NONE;
- /* Add all channels */
+ /* Add all BGP channels */
struct bgp_channel_config *cc;
BGP_CF_WALK_CHANNELS(cf, cc)
proto_add_channel(P, &cc->c);
+ /* Add MPLS channel */
+ proto_configure_channel(P, &P->mpls_channel, proto_cf_mpls_channel(CF));
+
return P;
}
@@ -2135,16 +2170,23 @@ bgp_reconfigure(struct proto *P, struct proto_config *CF)
WALK_LIST(C, p->p.channels)
C->stale = 1;
+ /* Reconfigure BGP channels */
BGP_CF_WALK_CHANNELS(new, cc)
{
C = (struct channel *) bgp_find_channel(p, cc->afi);
same = proto_configure_channel(P, &C, &cc->c) && same;
}
+ /* Reconfigure MPLS channel */
+ same = proto_configure_channel(P, &P->mpls_channel, proto_cf_mpls_channel(CF)) && same;
+
WALK_LIST_DELSAFE(C, C2, p->p.channels)
if (C->stale)
same = proto_configure_channel(P, &C, NULL) && same;
+ if (same)
+ proto_setup_mpls_map(P, RTS_BGP, 1);
+
if (same && (p->start_state > BSS_PREPARE))
bgp_update_bfd(p, new->bfd);
@@ -2246,12 +2288,13 @@ bgp_error(struct bgp_conn *c, uint code, uint subcode, byte *data, int len)
bgp_log_error(p, BE_BGP_TX, "Error", code, subcode, data, ABS(len));
bgp_store_error(p, c, BE_BGP_TX, (code << 16) | subcode);
- bgp_conn_enter_close_state(c);
c->notify_code = code;
c->notify_subcode = subcode;
c->notify_data = data;
c->notify_size = (len > 0) ? len : 0;
+
+ bgp_conn_enter_close_state(c);
bgp_schedule_packet(c, NULL, PKT_NOTIFICATION);
if (code != 6)
@@ -2619,7 +2662,7 @@ bgp_show_proto_info(struct proto *P)
}
}
-struct channel_class channel_bgp = {
+const struct channel_class channel_bgp = {
.channel_size = sizeof(struct bgp_channel),
.config_size = sizeof(struct bgp_channel_config),
.init = bgp_channel_init,
@@ -2634,7 +2677,7 @@ struct protocol proto_bgp = {
.template = "bgp%d",
.class = PROTOCOL_BGP,
.preference = DEF_PREF_BGP,
- .channel_mask = NB_IP | NB_VPN | NB_FLOW,
+ .channel_mask = NB_IP | NB_VPN | NB_FLOW | NB_MPLS,
.proto_size = sizeof(struct bgp_proto),
.config_size = sizeof(struct bgp_config),
.postconfig = bgp_postconfig,
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index 2808d479..2e95037c 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -96,8 +96,9 @@ struct bgp_config {
u32 default_med; /* Default value for MULTI_EXIT_DISC attribute */
int capabilities; /* Enable capability handshake [RFC 5492] */
int enable_refresh; /* Enable local support for route refresh [RFC 2918] */
+ int enable_enhanced_refresh; /* Enable local support for enhanced route refresh [RFC 7313] */
int enable_as4; /* Enable local support for 4B AS numbers [RFC 6793] */
- int enable_extended_messages; /* Enable local support for extended messages [draft] */
+ int enable_extended_messages; /* Enable local support for extended messages [RFC 8654] */
int enable_hostname; /* Enable local support for hostname [draft] */
u32 rr_cluster_id; /* Route reflector cluster ID, if different from local ID */
int rr_client; /* Whether neighbor is RR client of me */
@@ -108,6 +109,7 @@ struct bgp_config {
int interpret_communities; /* Hardwired handling of well-known communities */
int allow_local_as; /* Allow that number of local ASNs in incoming AS_PATHs */
int allow_local_pref; /* Allow LOCAL_PREF in EBGP sessions */
+ int allow_med; /* Allow BGP_MED in EBGP sessions */
int allow_as_sets; /* Allow AS_SETs in incoming AS_PATHs */
int enforce_first_as; /* Enable check for neighbor AS as first AS in AS_PATH */
int gr_mode; /* Graceful restart mode (BGP_GR_*) */
@@ -231,7 +233,7 @@ struct bgp_af_caps {
u8 llgr_able; /* Long-lived GR, RFC draft */
u32 llgr_time; /* Long-lived GR stale time */
u8 llgr_flags; /* Long-lived GR per-AF flags */
- u8 ext_next_hop; /* Extended IPv6 next hop, RFC 5549 */
+ u8 ext_next_hop; /* Extended IPv6 next hop, RFC 8950 */
u8 add_path; /* Multiple paths support, RFC 7911 */
};
@@ -239,7 +241,7 @@ struct bgp_caps {
u32 as4_number; /* Announced ASN */
u8 as4_support; /* Four-octet AS capability, RFC 6793 */
- u8 ext_messages; /* Extended message length, RFC draft */
+ u8 ext_messages; /* Extended message length, RFC 8654 */
u8 route_refresh; /* Route refresh capability, RFC 2918 */
u8 enhanced_refresh; /* Enhanced route refresh, RFC 7313 */
u8 role; /* BGP role capability, RFC 9234 */
@@ -286,6 +288,11 @@ struct bgp_conn {
u8 ext_messages; /* Session uses extended message length */
u32 received_as; /* ASN received in OPEN message */
+ byte *local_open_msg; /* Saved OPEN messages (no header) */
+ byte *remote_open_msg;
+ uint local_open_length;
+ uint remote_open_length;
+
struct bgp_caps *local_caps;
struct bgp_caps *remote_caps;
timer *connect_timer;
@@ -433,6 +440,7 @@ struct bgp_write_state {
int as4_session;
int add_path;
int mpls;
+ int sham;
eattr *mp_next_hop;
const adata *mpls_labels;
@@ -485,6 +493,7 @@ struct bgp_parse_state {
#define BGP_PORT 179
#define BGP_VERSION 4
#define BGP_HEADER_LENGTH 19
+#define BGP_HDR_MARKER_LENGTH 16
#define BGP_MAX_MESSAGE_LENGTH 4096
#define BGP_MAX_EXT_MSG_LENGTH 65535
#define BGP_RX_BUFFER_SIZE 4096
@@ -495,6 +504,13 @@ struct bgp_parse_state {
#define BGP_CF_WALK_CHANNELS(P,C) WALK_LIST(C, P->c.channels) if (C->c.channel == &channel_bgp)
#define BGP_WALK_CHANNELS(P,C) WALK_LIST(C, P->p.channels) if (C->c.channel == &channel_bgp)
+#define BGP_MSG_HDR_MARKER_SIZE 16
+#define BGP_MSG_HDR_MARKER_POS 0
+#define BGP_MSG_HDR_LENGTH_SIZE 2
+#define BGP_MSG_HDR_LENGTH_POS BGP_MSG_HDR_MARKER_SIZE
+#define BGP_MSG_HDR_TYPE_SIZE 1
+#define BGP_MSG_HDR_TYPE_POS (BGP_MSG_HDR_MARKER_SIZE + BGP_MSG_HDR_LENGTH_SIZE)
+
static inline int bgp_channel_is_ipv4(struct bgp_channel *c)
{ return BGP_AFI(c->afi) == BGP_AFI_IPV4; }
@@ -541,6 +557,8 @@ void bgp_store_error(struct bgp_proto *p, struct bgp_conn *c, u8 class, u32 code
void bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len);
const char *bgp_format_role_name(u8 role);
+void bgp_fix_attr_flags(ea_list *attrs);
+
static inline int
rte_resolvable(rte *rt)
{
@@ -587,10 +605,13 @@ bgp_set_attr_data(ea_list **to, struct linpool *pool, uint code, uint flags, voi
bgp_set_attr(to, pool, code, flags, (uintptr_t) a);
}
-#define bgp_unset_attr(to, pool, code) ea_unset_attr(to, pool, 0, code)
+static inline void
+bgp_unset_attr(ea_list **to, struct linpool *pool, uint code)
+{ ea_unset_attr(to, pool, 0, EA_CODE(PROTOCOL_BGP, code)); }
int bgp_encode_mp_reach_mrt(struct bgp_write_state *s, eattr *a, byte *buf, uint size);
+const char * bgp_attr_name(uint code);
int bgp_encode_attrs(struct bgp_write_state *s, ea_list *attrs, byte *buf, byte *end);
ea_list * bgp_decode_attrs(struct bgp_parse_state *s, byte *data, uint len);
void bgp_finish_attrs(struct bgp_parse_state *s, rta *a);
@@ -616,6 +637,8 @@ int bgp_get_attr(const struct eattr *e, byte *buf, int buflen);
void bgp_get_route_info(struct rte *, byte *buf);
int bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad);
+byte * bgp_bmp_encode_rte(struct bgp_channel *c, byte *buf, const net_addr *n, const struct rte *new, const struct rte_src *src);
+
#define BGP_AIGP_METRIC 1
#define BGP_AIGP_MAX U64(0xffffffffffffffff)
@@ -644,6 +667,7 @@ const char * bgp_error_dsc(unsigned code, unsigned subcode);
void bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned subcode, byte *data, unsigned len);
void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to);
+byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
/* Packet types */
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index a2dfa747..6981a117 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -24,7 +24,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
BGP_AGGREGATOR, BGP_COMMUNITY, BGP_EXT_COMMUNITY, BGP_LARGE_COMMUNITY,
SOURCE, ADDRESS, PASSWORD, RR, RS, CLIENT, CLUSTER, ID, AS4, ADVERTISE,
IPV4, CAPABILITIES, LIMIT, PASSIVE, PREFER, OLDER, MISSING, LLADDR,
- DROP, IGNORE, ROUTE, REFRESH, INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID,
+ DROP, IGNORE, ENHANCED, ROUTE, REFRESH, INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID,
BGP_CLUSTER_LIST, IGP, TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL,
SECURITY, DETERMINISTIC, SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX,
GRACEFUL, RESTART, AWARE, CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY,
@@ -45,9 +45,6 @@ CF_KEYWORDS(CEASE, PREFIX, LIMIT, HIT, ADMINISTRATIVE, SHUTDOWN, RESET, PEER,
CF_GRAMMAR
-/* Workaround for collisions between keywords and symbols */
-symbol: ROLE | PEER | PROVIDER | CUSTOMER | RS_SERVER | RS_CLIENT ;
-
proto: bgp_proto '}' ;
bgp_proto_start: proto_start BGP {
@@ -65,6 +62,7 @@ bgp_proto_start: proto_start BGP {
BGP_CFG->error_delay_time_min = 60;
BGP_CFG->error_delay_time_max = 300;
BGP_CFG->enable_refresh = 1;
+ BGP_CFG->enable_enhanced_refresh = 1;
BGP_CFG->enable_as4 = 1;
BGP_CFG->enable_hostname = 0;
BGP_CFG->capabilities = 2;
@@ -131,6 +129,7 @@ bgp_proto:
bgp_proto_start proto_name '{'
| bgp_proto proto_item ';'
| bgp_proto bgp_proto_channel ';'
+ | bgp_proto mpls_channel ';'
| bgp_proto LOCAL bgp_loc_opts ';'
| bgp_proto LOCAL ipa ipa_scope bgp_loc_opts ';' {
BGP_CFG->local_ip = $3;
@@ -178,7 +177,7 @@ bgp_proto:
| bgp_proto DEFAULT BGP_MED expr ';' { BGP_CFG->default_med = $4; }
| bgp_proto DEFAULT BGP_LOCAL_PREF expr ';' { BGP_CFG->default_local_pref = $4; }
| bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->local_ip = $4; }
- | bgp_proto START DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; log(L_WARN "%s: Start delay time option is deprecated, use connect delay time", this_proto->name); }
+ | bgp_proto START DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; cf_warn("%s: Start delay time option is deprecated, use connect delay time", this_proto->name); }
| bgp_proto CONNECT DELAY TIME expr ';' { BGP_CFG->connect_delay_time = $5; }
| bgp_proto CONNECT RETRY TIME expr ';' { BGP_CFG->connect_retry_time = $5; }
| bgp_proto KEEPALIVE TIME expr ';' { BGP_CFG->keepalive_time = $4; if (($4<1) || ($4>65535)) cf_error("Keepalive time must be in range 1-65535"); }
@@ -188,6 +187,7 @@ bgp_proto:
| bgp_proto DISABLE AFTER ERROR bool ';' { BGP_CFG->disable_after_error = $5; }
| bgp_proto DISABLE AFTER CEASE bgp_cease_mask ';' { BGP_CFG->disable_after_cease = $5; }
| bgp_proto ENABLE ROUTE REFRESH bool ';' { BGP_CFG->enable_refresh = $5; }
+ | bgp_proto ENABLE ENHANCED ROUTE REFRESH bool ';' { BGP_CFG->enable_enhanced_refresh = $6; }
| bgp_proto ENABLE AS4 bool ';' { BGP_CFG->enable_as4 = $4; }
| bgp_proto ENABLE EXTENDED MESSAGES bool ';' { BGP_CFG->enable_extended_messages = $5; }
| bgp_proto ADVERTISE HOSTNAME bool ';' { BGP_CFG->enable_hostname = $4; }
@@ -199,6 +199,7 @@ bgp_proto:
| bgp_proto ALLOW LOCAL AS ';' { BGP_CFG->allow_local_as = -1; }
| bgp_proto ALLOW LOCAL AS expr ';' { BGP_CFG->allow_local_as = $5; }
| bgp_proto ALLOW BGP_LOCAL_PREF bool ';' { BGP_CFG->allow_local_pref = $4; }
+ | bgp_proto ALLOW BGP_MED bool ';' { BGP_CFG->allow_med = $4; }
| bgp_proto ALLOW AS SETS bool ';' { BGP_CFG->allow_as_sets = $5; }
| bgp_proto GRACEFUL RESTART bool ';' { BGP_CFG->gr_mode = $4; }
| bgp_proto GRACEFUL RESTART AWARE ';' { BGP_CFG->gr_mode = BGP_GR_AWARE; }
@@ -270,7 +271,7 @@ bgp_channel_item:
| NEXT HOP KEEP bgp_nh { BGP_CC->next_hop_keep = $4; }
| NEXT HOP PREFER GLOBAL { BGP_CC->next_hop_prefer = NHP_GLOBAL; }
| MANDATORY bool { BGP_CC->mandatory = $2; }
- | MISSING LLADDR bgp_lladdr { log(L_WARN "%s.%s: Missing lladdr option is deprecated and ignored, remove it", this_proto->name, this_channel->name); }
+ | MISSING LLADDR bgp_lladdr { cf_warn("%s.%s: Missing lladdr option is deprecated and ignored, remove it", this_proto->name, this_channel->name); }
| GATEWAY DIRECT { BGP_CC->gw_mode = GW_DIRECT; }
| GATEWAY RECURSIVE { BGP_CC->gw_mode = GW_RECURSIVE; }
| SECONDARY bool { BGP_CC->secondary = $2; }
@@ -364,7 +365,19 @@ dynamic_attr: BGP_LARGE_COMMUNITY
dynamic_attr: BGP_OTC
{ $$ = f_new_dynamic_attr(EAF_TYPE_INT, T_INT, EA_CODE(PROTOCOL_BGP, BA_ONLY_TO_CUSTOMER)); } ;
-
+custom_attr: ATTRIBUTE BGP expr type symbol ';' {
+ if ($3 > 255 || $3 < 1)
+ cf_error("Invalid attribute number (Given %i, must be 1-255)", $3);
+ if ($4 != T_BYTESTRING)
+ cf_error("Attribute type must be bytestring, not %s", f_type_name($4));
+ if (bgp_attr_name($3))
+ cf_error("Attribute BGP.%d already known as %s", $3, bgp_attr_name($3));
+
+ struct f_dynamic_attr *a = cfg_alloc(sizeof(struct f_dynamic_attr));
+ *a = f_new_dynamic_attr(f_type_attr($4), T_BYTESTRING, EA_CODE(PROTOCOL_BGP, $3));
+ a->flags = BAF_TRANSITIVE | BAF_OPTIONAL;
+ cf_define_symbol(new_config, $5, SYM_ATTRIBUTE, attribute, a);
+};
CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE)
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 16818cf3..b7df5a7a 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -26,6 +26,7 @@
#include "nest/cli.h"
#include "bgp.h"
+#include "proto/bmp/bmp.h"
#define BGP_RR_REQUEST 0
@@ -213,6 +214,13 @@ bgp_af_caps_cmp(const void *X, const void *Y)
return (x->afi < y->afi) ? -1 : (x->afi > y->afi) ? 1 : 0;
}
+struct bgp_caps *
+bgp_alloc_capabilities(struct bgp_proto *p, int n)
+{
+ struct bgp_caps *caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + n * sizeof(struct bgp_af_caps));
+ caps->role = BGP_ROLE_UNDEFINED;
+ return caps;
+}
void
bgp_prepare_capabilities(struct bgp_conn *conn)
@@ -225,19 +233,19 @@ bgp_prepare_capabilities(struct bgp_conn *conn)
if (!p->cf->capabilities)
{
/* Just prepare empty local_caps */
- conn->local_caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps));
+ conn->local_caps = bgp_alloc_capabilities(p, 0);
return;
}
/* Prepare bgp_caps structure */
int n = list_length(&p->p.channels);
- caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + n * sizeof(struct bgp_af_caps));
+ caps = bgp_alloc_capabilities(p, n);
conn->local_caps = caps;
caps->as4_support = p->cf->enable_as4;
caps->ext_messages = p->cf->enable_extended_messages;
caps->route_refresh = p->cf->enable_refresh;
- caps->enhanced_refresh = p->cf->enable_refresh;
+ caps->enhanced_refresh = p->cf->enable_refresh && p->cf->enable_enhanced_refresh;
caps->role = p->cf->local_role;
if (caps->as4_support)
@@ -462,10 +470,7 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len)
u32 af;
if (!conn->remote_caps)
- {
- caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps) + sizeof(struct bgp_af_caps));
- caps->role = BGP_ROLE_UNDEFINED;
- }
+ caps = bgp_alloc_capabilities(p, 1);
else
{
caps = conn->remote_caps;
@@ -501,7 +506,7 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len)
caps->route_refresh = 1;
break;
- case 5: /* Extended next hop encoding capability, RFC 5549 */
+ case 5: /* Extended next hop encoding capability, RFC 8950 */
if (cl % 6)
goto err;
@@ -518,7 +523,7 @@ bgp_read_capabilities(struct bgp_conn *conn, byte *pos, int len)
}
break;
- case 6: /* Extended message length capability, RFC draft */
+ case 6: /* Extended message length capability, RFC 8654 */
if (cl != 0)
goto err;
@@ -711,7 +716,7 @@ bgp_read_options(struct bgp_conn *conn, byte *pos, uint len, uint rest)
struct bgp_proto *p = conn->bgp;
int ext = 0;
- /* Handle extended length (draft-ietf-idr-ext-opt-param-07) */
+ /* Handle extended length, RFC 9072 */
if ((len > 0) && (rest > 0) && (pos[0] == 255))
{
if (rest < 3)
@@ -761,7 +766,7 @@ bgp_read_options(struct bgp_conn *conn, byte *pos, uint len, uint rest)
/* Prepare empty caps if no capability option was announced */
if (!conn->remote_caps)
- conn->remote_caps = mb_allocz(p->p.pool, sizeof(struct bgp_caps));
+ conn->remote_caps = bgp_alloc_capabilities(p, 0);
return 0;
@@ -771,6 +776,14 @@ err:
}
static byte *
+bgp_copy_open(struct bgp_proto *p, const byte *pkt, uint len)
+{
+ char *buf = mb_alloc(p->p.pool, len - BGP_HEADER_LENGTH);
+ memcpy(buf, pkt + BGP_HEADER_LENGTH, len - BGP_HEADER_LENGTH);
+ return buf;
+}
+
+static byte *
bgp_create_open(struct bgp_conn *conn, byte *buf)
{
struct bgp_proto *p = conn->bgp;
@@ -796,7 +809,7 @@ bgp_create_open(struct bgp_conn *conn, byte *buf)
buf[10] = 2; /* Option 2: Capability list */
buf[11] = len; /* Option data length */
}
- else /* draft-ietf-idr-ext-opt-param-07 */
+ else /* Extended length, RFC 9072 */
{
/* Move capabilities 4 B forward */
memmove(buf + 16, pos, len);
@@ -844,6 +857,9 @@ bgp_rx_open(struct bgp_conn *conn, byte *pkt, uint len)
id = get_u32(pkt+24);
BGP_TRACE(D_PACKETS, "Got OPEN(as=%d,hold=%d,id=%R)", asn, hold, id);
+ conn->remote_open_msg = bgp_copy_open(p, pkt, len);
+ conn->remote_open_length = len - BGP_HEADER_LENGTH;
+
if (bgp_read_options(conn, pkt+29, pkt[28], len-29) < 0)
return;
@@ -1163,7 +1179,7 @@ bgp_use_gateway(struct bgp_export_state *s)
return 0;
/* Do not use gateway from different VRF */
- if (p->p.vrf_set && ra->nh.iface && (p->p.vrf != ra->nh.iface->master))
+ if (p->p.vrf_set && ra->nh.iface && !if_in_vrf(ra->nh.iface, p->p.vrf))
return 0;
/* Use it when exported to internal peers */
@@ -1201,11 +1217,10 @@ bgp_update_next_hop_ip(struct bgp_export_state *s, eattr *a, ea_list **to)
bgp_set_attr_data(to, s->pool, BA_NEXT_HOP, 0, nh, ipa_nonzero(nh[1]) ? 32 : 16);
s->local_next_hop = 1;
- /* TODO: Use local MPLS assigned label */
if (s->mpls)
{
- u32 implicit_null = BGP_MPLS_NULL;
- bgp_set_attr_data(to, s->pool, BA_MPLS_LABEL_STACK, 0, &implicit_null, 4);
+ u32 label = ea_get_int(s->route->attrs->eattrs, EA_MPLS_LABEL, BGP_MPLS_NULL);
+ bgp_set_attr_data(to, s->pool, BA_MPLS_LABEL_STACK, 0, &label, 4);
}
else
bgp_unset_attr(to, s->pool, BA_MPLS_LABEL_STACK);
@@ -1250,7 +1265,7 @@ bgp_encode_next_hop_ip(struct bgp_write_state *s, eattr *a, byte *buf, uint size
/*
* Both IPv4 and IPv6 next hops can be used (with ext_next_hop enabled). This
- * is specified in RFC 5549 for IPv4 and in RFC 4798 for IPv6. The difference
+ * is specified in RFC 8950 for IPv4 and in RFC 4798 for IPv6. The difference
* is that IPv4 address is directly encoded with IPv4 NLRI, but as IPv4-mapped
* IPv6 address with IPv6 NLRI.
*/
@@ -1325,7 +1340,7 @@ bgp_encode_next_hop_vpn(struct bgp_write_state *s, eattr *a, byte *buf, uint siz
/*
* Both IPv4 and IPv6 next hops can be used (with ext_next_hop enabled). This
- * is specified in RFC 5549 for VPNv4 and in RFC 4659 for VPNv6. The difference
+ * is specified in RFC 8950 for VPNv4 and in RFC 4659 for VPNv6. The difference
* is that IPv4 address is directly encoded with VPNv4 NLRI, but as IPv4-mapped
* IPv6 address with VPNv6 NLRI.
*/
@@ -1563,7 +1578,10 @@ bgp_encode_nlri_ip4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
memcpy(pos, &a, b);
ADVANCE(pos, size, b);
- bgp_free_prefix(s->channel, px);
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
}
return pos - buf;
@@ -1648,7 +1666,10 @@ bgp_encode_nlri_ip6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
memcpy(pos, &a, b);
ADVANCE(pos, size, b);
- bgp_free_prefix(s->channel, px);
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
}
return pos - buf;
@@ -1736,7 +1757,10 @@ bgp_encode_nlri_vpn4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *b
memcpy(pos, &a, b);
ADVANCE(pos, size, b);
- bgp_free_prefix(s->channel, px);
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
}
return pos - buf;
@@ -1833,7 +1857,10 @@ bgp_encode_nlri_vpn6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *b
memcpy(pos, &a, b);
ADVANCE(pos, size, b);
- bgp_free_prefix(s->channel, px);
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
}
return pos - buf;
@@ -1920,7 +1947,10 @@ bgp_encode_nlri_flow4(struct bgp_write_state *s, struct bgp_bucket *buck, byte *
memcpy(pos, net->data, flen);
ADVANCE(pos, size, flen);
- bgp_free_prefix(s->channel, px);
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
}
return pos - buf;
@@ -2012,7 +2042,10 @@ bgp_encode_nlri_flow6(struct bgp_write_state *s, struct bgp_bucket *buck, byte *
memcpy(pos, net->data, flen);
ADVANCE(pos, size, flen);
- bgp_free_prefix(s->channel, px);
+ if (!s->sham)
+ bgp_free_prefix(s->channel, px);
+ else
+ rem_node(&px->buck_node);
}
return pos - buf;
@@ -2256,7 +2289,8 @@ bgp_create_ip_reach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
if (la < 0)
{
/* Attribute list too long */
- bgp_withdraw_bucket(s->channel, buck);
+ if (!s->sham)
+ bgp_withdraw_bucket(s->channel, buck);
return NULL;
}
@@ -2303,7 +2337,8 @@ bgp_create_mp_reach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *bu
if (la < 0)
{
/* Attribute list too long */
- bgp_withdraw_bucket(s->channel, buck);
+ if (!s->sham)
+ bgp_withdraw_bucket(s->channel, buck);
return NULL;
}
@@ -2384,6 +2419,100 @@ bgp_create_mp_unreach(struct bgp_write_state *s, struct bgp_bucket *buck, byte *
return buf+11+len;
}
+
+#ifdef CONFIG_BMP
+
+static byte *
+bgp_create_update_bmp(struct bgp_channel *c, byte *buf, struct bgp_bucket *buck, bool update)
+{
+ struct bgp_proto *p = (void *) c->c.proto;
+ byte *end = buf + (BGP_MAX_EXT_MSG_LENGTH - BGP_HEADER_LENGTH);
+ byte *res = NULL;
+ /* FIXME: must be a bit shorter */
+
+ struct lp_state tmpp;
+ lp_save(tmp_linpool, &tmpp);
+
+ struct bgp_caps *peer = p->conn->remote_caps;
+ const struct bgp_af_caps *rem = bgp_find_af_caps(peer, c->afi);
+
+ struct bgp_write_state s = {
+ .proto = p,
+ .channel = c,
+ .pool = tmp_linpool,
+ .mp_reach = (c->afi != BGP_AF_IPV4) || (rem && rem->ext_next_hop),
+ .as4_session = 1,
+ .add_path = c->add_path_rx,
+ .mpls = c->desc->mpls,
+ .sham = 1,
+ };
+
+ if (!update)
+ {
+ res = !s.mp_reach ?
+ bgp_create_ip_unreach(&s, buck, buf, end):
+ bgp_create_mp_unreach(&s, buck, buf, end);
+ }
+ else
+ {
+ res = !s.mp_reach ?
+ bgp_create_ip_reach(&s, buck, buf, end):
+ bgp_create_mp_reach(&s, buck, buf, end);
+ }
+
+ lp_restore(tmp_linpool, &tmpp);
+
+ return res;
+}
+
+static byte *
+bgp_bmp_prepare_bgp_hdr(byte *buf, const u16 msg_size, const u8 msg_type)
+{
+ memset(buf + BGP_MSG_HDR_MARKER_POS, 0xff, BGP_MSG_HDR_MARKER_SIZE);
+ put_u16(buf + BGP_MSG_HDR_LENGTH_POS, msg_size);
+ put_u8(buf + BGP_MSG_HDR_TYPE_POS, msg_type);
+
+ return buf + BGP_MSG_HDR_TYPE_POS + BGP_MSG_HDR_TYPE_SIZE;
+}
+
+byte *
+bgp_bmp_encode_rte(struct bgp_channel *c, byte *buf, const net_addr *n,
+ const struct rte *new, const struct rte_src *src)
+{
+// struct bgp_proto *p = (void *) c->c.proto;
+ byte *pkt = buf + BGP_HEADER_LENGTH;
+
+ ea_list *attrs = new ? new->attrs->eattrs : NULL;
+ uint ea_size = new ? (sizeof(ea_list) + attrs->count * sizeof(eattr)) : 0;
+ uint bucket_size = sizeof(struct bgp_bucket) + ea_size;
+ uint prefix_size = sizeof(struct bgp_prefix) + n->length;
+
+ /* Sham bucket */
+ struct bgp_bucket *b = alloca(bucket_size);
+ *b = (struct bgp_bucket) { };
+ init_list(&b->prefixes);
+
+ if (attrs)
+ memcpy(b->eattrs, attrs, ea_size);
+
+ /* Sham prefix */
+ struct bgp_prefix *px = alloca(prefix_size);
+ *px = (struct bgp_prefix) { };
+ px->path_id = (u32) src->private_id;
+ net_copy(px->net, n);
+ add_tail(&b->prefixes, &px->buck_node);
+
+ byte *end = bgp_create_update_bmp(c, pkt, b, !!new);
+
+ if (end)
+ bgp_bmp_prepare_bgp_hdr(buf, end - buf, PKT_UPDATE);
+
+ return end;
+}
+
+#endif /* CONFIG_BMP */
+
+
static byte *
bgp_create_update(struct bgp_channel *c, byte *buf)
{
@@ -2484,6 +2613,14 @@ bgp_create_mp_end_mark(struct bgp_channel *c, byte *buf)
return buf+10;
}
+byte *
+bgp_create_end_mark_(struct bgp_channel *c, byte *buf)
+{
+ return (c->afi == BGP_AF_IPV4) ?
+ bgp_create_ip_end_mark(c, buf):
+ bgp_create_mp_end_mark(c, buf);
+}
+
static byte *
bgp_create_end_mark(struct bgp_channel *c, byte *buf)
{
@@ -2492,9 +2629,7 @@ bgp_create_end_mark(struct bgp_channel *c, byte *buf)
BGP_TRACE(D_PACKETS, "Sending END-OF-RIB");
p->stats.tx_updates++;
- return (c->afi == BGP_AF_IPV4) ?
- bgp_create_ip_end_mark(c, buf):
- bgp_create_mp_end_mark(c, buf);
+ return bgp_create_end_mark_(c, buf);
}
static inline void
@@ -2635,7 +2770,6 @@ bgp_rx_update(struct bgp_conn *conn, byte *pkt, uint len)
s.ip_reach_len = len - pos;
s.ip_reach_nlri = pkt + pos;
-
if (s.attr_len)
ea = bgp_decode_attrs(&s, s.attrs, s.attr_len);
else
@@ -2869,7 +3003,7 @@ bgp_send(struct bgp_conn *conn, uint type, uint len)
conn->bgp->stats.tx_messages++;
conn->bgp->stats.tx_bytes += len;
- memset(buf, 0xff, 16); /* Marker */
+ memset(buf, 0xff, BGP_HDR_MARKER_LENGTH);
put_u16(buf+16, len);
buf[18] = type;
@@ -2917,6 +3051,10 @@ bgp_fire_tx(struct bgp_conn *conn)
{
conn->packets_to_send &= ~(1 << PKT_OPEN);
end = bgp_create_open(conn, pkt);
+
+ conn->local_open_msg = bgp_copy_open(p, buf, end - buf);
+ conn->local_open_length = end - buf - BGP_HEADER_LENGTH;
+
return bgp_send(conn, PKT_OPEN, end - buf);
}
else if (s & (1 << PKT_KEEPALIVE))
@@ -3198,6 +3336,11 @@ bgp_rx_notification(struct bgp_conn *conn, byte *pkt, uint len)
bgp_log_error(p, BE_BGP_RX, "Received", code, subcode, pkt+21, len-21);
bgp_store_error(p, conn, BE_BGP_RX, (code << 16) | subcode);
+ conn->notify_code = code;
+ conn->notify_subcode = subcode;
+ conn->notify_data = pkt+21;
+ conn->notify_size = len-21;
+
bgp_conn_enter_close_state(conn);
bgp_schedule_packet(conn, NULL, PKT_SCHEDULE_CLOSE);
diff --git a/proto/bmp/Doc b/proto/bmp/Doc
new file mode 100644
index 00000000..69b6e807
--- /dev/null
+++ b/proto/bmp/Doc
@@ -0,0 +1 @@
+S bmp.c
diff --git a/proto/bmp/LICENSE b/proto/bmp/LICENSE
new file mode 100644
index 00000000..1772f08d
--- /dev/null
+++ b/proto/bmp/LICENSE
@@ -0,0 +1,2 @@
+The patch is provided under the terms of the GNU General Public License, either
+version 2, or any later version. \ No newline at end of file
diff --git a/proto/bmp/Makefile b/proto/bmp/Makefile
new file mode 100644
index 00000000..d6fca1aa
--- /dev/null
+++ b/proto/bmp/Makefile
@@ -0,0 +1,6 @@
+src := bmp.c buffer.c map.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files) \ No newline at end of file
diff --git a/proto/bmp/README.txt b/proto/bmp/README.txt
new file mode 100644
index 00000000..ed51529f
--- /dev/null
+++ b/proto/bmp/README.txt
@@ -0,0 +1,6 @@
+ABOUT
+This package |proto/bmp/*| provide implementation of BGP Monitoring Protocol (BMP).
+It has been started by Akamai Technologies, Inc. as a pilot program for support BMP in BIRD.
+It provides only basic features of BMP specification which are needed by Akamai evaluation of
+feasible BMP protocol.
+Content of this package has been provided as a patch for BIRD release v2.0.7.
diff --git a/proto/bmp/bmp.c b/proto/bmp/bmp.c
new file mode 100644
index 00000000..261e9fdd
--- /dev/null
+++ b/proto/bmp/bmp.c
@@ -0,0 +1,1367 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: BGP Monitoring Protocol (BMP)
+ *
+ * Supported standards:
+ * o RFC 7854 - BMP standard
+ *
+ * TODO:
+ * - Support Peer Distinguisher ID in Per-Peer Header
+ * - Support peer type as RD Instance in Peer Type field of Per-Peer Header.
+ * Currently, there are supported Global and Local Instance Peer types
+ * - Support corresponding FSM event code during send PEER DOWN NOTIFICATION
+ * - Support DE_CONFIGURED PEER DOWN REASON code in PEER DOWN NOTIFICATION message
+ * - If connection with BMP collector will lost then we don't establish connection again
+ * - Set Peer Type by its a global and local-scope IP address
+ *
+ * The BMP session is managed by a simple state machine with three states: Idle
+ * (!started, !sk), Connect (!started, sk active), and Established (started). It
+ * has three events: connect successful (Connect -> Established), socket error
+ * (any -> Idle), and connect timeout (Idle/Connect -> Connect, resetting the
+ * TCP socket).
+ */
+
+#include "proto/bmp/bmp.h"
+#include "proto/bmp/buffer.h"
+#include "proto/bmp/map.h"
+
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <limits.h>
+
+#include "nest/cli.h"
+#include "filter/filter.h"
+#include "proto/bgp/bgp.h"
+#include "sysdep/unix/unix.h"
+#include "lib/event.h"
+#include "lib/ip.h"
+#include "lib/lists.h"
+#include "lib/resource.h"
+#include "lib/unaligned.h"
+#include "nest/iface.h"
+#include "nest/route.h"
+
+// List of BMP instances
+static list STATIC_LIST_INIT(bmp_proto_list);
+
+#define HASH_PEER_KEY(n) n->bgp
+#define HASH_PEER_NEXT(n) n->next
+#define HASH_PEER_EQ(b1,b2) b1 == b2
+#define HASH_PEER_FN(b) ptr_hash(b)
+
+#define BMP_STREAM_KEY_POLICY 0x100
+
+#define HASH_STREAM_KEY(n) n->bgp, n->key
+#define HASH_STREAM_NEXT(n) n->next
+#define HASH_STREAM_EQ(b1,k1,b2,k2) b1 == b2 && k1 == k2
+#define HASH_STREAM_FN(b,k) ptr_hash(b) ^ u32_hash(k)
+
+#define HASH_TABLE_KEY(n) n->table
+#define HASH_TABLE_NEXT(n) n->next
+#define HASH_TABLE_EQ(t1,t2) t1 == t2
+#define HASH_TABLE_FN(t) ptr_hash(t)
+
+/* BMP Common Header [RFC 7854 - Section 4.1] */
+enum bmp_version {
+ BMP_VER_UNUSED = 0, // Version 0 is reserved and MUST NOT be sent
+ BMP_VERSION_1 = 1, // Version 1 was used by draft version of RFC 7854
+ BMP_VERSION_2 = 2, // Version 2 was used by draft version of RFC 7854
+ BMP_VERSION_3 = 3 // Version 3 is used by all messages defined in RFC 7854
+};
+
+enum bmp_message_type {
+ BMP_ROUTE_MONITOR = 0, // Route Monitoring
+ BMP_STATS_REPORT = 1, // Statistics Report
+ BMP_PEER_DOWN_NOTIF = 2, // Peer Down Notification
+ BMP_PEER_UP_NOTIF = 3, // Peer Up Notification
+ BMP_INIT_MSG = 4, // Initiation Message
+ BMP_TERM_MSG = 5, // Termination Message
+ BMP_ROUTE_MIRROR_MSG = 6 // Route Mirroring Message
+};
+
+// Total size of Common Header
+#define BMP_COMMON_HDR_SIZE 6
+// Defines size of padding when IPv4 address is going to be put into field
+// which can accept also IPv6 address
+#define BMP_PADDING_IP4_ADDR_SIZE 12
+
+/* BMP Per-Peer Header [RFC 7854 - Section 4.2] */
+// Total size of Per-Peer Header
+#define BMP_PER_PEER_HDR_SIZE 42
+
+enum bmp_peer_type {
+ BMP_PEER_TYPE_GLOBAL_INSTANCE = 0,
+ BMP_PEER_TYPE_RD_INSTANCE = 1,
+ BMP_PEER_TYPE_LOCAL_INSTANCE = 2
+};
+
+#define BMP_PEER_HDR_FLAG_V_SHIFT 7
+enum bmp_peer_flag_v_t {
+ // The Peer address is an IPv4 address
+ BMP_PEER_HDR_FLAG_V_IP4 = (0 << BMP_PEER_HDR_FLAG_V_SHIFT),
+ // The Peer address is an IPv6 address
+ BMP_PEER_HDR_FLAG_V_IP6 = (1 << BMP_PEER_HDR_FLAG_V_SHIFT)
+};
+
+#define BMP_PEER_HDR_FLAG_L_SHIFT 6
+enum bmp_peer_flag_l {
+ BMP_PEER_HDR_FLAG_L_PRE_POLICY_ADJ_RIB_IN = (0 << BMP_PEER_HDR_FLAG_L_SHIFT),
+ BMP_PEER_HDR_FLAG_L_POST_POLICY_ADJ_RIB_IN = (1 << BMP_PEER_HDR_FLAG_L_SHIFT)
+};
+
+#define BMP_PEER_HDR_FLAG_A_SHIFT 5
+enum bmp_peer_flag_a {
+ // The 4-byte AS_PATH format
+ BMP_PEER_HDR_FLAG_A_AS_PATH_4B = (0 << BMP_PEER_HDR_FLAG_A_SHIFT),
+ // The legacy 2-byte AS_PATH format
+ BMP_PEER_HDR_FLAG_A_AS_PATH_2B = (1 << BMP_PEER_HDR_FLAG_A_SHIFT)
+};
+
+#define BMP_PEER_HDR_FLAGS_INIT(flags) \
+ (flags) = 0
+#define BMP_PEER_HDR_FLAGS_SET(flags, bit_mask) \
+ (flags) |= (bit_mask)
+
+/* BMP Information TLV header [RFC 7854 - Section 4.4] */
+// Total size of Type and Length fields of Information TLV Header without
+// variable part
+#define BMP_INFO_TLV_FIX_SIZE 4
+
+enum bmp_info_tlv_type {
+ BMP_INFO_TLV_TYPE_STRING = 0, // String
+ BMP_INFO_TLV_TYPE_SYS_DESCR = 1, // SysDescr
+ BMP_INFO_TLV_TYPE_SYS_NAME = 2 // SysName
+};
+
+/* BMP Peer Up Notification message header [RFC 7854 - Section 4.10] */
+// Total size of all fields of Peer Up Notification message except variable part
+#define BMP_PEER_UP_NOTIF_MSG_FIX_SIZE 20
+
+enum bmp_peer_down_notif_reason {
+ // The local system closed the session
+ BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION = 1,
+ // The local system closed the session
+ BMP_PEER_DOWN_REASON_LOCAL_NO_NOTIFICATION = 2,
+ // The remote system closed the session with a notification message
+ BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION = 3,
+ // The remote system closed the session without a notification message
+ BMP_PEER_DOWN_REASON_REMOTE_NO_NOTIFICATION = 4,
+ // Information for this peer will no longer be sent to the monitoring station
+ // for configuration reasons
+ BMP_PEER_DOWN_REASON_PEER_DE_CONFIGURED = 5
+};
+
+/* BMP Termination Message [RFC 7854 - Section 4.5] */
+#define BMP_TERM_INFO_TYPE_SIZE 2
+enum bmp_term_info_type {
+ BMP_TERM_INFO_STRING = 0, // The Information field contains string
+ BMP_TERM_INFO_REASON = 1, // The Information field contains 2-byte reason code
+};
+
+// 2-byte code in the Information field
+#define BMP_TERM_REASON_CODE_SIZE 2
+enum bmp_term_reason {
+ BMP_TERM_REASON_ADM = 0, // Session administratively closed
+ BMP_TERM_REASON_UNK = 1, // Unspecified reason
+ BMP_TERM_REASON_OOR = 2, // Out of resources
+ BMP_TERM_REASON_DUP = 3, // Redundant connection
+ BMP_TERM_REASON_PERM = 4, // Session permanently administratively closed
+};
+
+// Size of Information Length field in Termination Message header
+#define BMP_TERM_INFO_LEN_FIELD_SIZE 2
+
+// Default chunk size request when memory allocation
+#define DEFAULT_MEM_BLOCK_SIZE 4096
+
+// Initial delay for connection to the BMP collector
+#define CONNECT_INIT_TIME (200 MS)
+
+// Timeout for connection to the BMP collector retry
+#define CONNECT_RETRY_TIME (10 S)
+
+#define IP4_MAX_TTL 255
+
+
+#define IF_COND_TRUE_PRINT_ERR_MSG_AND_RETURN_OPT_VAL(expr, msg, rv...) \
+ do { \
+ if ((expr)) \
+ { \
+ log(L_WARN "[BMP] " msg); \
+ return rv; \
+ } \
+ } while (0)
+
+
+#define IF_PTR_IS_NULL_PRINT_ERR_MSG_AND_RETURN_OPT_VAL(p, msg, rv...) \
+ do { \
+ IF_COND_TRUE_PRINT_ERR_MSG_AND_RETURN_OPT_VAL(!(p), msg, rv); \
+ } while (0)
+
+
+static void bmp_connected(struct birdsock *sk);
+static void bmp_sock_err(sock *sk, int err);
+static void bmp_close_socket(struct bmp_proto *p);
+
+static void
+bmp_send_peer_up_notif_msg(struct bmp_proto *p, const struct bgp_proto *bgp,
+ const byte *tx_data, const size_t tx_data_size,
+ const byte *rx_data, const size_t rx_data_size);
+
+static void bmp_route_monitor_end_of_rib(struct bmp_proto *p, struct bmp_stream *bs);
+
+// Stores necessary any data in list
+struct bmp_data_node {
+ node n;
+ byte *data;
+ size_t data_size;
+
+ u32 remote_as;
+ u32 remote_id;
+ ip_addr remote_ip;
+ btime timestamp;
+ bool global_peer;
+ bool policy;
+};
+
+static void
+bmp_common_hdr_serialize(buffer *stream, const enum bmp_message_type type, const u32 data_size)
+{
+ bmp_put_u8(stream, BMP_VERSION_3);
+ bmp_put_u32(stream, BMP_COMMON_HDR_SIZE + data_size);
+ bmp_put_u8(stream, type);
+}
+
+static void
+bmp_info_tlv_hdr_serialize(buffer *stream, const enum bmp_info_tlv_type type,
+ const char *str)
+{
+ size_t str_len = strlen(str);
+ str_len = MIN(str_len, MIB_II_STR_LEN);
+
+ bmp_put_u16(stream, type);
+ bmp_put_u16(stream, str_len);
+ bmp_put_data(stream, str, str_len);
+}
+
+// Serializes BMP Initiation message header [RFC 7854 - Section 4.3]
+static void
+bmp_init_msg_serialize(buffer *stream, const char *sys_descr, const char *sys_name)
+{
+ const size_t sys_descr_len = strlen(sys_descr);
+ const size_t sys_name_len = strlen(sys_name);
+ // We include MIB-II sysDescr and sysName in BMP INIT MSG so that's why
+ // allocated 2x BMP_INFO_TLV_FIX_SIZE memory pool size
+ const size_t data_size = (2 * BMP_INFO_TLV_FIX_SIZE) + sys_descr_len + sys_name_len;
+ bmp_buffer_need(stream, BMP_COMMON_HDR_SIZE + data_size);
+ bmp_common_hdr_serialize(stream, BMP_INIT_MSG, data_size);
+ bmp_info_tlv_hdr_serialize(stream, BMP_INFO_TLV_TYPE_SYS_DESCR, sys_descr);
+ bmp_info_tlv_hdr_serialize(stream, BMP_INFO_TLV_TYPE_SYS_NAME, sys_name);
+}
+
+static void
+bmp_schedule_tx_packet(struct bmp_proto *p, const byte *payload, const size_t size)
+{
+ ASSERT(p->started);
+
+ struct bmp_data_node *tx_data = mb_alloc(p->tx_mem_pool, sizeof (struct bmp_data_node));
+ tx_data->data = mb_alloc(p->tx_mem_pool, size);
+ memcpy(tx_data->data, payload, size);
+ tx_data->data_size = size;
+ add_tail(&p->tx_queue, &tx_data->n);
+
+ if (sk_tx_buffer_empty(p->sk)
+ && !ev_active(p->tx_ev))
+ {
+ ev_schedule(p->tx_ev);
+ }
+}
+
+static void
+bmp_fire_tx(void *p_)
+{
+ struct bmp_proto *p = p_;
+
+ if (!p->started)
+ return;
+
+ IF_COND_TRUE_PRINT_ERR_MSG_AND_RETURN_OPT_VAL(
+ EMPTY_LIST(p->tx_queue),
+ "Called BMP TX event handler when there is not any data to send"
+ );
+
+ size_t cnt = 0; // Counts max packets which we want to send per TX slot
+ struct bmp_data_node *tx_data;
+ struct bmp_data_node *tx_data_next;
+ WALK_LIST_DELSAFE(tx_data, tx_data_next, p->tx_queue)
+ {
+ if (tx_data->data_size > p->sk->tbsize)
+ {
+ sk_set_tbsize(p->sk, tx_data->data_size);
+ }
+
+ size_t data_size = tx_data->data_size;
+ memcpy(p->sk->tbuf, tx_data->data, data_size);
+ mb_free(tx_data->data);
+ rem_node((node *) tx_data);
+ mb_free(tx_data);
+
+ if (sk_send(p->sk, data_size) <= 0)
+ return;
+
+ // BMP packets should be treat with lowest priority when scheduling sending
+ // packets to target. That's why we want to send max. 32 packets per event
+ // call
+ if (++cnt > 32)
+ {
+ if (!ev_active(p->tx_ev))
+ {
+ ev_schedule(p->tx_ev);
+ }
+
+ return;
+ }
+ }
+}
+
+static void
+bmp_tx(struct birdsock *sk)
+{
+ bmp_fire_tx(sk->data);
+}
+
+/* We need RX hook just to accept socket close events */
+static int
+bmp_rx(struct birdsock *sk UNUSED, uint size UNUSED)
+{
+ return 0;
+}
+
+
+static inline void
+bmp_put_ipa(buffer *stream, const ip_addr addr)
+{
+ bmp_put_ip6(stream, ipa_is_ip4(addr) ?
+ ip6_build(0,0,0, ipa_to_u32(addr)) :
+ ipa_to_ip6(addr));
+}
+
+static void
+bmp_put_bgp_hdr(buffer *stream, const u8 msg_type, const u16 msg_length)
+{
+ bmp_buffer_need(stream, BGP_HEADER_LENGTH);
+
+ memset(stream->pos, 0xff, BGP_HDR_MARKER_LENGTH);
+ stream->pos += BGP_HDR_MARKER_LENGTH;
+
+ bmp_put_u16(stream, msg_length);
+ bmp_put_u8(stream, msg_type);
+}
+
+/**
+ * bmp_per_peer_hdr_serialize - serializes Per-Peer Header
+ *
+ * @is_post_policy: indicate the message reflects the post-policy Adj-RIB-In
+ * @peer_addr: the remote IP address associated with the TCP session
+ * @peer_as: the Autonomous System number of the peer
+ * @peer_bgp_id: the BGP Identifier of the peer
+ * @ts_sec: the time in seconds when the encapsulated routes were received
+ * @ts_usec: the time in microseconds when the encapsulated routes were received
+ */
+static void
+bmp_per_peer_hdr_serialize(buffer *stream, const bool is_global_instance_peer,
+ const bool is_post_policy, const bool is_as_path_4bytes,
+ const ip_addr peer_addr, const u32 peer_as, const u32 peer_bgp_id,
+ const u32 ts_sec, const u32 ts_usec)
+{
+ // TODO: ATM we don't support BMP_PEER_TYPE_RD_INSTANCE
+ const enum bmp_peer_type peer_type = is_global_instance_peer
+ ? BMP_PEER_TYPE_GLOBAL_INSTANCE
+ : BMP_PEER_TYPE_LOCAL_INSTANCE;
+ const u8 peer_flag_v = ipa_is_ip4(peer_addr)
+ ? BMP_PEER_HDR_FLAG_V_IP4
+ : BMP_PEER_HDR_FLAG_V_IP6;
+ const u8 peer_flag_l = is_post_policy
+ ? BMP_PEER_HDR_FLAG_L_POST_POLICY_ADJ_RIB_IN
+ : BMP_PEER_HDR_FLAG_L_PRE_POLICY_ADJ_RIB_IN;
+ const u8 peer_flag_a = is_as_path_4bytes
+ ? BMP_PEER_HDR_FLAG_A_AS_PATH_4B
+ : BMP_PEER_HDR_FLAG_A_AS_PATH_2B;
+ u8 peer_flags;
+ BMP_PEER_HDR_FLAGS_INIT(peer_flags);
+ BMP_PEER_HDR_FLAGS_SET(peer_flags, peer_flag_v);
+ BMP_PEER_HDR_FLAGS_SET(peer_flags, peer_flag_l);
+ BMP_PEER_HDR_FLAGS_SET(peer_flags, peer_flag_a);
+
+ bmp_put_u8(stream, peer_type);
+ bmp_put_u8(stream, peer_flags);
+ // TODO: Provide appropriate peer Route Distinguisher if applicable
+ bmp_put_u64(stream, 0x00); // 0x00 - Not supported peer distinguisher
+ bmp_put_ipa(stream, peer_addr);
+ bmp_put_u32(stream, peer_as);
+ bmp_put_u32(stream, peer_bgp_id);
+ bmp_put_u32(stream, ts_sec);
+ bmp_put_u32(stream, ts_usec);
+}
+
+/* [4.6] Route Monitoring */
+static void
+bmp_route_monitor_msg_serialize(buffer *stream, const bool is_peer_global,
+ const bool table_in_post_policy, const u32 peer_as, const u32 peer_bgp_id,
+ const bool as4_support, const ip_addr remote_addr, const byte *update_msg,
+ const size_t update_msg_size, btime timestamp)
+{
+ const size_t data_size = BMP_PER_PEER_HDR_SIZE + update_msg_size;
+ u32 ts_sec = timestamp TO_S;
+ u32 ts_usec = timestamp - (ts_sec S);
+
+ bmp_buffer_need(stream, BMP_COMMON_HDR_SIZE + data_size);
+ bmp_common_hdr_serialize(stream, BMP_ROUTE_MONITOR, data_size);
+ bmp_per_peer_hdr_serialize(stream, is_peer_global, table_in_post_policy,
+ as4_support, remote_addr, peer_as, peer_bgp_id, ts_sec, ts_usec);
+ bmp_put_data(stream, update_msg, update_msg_size);
+}
+
+static void
+bmp_peer_up_notif_msg_serialize(buffer *stream, const bool is_peer_global,
+ const u32 peer_as, const u32 peer_bgp_id, const bool as4_support,
+ const ip_addr local_addr, const ip_addr remote_addr, const u16 local_port,
+ const u16 remote_port, const byte *sent_msg, const size_t sent_msg_length,
+ const byte *recv_msg, const size_t recv_msg_length)
+{
+ const size_t data_size =
+ BMP_PER_PEER_HDR_SIZE + BMP_PEER_UP_NOTIF_MSG_FIX_SIZE +
+ BGP_HEADER_LENGTH + sent_msg_length + BGP_HEADER_LENGTH + recv_msg_length;
+
+ bmp_buffer_need(stream, BMP_COMMON_HDR_SIZE + data_size);
+ bmp_common_hdr_serialize(stream, BMP_PEER_UP_NOTIF, data_size);
+ bmp_per_peer_hdr_serialize(stream, is_peer_global,
+ false /* TODO: Hardcoded pre-policy Adj-RIB-In */, as4_support, remote_addr,
+ peer_as, peer_bgp_id, 0, 0); // 0, 0 - No timestamp provided
+ bmp_put_ipa(stream, local_addr);
+ bmp_put_u16(stream, local_port);
+ bmp_put_u16(stream, remote_port);
+ bmp_put_bgp_hdr(stream, PKT_OPEN, BGP_HEADER_LENGTH + sent_msg_length);
+ bmp_put_data(stream, sent_msg, sent_msg_length);
+ bmp_put_bgp_hdr(stream, PKT_OPEN, BGP_HEADER_LENGTH + recv_msg_length);
+ bmp_put_data(stream, recv_msg, recv_msg_length);
+}
+
+static void
+bmp_peer_down_notif_msg_serialize(buffer *stream, const bool is_peer_global,
+ const u32 peer_as, const u32 peer_bgp_id, const bool as4_support,
+ const ip_addr remote_addr, const byte *data, const size_t data_size)
+{
+ const size_t payload_size = BMP_PER_PEER_HDR_SIZE + data_size;
+ bmp_buffer_need(stream, BMP_COMMON_HDR_SIZE + payload_size);
+ bmp_common_hdr_serialize(stream, BMP_PEER_DOWN_NOTIF, payload_size);
+ bmp_per_peer_hdr_serialize(stream, is_peer_global,
+ false /* TODO: Hardcoded pre-policy adj RIB IN */, as4_support, remote_addr,
+ peer_as, peer_bgp_id, 0, 0); // 0, 0 - No timestamp provided
+ bmp_put_data(stream, data, data_size);
+}
+
+
+/*
+ * BMP tables
+ */
+
+static struct bmp_table *
+bmp_find_table(struct bmp_proto *p, struct rtable *tab)
+{
+ return HASH_FIND(p->table_map, HASH_TABLE, tab);
+}
+
+static struct bmp_table *
+bmp_add_table(struct bmp_proto *p, struct rtable *tab)
+{
+ struct bmp_table *bt = mb_allocz(p->p.pool, sizeof(struct bmp_table));
+ bt->table = tab;
+ rt_lock_table(bt->table);
+
+ HASH_INSERT(p->table_map, HASH_TABLE, bt);
+
+ struct channel_config cc = {
+ .name = "monitor",
+ .channel = &channel_basic,
+ .table = tab->config,
+ .in_filter = FILTER_REJECT,
+ .net_type = tab->addr_type,
+ .ra_mode = RA_ANY,
+ .bmp_hack = 1,
+ };
+
+ bt->channel = proto_add_channel(&p->p, &cc);
+ channel_set_state(bt->channel, CS_UP);
+
+ return bt;
+}
+
+static void
+bmp_remove_table(struct bmp_proto *p, struct bmp_table *bt)
+{
+ channel_set_state(bt->channel, CS_FLUSHING);
+ channel_set_state(bt->channel, CS_DOWN);
+ proto_remove_channel(&p->p, bt->channel);
+
+ HASH_REMOVE(p->table_map, HASH_TABLE, bt);
+
+ rt_unlock_table(bt->table);
+ bt->table = NULL;
+
+ mb_free(bt);
+}
+
+static inline struct bmp_table *bmp_get_table(struct bmp_proto *p, struct rtable *tab)
+{ return bmp_find_table(p, tab) ?: bmp_add_table(p, tab); }
+
+static inline void bmp_lock_table(struct bmp_proto *p UNUSED, struct bmp_table *bt)
+{ bt->uc++; }
+
+static inline void bmp_unlock_table(struct bmp_proto *p, struct bmp_table *bt)
+{ bt->uc--; if (!bt->uc) bmp_remove_table(p, bt); }
+
+
+/*
+ * BMP streams
+ */
+
+static inline u32 bmp_stream_key(u32 afi, bool policy)
+{ return afi ^ (policy ? BMP_STREAM_KEY_POLICY : 0); }
+
+static inline u32 bmp_stream_afi(struct bmp_stream *bs)
+{ return bs->key & ~BMP_STREAM_KEY_POLICY; }
+
+static inline bool bmp_stream_policy(struct bmp_stream *bs)
+{ return !!(bs->key & BMP_STREAM_KEY_POLICY); }
+
+static struct bmp_stream *
+bmp_find_stream(struct bmp_proto *p, const struct bgp_proto *bgp, u32 afi, bool policy)
+{
+ return HASH_FIND(p->stream_map, HASH_STREAM, bgp, bmp_stream_key(afi, policy));
+}
+
+static struct bmp_stream *
+bmp_add_stream(struct bmp_proto *p, struct bmp_peer *bp, u32 afi, bool policy, struct rtable *tab, struct bgp_channel *sender)
+{
+ struct bmp_stream *bs = mb_allocz(p->p.pool, sizeof(struct bmp_stream));
+ bs->bgp = bp->bgp;
+ bs->key = bmp_stream_key(afi, policy);
+
+ add_tail(&bp->streams, &bs->n);
+ HASH_INSERT(p->stream_map, HASH_STREAM, bs);
+
+ bs->table = bmp_get_table(p, tab);
+ bmp_lock_table(p, bs->table);
+
+ bs->sender = sender;
+ bs->sync = false;
+
+ return bs;
+}
+
+static void
+bmp_remove_stream(struct bmp_proto *p, struct bmp_stream *bs)
+{
+ rem_node(&bs->n);
+ HASH_REMOVE(p->stream_map, HASH_STREAM, bs);
+
+ bmp_unlock_table(p, bs->table);
+ bs->table = NULL;
+
+ mb_free(bs);
+}
+
+
+/*
+ * BMP peers
+ */
+
+static struct bmp_peer *
+bmp_find_peer(struct bmp_proto *p, const struct bgp_proto *bgp)
+{
+ return HASH_FIND(p->peer_map, HASH_PEER, bgp);
+}
+
+static struct bmp_peer *
+bmp_add_peer(struct bmp_proto *p, struct bgp_proto *bgp)
+{
+ struct bmp_peer *bp = mb_allocz(p->p.pool, sizeof(struct bmp_peer));
+ bp->bgp = bgp;
+
+ init_list(&bp->streams);
+
+ HASH_INSERT(p->peer_map, HASH_PEER, bp);
+
+ struct bgp_channel *c;
+ BGP_WALK_CHANNELS(bgp, c)
+ {
+ if (p->monitoring_rib.in_pre_policy && c->c.in_table)
+ bmp_add_stream(p, bp, c->afi, false, c->c.in_table, c);
+
+ if (p->monitoring_rib.in_post_policy && c->c.table)
+ bmp_add_stream(p, bp, c->afi, true, c->c.table, c);
+ }
+
+ return bp;
+}
+
+static void
+bmp_remove_peer(struct bmp_proto *p, struct bmp_peer *bp)
+{
+ struct bmp_stream *bs, *bs_next;
+ WALK_LIST_DELSAFE(bs, bs_next, bp->streams)
+ bmp_remove_stream(p, bs);
+
+ HASH_REMOVE(p->peer_map, HASH_PEER, bp);
+
+ mb_free(bp);
+}
+
+static void
+bmp_peer_up_(struct bmp_proto *p, struct bgp_proto *bgp, bool sync,
+ const byte *tx_open_msg, uint tx_open_length,
+ const byte *rx_open_msg, uint rx_open_length)
+{
+ if (!p->started)
+ return;
+
+ struct bmp_peer *bp = bmp_find_peer(p, bgp);
+ if (bp)
+ return;
+
+ TRACE(D_STATES, "Peer up for %s", bgp->p.name);
+
+ bp = bmp_add_peer(p, bgp);
+
+ bmp_send_peer_up_notif_msg(p, bgp, tx_open_msg, tx_open_length, rx_open_msg, rx_open_length);
+
+ /*
+ * We asssume peer_up() notifications are received before any route
+ * notifications from that peer. Therefore, peers established after BMP
+ * session coould be considered synced with empty RIB.
+ */
+ if (sync)
+ {
+ struct bmp_stream *bs;
+ WALK_LIST(bs, bp->streams)
+ {
+ bmp_route_monitor_end_of_rib(p, bs);
+ bs->sync = true;
+ }
+ }
+}
+
+void
+bmp_peer_up(struct bgp_proto *bgp,
+ const byte *tx_open_msg, uint tx_open_length,
+ const byte *rx_open_msg, uint rx_open_length)
+{
+ struct bmp_proto *p; node *n;
+ WALK_LIST2(p, n, bmp_proto_list, bmp_node)
+ bmp_peer_up_(p, bgp, true, tx_open_msg, tx_open_length, rx_open_msg, rx_open_length);
+}
+
+static void
+bmp_peer_init(struct bmp_proto *p, struct bgp_proto *bgp)
+{
+ struct bgp_conn *conn = bgp->conn;
+
+ if (!conn || (conn->state != BS_ESTABLISHED) ||
+ !conn->local_open_msg || !conn->remote_open_msg)
+ return;
+
+ bmp_peer_up_(p, bgp, false, conn->local_open_msg, conn->local_open_length,
+ conn->remote_open_msg, conn->remote_open_length);
+}
+
+
+
+static const struct birdsock *
+bmp_get_birdsock(const struct bgp_proto *bgp)
+{
+ if (bgp->conn && bgp->conn->sk)
+ return bgp->conn->sk;
+
+ return NULL;
+}
+
+static const struct birdsock *
+bmp_get_birdsock_ext(const struct bgp_proto *bgp)
+{
+ const struct birdsock *sk = bmp_get_birdsock(bgp);
+ if (sk != NULL)
+ return sk;
+
+ if (bgp->incoming_conn.sk)
+ {
+ sk = bgp->incoming_conn.sk;
+ }
+ else if (bgp->outgoing_conn.sk)
+ {
+ sk = bgp->outgoing_conn.sk;
+ }
+
+ return sk;
+}
+
+static const struct bgp_caps *
+bmp_get_bgp_remote_caps(const struct bgp_proto *bgp)
+{
+ if (bgp->conn && bgp->conn->remote_caps)
+ return bgp->conn->remote_caps;
+
+ return NULL;
+}
+
+static const struct bgp_caps *
+bmp_get_bgp_remote_caps_ext(const struct bgp_proto *bgp)
+{
+ const struct bgp_caps *remote_caps = bmp_get_bgp_remote_caps(bgp);
+ if (remote_caps != NULL)
+ return remote_caps;
+
+ if (bgp->incoming_conn.remote_caps)
+ {
+ remote_caps = bgp->incoming_conn.remote_caps;
+ }
+ else if (bgp->outgoing_conn.remote_caps)
+ {
+ remote_caps = bgp->outgoing_conn.remote_caps;
+ }
+
+ return remote_caps;
+}
+
+static bool
+bmp_is_peer_global_instance(const struct bgp_proto *bgp)
+{
+ return (bgp->cf->peer_type != BGP_PT_EXTERNAL &&
+ bgp->cf->peer_type != BGP_PT_INTERNAL)
+ ? (bgp->local_as != bgp->remote_as)
+ : (bgp->cf->peer_type == BGP_PT_EXTERNAL);
+}
+
+static void
+bmp_send_peer_up_notif_msg(struct bmp_proto *p, const struct bgp_proto *bgp,
+ const byte *tx_data, const size_t tx_data_size,
+ const byte *rx_data, const size_t rx_data_size)
+{
+ ASSERT(p->started);
+
+ const struct birdsock *sk = bmp_get_birdsock_ext(bgp);
+ IF_PTR_IS_NULL_PRINT_ERR_MSG_AND_RETURN_OPT_VAL(
+ sk,
+ "[BMP] No BGP socket"
+ );
+
+ const bool is_global_instance_peer = bmp_is_peer_global_instance(bgp);
+ buffer payload = bmp_buffer_alloc(p->buffer_mpool, DEFAULT_MEM_BLOCK_SIZE);
+ bmp_peer_up_notif_msg_serialize(&payload, is_global_instance_peer,
+ bgp->remote_as, bgp->remote_id, 1,
+ sk->saddr, sk->daddr, sk->sport, sk->dport, tx_data, tx_data_size,
+ rx_data, rx_data_size);
+ bmp_schedule_tx_packet(p, bmp_buffer_data(&payload), bmp_buffer_pos(&payload));
+ bmp_buffer_free(&payload);
+}
+
+static void
+bmp_route_monitor_put_update(struct bmp_proto *p, struct bmp_stream *bs, const byte *data, size_t length, btime timestamp)
+{
+ struct bmp_data_node *upd_msg = mb_alloc(p->update_msg_mem_pool,
+ sizeof (struct bmp_data_node));
+ upd_msg->data = mb_alloc(p->update_msg_mem_pool, length);
+ memcpy(upd_msg->data, data, length);
+ upd_msg->data_size = length;
+ add_tail(&p->update_msg_queue, &upd_msg->n);
+
+ /* Save some metadata */
+ struct bgp_proto *bgp = bs->bgp;
+ upd_msg->remote_as = bgp->remote_as;
+ upd_msg->remote_id = bgp->remote_id;
+ upd_msg->remote_ip = bgp->remote_ip;
+ upd_msg->timestamp = timestamp;
+ upd_msg->global_peer = bmp_is_peer_global_instance(bgp);
+ upd_msg->policy = bmp_stream_policy(bs);
+
+ /* Kick the commit */
+ if (!ev_active(p->update_ev))
+ ev_schedule(p->update_ev);
+}
+
+static void
+bmp_route_monitor_notify(struct bmp_proto *p, struct bmp_stream *bs,
+ const net_addr *n, const struct rte *new, const struct rte_src *src)
+{
+ byte buf[BGP_MAX_EXT_MSG_LENGTH];
+ byte *end = bgp_bmp_encode_rte(bs->sender, buf, n, new, src);
+
+ btime delta_t = new ? current_time() - new->lastmod : 0;
+ btime timestamp = current_real_time() - delta_t;
+
+ if (end)
+ bmp_route_monitor_put_update(p, bs, buf, end - buf, timestamp);
+ else
+ log(L_WARN "%s: Cannot encode update for %N", p->p.name, n);
+}
+
+static void
+bmp_route_monitor_commit(void *p_)
+{
+ struct bmp_proto *p = p_;
+
+ if (!p->started)
+ return;
+
+ buffer payload
+ = bmp_buffer_alloc(p->buffer_mpool, DEFAULT_MEM_BLOCK_SIZE);
+
+ struct bmp_data_node *data, *data_next;
+ WALK_LIST_DELSAFE(data, data_next, p->update_msg_queue)
+ {
+ bmp_route_monitor_msg_serialize(&payload,
+ data->global_peer, data->policy,
+ data->remote_as, data->remote_id, true,
+ data->remote_ip, data->data, data->data_size,
+ data->timestamp);
+
+ bmp_schedule_tx_packet(p, bmp_buffer_data(&payload), bmp_buffer_pos(&payload));
+
+ bmp_buffer_flush(&payload);
+
+ mb_free(data->data);
+ rem_node(&data->n);
+ mb_free(data);
+ }
+
+ bmp_buffer_free(&payload);
+}
+
+static void
+bmp_route_monitor_end_of_rib(struct bmp_proto *p, struct bmp_stream *bs)
+{
+ TRACE(D_PACKETS, "Sending END-OF-RIB for %s.%s", bs->bgp->p.name, bs->sender->c.name);
+
+ byte rx_end_payload[DEFAULT_MEM_BLOCK_SIZE];
+ byte *pos = bgp_create_end_mark_(bs->sender, rx_end_payload + BGP_HEADER_LENGTH);
+ memset(rx_end_payload + BGP_MSG_HDR_MARKER_POS, 0xff,
+ BGP_MSG_HDR_MARKER_SIZE); // BGP UPDATE MSG marker
+ put_u16(rx_end_payload + BGP_MSG_HDR_LENGTH_POS, pos - rx_end_payload);
+ put_u8(rx_end_payload + BGP_MSG_HDR_TYPE_POS, PKT_UPDATE);
+
+ bmp_route_monitor_put_update(p, bs, rx_end_payload, pos - rx_end_payload, current_real_time());
+}
+
+static void
+bmp_send_peer_down_notif_msg(struct bmp_proto *p, const struct bgp_proto *bgp,
+ const byte *data, const size_t data_size)
+{
+ ASSERT(p->started);
+
+ const struct bgp_caps *remote_caps = bmp_get_bgp_remote_caps_ext(bgp);
+ bool is_global_instance_peer = bmp_is_peer_global_instance(bgp);
+ buffer payload
+ = bmp_buffer_alloc(p->buffer_mpool, DEFAULT_MEM_BLOCK_SIZE);
+ bmp_peer_down_notif_msg_serialize(&payload, is_global_instance_peer,
+ bgp->remote_as, bgp->remote_id,
+ remote_caps ? remote_caps->as4_support : bgp->as4_session,
+ bgp->remote_ip, data, data_size);
+ bmp_schedule_tx_packet(p, bmp_buffer_data(&payload), bmp_buffer_pos(&payload));
+
+ bmp_buffer_free(&payload);
+}
+
+static void
+bmp_peer_down_(struct bmp_proto *p, const struct bgp_proto *bgp,
+ int err_class, int err_code, int err_subcode, const byte *data, int length)
+{
+ if (!p->started)
+ return;
+
+ struct bmp_peer *bp = bmp_find_peer(p, bgp);
+ if (!bp)
+ return;
+
+ TRACE(D_STATES, "Peer down for %s", bgp->p.name);
+
+ uint bmp_code = 0;
+ uint fsm_code = 0;
+
+ switch (err_class)
+ {
+ case BE_BGP_RX:
+ bmp_code = BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION;
+ break;
+
+ case BE_BGP_TX:
+ case BE_AUTO_DOWN:
+ case BE_MAN_DOWN:
+ bmp_code = BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION;
+ break;
+
+ default:
+ bmp_code = BMP_PEER_DOWN_REASON_REMOTE_NO_NOTIFICATION;
+ length = 0;
+ break;
+ }
+
+ buffer payload = bmp_buffer_alloc(p->buffer_mpool, 1 + BGP_HEADER_LENGTH + 2 + length);
+ bmp_put_u8(&payload, bmp_code);
+
+ switch (bmp_code)
+ {
+ case BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION:
+ case BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION:
+ bmp_put_bgp_hdr(&payload, BGP_HEADER_LENGTH + 2 + length, PKT_NOTIFICATION);
+ bmp_put_u8(&payload, err_code);
+ bmp_put_u8(&payload, err_subcode);
+ bmp_put_data(&payload, data, length);
+ break;
+
+ case BMP_PEER_DOWN_REASON_LOCAL_NO_NOTIFICATION:
+ bmp_put_u16(&payload, fsm_code);
+ break;
+ }
+
+ bmp_send_peer_down_notif_msg(p, bgp, bmp_buffer_data(&payload), bmp_buffer_pos(&payload));
+
+ bmp_buffer_free(&payload);
+
+ bmp_remove_peer(p, bp);
+}
+
+void
+bmp_peer_down(const struct bgp_proto *bgp,
+ int err_class, int code, int subcode, const byte *data, int length)
+{
+ struct bmp_proto *p; node *n;
+ WALK_LIST2(p, n, bmp_proto_list, bmp_node)
+ bmp_peer_down_(p, bgp, err_class, code, subcode, data, length);
+}
+
+static void
+bmp_send_termination_msg(struct bmp_proto *p,
+ const enum bmp_term_reason reason)
+{
+ const size_t term_msg_hdr_size = BMP_TERM_INFO_TYPE_SIZE
+ + BMP_TERM_INFO_LEN_FIELD_SIZE
+ + BMP_TERM_REASON_CODE_SIZE;
+ const size_t term_msg_size = BMP_COMMON_HDR_SIZE + term_msg_hdr_size;
+ buffer stream = bmp_buffer_alloc(p->buffer_mpool, term_msg_size);
+ bmp_common_hdr_serialize(&stream, BMP_TERM_MSG, term_msg_hdr_size);
+ bmp_put_u16(&stream, BMP_TERM_INFO_REASON);
+ bmp_put_u16(&stream, BMP_TERM_REASON_CODE_SIZE); // 2-byte code indication the reason
+ bmp_put_u16(&stream, reason);
+ memcpy(p->sk->tbuf, bmp_buffer_data(&stream), bmp_buffer_pos(&stream));
+ IF_COND_TRUE_PRINT_ERR_MSG_AND_RETURN_OPT_VAL(
+ sk_send(p->sk, bmp_buffer_pos(&stream)) < 0,
+ "Failed to send BMP termination message"
+ );
+
+ bmp_buffer_free(&stream);
+}
+
+int
+bmp_preexport(struct channel *C UNUSED, rte *e)
+{
+ /* Reject non-direct routes */
+ if (e->src->proto != e->sender->proto)
+ return -1;
+
+ /* Reject non-BGP routes */
+ if (e->sender->channel != &channel_bgp)
+ return -1;
+
+ return 1;
+}
+
+static void
+bmp_rt_notify(struct proto *P, struct channel *c, struct network *net,
+ struct rte *new, struct rte *old)
+{
+ struct bmp_proto *p = (void *) P;
+
+ struct bgp_channel *src = (void *) (new ?: old)->sender;
+ struct bgp_proto *bgp = (void *) src->c.proto;
+ bool policy = (c->table == src->c.table);
+
+ /*
+ * We assume that we receive peer_up before the first route and peer_down
+ * synchronously with BGP session close. So if bmp_stream exists, the related
+ * BGP session is up and could be accessed. That may not be true in
+ * multithreaded setup.
+ */
+
+ struct bmp_stream *bs = bmp_find_stream(p, bgp, src->afi, policy);
+ if (!bs)
+ return;
+
+ bmp_route_monitor_notify(p, bs, net->n.addr, new, (new ?: old)->src);
+}
+
+static void
+bmp_feed_end(struct channel *c)
+{
+ struct bmp_proto *p = (void *) c->proto;
+
+ struct bmp_table *bt = bmp_find_table(p, c->table);
+ if (!bt)
+ return;
+
+ /*
+ * Unsynced streams are added in one moment during BMP session establishment,
+ * therefore we can assume that all unsynced streams (for given channel)
+ * already received full feed now and are synced.
+ *
+ * TODO: Use more efficent way to find bmp_stream from bmp_table
+ */
+
+ HASH_WALK(p->stream_map, next, bs)
+ {
+ if ((bs->table == bt) && !bs->sync)
+ {
+ bmp_route_monitor_end_of_rib(p, bs);
+ bs->sync = true;
+ }
+ }
+ HASH_WALK_END;
+}
+
+
+/**
+ * bmp_startup - enter established state
+ * @p: BMP instance
+ *
+ * The bgp_startup() function is called when the BMP session is established.
+ * It sends initiation and peer up messagages.
+ */
+static void
+bmp_startup(struct bmp_proto *p)
+{
+ ASSERT(!p->started);
+ p->started = true;
+ p->sock_err = 0;
+
+ TRACE(D_EVENTS, "BMP session established");
+
+ proto_notify_state(&p->p, PS_UP);
+
+ /* Send initiation message */
+ buffer payload = bmp_buffer_alloc(p->buffer_mpool, DEFAULT_MEM_BLOCK_SIZE);
+ bmp_init_msg_serialize(&payload, p->sys_descr, p->sys_name);
+ bmp_schedule_tx_packet(p, bmp_buffer_data(&payload), bmp_buffer_pos(&payload));
+ bmp_buffer_free(&payload);
+
+ /* Send Peer Up messages */
+ struct proto *peer;
+ WALK_LIST(peer, proto_list)
+ if ((peer->proto->class == PROTOCOL_BGP) && (peer->proto_state == PS_UP))
+ bmp_peer_init(p, (struct bgp_proto *) peer);
+}
+
+/**
+ * bmp_down - leave established state
+ * @p: BMP instance
+ *
+ * The bgp_down() function is called when the BMP session fails. The caller is
+ * responsible for changing protocol state.
+ */
+static void
+bmp_down(struct bmp_proto *p)
+{
+ ASSERT(p->started);
+ p->started = false;
+
+ TRACE(D_EVENTS, "BMP session closed");
+
+ /* Unregister existing peer structures */
+ HASH_WALK_DELSAFE(p->peer_map, next, bp)
+ {
+ bmp_remove_peer(p, bp);
+ }
+ HASH_WALK_END;
+
+ /* Removing peers should also remove all streams and tables */
+ ASSERT(!p->peer_map.count && !p->stream_map.count && !p->table_map.count);
+}
+
+/**
+ * bmp_connect - initiate an outgoing connection
+ * @p: BMP instance
+ *
+ * The bmp_connect() function creates the socket and initiates an outgoing TCP
+ * connection to the monitoring station. It is called to enter Connect state.
+ */
+static void
+bmp_connect(struct bmp_proto *p)
+{
+ ASSERT(!p->started);
+
+ sock *sk = sk_new(p->p.pool);
+ sk->type = SK_TCP_ACTIVE;
+ sk->saddr = p->local_addr;
+ sk->daddr = p->station_ip;
+ sk->dport = p->station_port;
+ sk->ttl = IP4_MAX_TTL;
+ sk->tos = IP_PREC_INTERNET_CONTROL;
+ sk->tbsize = BGP_TX_BUFFER_EXT_SIZE;
+ sk->tx_hook = bmp_connected;
+ sk->err_hook = bmp_sock_err;
+
+ p->sk = sk;
+ sk->data = p;
+
+ TRACE(D_EVENTS, "Connecting to %I port %u", sk->daddr, sk->dport);
+
+ int rc = sk_open(sk);
+
+ if (rc < 0)
+ sk_log_error(sk, p->p.name);
+
+ tm_start(p->connect_retry_timer, CONNECT_RETRY_TIME);
+}
+
+/* BMP connect successful event - switch from Connect to Established state */
+static void
+bmp_connected(struct birdsock *sk)
+{
+ struct bmp_proto *p = (void *) sk->data;
+
+ TRACE(D_EVENTS, "Connected");
+
+ sk->rx_hook = bmp_rx;
+ sk->tx_hook = bmp_tx;
+ tm_stop(p->connect_retry_timer);
+
+ bmp_startup(p);
+}
+
+/* BMP socket error event - switch from any state to Idle state */
+static void
+bmp_sock_err(sock *sk, int err)
+{
+ struct bmp_proto *p = sk->data;
+
+ p->sock_err = err;
+
+ if (err)
+ TRACE(D_EVENTS, "Connection lost (%M)", err);
+ else
+ TRACE(D_EVENTS, "Connection closed");
+
+ if (p->started)
+ bmp_down(p);
+
+ bmp_close_socket(p);
+ tm_start(p->connect_retry_timer, CONNECT_RETRY_TIME);
+
+ proto_notify_state(&p->p, PS_START);
+}
+
+/* BMP connect timeout event - switch from Idle/Connect state to Connect state */
+static void
+bmp_connection_retry(timer *t)
+{
+ struct bmp_proto *p = t->data;
+
+ if (p->started)
+ return;
+
+ bmp_close_socket(p);
+ bmp_connect(p);
+}
+
+static void
+bmp_close_socket(struct bmp_proto *p)
+{
+ rfree(p->sk);
+ p->sk = NULL;
+}
+
+
+static void
+bmp_postconfig(struct proto_config *CF)
+{
+ struct bmp_config *cf = (void *) CF;
+
+ /* Do not check templates at all */
+ if (cf->c.class == SYM_TEMPLATE)
+ return;
+
+ if (ipa_zero(cf->station_ip))
+ cf_error("Station IP address not specified");
+
+ if (!cf->station_port)
+ cf_error("Station port number not specified");
+}
+
+/** Configuration handle section **/
+static struct proto *
+bmp_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct bmp_proto *p = (void *) P;
+ struct bmp_config *cf = (void *) CF;
+
+ P->rt_notify = bmp_rt_notify;
+ P->preexport = bmp_preexport;
+ P->feed_end = bmp_feed_end;
+
+ p->cf = cf;
+ p->local_addr = cf->local_addr;
+ p->station_ip = cf->station_ip;
+ p->station_port = cf->station_port;
+ strcpy(p->sys_descr, cf->sys_descr);
+ strcpy(p->sys_name, cf->sys_name);
+ p->monitoring_rib.in_pre_policy = cf->monitoring_rib_in_pre_policy;
+ p->monitoring_rib.in_post_policy = cf->monitoring_rib_in_post_policy;
+
+ return P;
+}
+
+/**
+ * bmp_start - initialize internal resources of BMP implementation.
+ * NOTE: It does not connect to BMP collector yet.
+ */
+static int
+bmp_start(struct proto *P)
+{
+ struct bmp_proto *p = (void *) P;
+
+ p->buffer_mpool = rp_new(P->pool, "BMP Buffer");
+ p->map_mem_pool = rp_new(P->pool, "BMP Map");
+ p->tx_mem_pool = rp_new(P->pool, "BMP Tx");
+ p->update_msg_mem_pool = rp_new(P->pool, "BMP Update");
+ p->tx_ev = ev_new_init(p->p.pool, bmp_fire_tx, p);
+ p->update_ev = ev_new_init(p->p.pool, bmp_route_monitor_commit, p);
+ p->connect_retry_timer = tm_new_init(p->p.pool, bmp_connection_retry, p, 0, 0);
+ p->sk = NULL;
+
+ HASH_INIT(p->peer_map, P->pool, 4);
+ HASH_INIT(p->stream_map, P->pool, 4);
+ HASH_INIT(p->table_map, P->pool, 4);
+
+ init_list(&p->tx_queue);
+ init_list(&p->update_msg_queue);
+ p->started = false;
+ p->sock_err = 0;
+ add_tail(&bmp_proto_list, &p->bmp_node);
+
+ tm_start(p->connect_retry_timer, CONNECT_INIT_TIME);
+
+ return PS_START;
+}
+
+static int
+bmp_shutdown(struct proto *P)
+{
+ struct bmp_proto *p = (void *) P;
+
+ if (p->started)
+ {
+ bmp_send_termination_msg(p, BMP_TERM_REASON_ADM);
+ bmp_down(p);
+ }
+
+ p->sock_err = 0;
+ rem_node(&p->bmp_node);
+
+ return PS_DOWN;
+}
+
+static int
+bmp_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct bmp_proto *p = (void *) P;
+ const struct bmp_config *new = (void *) CF;
+ const struct bmp_config *old = p->cf;
+
+ int needs_restart = bstrcmp(new->sys_descr, old->sys_descr)
+ || bstrcmp(new->sys_name, old->sys_name)
+ || !ipa_equal(new->local_addr, old->local_addr)
+ || !ipa_equal(new->station_ip, old->station_ip)
+ || (new->station_port != old->station_port)
+ || (new->monitoring_rib_in_pre_policy != old->monitoring_rib_in_pre_policy)
+ || (new->monitoring_rib_in_post_policy != old->monitoring_rib_in_post_policy);
+
+ /* If there is any change, restart the protocol */
+ if (needs_restart)
+ return 0;
+
+ /* We must update our copy of configuration ptr */
+ p->cf = new;
+
+ return 1;
+}
+
+static void
+bmp_get_status(struct proto *P, byte *buf)
+{
+ struct bmp_proto *p = (void *) P;
+
+ if (P->proto_state == PS_DOWN)
+ bsprintf(buf, "Down");
+ else
+ {
+ const char *state = !p->started ? (!p->sk ? "Idle" : "Connect") : "Established";
+
+ if (!p->sock_err)
+ bsprintf(buf, "%s", state);
+ else
+ bsprintf(buf, "%-14s%s %M", state, "Error:", p->sock_err);
+ }
+}
+
+static void
+bmp_show_proto_info(struct proto *P)
+{
+ struct bmp_proto *p = (void *) P;
+
+ if (P->proto_state != PS_DOWN)
+ {
+ cli_msg(-1006, " %-19s %I", "Station address:", p->station_ip);
+ cli_msg(-1006, " %-19s %u", "Station port:", p->station_port);
+
+ if (!ipa_zero(p->local_addr))
+ cli_msg(-1006, " %-19s %I", "Local address:", p->local_addr);
+
+ if (p->sock_err)
+ cli_msg(-1006, " %-19s %M", "Last error:", p->sock_err);
+ }
+}
+
+struct protocol proto_bmp = {
+ .name = "BMP",
+ .template = "bmp%d",
+ .class = PROTOCOL_BMP,
+ .proto_size = sizeof(struct bmp_proto),
+ .config_size = sizeof(struct bmp_config),
+ .postconfig = bmp_postconfig,
+ .init = bmp_init,
+ .start = bmp_start,
+ .shutdown = bmp_shutdown,
+ .reconfigure = bmp_reconfigure,
+ .get_status = bmp_get_status,
+ .show_proto_info = bmp_show_proto_info,
+};
+
+void
+bmp_build(void)
+{
+ proto_build(&proto_bmp);
+}
diff --git a/proto/bmp/bmp.h b/proto/bmp/bmp.h
new file mode 100644
index 00000000..2d700c25
--- /dev/null
+++ b/proto/bmp/bmp.h
@@ -0,0 +1,129 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_BMP_H_
+#define _BIRD_BMP_H_
+
+#include "nest/bird.h"
+#include "nest/protocol.h"
+#include "lib/lists.h"
+#include "nest/route.h"
+#include "lib/event.h"
+#include "lib/hash.h"
+#include "lib/socket.h"
+#include "proto/bmp/map.h"
+
+#include <stdbool.h>
+
+// Max length of MIB-II description object
+#define MIB_II_STR_LEN 255
+
+// The following fields of this structure controls whether there will be put
+// specific routes into Route Monitoring message and send to BMP collector
+struct monitoring_rib {
+ bool in_pre_policy; // Monitoring pre-policy Adj-Rib-In
+ bool in_post_policy; // Monitoring post-policy Adj-Rib-In
+ bool local; // Monitoring Local Rib
+};
+
+struct bmp_config {
+ struct proto_config c;
+ const char *sys_descr; // sysDescr MIB-II [RFC1213] object
+ const char *sys_name; // sysName MIB-II [RFC1213] object
+ ip_addr local_addr; // Local IP address
+ ip_addr station_ip; // Monitoring station address
+ u16 station_port; // Monitoring station TCP port
+ bool monitoring_rib_in_pre_policy; // Route monitoring pre-policy Adj-Rib-In
+ bool monitoring_rib_in_post_policy; // Route monitoring post-policy Adj-Rib-In
+};
+
+/* Forward declarations */
+struct bgp_proto;
+struct bmp_proto;
+
+struct bmp_proto {
+ struct proto p; // Parent proto
+ const struct bmp_config *cf; // Shortcut to BMP configuration
+ node bmp_node; // Node in bmp_proto_list
+
+ HASH(struct bmp_peer) peer_map;
+ HASH(struct bmp_stream) stream_map;
+ HASH(struct bmp_table) table_map;
+
+ sock *sk; // TCP connection
+ event *tx_ev; // TX event
+ event *update_ev; // Update event
+ char sys_descr[MIB_II_STR_LEN]; // sysDescr MIB-II [RFC1213] object
+ char sys_name[MIB_II_STR_LEN]; // sysName MIB-II [RFC1213] object
+ ip_addr local_addr; // Source local IP address
+ ip_addr station_ip; // Monitoring station IP address
+ u16 station_port; // Monitoring station TCP port
+ struct monitoring_rib monitoring_rib;
+ // Below fields are for internal use
+ // struct bmp_peer_map bgp_peers; // Stores 'bgp_proto' structure per BGP peer
+ pool *buffer_mpool; // Memory pool used for BMP buffer allocations
+ pool *map_mem_pool; // Memory pool used for BMP map allocations
+ pool *tx_mem_pool; // Memory pool used for packet allocations designated to BMP collector
+ pool *update_msg_mem_pool; // Memory pool used for BPG UPDATE MSG allocations
+ list tx_queue; // Stores queued packets going to be sent
+ timer *connect_retry_timer; // Timer for retrying connection to the BMP collector
+ list update_msg_queue; // Stores all composed BGP UPDATE MSGs
+ bool started; // Flag that stores running status of BMP instance
+ int sock_err; // Last socket error code
+};
+
+struct bmp_peer {
+ struct bgp_proto *bgp;
+ struct bmp_peer *next;
+ list streams;
+};
+
+struct bmp_stream {
+ node n;
+ struct bgp_proto *bgp;
+ u32 key;
+ bool sync;
+ struct bmp_stream *next;
+ struct bmp_table *table;
+ struct bgp_channel *sender;
+};
+
+struct bmp_table {
+ struct rtable *table;
+ struct bmp_table *next;
+ struct channel *channel;
+ u32 uc;
+};
+
+
+#ifdef CONFIG_BMP
+
+/**
+ * bmp_peer_up - send notification that BGP peer connection is established
+ */
+void
+bmp_peer_up(struct bgp_proto *bgp,
+ const byte *tx_open_msg, uint tx_open_length,
+ const byte *rx_open_msg, uint rx_open_length);
+
+/**
+ * bmp_peer_down - send notification that BGP peer connection is not in
+ * established state
+ */
+void
+bmp_peer_down(const struct bgp_proto *bgp, int err_class, int code, int subcode, const byte *data, int length);
+
+
+#else /* BMP build disabled */
+
+static inline void bmp_peer_up(struct bgp_proto *bgp UNUSED, const byte *tx_open_msg UNUSED, uint tx_open_length UNUSED, const byte *rx_open_msg UNUSED, uint rx_open_length UNUSED) { }
+static inline void bmp_peer_down(const struct bgp_proto *bgp UNUSED, const int err_class UNUSED, int code UNUSED, int subcode UNUSED, const byte *data UNUSED, int length UNUSED) { }
+
+#endif /* CONFIG_BMP */
+
+#endif /* _BIRD_BMP_H_ */
diff --git a/proto/bmp/buffer.c b/proto/bmp/buffer.c
new file mode 100644
index 00000000..be9dd698
--- /dev/null
+++ b/proto/bmp/buffer.c
@@ -0,0 +1,65 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "proto/bmp/buffer.h"
+
+buffer
+bmp_buffer_alloc(pool *ppool, const size_t n)
+{
+ buffer buf;
+ buf.start = mb_alloc(ppool, n);
+ buf.pos = buf.start;
+ buf.end = buf.start + n;
+ return buf;
+}
+
+void
+bmp_buffer_free(buffer *buf)
+{
+ mb_free(buf->start);
+ buf->start = buf->pos = buf->end = NULL;
+}
+
+/**
+ * @brief bmp_buffer_grow
+ * @param buf - buffer to grow
+ * @param n - required amount of available space
+ * Resize buffer in a way that there is at least @n bytes of available space.
+ */
+static void
+bmp_buffer_grow(buffer *buf, const size_t n)
+{
+ size_t pos = bmp_buffer_pos(buf);
+ size_t size = bmp_buffer_size(buf);
+ size_t req = pos + n;
+
+ while (size < req)
+ size = size * 3 / 2;
+
+ buf->start = mb_realloc(buf->start, size);
+ buf->pos = buf->start + pos;
+ buf->end = buf->start + size;
+}
+
+void
+bmp_buffer_need(buffer *buf, const size_t n)
+{
+ if (bmp_buffer_avail(buf) < n)
+ bmp_buffer_grow(buf, n);
+}
+
+void
+bmp_put_data(buffer *buf, const void *src, const size_t n)
+{
+ if (!n)
+ return;
+
+ bmp_buffer_need(buf, n);
+ memcpy(buf->pos, src, n);
+ buf->pos += n;
+}
diff --git a/proto/bmp/buffer.h b/proto/bmp/buffer.h
new file mode 100644
index 00000000..f752cf5d
--- /dev/null
+++ b/proto/bmp/buffer.h
@@ -0,0 +1,77 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_BMP_BUFFER_H_
+#define _BIRD_BMP_BUFFER_H_
+
+#include "proto/bmp/bmp.h"
+
+#include <stdlib.h>
+
+#include "lib/resource.h"
+
+buffer
+bmp_buffer_alloc(pool *ppool, const size_t n);
+
+void
+bmp_buffer_free(buffer *buf);
+
+static inline void
+bmp_buffer_flush(buffer *buf)
+{
+ buf->pos = buf->start;
+}
+
+static inline size_t
+bmp_buffer_size(const buffer *buf)
+{
+ return buf->end - buf->start;
+}
+
+static inline size_t
+bmp_buffer_avail(const buffer *buf)
+{
+ return buf->end - buf->pos;
+}
+
+static inline size_t
+bmp_buffer_pos(const buffer *buf)
+{
+ return buf->pos - buf->start;
+}
+
+static inline byte *
+bmp_buffer_data(const buffer *buf)
+{
+ return buf->start;
+}
+
+void
+bmp_buffer_need(buffer *buf, const size_t n);
+
+// Idea for following macros has been taken from |proto/mrt/mrt.c|
+#define BMP_DEFINE_PUT_FUNC(S, T) \
+ static inline void \
+ bmp_put_##S(buffer *b, const T x) \
+ { \
+ bmp_buffer_need(b, sizeof(T)); \
+ put_##S(b->pos, x); \
+ b->pos += sizeof(T); \
+ }
+
+BMP_DEFINE_PUT_FUNC(u8, u8)
+BMP_DEFINE_PUT_FUNC(u16, u16)
+BMP_DEFINE_PUT_FUNC(u32, u32)
+BMP_DEFINE_PUT_FUNC(u64, u64)
+BMP_DEFINE_PUT_FUNC(ip4, ip4_addr)
+BMP_DEFINE_PUT_FUNC(ip6, ip6_addr)
+
+void
+bmp_put_data(buffer *buf, const void *src, const size_t n);
+
+#endif /* _BIRD_BMP_BUFFER_H_ */
diff --git a/proto/bmp/config.Y b/proto/bmp/config.Y
new file mode 100644
index 00000000..acb0c4d9
--- /dev/null
+++ b/proto/bmp/config.Y
@@ -0,0 +1,78 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/bmp/bmp.h"
+
+CF_DEFINES
+
+#define BMP_CFG ((struct bmp_config *) this_proto)
+
+CF_DECLS
+
+CF_KEYWORDS(BMP, DESCRIPTION, ENABLED, IN, IP, MONITORING, NAME, PORT,
+ PRE_POLICY, POST_POLICY, RIB, STATION, SYSTEM)
+
+CF_GRAMMAR
+
+proto: bmp_proto '}' ;
+
+bmp_proto_start: proto_start BMP {
+ this_proto = proto_config_new(&proto_bmp, $1);
+ BMP_CFG->sys_descr = "Not defined";
+ BMP_CFG->sys_name = "Not defined";
+ }
+ ;
+
+bmp_station_address:
+ /* empty */
+ | bmp_station_address IP ipa {
+ if (ipa_zero($3))
+ cf_error("Invalid BMP monitoring station IP address");
+ BMP_CFG->station_ip = $3;
+ }
+ | bmp_station_address PORT expr {
+ if (($3 < 1) || ($3 > 65535))
+ cf_error("Invalid BMP monitoring station port number");
+ BMP_CFG->station_port = $3;
+ }
+ ;
+
+bmp_proto:
+ bmp_proto_start proto_name '{'
+ | bmp_proto proto_item ';'
+ | bmp_proto LOCAL ADDRESS ipa ';' {
+ BMP_CFG->local_addr = $4;
+ }
+ | bmp_proto STATION ADDRESS bmp_station_address ';'
+ | bmp_proto SYSTEM DESCRIPTION text ';' {
+ if (!$4 || (strlen($4) == 0))
+ cf_error("String is empty");
+ else if (strlen($4) > 255)
+ cf_error("Invalid string length");
+ BMP_CFG->sys_descr = $4;
+ }
+ | bmp_proto SYSTEM NAME text ';' {
+ if (!$4 || (strlen($4) == 0))
+ cf_error("String is empty");
+ else if (strlen($4) > 255)
+ cf_error("Invalid string length");
+ BMP_CFG->sys_name = $4;
+ }
+ | bmp_proto MONITORING RIB IN PRE_POLICY bool ';' {
+ BMP_CFG->monitoring_rib_in_pre_policy = $6;
+ }
+ | bmp_proto MONITORING RIB IN POST_POLICY bool ';' {
+ BMP_CFG->monitoring_rib_in_post_policy = $6;
+ }
+ ;
+
+CF_CODE
+
+CF_END
diff --git a/proto/bmp/map.c b/proto/bmp/map.c
new file mode 100644
index 00000000..16e714fd
--- /dev/null
+++ b/proto/bmp/map.c
@@ -0,0 +1,119 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "proto/bmp/map.h"
+
+/* Peer Index Table */
+#define PEER_KEY(n) (n)->peer_as, (n)->peer_ip
+#define PEER_NEXT(n) (n)->next
+#define PEER_EQ(as1,ip1,as2,ip2) \
+ (as1) == (as2) && ipa_equal(ip1, ip2)
+#define PEER_FN(as,ip) ipa_hash(ip)
+
+#define PEER_REHASH bmp_peer_rehash
+#define PEER_PARAMS /8, *2, 2, 2, 6, 20
+
+HASH_DEFINE_REHASH_FN(PEER, struct bmp_peer_map_key)
+
+#define PEER_INIT_ORDER 6
+
+void
+bmp_peer_map_init(struct bmp_peer_map *map, pool *mpool)
+{
+ map->mpool = mpool;
+ HASH_INIT(map->peer_hash, map->mpool, PEER_INIT_ORDER);
+}
+
+struct bmp_peer_map_key
+bmp_peer_map_key_create(const ip_addr peer_ip, const u32 peer_as)
+{
+ struct bmp_peer_map_key key;
+ key.next = NULL;
+ key.peer_ip = peer_ip;
+ key.peer_as = peer_as;
+
+ return key;
+}
+
+void
+bmp_peer_map_flush(struct bmp_peer_map *map)
+{
+ struct bmp_peer_map_entry *entry;
+ HASH_WALK_DELSAFE(map->peer_hash, next, e)
+ {
+ entry = (struct bmp_peer_map_entry *) e;
+ mb_free(entry->data.buf);
+ HASH_DELETE(map->peer_hash, PEER, PEER_KEY(&entry->key));
+ mb_free(entry);
+ }
+ HASH_WALK_DELSAFE_END;
+
+ HASH_MAY_RESIZE_DOWN(map->peer_hash, PEER, map->mpool);
+}
+
+void
+bmp_peer_map_free(struct bmp_peer_map *map)
+{
+ bmp_peer_map_flush(map);
+ HASH_FREE(map->peer_hash);
+}
+
+void
+bmp_peer_map_insert(struct bmp_peer_map *map, const struct bmp_peer_map_key key,
+ const byte *data, const size_t data_size)
+{
+ struct bmp_peer_map_entry *entry
+ = (void *) HASH_FIND(map->peer_hash, PEER, PEER_KEY(&key));
+
+ if (entry)
+ {
+ mb_free(entry->data.buf);
+ entry->data.buf = mb_alloc(map->mpool, data_size);
+ memcpy(entry->data.buf, data, data_size);
+ entry->data.buf_size = data_size;
+ return;
+ }
+
+ entry = mb_alloc(map->mpool, sizeof (struct bmp_peer_map_entry));
+ entry->data.buf = mb_alloc(map->mpool, data_size);
+ memcpy(entry->data.buf, data, data_size);
+ entry->data.buf_size = data_size;
+ entry->key = key;
+ HASH_INSERT2(map->peer_hash, PEER, map->mpool, &entry->key);
+}
+
+void
+bmp_peer_map_remove(struct bmp_peer_map *map, const struct bmp_peer_map_key key)
+{
+ struct bmp_peer_map_entry *entry
+ = (void *) HASH_DELETE(map->peer_hash, PEER, PEER_KEY(&key));
+
+ if (!entry)
+ return;
+
+ mb_free(entry->data.buf);
+ mb_free(entry);
+}
+
+const struct bmp_peer_map_entry *
+bmp_peer_map_get(struct bmp_peer_map *map, const struct bmp_peer_map_key key)
+{
+ return (struct bmp_peer_map_entry *) HASH_FIND(map->peer_hash, PEER, PEER_KEY(&key));
+}
+
+void
+bmp_peer_map_walk(const struct bmp_peer_map *map, bmp_peer_map_walk_action action, void *arg)
+{
+ struct bmp_peer_map_entry *entry;
+ HASH_WALK_FILTER(map->peer_hash, next, e, _)
+ {
+ entry = (struct bmp_peer_map_entry *) e;
+ action(entry->key, entry->data.buf, entry->data.buf_size, arg);
+ }
+ HASH_WALK_FILTER_END;
+}
diff --git a/proto/bmp/map.h b/proto/bmp/map.h
new file mode 100644
index 00000000..8e7ea695
--- /dev/null
+++ b/proto/bmp/map.h
@@ -0,0 +1,68 @@
+/*
+ * BIRD -- The BGP Monitoring Protocol (BMP)
+ *
+ * (c) 2020 Akamai Technologies, Inc. (Pawel Maslanka, pmaslank@akamai.com)
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * This map implementation binds peer IP address as container key with custom data.
+ */
+#ifndef _BIRD_BMP_MAP_H_
+#define _BIRD_BMP_MAP_H_
+
+#include "nest/bird.h"
+#include "lib/hash.h"
+#include "lib/resource.h"
+
+struct bmp_peer_map_key {
+ struct bmp_peer_map_key *next;
+ ip_addr peer_ip;
+ u32 peer_as;
+};
+
+struct bmp_peer_map_data {
+ void *buf;
+ size_t buf_size;
+};
+
+struct bmp_peer_map_entry {
+ struct bmp_peer_map_key key;
+ struct bmp_peer_map_data data;
+};
+
+struct bmp_peer_map {
+ pool *mpool; // Memory pool for peer entries in peer_hash
+ HASH(struct bmp_peer_map_key) peer_hash; // Hash for peers to find the index
+};
+
+void
+bmp_peer_map_init(struct bmp_peer_map *map, pool *mpool);
+
+struct bmp_peer_map_key
+bmp_peer_map_key_create(const ip_addr peer_ip, const u32 peer_as);
+
+void
+bmp_peer_map_free(struct bmp_peer_map *map);
+
+void
+bmp_peer_map_flush(struct bmp_peer_map *map);
+
+void
+bmp_peer_map_insert(struct bmp_peer_map *map, const struct bmp_peer_map_key key,
+ const byte *data, const size_t data_size);
+
+void
+bmp_peer_map_remove(struct bmp_peer_map *map, const struct bmp_peer_map_key key);
+
+const struct bmp_peer_map_entry *
+bmp_peer_map_get(struct bmp_peer_map *map, const struct bmp_peer_map_key key);
+
+typedef void (*bmp_peer_map_walk_action)(const struct bmp_peer_map_key key,
+ const byte *data, const size_t data_size, void *arg);
+
+void
+bmp_peer_map_walk(const struct bmp_peer_map *map, bmp_peer_map_walk_action action, void *arg);
+
+#endif /* _BIRD_BMP_MAP_H_ */
diff --git a/proto/l3vpn/Doc b/proto/l3vpn/Doc
new file mode 100644
index 00000000..ca2fd130
--- /dev/null
+++ b/proto/l3vpn/Doc
@@ -0,0 +1 @@
+S l3vpn.c
diff --git a/proto/l3vpn/Makefile b/proto/l3vpn/Makefile
new file mode 100644
index 00000000..c109b231
--- /dev/null
+++ b/proto/l3vpn/Makefile
@@ -0,0 +1,6 @@
+src := l3vpn.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/l3vpn/config.Y b/proto/l3vpn/config.Y
new file mode 100644
index 00000000..e16e0c48
--- /dev/null
+++ b/proto/l3vpn/config.Y
@@ -0,0 +1,101 @@
+/*
+ * BIRD -- BGP/MPLS IP Virtual Private Networks (L3VPN)
+ *
+ * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/l3vpn/l3vpn.h"
+
+
+CF_DEFINES
+
+#define L3VPN_CFG ((struct l3vpn_config *) this_proto)
+
+static void
+f_tree_only_rt(struct f_tree *t)
+{
+ /* Parsed degenerate trees have link to the last node in t->right */
+ t->right = NULL;
+
+ while (t)
+ {
+ uint type1 = t->from.val.ec >> 48;
+ uint type2 = t->to.val.ec >> 48;
+ ASSERT(type1 == type2);
+
+ if (!ec_type_is_rt(type1))
+ cf_error("Extended community is not route target");
+
+ ASSERT(!t->right);
+ t = t->left;
+ }
+}
+
+
+CF_DECLS
+
+CF_KEYWORDS(L3VPN, ROUTE, IMPORT, EXPORT, TARGET, RD, DISTINGUISHER)
+
+%type <e> l3vpn_targets
+%type <cc> l3vpn_channel_start l3vpn_channel
+
+CF_GRAMMAR
+
+proto: l3vpn_proto;
+
+
+l3vpn_channel_start: net_type_base
+{
+ /* Redefining proto_channel to change default values */
+ $$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto);
+ if (!this_channel->copy)
+ {
+ this_channel->out_filter = FILTER_ACCEPT;
+ this_channel->preference = net_val_match($1, NB_IP) ?
+ DEF_PREF_L3VPN_IMPORT :
+ DEF_PREF_L3VPN_EXPORT;
+ }
+};
+
+l3vpn_channel: l3vpn_channel_start channel_opt_list channel_end;
+
+l3vpn_proto_start: proto_start L3VPN
+{
+ this_proto = proto_config_new(&proto_l3vpn, $1);
+};
+
+
+l3vpn_proto_item:
+ proto_item
+ | l3vpn_channel
+ | mpls_channel
+ | RD VPN_RD { L3VPN_CFG->rd = $2; }
+ | ROUTE DISTINGUISHER VPN_RD { L3VPN_CFG->rd = $3; }
+ | IMPORT TARGET l3vpn_targets { L3VPN_CFG->import_target = $3; }
+ | EXPORT TARGET l3vpn_targets { L3VPN_CFG->export_target = $3; }
+ | ROUTE TARGET l3vpn_targets { L3VPN_CFG->import_target = L3VPN_CFG->export_target = $3; }
+ ;
+
+l3vpn_proto_opts:
+ /* empty */
+ | l3vpn_proto_opts l3vpn_proto_item ';'
+ ;
+
+l3vpn_proto:
+ l3vpn_proto_start proto_name '{' l3vpn_proto_opts '}';
+
+
+l3vpn_targets:
+ ec_item { f_tree_only_rt($1); $$ = $1; }
+ | '[' ec_items ']' { f_tree_only_rt($2); $$ = build_tree($2); }
+ ;
+
+
+CF_CODE
+
+CF_END
diff --git a/proto/l3vpn/l3vpn.c b/proto/l3vpn/l3vpn.c
new file mode 100644
index 00000000..8b56cd73
--- /dev/null
+++ b/proto/l3vpn/l3vpn.c
@@ -0,0 +1,491 @@
+/*
+ * BIRD -- BGP/MPLS IP Virtual Private Networks (L3VPN)
+ *
+ * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: L3VPN
+ *
+ * The L3VPN protocol implements RFC 4364 BGP/MPLS VPNs using MPLS backbone.
+ * It works similarly to pipe. It connects IP table (one per VRF) with (global)
+ * VPN table. Routes passed from VPN table to IP table are stripped of RD and
+ * filtered by import targets, routes passed in the other direction are extended
+ * with RD, MPLS labels and export targets in extended communities. Separate
+ * MPLS channel is used to announce MPLS routes for the labels.
+ *
+ * Note that in contrast to the pipe protocol, L3VPN protocol has both IPv4 and
+ * IPv6 channels in one instance, Also both IP and VPN channels are presented to
+ * users as separate channels, although that will change in the future.
+ *
+ * The L3VPN protocol has different default preferences on IP and VPN sides.
+ * The reason is that in import direction (VPN->IP) routes should have lower
+ * preferences that ones received from local CE (perhaps by EBGP), while in
+ * export direction (IP->VPN) routes should have higher preferences that ones
+ * received from remote PEs (by IBGP).
+ *
+ * Supported standards:
+ * RFC 4364 - BGP/MPLS IP Virtual Private Networks (L3VPN)
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/route.h"
+#include "nest/mpls.h"
+#include "nest/cli.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "filter/data.h"
+#include "lib/string.h"
+
+#include "l3vpn.h"
+
+#include "proto/bgp/bgp.h"
+
+/*
+ * TODO:
+ * - check for simple nodes in export route
+ * - replace pair of channels with shared channel for one address family
+ * - improve route comparisons in VRFs
+ * - optional import/export target all
+ * - optional support for route origins
+ * - optional automatic assignment of RDs
+ * - MPLS-in-IP encapsulation
+ */
+
+#define EA_BGP_NEXT_HOP EA_CODE(PROTOCOL_BGP, BA_NEXT_HOP)
+#define EA_BGP_EXT_COMMUNITY EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)
+#define EA_BGP_MPLS_LABEL_STACK EA_CODE(PROTOCOL_BGP, BA_MPLS_LABEL_STACK)
+
+static inline const struct adata * ea_get_adata(ea_list *e, uint id)
+{ eattr *a = ea_find(e, id); return a ? a->u.ptr : &null_adata; }
+
+static inline int
+mpls_valid_nexthop(const rta *a)
+{
+ /* MPLS does not support special blackhole targets */
+ if (a->dest != RTD_UNICAST)
+ return 0;
+
+ /* MPLS does not support ARP / neighbor discovery */
+ for (const struct nexthop *nh = &a->nh; nh ; nh = nh->next)
+ if (ipa_zero(nh->gw) && (nh->iface->flags & IF_MULTIACCESS))
+ return 0;
+
+ return 1;
+}
+
+static int
+l3vpn_import_targets(struct l3vpn_proto *p, const struct adata *list)
+{
+ return (p->import_target_one) ?
+ ec_set_contains(list, p->import_target->from.val.ec) :
+ eclist_match_set(list, p->import_target);
+}
+
+static struct adata *
+l3vpn_export_targets(struct l3vpn_proto *p, const struct adata *src)
+{
+ u32 *s = int_set_get_data(src);
+ int len = int_set_get_size(src);
+
+ struct adata *dst = lp_alloc(tmp_linpool, sizeof(struct adata) + (len + p->export_target_length) * sizeof(u32));
+ u32 *d = int_set_get_data(dst);
+ int end = 0;
+
+ for (int i = 0; i < len; i += 2)
+ {
+ /* Remove existing route targets */
+ uint type = s[i] >> 16;
+ if (ec_type_is_rt(type))
+ continue;
+
+ d[end++] = s[i];
+ d[end++] = s[i+1];
+ }
+
+ /* Add new route targets */
+ memcpy(d + end, p->export_target_data, p->export_target_length * sizeof(u32));
+ end += p->export_target_length;
+
+ /* Set length */
+ dst->length = end * sizeof(u32);
+
+ return dst;
+}
+
+static inline void
+l3vpn_prepare_import_targets(struct l3vpn_proto *p)
+{
+ const struct f_tree *t = p->import_target;
+ p->import_target_one = !t->left && !t->right && (t->from.val.ec == t->to.val.ec);
+}
+
+static void
+l3vpn_add_ec(const struct f_tree *t, void *P)
+{
+ struct l3vpn_proto *p = P;
+ ec_put(p->export_target_data, p->export_target_length, t->from.val.ec);
+ p->export_target_length += 2;
+}
+
+static void
+l3vpn_prepare_export_targets(struct l3vpn_proto *p)
+{
+ if (p->export_target_data)
+ mb_free(p->export_target_data);
+
+ uint len = 2 * tree_node_count(p->export_target);
+ p->export_target_data = mb_alloc(p->p.pool, len * sizeof(u32));
+ p->export_target_length = 0;
+ tree_walk(p->export_target, l3vpn_add_ec, p);
+ ASSERT(p->export_target_length == len);
+}
+
+static void
+l3vpn_rt_notify(struct proto *P, struct channel *c0, net *net, rte *new, rte *old UNUSED)
+{
+ struct l3vpn_proto *p = (void *) P;
+ struct rte_src *src = NULL;
+ struct channel *dst = NULL;
+ int export;
+
+ const net_addr *n0 = net->n.addr;
+ net_addr *n = alloca(sizeof(net_addr_vpn6));
+
+ switch (c0->net_type)
+ {
+ case NET_IP4:
+ net_fill_vpn4(n, net4_prefix(n0), net4_pxlen(n0), p->rd);
+ src = p->p.main_source;
+ dst = p->vpn4_channel;
+ export = 1;
+ break;
+
+ case NET_IP6:
+ net_fill_vpn6(n, net6_prefix(n0), net6_pxlen(n0), p->rd);
+ src = p->p.main_source;
+ dst = p->vpn6_channel;
+ export = 1;
+ break;
+
+ case NET_VPN4:
+ net_fill_ip4(n, net4_prefix(n0), net4_pxlen(n0));
+ src = rt_get_source(&p->p, ((const net_addr_vpn4 *) n0)->rd);
+ dst = p->ip4_channel;
+ export = 0;
+ break;
+
+ case NET_VPN6:
+ net_fill_ip6(n, net6_prefix(n0), net6_pxlen(n0));
+ src = rt_get_source(&p->p, ((const net_addr_vpn6 *) n0)->rd);
+ dst = p->ip6_channel;
+ export = 0;
+ break;
+
+ case NET_MPLS:
+ return;
+ }
+
+ if (new)
+ {
+ const rta *a0 = new->attrs;
+ rta *a = alloca(RTA_MAX_SIZE);
+ *a = (rta) {
+ .source = RTS_L3VPN,
+ .scope = SCOPE_UNIVERSE,
+ .dest = a0->dest,
+ .pref = dst->preference,
+ .eattrs = a0->eattrs
+ };
+
+ nexthop_link(a, &a0->nh);
+
+ /* Do not keep original labels, we may assign new ones */
+ ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_MPLS_LABEL);
+ ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_MPLS_POLICY);
+
+ /* We are crossing VRF boundary, NEXT_HOP is no longer valid */
+ ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_BGP_NEXT_HOP);
+ ea_unset_attr(&a->eattrs, tmp_linpool, 0, EA_BGP_MPLS_LABEL_STACK);
+
+ if (export)
+ {
+ struct mpls_channel *mc = (void *) p->p.mpls_channel;
+ ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_POLICY, 0, EAF_TYPE_INT, mc->label_policy);
+
+ struct adata *ad = l3vpn_export_targets(p, ea_get_adata(a0->eattrs, EA_BGP_EXT_COMMUNITY));
+ ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0, EAF_TYPE_EC_SET, ad);
+
+ /* Replace MPLS-incompatible nexthop with lookup in VRF table */
+ if (!mpls_valid_nexthop(a) && p->p.vrf)
+ {
+ a->dest = RTD_UNICAST;
+ a->nh = (struct nexthop) { .iface = p->p.vrf };
+ }
+ }
+
+ /* Keep original IGP metric as a base for L3VPN metric */
+ if (!export)
+ a->igp_metric = a0->igp_metric;
+
+ rte *e = rte_get_temp(a, src);
+ rte_update2(dst, n, e, src);
+ }
+ else
+ {
+ rte_update2(dst, n, NULL, src);
+ }
+}
+
+
+static int
+l3vpn_preexport(struct channel *C, rte *e)
+{
+ struct l3vpn_proto *p = (void *) C->proto;
+ struct proto *pp = e->sender->proto;
+
+ if (pp == C->proto)
+ return -1; /* Avoid local loops automatically */
+
+ switch (C->net_type)
+ {
+ case NET_IP4:
+ case NET_IP6:
+ return 0;
+
+ case NET_VPN4:
+ case NET_VPN6:
+ return l3vpn_import_targets(p, ea_get_adata(e->attrs->eattrs, EA_BGP_EXT_COMMUNITY)) ? 0 : -1;
+
+ case NET_MPLS:
+ return -1;
+
+ default:
+ bug("invalid type");
+ }
+}
+
+static void
+l3vpn_reload_routes(struct channel *C)
+{
+ struct l3vpn_proto *p = (void *) C->proto;
+
+ /* Route reload on one channel is just refeed on the other */
+ switch (C->net_type)
+ {
+ case NET_IP4:
+ channel_request_feeding(p->vpn4_channel);
+ break;
+
+ case NET_IP6:
+ channel_request_feeding(p->vpn6_channel);
+ break;
+
+ case NET_VPN4:
+ channel_request_feeding(p->ip4_channel);
+ break;
+
+ case NET_VPN6:
+ channel_request_feeding(p->ip6_channel);
+ break;
+
+ case NET_MPLS:
+ channel_request_feeding(p->ip4_channel);
+ channel_request_feeding(p->ip6_channel);
+ break;
+ }
+}
+
+static inline u32
+l3vpn_metric(rte *e)
+{
+ u32 metric = ea_get_int(e->attrs->eattrs, EA_GEN_IGP_METRIC, e->attrs->igp_metric);
+ return MIN(metric, IGP_METRIC_UNKNOWN);
+}
+
+static int
+l3vpn_rte_better(rte *new, rte *old)
+{
+ /* This is hack, we should have full BGP-style comparison */
+ return l3vpn_metric(new) < l3vpn_metric(old);
+}
+
+static void
+l3vpn_postconfig(struct proto_config *CF)
+{
+ struct l3vpn_config *cf = (void *) CF;
+
+ if (!!proto_cf_find_channel(CF, NET_IP4) != !!proto_cf_find_channel(CF, NET_VPN4))
+ cf_error("For IPv4 L3VPN, both IPv4 and VPNv4 channels must be specified");
+
+ if (!!proto_cf_find_channel(CF, NET_IP6) != !!proto_cf_find_channel(CF, NET_VPN6))
+ cf_error("For IPv6 L3VPN, both IPv6 and VPNv6 channels must be specified");
+
+ if (!proto_cf_find_channel(CF, NET_MPLS))
+ cf_error("MPLS channel not specified");
+
+ if (!cf->rd)
+ cf_error("Route distinguisher not specified");
+
+ if (!cf->import_target && !cf->export_target)
+ cf_error("Route target not specified");
+
+ if (!cf->import_target)
+ cf_error("Import target not specified");
+
+ if (!cf->export_target)
+ cf_error("Export target not specified");
+}
+
+static struct proto *
+l3vpn_init(struct proto_config *CF)
+{
+ struct proto *P = proto_new(CF);
+ struct l3vpn_proto *p = (void *) P;
+ // struct l3vpn_config *cf = (void *) CF;
+
+ proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4));
+ proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6));
+ proto_configure_channel(P, &p->vpn4_channel, proto_cf_find_channel(CF, NET_VPN4));
+ proto_configure_channel(P, &p->vpn6_channel, proto_cf_find_channel(CF, NET_VPN6));
+ proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF, NET_MPLS));
+
+ P->rt_notify = l3vpn_rt_notify;
+ P->preexport = l3vpn_preexport;
+ P->reload_routes = l3vpn_reload_routes;
+ P->rte_better = l3vpn_rte_better;
+
+ return P;
+}
+
+static int
+l3vpn_start(struct proto *P)
+{
+ struct l3vpn_proto *p = (void *) P;
+ struct l3vpn_config *cf = (void *) P->cf;
+
+ p->rd = cf->rd;
+ p->import_target = cf->import_target;
+ p->export_target = cf->export_target;
+ p->export_target_data = NULL;
+
+ l3vpn_prepare_import_targets(p);
+ l3vpn_prepare_export_targets(p);
+
+ proto_setup_mpls_map(P, RTS_L3VPN, 1);
+
+ if (P->vrf_set)
+ P->mpls_map->vrf_iface = P->vrf;
+
+ return PS_UP;
+}
+
+static int
+l3vpn_shutdown(struct proto *P)
+{
+ // struct l3vpn_proto *p = (void *) P;
+
+ proto_shutdown_mpls_map(P, 1);
+
+ return PS_DOWN;
+}
+
+static int
+l3vpn_reconfigure(struct proto *P, struct proto_config *CF)
+{
+ struct l3vpn_proto *p = (void *) P;
+ struct l3vpn_config *cf = (void *) CF;
+
+ if (!proto_configure_channel(P, &p->ip4_channel, proto_cf_find_channel(CF, NET_IP4)) ||
+ !proto_configure_channel(P, &p->ip6_channel, proto_cf_find_channel(CF, NET_IP6)) ||
+ !proto_configure_channel(P, &p->vpn4_channel, proto_cf_find_channel(CF, NET_VPN4)) ||
+ !proto_configure_channel(P, &p->vpn6_channel, proto_cf_find_channel(CF, NET_VPN6)) ||
+ !proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF, NET_MPLS)))
+ return 0;
+
+ if (p->rd != cf->rd)
+ return 0;
+
+ int import_changed = !same_tree(p->import_target, cf->import_target);
+ int export_changed = !same_tree(p->export_target, cf->export_target);
+
+ /* Update pointers to config structures */
+ p->import_target = cf->import_target;
+ p->export_target = cf->export_target;
+
+ proto_setup_mpls_map(P, RTS_L3VPN, 1);
+
+ if (import_changed)
+ {
+ TRACE(D_EVENTS, "Import target changed");
+
+ l3vpn_prepare_import_targets(p);
+
+ if (p->vpn4_channel && (p->vpn4_channel->channel_state == CS_UP))
+ channel_request_feeding(p->vpn4_channel);
+
+ if (p->vpn6_channel && (p->vpn6_channel->channel_state == CS_UP))
+ channel_request_feeding(p->vpn6_channel);
+ }
+
+ if (export_changed)
+ {
+ TRACE(D_EVENTS, "Export target changed");
+
+ l3vpn_prepare_export_targets(p);
+
+ if (p->ip4_channel && (p->ip4_channel->channel_state == CS_UP))
+ channel_request_feeding(p->ip4_channel);
+
+ if (p->ip6_channel && (p->ip6_channel->channel_state == CS_UP))
+ channel_request_feeding(p->ip6_channel);
+ }
+
+ return 1;
+}
+
+static void
+l3vpn_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED)
+{
+ /* Just a shallow copy, not many items here */
+}
+
+static void
+l3vpn_get_route_info(rte *rte, byte *buf)
+{
+ u32 metric = l3vpn_metric(rte);
+ if (metric < IGP_METRIC_UNKNOWN)
+ bsprintf(buf, " (%u/%u)", rte->attrs->pref, metric);
+ else
+ bsprintf(buf, " (%u/?)", rte->attrs->pref);
+}
+
+
+struct protocol proto_l3vpn = {
+ .name = "L3VPN",
+ .template = "l3vpn%d",
+ .class = PROTOCOL_L3VPN,
+ .channel_mask = NB_IP | NB_VPN | NB_MPLS,
+ .proto_size = sizeof(struct l3vpn_proto),
+ .config_size = sizeof(struct l3vpn_config),
+ .postconfig = l3vpn_postconfig,
+ .init = l3vpn_init,
+ .start = l3vpn_start,
+ .shutdown = l3vpn_shutdown,
+ .reconfigure = l3vpn_reconfigure,
+ .copy_config = l3vpn_copy_config,
+ .get_route_info = l3vpn_get_route_info
+};
+
+void
+l3vpn_build(void)
+{
+ proto_build(&proto_l3vpn);
+}
diff --git a/proto/l3vpn/l3vpn.h b/proto/l3vpn/l3vpn.h
new file mode 100644
index 00000000..52a9f36d
--- /dev/null
+++ b/proto/l3vpn/l3vpn.h
@@ -0,0 +1,36 @@
+/*
+ * BIRD -- BGP/MPLS IP Virtual Private Networks (L3VPN)
+ *
+ * (c) 2022 Ondrej Zajicek <santiago@crfreenet.org>
+ * (c) 2022 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_L3VPN_H_
+#define _BIRD_L3VPN_H_
+
+struct l3vpn_config {
+ struct proto_config c;
+
+ u64 rd;
+ struct f_tree *import_target;
+ struct f_tree *export_target;
+};
+
+struct l3vpn_proto {
+ struct proto p;
+ struct channel *ip4_channel;
+ struct channel *ip6_channel;
+ struct channel *vpn4_channel;
+ struct channel *vpn6_channel;
+
+ u64 rd;
+ struct f_tree *import_target;
+ struct f_tree *export_target;
+ u32 *export_target_data;
+ uint export_target_length;
+ uint import_target_one;
+};
+
+#endif
diff --git a/proto/mrt/mrt.c b/proto/mrt/mrt.c
index d1c334e1..3378bace 100644
--- a/proto/mrt/mrt.c
+++ b/proto/mrt/mrt.c
@@ -488,7 +488,7 @@ mrt_rib_table_entry(struct mrt_table_dump_state *s, rte *r)
/* Path Identifier */
if (s->add_path)
- mrt_put_u32(b, r->src->private_id);
+ mrt_put_u32(b, (u32) r->src->private_id);
/* Route Attributes */
mrt_put_u16(b, 0);
diff --git a/proto/ospf/config.Y b/proto/ospf/config.Y
index 4b7d5a36..71d9d05b 100644
--- a/proto/ospf/config.Y
+++ b/proto/ospf/config.Y
@@ -38,10 +38,10 @@ ospf_iface_finish(void)
ip->passwords = get_passwords();
if (ospf_cfg_is_v2() && (ip->autype == OSPF_AUTH_CRYPT) && (ip->helloint < 5))
- log(L_WARN "Hello or poll interval less that 5 makes cryptographic authenication prone to replay attacks");
+ cf_warn("Hello or poll interval less that 5 makes cryptographic authenication prone to replay attacks");
if ((ip->autype == OSPF_AUTH_NONE) && (ip->passwords != NULL))
- log(L_WARN "Password option without authentication option does not make sense");
+ cf_warn("Password option without authentication option does not make sense");
if (ip->passwords)
{
@@ -119,7 +119,7 @@ ospf_proto_finish(void)
if (!ic->instance_id_set)
ic->instance_id = base;
else if (ic->instance_id >= 128)
- log(L_WARN "Instance ID %d from unassigned/private range", ic->instance_id);
+ cf_warn("Instance ID %d from unassigned/private range", ic->instance_id);
else if ((ic->instance_id < base) || (ic->instance_id >= (base + 32)))
cf_error("Instance ID %d invalid for given channel type", ic->instance_id);
}
@@ -190,7 +190,8 @@ ospf_check_auth(void)
CF_DECLS
-CF_KEYWORDS(OSPF, V2, V3, OSPF_METRIC1, OSPF_METRIC2, OSPF_TAG, OSPF_ROUTER_ID)
+CF_KEYWORDS_EXCLUSIVE(V2, V3)
+CF_KEYWORDS(OSPF, OSPF_METRIC1, OSPF_METRIC2, OSPF_TAG, OSPF_ROUTER_ID)
CF_KEYWORDS(AREA, NEIGHBORS, RFC1583COMPAT, STUB, TICK, COST, COST2, RETRANSMIT)
CF_KEYWORDS(HELLO, TRANSMIT, PRIORITY, DEAD, TYPE, BROADCAST, BCAST, DEFAULT)
CF_KEYWORDS(NONBROADCAST, NBMA, POINTOPOINT, PTP, POINTOMULTIPOINT, PTMP)
diff --git a/proto/ospf/iface.c b/proto/ospf/iface.c
index 87e3d95e..dd922b00 100644
--- a/proto/ospf/iface.c
+++ b/proto/ospf/iface.c
@@ -1227,7 +1227,7 @@ ospf_reconfigure_ifaces2(struct ospf_proto *p)
WALK_LIST(iface, iface_list)
{
- if (p->p.vrf_set && p->p.vrf != iface->master)
+ if (p->p.vrf_set && !if_in_vrf(iface, p->p.vrf))
continue;
if (! (iface->flags & IF_UP))
@@ -1276,7 +1276,7 @@ ospf_reconfigure_ifaces3(struct ospf_proto *p)
WALK_LIST(iface, iface_list)
{
- if (p->p.vrf_set && p->p.vrf != iface->master)
+ if (p->p.vrf_set && !if_in_vrf(iface, p->p.vrf))
continue;
if (! (iface->flags & IF_UP))
diff --git a/proto/radv/config.Y b/proto/radv/config.Y
index 8d4a3ab9..3a898b8d 100644
--- a/proto/radv/config.Y
+++ b/proto/radv/config.Y
@@ -25,6 +25,15 @@ static struct radv_dnssl_config this_radv_dnssl;
static list radv_dns_list; /* Used by radv_rdnss and radv_dnssl */
static u8 radv_mult_val; /* Used by radv_mult for second return value */
+static inline void
+radv_add_to_custom_list(list *l, int type, const struct adata *payload)
+{
+ if (type < 0 || type > 255) cf_error("RA cusom type must be in range 0-255");
+ struct radv_custom_config *cf = cfg_allocz(sizeof(struct radv_custom_config));
+ add_tail(l, NODE cf);
+ cf->type = type;
+ cf->payload = payload;
+}
CF_DECLS
@@ -33,7 +42,7 @@ CF_KEYWORDS(RADV, PREFIX, INTERFACE, MIN, MAX, RA, DELAY, INTERVAL, SOLICITED,
RETRANS, TIMER, CURRENT, HOP, LIMIT, DEFAULT, VALID, PREFERRED, MULT,
LIFETIME, SKIP, ONLINK, AUTONOMOUS, RDNSS, DNSSL, NS, DOMAIN, LOCAL,
TRIGGER, SENSITIVE, PREFERENCE, LOW, MEDIUM, HIGH, PROPAGATE, ROUTE,
- ROUTES, RA_PREFERENCE, RA_LIFETIME)
+ ROUTES, RA_PREFERENCE, RA_LIFETIME, CUSTOM, OPTION, TYPE, VALUE)
CF_ENUM(T_ENUM_RA_PREFERENCE, RA_PREF_, LOW, MEDIUM, HIGH)
@@ -52,6 +61,7 @@ radv_proto_start: proto_start RADV
init_list(&RADV_CFG->pref_list);
init_list(&RADV_CFG->rdnss_list);
init_list(&RADV_CFG->dnssl_list);
+ init_list(&RADV_CFG->custom_list);
};
radv_proto_item:
@@ -61,6 +71,7 @@ radv_proto_item:
| PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); }
| RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_CFG->rdnss_list, &radv_dns_list); }
| DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_CFG->dnssl_list, &radv_dns_list); }
+ | CUSTOM OPTION TYPE expr VALUE bytestring { radv_add_to_custom_list(&RADV_CFG->custom_list, $4, $6); }
| TRIGGER net_ip6 { RADV_CFG->trigger = $2; }
| PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; }
;
@@ -82,6 +93,7 @@ radv_iface_start:
init_list(&RADV_IFACE->pref_list);
init_list(&RADV_IFACE->rdnss_list);
init_list(&RADV_IFACE->dnssl_list);
+ init_list(&RADV_IFACE->custom_list);
RADV_IFACE->min_ra_int = (u32) -1; /* undefined */
RADV_IFACE->max_ra_int = DEFAULT_MAX_RA_INT;
@@ -124,8 +136,10 @@ radv_iface_item:
| PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); }
| RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_IFACE->rdnss_list, &radv_dns_list); }
| DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_IFACE->dnssl_list, &radv_dns_list); }
+ | CUSTOM OPTION TYPE expr VALUE bytestring { radv_add_to_custom_list(&RADV_IFACE->custom_list, $4, $6); }
| RDNSS LOCAL bool { RADV_IFACE->rdnss_local = $3; }
| DNSSL LOCAL bool { RADV_IFACE->dnssl_local = $3; }
+ | CUSTOM OPTION LOCAL bool { RADV_IFACE->custom_local = $4; }
;
radv_preference:
diff --git a/proto/radv/packets.c b/proto/radv/packets.c
index 5cd8b2de..77c98794 100644
--- a/proto/radv/packets.c
+++ b/proto/radv/packets.c
@@ -82,6 +82,13 @@ struct radv_opt_dnssl
char domain[];
};
+struct radv_opt_custom
+{
+ u8 type;
+ u8 length;
+ u8 payload[];
+};
+
static int
radv_prepare_route(struct radv_iface *ifa, struct radv_route *rt,
char **buf, char *bufend)
@@ -255,6 +262,34 @@ radv_prepare_dnssl(struct radv_iface *ifa, list *dnssl_list, char **buf, char *b
}
static int
+radv_prepare_custom(struct radv_iface *ifa, list *custom_list, char **buf, char *bufend)
+{
+ struct radv_custom_config *ccf;
+ WALK_LIST(ccf, *custom_list)
+ {
+ struct radv_opt_custom *op = (void *) *buf;
+ /* Add 2 octets for type and size and 8 - 1 for ceiling the division up to 8 octets */
+ int size = (ccf->payload->length + 2 + 8 - 1) / 8;
+ if (bufend - *buf < size * 8)
+ goto too_much;
+
+ memset(op, 0, size * 8); /* Clear buffer so there is no tail garbage */
+ op->type = ccf->type;
+ op->length = size;
+ memcpy(op->payload, ccf->payload->data, ccf->payload->length);
+
+ *buf += 8 * op->length;
+ }
+
+ return 0;
+
+ too_much:
+ log(L_WARN "%s: Too many RA options on interface %s",
+ ifa->ra->p.name, ifa->iface->name);
+ return -1;
+}
+
+static int
radv_prepare_prefix(struct radv_iface *ifa, struct radv_prefix *px,
char **buf, char *bufend)
{
@@ -352,6 +387,14 @@ radv_prepare_ra(struct radv_iface *ifa)
if (radv_prepare_dnssl(ifa, &ic->dnssl_list, &buf, bufend) < 0)
goto done;
+ if (! ic->custom_local)
+ if (radv_prepare_custom(ifa, &cf->custom_list, &buf, bufend) < 0)
+ goto done;
+
+ if (radv_prepare_custom(ifa, &ic->custom_list, &buf, bufend) < 0)
+ goto done;
+
+
if (p->fib_up)
{
FIB_WALK(&p->routes, struct radv_route, rt)
diff --git a/proto/radv/radv.c b/proto/radv/radv.c
index 119a8dc4..ba31e1a8 100644
--- a/proto/radv/radv.c
+++ b/proto/radv/radv.c
@@ -663,7 +663,7 @@ radv_reconfigure(struct proto *P, struct proto_config *CF)
struct iface *iface;
WALK_LIST(iface, iface_list)
{
- if (p->p.vrf_set && p->p.vrf != iface->master)
+ if (p->p.vrf_set && !if_in_vrf(iface, p->p.vrf))
continue;
if (!(iface->flags & IF_UP))
diff --git a/proto/radv/radv.h b/proto/radv/radv.h
index 14d40f8a..ba4a1b6c 100644
--- a/proto/radv/radv.h
+++ b/proto/radv/radv.h
@@ -51,6 +51,7 @@ struct radv_config
list pref_list; /* Global list of prefix configs (struct radv_prefix_config) */
list rdnss_list; /* Global list of RDNSS configs (struct radv_rdnss_config) */
list dnssl_list; /* Global list of DNSSL configs (struct radv_dnssl_config) */
+ list custom_list; /* Global list of custom configs (struct radv_custom_config) */
net_addr trigger; /* Prefix of a trigger route, if defined */
u8 propagate_routes; /* Do we propagate more specific routes (RFC 4191)? */
@@ -63,6 +64,7 @@ struct radv_iface_config
list pref_list; /* Local list of prefix configs (struct radv_prefix_config) */
list rdnss_list; /* Local list of RDNSS configs (struct radv_rdnss_config) */
list dnssl_list; /* Local list of DNSSL configs (struct radv_dnssl_config) */
+ list custom_list; /* Local list of custom configs (struct radv_custom_config) */
u32 min_ra_int; /* Standard options from RFC 4861 */
u32 max_ra_int;
@@ -75,6 +77,7 @@ struct radv_iface_config
u8 rdnss_local; /* Global list is not used for RDNSS */
u8 dnssl_local; /* Global list is not used for DNSSL */
+ u8 custom_local; /* Global list is not used for custom */
u8 managed; /* Standard options from RFC 4861 */
u8 other_config;
@@ -122,6 +125,13 @@ struct radv_dnssl_config
const char *domain; /* Domain for DNS search list, in processed form */
};
+struct radv_custom_config
+{
+ node n;
+ u8 type; /* Identifier of the type of option */
+ const struct adata *payload; /* Payload of the option */
+};
+
/*
* One more specific route as per RFC 4191.
*
diff --git a/proto/rip/config.Y b/proto/rip/config.Y
index 28ee9609..44f2742c 100644
--- a/proto/rip/config.Y
+++ b/proto/rip/config.Y
@@ -32,7 +32,8 @@ rip_check_auth(void)
CF_DECLS
-CF_KEYWORDS(RIP, NG, ECMP, LIMIT, WEIGHT, INFINITY, METRIC, UPDATE, TIMEOUT,
+CF_KEYWORDS_EXCLUSIVE(NG)
+CF_KEYWORDS(RIP, ECMP, LIMIT, WEIGHT, INFINITY, METRIC, UPDATE, TIMEOUT,
GARBAGE, RETRANSMIT, PORT, ADDRESS, MODE, BROADCAST, MULTICAST,
PASSIVE, VERSION, SPLIT, HORIZON, POISON, REVERSE, CHECK, ZERO,
TIME, BFD, AUTHENTICATION, NONE, PLAINTEXT, CRYPTOGRAPHIC, MD5,
@@ -116,7 +117,7 @@ rip_iface_finish:
RIP_IFACE->passwords = get_passwords();
if (!RIP_IFACE->auth_type != !RIP_IFACE->passwords)
- log(L_WARN "Authentication and password options should be used together");
+ cf_warn("Authentication and password options should be used together");
if (RIP_IFACE->passwords)
{
diff --git a/proto/rip/rip.c b/proto/rip/rip.c
index 8c2d5aeb..1c3509e4 100644
--- a/proto/rip/rip.c
+++ b/proto/rip/rip.c
@@ -797,7 +797,7 @@ rip_reconfigure_ifaces(struct rip_proto *p, struct rip_config *cf)
WALK_LIST(iface, iface_list)
{
- if (p->p.vrf_set && p->p.vrf != iface->master)
+ if (p->p.vrf_set && !if_in_vrf(iface, p->p.vrf))
continue;
if (!(iface->flags & IF_UP))
diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y
index d6d326b8..c28cab7a 100644
--- a/proto/rpki/config.Y
+++ b/proto/rpki/config.Y
@@ -89,20 +89,22 @@ rpki_keep_interval:
rpki_proto_item_port: PORT expr { check_u16($2); RPKI_CFG->port = $2; };
-rpki_cache_addr:
- text {
- rpki_check_unused_hostname();
- RPKI_CFG->hostname = $1;
- }
- | ipa {
- rpki_check_unused_hostname();
- RPKI_CFG->ip = $1;
- /* Ensure hostname is filled */
- char *hostname = cfg_allocz(INET6_ADDRSTRLEN + 1);
- bsnprintf(hostname, INET6_ADDRSTRLEN+1, "%I", RPKI_CFG->ip);
- RPKI_CFG->hostname = hostname;
- }
- ;
+rpki_cache_addr: text_or_ipa
+{
+ rpki_check_unused_hostname();
+ if ($1.type == T_STRING)
+ RPKI_CFG->hostname = $1.val.s;
+ else if ($1.type == T_IP)
+ {
+ RPKI_CFG->ip = $1.val.ip;
+
+ /* Ensure hostname is filled */
+ char *hostname = cfg_allocz(INET6_ADDRSTRLEN + 1);
+ bsnprintf(hostname, INET6_ADDRSTRLEN+1, "%I", RPKI_CFG->ip);
+ RPKI_CFG->hostname = hostname;
+ }
+ else bug("Bad text_or_ipa");
+};
rpki_transport:
TCP rpki_transport_tcp_init
diff --git a/proto/static/config.Y b/proto/static/config.Y
index 9d26ee82..215681e8 100644
--- a/proto/static/config.Y
+++ b/proto/static/config.Y
@@ -63,6 +63,7 @@ static_proto:
static_proto_start proto_name '{'
| static_proto proto_item ';'
| static_proto proto_channel ';' { this_proto->net_type = $2->net_type; }
+ | static_proto mpls_channel ';'
| static_proto CHECK LINK bool ';' { STATIC_CFG->check_link = $4; }
| static_proto IGP TABLE rtable ';' {
if ($4->addr_type == NET_IP4)
@@ -106,15 +107,21 @@ stat_nexthops:
| stat_nexthops stat_nexthop
;
+stat_mpls:
+ /* empty */
+ | MPLS expr { this_srt->mpls_label = $2; if ($2 >= MPLS_MAX_LABEL) cf_error("MPLS label must be less than 2^20"); }
+ ;
+
stat_route0: ROUTE net_any {
this_srt = cfg_allocz(sizeof(struct static_route));
add_tail(&STATIC_CFG->routes, &this_srt->n);
this_srt->net = $2;
+ this_srt->mpls_label = (uint) -1;
this_srt_cmds = NULL;
this_srt_last_cmd = NULL;
this_srt->mp_next = NULL;
this_snh = NULL;
- }
+ } stat_mpls
;
stat_route:
diff --git a/proto/static/static.c b/proto/static/static.c
index bb93305e..071803a8 100644
--- a/proto/static/static.c
+++ b/proto/static/static.c
@@ -39,6 +39,7 @@
#include "nest/iface.h"
#include "nest/protocol.h"
#include "nest/route.h"
+#include "nest/mpls.h"
#include "nest/cli.h"
#include "conf/conf.h"
#include "filter/filter.h"
@@ -98,6 +99,43 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
rta_set_recursive_next_hop(p->p.main_channel->table, a, tab, r->via, IPA_NONE, r->mls);
}
+ if (p->p.mpls_channel)
+ {
+ struct mpls_channel *mc = (void *) p->p.mpls_channel;
+
+ ea_list *ea = alloca(sizeof(ea_list) + 2 * sizeof(eattr));
+ *ea = (ea_list) { .flags = EALF_SORTED };
+ ea->next = a->eattrs;
+ a->eattrs = ea;
+
+ if (r->mpls_label != (uint) -1)
+ {
+ ea->attrs[0] = (eattr) {
+ .id = EA_MPLS_LABEL,
+ .type = EAF_TYPE_INT,
+ .u.data = r->mpls_label,
+ };
+
+ ea->attrs[1] = (eattr) {
+ .id = EA_MPLS_POLICY,
+ .type = EAF_TYPE_INT,
+ .u.data = MPLS_POLICY_STATIC,
+ };
+
+ ea->count = 2;
+ }
+ else
+ {
+ ea->attrs[0] = (eattr) {
+ .id = EA_MPLS_POLICY,
+ .type = EAF_TYPE_INT,
+ .u.data = mc->label_policy,
+ };
+
+ ea->count = 1;
+ }
+ }
+
/* Already announced */
if (r->state == SRS_CLEAN)
return;
@@ -113,7 +151,7 @@ static_announce_rte(struct static_proto *p, struct static_route *r)
net_copy(e->net->n.addr, r->net);
/* Evaluate the filter */
- f_eval_rte(r->cmds, &e, static_lp);
+ f_eval_rte(r->cmds, &e, static_lp, 0, NULL, NULL);
/* Remove the temporary node */
e->net = NULL;
@@ -356,7 +394,7 @@ static inline int
static_same_rte(struct static_route *or, struct static_route *nr)
{
/* Note that i_same() requires arguments in (new, old) order */
- return static_same_dest(or, nr) && f_same(nr->cmds, or->cmds);
+ return (or->mpls_label == nr->mpls_label) && static_same_dest(or, nr) && f_same(nr->cmds, or->cmds);
}
static void
@@ -434,10 +472,15 @@ static_postconfig(struct proto_config *CF)
struct static_config *cf = (void *) CF;
struct static_route *r;
+ /* If there is just a MPLS channel, use it as a main channel */
+ if (!CF->net_type && proto_cf_mpls_channel(CF))
+ CF->net_type = NET_MPLS;
+
if (! proto_cf_main_channel(CF))
cf_error("Channel not specified");
struct channel_config *cc = proto_cf_main_channel(CF);
+ struct channel_config *mc = proto_cf_mpls_channel(CF);
if (!cf->igp_table_ip4)
cf->igp_table_ip4 = (cc->table->addr_type == NET_IP4) ?
@@ -448,9 +491,14 @@ static_postconfig(struct proto_config *CF)
cc->table : cf->c.global->def_tables[NET_IP6];
WALK_LIST(r, cf->routes)
+ {
if (r->net && (r->net->type != CF->net_type))
cf_error("Route %N incompatible with channel type", r->net);
+ if ((r->mpls_label != (uint) -1) && !mc)
+ cf_error("Route %N has MPLS label, but MPLS channel not specified", r->net);
+ }
+
static_index_routes(cf);
}
@@ -463,6 +511,8 @@ static_init(struct proto_config *CF)
P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF));
+ proto_configure_channel(P, &P->mpls_channel, proto_cf_mpls_channel(CF));
+
P->neigh_notify = static_neigh_notify;
P->reload_routes = static_reload_routes;
P->rte_better = static_rte_better;
@@ -497,6 +547,8 @@ static_start(struct proto *P)
BUFFER_INIT(p->marked, p->p.pool, 4);
+ proto_setup_mpls_map(P, RTS_STATIC, 1);
+
/* We have to go UP before routes could be installed */
proto_notify_state(P, PS_UP);
@@ -513,6 +565,8 @@ static_shutdown(struct proto *P)
struct static_config *cf = (void *) P->cf;
struct static_route *r;
+ proto_shutdown_mpls_map(P, 1);
+
/* Just reset the flag, the routes will be flushed by the nest */
WALK_LIST(r, cf->routes)
static_reset_rte(p, r);
@@ -615,9 +669,12 @@ static_reconfigure(struct proto *P, struct proto_config *CF)
(IGP_TABLE(o, ip6) != IGP_TABLE(n, ip6)))
return 0;
- if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
+ if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)) ||
+ !proto_configure_channel(P, &P->mpls_channel, proto_cf_mpls_channel(CF)))
return 0;
+ proto_setup_mpls_map(P, RTS_STATIC, 1);
+
p->p.cf = CF;
/* Reset route lists in neighbor entries */
diff --git a/proto/static/static.h b/proto/static/static.h
index fc91f71c..a0a95a4b 100644
--- a/proto/static/static.h
+++ b/proto/static/static.h
@@ -48,6 +48,7 @@ struct static_route {
byte onlink; /* Gateway is onlink regardless of IP ranges */
byte weight; /* Multipath next hop weight */
byte use_bfd; /* Configured to use BFD */
+ uint mpls_label; /* Local MPLS label, -1 if unused */
struct bfd_request *bfd_req; /* BFD request, if BFD is used */
mpls_label_stack *mls; /* MPLS label stack; may be NULL */
};
diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in
index 3db21c2f..13dfbca4 100644
--- a/sysdep/autoconf.h.in
+++ b/sysdep/autoconf.h.in
@@ -3,6 +3,9 @@
/* Define if building universal (internal helper macro) */
#undef AC_APPLE_UNIVERSAL_BUILD
+/* Aggregator protocol */
+#undef CONFIG_AGGREGATOR
+
/* Babel protocol */
#undef CONFIG_BABEL
@@ -12,6 +15,12 @@
/* BGP protocol */
#undef CONFIG_BGP
+/* BMP protocol */
+#undef CONFIG_BMP
+
+/* L3VPN protocol */
+#undef CONFIG_L3VPN
+
/* MRT protocol */
#undef CONFIG_MRT
diff --git a/sysdep/bsd/krt-sock.c b/sysdep/bsd/krt-sock.c
index 1f793293..d13e20a3 100644
--- a/sysdep/bsd/krt-sock.c
+++ b/sysdep/bsd/krt-sock.c
@@ -314,7 +314,7 @@ krt_send_route(struct krt_proto *p, int cmd, rte *e)
if (ipa_is_link_local(gw))
_I0(gw) = 0xfe800000 | (i->index & 0x0000ffff);
- sockaddr_fill(&gate, af, gw, NULL, 0);
+ sockaddr_fill(&gate, (ipa_is_ip4(gw) ? AF_INET : AF_INET6), gw, NULL, 0);
msg.rtm.rtm_flags |= RTF_GATEWAY;
msg.rtm.rtm_addrs |= RTA_GATEWAY;
break;
@@ -469,7 +469,7 @@ krt_read_route(struct ks_msg *msg, struct krt_proto *p, int scan)
idst = ipa_from_sa(&dst);
imask = ipa_from_sa(&mask);
- igate = (gate.sa.sa_family == dst.sa.sa_family) ? ipa_from_sa(&gate) : IPA_NONE;
+ igate = ipa_from_sa(&gate);
#ifdef KRT_SHARED_SOCKET
if (!scan)
diff --git a/sysdep/config.h b/sysdep/config.h
index 861e52bc..ffab1670 100644
--- a/sysdep/config.h
+++ b/sysdep/config.h
@@ -13,7 +13,7 @@
#ifdef GIT_LABEL
#define BIRD_VERSION XSTR1(GIT_LABEL)
#else
-#define BIRD_VERSION "2.0.12"
+#define BIRD_VERSION "2.14"
#endif
/* Include parameters determined by configure script */
diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c
index 7f0d4736..29446cab 100644
--- a/sysdep/linux/netlink.c
+++ b/sysdep/linux/netlink.c
@@ -333,13 +333,21 @@ struct nl_want_attrs {
};
-#define BIRD_IFLA_MAX (IFLA_WIRELESS+1)
+#define BIRD_IFLA_MAX (IFLA_LINKINFO+1)
static struct nl_want_attrs ifla_attr_want[BIRD_IFLA_MAX] = {
[IFLA_IFNAME] = { 1, 0, 0 },
[IFLA_MTU] = { 1, 1, sizeof(u32) },
[IFLA_MASTER] = { 1, 1, sizeof(u32) },
[IFLA_WIRELESS] = { 1, 0, 0 },
+ [IFLA_LINKINFO] = { 1, 0, 0 },
+};
+
+#define BIRD_INFO_MAX (IFLA_INFO_DATA+1)
+
+static struct nl_want_attrs ifinfo_attr_want[BIRD_INFO_MAX] = {
+ [IFLA_INFO_KIND]= { 1, 0, 0 },
+ [IFLA_INFO_DATA]= { 1, 0, 0 },
};
@@ -485,7 +493,6 @@ static inline ip_addr rta_get_ipa(struct rtattr *a)
return ipa_from_ip6(rta_get_ip6(a));
}
-#ifdef HAVE_MPLS_KERNEL
static inline ip_addr rta_get_via(struct rtattr *a)
{
struct rtvia *v = RTA_DATA(a);
@@ -496,6 +503,7 @@ static inline ip_addr rta_get_via(struct rtattr *a)
return IPA_NONE;
}
+#ifdef HAVE_MPLS_KERNEL
static u32 rta_mpls_stack[MPLS_MAX_LABEL_STACK];
static inline int rta_get_mpls(struct rtattr *a, u32 *stack)
{
@@ -763,10 +771,8 @@ nl_parse_multipath(struct nl_parse_state *s, struct krt_proto *p, const net_addr
if (a[RTA_FLOW])
s->rta_flow = rta_get_u32(a[RTA_FLOW]);
-#ifdef HAVE_MPLS_KERNEL
if (a[RTA_VIA])
rv->gw = rta_get_via(a[RTA_VIA]);
-#endif
if (nh->rtnh_flags & RTNH_F_ONLINK)
rv->flags |= RNF_ONLINK;
@@ -870,7 +876,7 @@ nl_parse_link(struct nlmsghdr *h, int scan)
int new = h->nlmsg_type == RTM_NEWLINK;
struct iface f = {};
struct iface *ifi;
- char *name;
+ const char *name, *kind = NULL;
u32 mtu, master = 0;
uint fl;
@@ -897,6 +903,15 @@ nl_parse_link(struct nlmsghdr *h, int scan)
if (a[IFLA_MASTER])
master = rta_get_u32(a[IFLA_MASTER]);
+ if (a[IFLA_LINKINFO])
+ {
+ struct rtattr *li[BIRD_INFO_MAX];
+ nl_attr_len = RTA_PAYLOAD(a[IFLA_LINKINFO]);
+ nl_parse_attrs(RTA_DATA(a[IFLA_LINKINFO]), ifinfo_attr_want, li, sizeof(li));
+ if (li[IFLA_INFO_KIND])
+ kind = RTA_DATA(li[IFLA_INFO_KIND]);
+ }
+
ifi = if_find_by_index(i->ifi_index);
if (!new)
{
@@ -936,6 +951,9 @@ nl_parse_link(struct nlmsghdr *h, int scan)
if (fl & IFF_MULTICAST)
f.flags |= IF_MULTICAST;
+ if (kind && !strcmp(kind, "vrf"))
+ f.flags |= IF_VRF;
+
ifi = if_update(&f);
if (!scan)
@@ -1580,7 +1598,7 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
case RTPROT_KERNEL:
krt_src = KRT_SRC_KERNEL;
- return;
+ break;
case RTPROT_BIRD:
if (!s->scan)
@@ -1659,10 +1677,8 @@ nl_parse_route(struct nl_parse_state *s, struct nlmsghdr *h)
if (a[RTA_GATEWAY])
ra->nh.gw = rta_get_ipa(a[RTA_GATEWAY]);
-#ifdef HAVE_MPLS_KERNEL
if (a[RTA_VIA])
ra->nh.gw = rta_get_via(a[RTA_VIA]);
-#endif
if (i->rtm_flags & RTNH_F_ONLINK)
ra->nh.flags |= RNF_ONLINK;
diff --git a/sysdep/unix/config.Y b/sysdep/unix/config.Y
index 5c4b5bef..fce64794 100644
--- a/sysdep/unix/config.Y
+++ b/sysdep/unix/config.Y
@@ -19,7 +19,7 @@ CF_DECLS
CF_KEYWORDS(LOG, SYSLOG, ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG, STDERR, SOFT)
CF_KEYWORDS(NAME, CONFIRM, UNDO, CHECK, TIMEOUT, DEBUG, LATENCY, LIMIT, WATCHDOG, WARNING, STATUS)
-CF_KEYWORDS(GRACEFUL, RESTART)
+CF_KEYWORDS(GRACEFUL, RESTART, THREADS)
%type <i> log_mask log_mask_list log_cat cfg_timeout
%type <t> cfg_name
@@ -28,6 +28,9 @@ CF_KEYWORDS(GRACEFUL, RESTART)
CF_GRAMMAR
+/* Dummy threads setting for forward compatibility */
+conf: THREADS expr ';' ;
+
conf: log_config ;
log_begin: { this_log = cfg_allocz(sizeof(struct log_config)); };
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index e131ca41..4b3eef48 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -171,6 +171,19 @@ times_update_real_time(struct timeloop *loop)
loop->real_time = ts.tv_sec S + ts.tv_nsec NS;
}
+btime
+current_time_now(void)
+{
+ struct timespec ts;
+ int rv;
+
+ rv = clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (rv < 0)
+ die("clock_gettime: %m");
+
+ return ts.tv_sec S + ts.tv_nsec NS;
+}
+
/**
* DOC: Sockets
@@ -2213,12 +2226,12 @@ io_loop(void)
for(;;)
{
times_update(&main_timeloop);
- events = ev_run_list(&global_event_list);
- events = ev_run_list_limited(&global_work_list, WORK_EVENTS_MAX) || events;
+ ev_run_list(&global_event_list);
+ ev_run_list_limited(&global_work_list, WORK_EVENTS_MAX);
timers_fire(&main_timeloop);
io_close_event();
- // FIXME
+ events = !EMPTY_LIST(global_event_list) || !EMPTY_LIST(global_work_list);
poll_tout = (events ? 0 : 3000); /* Time in milliseconds */
if (t = timers_first(&main_timeloop))
{
diff --git a/sysdep/unix/krt.Y b/sysdep/unix/krt.Y
index 95b54d65..90297d3f 100644
--- a/sysdep/unix/krt.Y
+++ b/sysdep/unix/krt.Y
@@ -32,7 +32,9 @@ CF_DECLS
CF_KEYWORDS(KERNEL, PERSIST, SCAN, TIME, LEARN, DEVICE, ROUTES, GRACEFUL, RESTART, KRT_SOURCE, KRT_METRIC, MERGE, PATHS)
CF_KEYWORDS(INTERFACE, PREFERRED)
+%type <i> kern_learn
%type <i> kern_mp_limit
+%type <cc> kern_channel
CF_GRAMMAR
@@ -48,20 +50,31 @@ kern_proto_start: proto_start KERNEL {
kern_proto: kern_proto_start proto_name '{' ;
kern_proto: kern_proto kern_item ';' ;
+kern_learn:
+ bool { $$ = $1 ? KRT_LEARN_ALIEN : KRT_LEARN_NONE; }
+ | ALL { $$ = KRT_LEARN_ALL; }
+ ;
+
kern_mp_limit:
/* empty */ { $$ = KRT_DEFAULT_ECMP_LIMIT; }
| LIMIT expr { $$ = $2; if (($2 <= 0) || ($2 > 255)) cf_error("Merge paths limit must be in range 1-255"); }
;
+
+kern_channel:
+ proto_channel
+ | mpls_channel
+ ;
+
kern_item:
proto_item
- | proto_channel { this_proto->net_type = $1->net_type; }
+ | kern_channel { this_proto->net_type = $1->net_type; }
| PERSIST bool { THIS_KRT->persist = $2; }
| SCAN TIME expr {
/* Scan time of 0 means scan on startup only */
THIS_KRT->scan_time = $3 S_;
}
- | LEARN bool {
+ | LEARN kern_learn {
THIS_KRT->learn = $2;
#ifndef KRT_ALLOW_LEARN
if ($2)
diff --git a/sysdep/unix/krt.c b/sysdep/unix/krt.c
index 9f95247f..3a4b24dc 100644
--- a/sysdep/unix/krt.c
+++ b/sysdep/unix/krt.c
@@ -639,12 +639,14 @@ krt_got_route(struct krt_proto *p, rte *e, s8 src)
#ifdef KRT_ALLOW_LEARN
switch (src)
{
- case KRT_SRC_KERNEL:
- goto ignore;
-
case KRT_SRC_REDIRECT:
goto delete;
+ case KRT_SRC_KERNEL:
+ if (KRT_CF->learn != KRT_LEARN_ALL)
+ goto ignore;
+ /* fallthrough */
+
case KRT_SRC_ALIEN:
if (KRT_CF->learn)
krt_learn_scan(p, e);
@@ -780,6 +782,11 @@ krt_got_route_async(struct krt_proto *p, rte *e, int new, s8 src)
break;
#ifdef KRT_ALLOW_LEARN
+ case KRT_SRC_KERNEL:
+ if (KRT_CF->learn != KRT_LEARN_ALL)
+ break;
+ /* fallthrough */
+
case KRT_SRC_ALIEN:
if (KRT_CF->learn)
{
diff --git a/sysdep/unix/krt.h b/sysdep/unix/krt.h
index 18a206e6..e25f0b12 100644
--- a/sysdep/unix/krt.h
+++ b/sysdep/unix/krt.h
@@ -27,6 +27,10 @@ struct kif_proto;
#define KRT_REF_SEEN 0x1 /* Seen in table */
#define KRT_REF_BEST 0x2 /* Best in table */
+#define KRT_LEARN_NONE 0 /* Do not learn */
+#define KRT_LEARN_ALIEN 1 /* Learn KRT_SRC_ALIEN routes */
+#define KRT_LEARN_ALL 2 /* Learn both KRT_SRC_ALIEN and KRT_SRC_KERNEL routes */
+
/* Whenever we recognize our own routes, we allow learing of foreign routes */
#ifdef CONFIG_SELF_CONSCIOUS
diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c
index 627d7a4a..0d7788bb 100644
--- a/sysdep/unix/main.c
+++ b/sysdep/unix/main.c
@@ -33,6 +33,7 @@
#include "nest/route.h"
#include "nest/protocol.h"
#include "nest/iface.h"
+#include "nest/mpls.h"
#include "nest/cli.h"
#include "nest/locks.h"
#include "conf/conf.h"
@@ -111,21 +112,21 @@ get_hostname(linpool *lp)
#ifdef PATH_IPROUTE_DIR
static inline void
-add_num_const(char *name, int val, const char *file, const uint line)
+add_num_const(struct config *conf, char *name, int val, const char *file, const uint line)
{
struct f_val *v = cfg_alloc(sizeof(struct f_val));
*v = (struct f_val) { .type = T_INT, .val.i = val };
- struct symbol *sym = cf_get_symbol(name);
- if (sym->class && cf_symbol_is_local(sym))
+ struct symbol *sym = cf_get_symbol(conf, name);
+ if (sym->class && cf_symbol_is_local(conf, sym))
cf_error("Error reading value for %s from %s:%d: already defined", name, file, line);
- cf_define_symbol(sym, SYM_CONSTANT | T_INT, val, v);
+ cf_define_symbol(conf, sym, SYM_CONSTANT | T_INT, val, v);
}
/* the code of read_iproute_table() is based on
rtnl_tab_initialize() from iproute2 package */
static void
-read_iproute_table(char *file, char *prefix, uint max)
+read_iproute_table(struct config *conf, char *file, char *prefix, uint max)
{
char buf[512], namebuf[512];
char *name;
@@ -162,7 +163,7 @@ read_iproute_table(char *file, char *prefix, uint max)
if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z') && (*p < '0' || *p > '9') && (*p != '_'))
*p = '_';
- add_num_const(namebuf, val, file, line);
+ add_num_const(conf, namebuf, val, file, line);
}
fclose(fp);
@@ -191,10 +192,10 @@ sysdep_preconfig(struct config *c)
c->watchdog_warning = UNIX_DEFAULT_WATCHDOG_WARNING;
#ifdef PATH_IPROUTE_DIR
- read_iproute_table(PATH_IPROUTE_DIR "/rt_protos", "ipp_", 255);
- read_iproute_table(PATH_IPROUTE_DIR "/rt_realms", "ipr_", 0xffffffff);
- read_iproute_table(PATH_IPROUTE_DIR "/rt_scopes", "ips_", 255);
- read_iproute_table(PATH_IPROUTE_DIR "/rt_tables", "ipt_", 0xffffffff);
+ read_iproute_table(c, PATH_IPROUTE_DIR "/rt_protos", "ipp_", 255);
+ read_iproute_table(c, PATH_IPROUTE_DIR "/rt_realms", "ipr_", 0xffffffff);
+ read_iproute_table(c, PATH_IPROUTE_DIR "/rt_scopes", "ips_", 255);
+ read_iproute_table(c, PATH_IPROUTE_DIR "/rt_tables", "ipt_", 0xffffffff);
#endif
}
@@ -889,13 +890,13 @@ main(int argc, char **argv)
log_switch(1, NULL, NULL);
random_init();
- net_init();
resource_init();
timer_init();
olock_init();
io_init();
rt_init();
if_init();
+ mpls_init();
// roa_init();
config_init();
diff --git a/test/birdtest.h b/test/birdtest.h
index ad5f8f9c..540092d6 100644
--- a/test/birdtest.h
+++ b/test/birdtest.h
@@ -37,10 +37,14 @@ int bt_test_suite_base(int (*test_fn)(const void *), const char *test_id, const
static inline u64 bt_random(void)
{ return ((u64) random() & 0xffffffff) | ((u64) random() << 32); }
+static inline u32 bt_random_n(u32 max)
+{ return random() % max; }
+
+
void bt_log_suite_result(int result, const char *fmt, ...);
void bt_log_suite_case_result(int result, const char *fmt, ...);
-#define BT_TIMEOUT 5 /* Default timeout in seconds */
+#define BT_TIMEOUT 60 /* Default timeout in seconds */
#define BT_FORKING 1 /* Forking is enabled in default */
#define BT_RANDOM_SEED 0x5097d2bb
diff --git a/test/bt-utils.c b/test/bt-utils.c
index 8496e185..a0353a21 100644
--- a/test/bt-utils.c
+++ b/test/bt-utils.c
@@ -16,6 +16,7 @@
#include "nest/bird.h"
#include "nest/route.h"
#include "nest/protocol.h"
+#include "nest/mpls.h"
#include "sysdep/unix/unix.h"
#include "sysdep/unix/krt.h"
@@ -65,6 +66,7 @@ bt_bird_init(void)
io_init();
rt_init();
if_init();
+ mpls_init();
config_init();
protos_build();
@@ -219,3 +221,135 @@ bt_bytes_to_hex(char *buf, const byte *in_data, size_t size)
sprintf(buf + i*2, "%02x", in_data[i]);
}
+void
+bt_random_net(net_addr *net, int type)
+{
+ ip4_addr ip4;
+ ip6_addr ip6;
+ uint pxlen;
+
+ switch (type)
+ {
+ case NET_IP4:
+ pxlen = bt_random_n(24)+8;
+ ip4 = ip4_from_u32((u32) bt_random());
+ net_fill_ip4(net, ip4_and(ip4, ip4_mkmask(pxlen)), pxlen);
+ break;
+
+ case NET_IP6:
+ pxlen = bt_random_n(120)+8;
+ ip6 = ip6_build(bt_random(), bt_random(), bt_random(), bt_random());
+ net_fill_ip6(net, ip6_and(ip6, ip6_mkmask(pxlen)), pxlen);
+ break;
+
+ default:
+ die("Net type %d not implemented", type);
+ }
+}
+
+net_addr *
+bt_random_nets(int type, uint n)
+{
+ net_addr *nets = tmp_alloc(n * sizeof(net_addr));
+
+ for (uint i = 0; i < n; i++)
+ bt_random_net(&nets[i], type);
+
+ return nets;
+}
+
+net_addr *
+bt_random_net_subset(net_addr *src, uint sn, uint dn)
+{
+ net_addr *nets = tmp_alloc(dn * sizeof(net_addr));
+
+ for (uint i = 0; i < dn; i++)
+ net_copy(&nets[i], &src[bt_random_n(sn)]);
+
+ return nets;
+}
+
+void
+bt_read_net(const char *str, net_addr *net, int type)
+{
+ ip4_addr ip4;
+ ip6_addr ip6;
+ uint pxlen;
+ char addr[64];
+
+ switch (type)
+ {
+ case NET_IP4:
+ if (sscanf(str, "%[0-9.]/%u", addr, &pxlen) != 2)
+ goto err;
+
+ if (!ip4_pton(addr, &ip4))
+ goto err;
+
+ if (!net_validate_px4(ip4, pxlen))
+ goto err;
+
+ net_fill_ip4(net, ip4, pxlen);
+ break;
+
+ case NET_IP6:
+ if (sscanf(str, "%[0-9a-fA-F:.]/%u", addr, &pxlen) != 2)
+ goto err;
+
+ if (!ip6_pton(addr, &ip6))
+ goto err;
+
+ if (!net_validate_px6(ip6, pxlen))
+ goto err;
+
+ net_fill_ip6(net, ip6, pxlen);
+ break;
+
+ default:
+ die("Net type %d not implemented", type);
+ }
+ return;
+
+err:
+ bt_abort_msg("Invalid network '%s'", str);
+}
+
+net_addr *
+bt_read_nets(FILE *f, int type, uint *n)
+{
+ char str[80];
+
+ net_addr *nets = tmp_alloc(*n * sizeof(net_addr));
+ uint i = 0;
+
+ errno = 0;
+ while (fgets(str, sizeof(str), f))
+ {
+ if (str[0] == '\n')
+ break;
+
+ if (i >= *n)
+ bt_abort_msg("Too many networks");
+
+ bt_read_net(str, &nets[i], type);
+ bt_debug("ADD %s\n", str);
+ i++;
+ }
+ bt_syscall(errno, "fgets()");
+
+ bt_debug("DONE reading %u nets\n", i);
+
+ *n = i;
+ return nets;
+}
+
+net_addr *
+bt_read_net_file(const char *filename, int type, uint *n)
+{
+ FILE *f = fopen(filename, "r");
+ bt_syscall(!f, "fopen(%s)", filename);
+ net_addr *nets = bt_read_nets(f, type, n);
+ fclose(f);
+
+ return nets;
+}
diff --git a/test/bt-utils.h b/test/bt-utils.h
index 13d267cc..d29a0b7c 100644
--- a/test/bt-utils.h
+++ b/test/bt-utils.h
@@ -26,6 +26,12 @@
uint bt_naive_pow(uint base, uint power);
void bt_bytes_to_hex(char *buf, const byte *in_data, size_t size);
+void bt_random_net(net_addr *net, int type);
+net_addr *bt_random_nets(int type, uint n);
+net_addr *bt_random_net_subset(net_addr *src, uint sn, uint dn);
+void bt_read_net(const char *str, net_addr *net, int type);
+net_addr *bt_read_nets(FILE *f, int type, uint *n);
+net_addr *bt_read_net_file(const char *filename, int type, uint *n);
void bt_bird_init(void);
void bt_bird_cleanup(void);