Skip to main content

bgpsim_macros/
lib.rs

1// BgpSim: BGP Network Simulator written in Rust
2// Copyright 2022-2023 Tibor Schneider <sctibor@ethz.ch>
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc(html_logo_url = "https://bgpsim.github.io/dark_only.svg")]
17
18use proc_macro::TokenStream;
19
20mod formatter;
21mod ip;
22mod net;
23use ip::PrefixInput;
24use net::Net;
25use syn::parse_macro_input;
26
27/// Create a `Network` using a domain specific language. This proc-macro will check at compile time
28/// that all invariants are satisfied.
29///
30/// # Syntax
31/// The content can contain the following parts:
32///
33/// - `links`: An enumeration of links in the network. Each link is written as `SRC -> DST: WEIGHT`,
34///   where both `SRC` and `DST` are identifiers of a node, and `WEIGHT` is a number defining the
35///   weight. By default, this notation will automatically create the link, and set the link weight
36///   in both directions. However, you can also set the link weight in the opposite direction by
37///   writing `DST -> SRC: WEIGHT`.
38///
39/// - `sessions`: An enumeration of all BGP sessions in the network. Each session is written as `SRC
40///   -> DST[: TYPE]`, where both `SRC` and `DST` are identifiers of a node. The `TYPE` is optiona,
41///   and can be omitted. If the type is omitted, then it will be a `BgpSessionType::IBgpPeer` for
42///   internal sessions, and `BgpSessionType::EBgp` for external sessions. The `TYPE` can be one of
43///   the following identifiers:
44///
45///   - `ebgp`, which maps to `BgpSessionType::EBgp`,
46///   - `peer`, which maps to `BgpSessionType::IBgpPeer`,
47///   - `client`, which maps to `BgpSessionType::IBgpClient`.
48///
49///   This macro will **automatically add links between nodes for external sessions** if they are
50///   not already defined in `links`.
51///
52/// - `default_asn`: The default ASN that is used when no AS number is given otherwise.
53///
54/// - `routes`: An enumeration of all BGP announcements from external routers. Each announcement is
55///   written as `SRC -> PREFIX as {path: P, [med: M], [communities: C]}`. The symbols mean the
56///   following:
57///   - `SRC` is the external router that announces the prefix.
58///   - `PREFIX` is the prefix that should be announced. The prefix can either be a number, a string
59///     containing an IP prefix (see [`prefix!`]), or an identifier of a local variable that was
60///     already defined earlier.
61///   - `P` is the AS path and is required. It can be either a single number (which will be turned
62///     into a path of length 1), an array of numbers representing the path, or any other arbitrary
63///     expression that evaluates to `impl Iterator<Item = I> where I: Into<AsId>`.
64///   - `M` is the MED value but is optional. If omitted, then the MED value will not be set in the
65///     announcement. `M` must be either a number, or an expression that evaluates to `Option<u32>`.
66///   - `C` is the set of communities present in the route, and is optional. Similar to `P`, it can
67///     also either take a single number, an array of numbers, or any other arbitrary expression
68///     that evaluates to `impl Iterator<Item = I> where I: Into<u32>`.
69///
70/// - `route_map`: Configure route-maps using `bgpsim::route_map::RouteMap::from_str`. Each
71///   route-map is written as `ROUTER <- NEIGHBOR: match {"MACH" => "ACTION", ...}`. The arrow
72///   indicates the route map direction (`<-` is incoming, `->` is outgoing). The clauses inside the
73///   match are the condition and actions as strings. For their syntax, see the documentation of
74///   `bgpsim::route_map::RouteMap::from_str`.
75///
76/// - `Prefix`: The type of the prefix. Choose either `SinglePrefix`, `SimplePrefix`, or
77///   `Ipv4Prefix` here (optional).
78///
79/// - `Queue`: The type of the queue (optional).
80///
81/// - `queue`: The expression to create the empty queue. If no queue is provided, then the expanded
82///   macro will use `Default::default()`.
83///
84/// - `return`: A nested tuple of identifiers that referr to previously defined nodes.
85///
86/// # Defining Routers
87/// Every node identifier can also be written like a function invocation by appending a `(ASN)`,
88/// where `ASN` is a literal number. This must be done for each node at least once in the macro
89/// invocation (must not be the first time) to define the AS number. Alternatively, you can also set
90/// the default ASN by adding `default_asn = ASN` somewhere.
91///
92/// # Example
93/// ```rust
94/// use bgpsim::prelude::*;
95///
96/// let (net, ((r0, r1), (e0, e1))) = net! {
97///     Prefix = Ipv4Prefix;
98///     default_asn = 100;
99///     links = {
100///         b0 -> r0: 1;
101///         r0 -> r1: 1;
102///         r1 -> b1: 1;
103///     };
104///     sessions = {
105///         b1 -> e1(1);
106///         b0 -> e0(2);
107///         r0 -> r1;
108///         r0 -> b0: client;
109///         r1 -> b1: client;
110///     };
111///     routes = {
112///         e0 -> "10.0.0.0/8" as {path: [1, 3, 4], med: 100, community: (0x65535, 666)};
113///         e1 -> "10.0.0.0/8" as {path: [2, 4]};
114///     };
115///     route_maps = {
116///         b0 <- e0: match {
117///             "*" => "local-pref 200; community set 100:1",
118///         };
119///         b0 -> e0: match {
120///             "*" => "permit",
121///         };
122///         b1 <- e1: match {
123///             "*" => "local-pref 100; community set 100:2",
124///         };
125///         b1 -> e1: match {
126///             "community 100:2" => "permit",
127///             "*" => "deny",
128///         };
129///     };
130///     return ((r0, r1), (e0, e1))
131/// };
132///
133/// assert_eq!(net.ospf_network().get_weight(r0, r1), 1.0);
134/// ```
135///
136/// This example will be expanded into the following code. This code was cleaned-up, so the
137/// different parts can be seen better.
138///
139/// ```rust
140/// use bgpsim::prelude::*;
141/// // these imports are added for compactness
142/// use ipnet::Ipv4Net;
143/// use std::net::Ipv4Addr;
144///
145/// let (net, ((b0, b1), (e0, e1))) = {
146///     let mut _net: Network<Ipv4Prefix, _> = Network::new(BasicEventQueue::default());
147///     let b0 = _net.add_router("b0", 100);
148///     let b1 = _net.add_router("b1", 100);
149///     let r0 = _net.add_router("r0", 100);
150///     let r1 = _net.add_router("r1", 100);
151///     let e0 = _net.add_router("e0", 2u32);
152///     let e1 = _net.add_router("e1", 1u32);
153///
154///     _net.add_link(b0, r0);
155///     _net.add_link(r1, b1);
156///     _net.add_link(r0, r1);
157///     _net.add_link(b1, e1);
158///     _net.add_link(b0, e0);
159///
160///     _net.set_link_weight(b0, r0, 1f64).unwrap();
161///     _net.set_link_weight(r0, b0, 1f64).unwrap();
162///     _net.set_link_weight(r1, b1, 1f64).unwrap();
163///     _net.set_link_weight(b1, r1, 1f64).unwrap();
164///     _net.set_link_weight(r0, r1, 1f64).unwrap();
165///     _net.set_link_weight(r1, r0, 1f64).unwrap();
166///
167///     _net.set_bgp_session(b0, e0, Some(false)).unwrap();
168///     _net.set_bgp_session(r1, b1, Some(true)).unwrap();
169///     _net.set_bgp_session(r0, r1, Some(false)).unwrap();
170///     _net.set_bgp_session(b1, e1, Some(false)).unwrap();
171///     _net.set_bgp_session(r0, b0, Some(true)).unwrap();
172///
173///     _net.advertise_route(
174///             e0,
175///             Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 0),8).unwrap(),
176///             [1, 3, 4],
177///             Some(100),
178///             [(65535, 666).into()],
179///         ).unwrap();
180///     _net.advertise_route(
181///             e1,
182///             Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 0),8).unwrap(),
183///             [2, 4],
184///             None,
185///             [],
186///         ).unwrap();
187///     (_net, ((b0, b1), (e0, e1)))
188/// };
189/// ```
190///
191/// ## Order or assigned Router-IDs
192///
193/// The router-IDs are assigned in order of their first occurrence. The first named router will be
194/// assigned id 0, the second 1, and so on. The first occurrence must not necessarily be in the
195/// `routers` block, but it also includes the mentioning of a router in a link or BGP session. Here
196/// is an example:
197///
198/// ```rust
199/// use bgpsim::prelude::*;
200///
201/// let (net, ((b0, b1), (r0, r1), (e0, e1))) = net! {
202///     Prefix = Ipv4Prefix;
203///     default_asn = 100;
204///     links = {
205///         b0 -> r0: 1;
206///         r0 -> r1: 1;
207///         r1 -> b1: 1;
208///     };
209///     sessions = {
210///         b1 -> e1(1);
211///         b0 -> e0(2);
212///         r0 -> r1: peer;
213///         r0 -> b0: client;
214///         r1 -> b1: client;
215///     };
216///     routes = {
217///         e0 -> "10.0.0.0/8" as {path: [1, 3, 4], med: 100, community: [(65535,666), (65535, 1)]};
218///         e1 -> "10.0.0.0/8" as {path: [2, 4]};
219///     };
220///     return ((b0, b1), (r0, r1), (e0, e1))
221/// };
222///
223/// assert_eq!(b0.index(), 0);
224/// assert_eq!(r0.index(), 1);
225/// assert_eq!(r1.index(), 2);
226/// assert_eq!(b1.index(), 3);
227/// assert_eq!(e1.index(), 4);
228/// assert_eq!(e0.index(), 5);
229/// ```
230#[proc_macro]
231pub fn net(input: TokenStream) -> TokenStream {
232    // 1. Use syn to parse the input tokens into a syntax tree.
233    // 2. Use quote to generate new tokens based on what we parsed.
234    // 3. Return the generated tokens.
235    parse_macro_input!(input as Net).quote()
236}
237
238/// Create a `Prefix` from an [`ipnet::Ipv4Net`] string. If you provide an `as`, you can
239/// specify to which type the resulting `Ipv4Net` will be casted. If you omit the type parameter
240/// after `as`, then the macro will simply invoke `.into()` on the generated `IpvtNet`.
241///
242/// ```
243/// # use bgpsim_macros::*;
244/// # use ipnet::Ipv4Net as P;
245/// // `p` will be an `Ipv4Net`
246/// let p = prefix!("192.168.0.0/24");
247///
248/// // `p` will have type `P`, but `P` must implement `From<Ipv4Net>`.
249/// let p = prefix!("192.168.0.0/24" as P);
250/// let p: P = prefix!("192.168.0.0/24" as);
251/// ```
252#[proc_macro]
253pub fn prefix(input: TokenStream) -> TokenStream {
254    parse_macro_input!(input as PrefixInput).quote()
255}
256
257/// Automatically implement the NetworkFormatter for the given type. The strings are generated
258/// similar to the derived `std::fmt::Debug` implementation.
259///
260/// You can control the way in which individual fields are formatted. To do so, you can use the
261/// `#[formatter(...)]` attribute. You can use the following values:
262///
263/// - `skip` will skip that field entirely.
264/// - `fmt = ...` controls which function to use for the (single-line) formatting. You have the
265///   following options:
266///   - `path::to::fn`: A path to a function that takes a reference to the value and to the network
267///     the same function signature as `NetworkFormatter::fmt`. If you pick a
268///     custom function without specifying a `multiline` attribute, then the same function will be
269///     used when formatting the field for multiple lines.
270///   - `"fmt"`: The default (single-line) formatter (used by default). See `NetworkFormatter::fmt`.
271///   - `"fmt_set`: Format any iterable as a set. See `NetworkFormatterSequence::fmt_set`.
272///   - `"fmt_list`: Format any iterable as a list. See `NetworkFormatterSequence::fmt_list`.
273///   - `"fmt_path`: Format any iterable as a path, in the form of `a -> b -> c`. See
274///     `NetworkFormatterSequence::fmt_path`.
275///   - `"fmt_map`: Format the content as a mapping. See `NetworkFormatterMap::fmt_map`.
276///   - `"fmt_map`: Format the content as a mapping. See `NetworkFormatterMap::fmt_map`.
277///   - `"fmt_path_options`: Format a nested iterator as a path option set, in the form of `a -> b |
278///     a -> b -> c`. See `NetworkFormatterNestedSequence::fmt_path_options`.
279///   - `"fmt_path_set`: Format a nested iterator as a path option set, in the form of `{a -> b,
280///     a -> b -> c}`. See `NetworkFormatterNestedSequence::fmt_path_set`.
281///   - `"fmt_ext`: Format any iterable using the extension formatter, see
282///     `NetworkFormatterExt::fmt_ext`.
283/// - `multiline = ...` controls which function to use for the multiline formatting. By default, it
284///   will pick the multi-line variant of the `fmt` option (for instance, setting `fmt = "fmt_set"`
285///   will automatically configure `multiline = "fmt_set_multiline"`). In addition to those, you
286///   have the following options:
287///   - `path::to::fn`: A path to a function that takes a reference to the value, to the network,
288///     and an usize counting the current indentation level. It must have the same function
289///     signature as `NetworkFormatter::fmt_multiline_indent`.
290///   - `"fmt_multiline"`: The default multi-line formatter (used by default). See
291///     `NetworkFormatter::fmt_multiline_indent`.
292///   - `"fmt_set_multiline`: Format any iterable as a set. See
293///     `NetworkFormatterSequence::fmt_set_multiline`.
294///   - `"fmt_list_multiline`: Format any iterable as a list. See
295///     `NetworkFormatterSequence::fmt_list_multiline`.
296///   - `"fmt_map_multiline`: Format the content as a mapping. See
297///     `NetworkFormatterMap::fmt_map_multiline`.
298///   - `"fmt_path_multiline`: Format the content as a set of paths. See
299///     `NetworkFormatterNestedSequence::fmt_path_multiline`.
300///
301/// ```
302/// use bgpsim::prelude::*;
303/// # use std::collections::HashSet;
304///
305/// #[derive(NetworkFormatter)]
306/// struct Foo {
307///     /// Will be printed regularly
308///     counter: usize,
309///     // Will be hidden
310///     #[formatter(skip)]
311///     internal_counter: usize,
312///     /// This will print a path instead of a list
313///     #[formatter(fmt = "fmt_path")]
314///     path: Vec<RouterId>,
315///     /// Do not print this field with multiple lines
316///     #[formatter(multiline = "fmt")]
317///     visited: HashSet<RouterId>,
318/// }
319/// ```
320#[proc_macro_derive(NetworkFormatter, attributes(formatter))]
321pub fn network_formatter_derive(input: TokenStream) -> TokenStream {
322    formatter::derive(input)
323}