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/// - `routes`: An enumeration of all BGP announcements from external routers. Each announcement is
53/// written as `SRC -> PREFIX as {path: P, [med: M], [communities: C]}`. The symbols mean the
54/// following:
55/// - `SRC` is the external router that announces the prefix.
56/// - `PREFIX` is the prefix that should be announced. The prefix can either be a number, a string
57/// containing an IP prefix (see [`prefix!`]), or an identifier of a local variable that was
58/// already defined earlier.
59/// - `P` is the AS path and is required. It can be either a single number (which will be turned
60/// into a path of length 1), an array of numbers representing the path, or any other arbitrary
61/// expression that evaluates to `impl Iterator<Item = I> where I: Into<AsId>`.
62/// - `M` is the MED value but is optional. If omitted, then the MED value will not be set in the
63/// announcement. `M` must be either a number, or an expression that evaluates to `Option<u32>`.
64/// - `C` is the set of communities present in the route, and is optional. Similar to `P`, it can
65/// also either take a single number, an array of numbers, or any other arbitrary expression
66/// that evaluates to `impl Iterator<Item = I> where I: Into<u32>`.
67///
68/// - `Prefix`: The type of the prefix. Choose either `SinglePrefix`, `SimplePrefix`, or
69/// `Ipv4Prefix` here (optional).
70///
71/// - `Queue`: The type of the queue (optional).
72///
73/// - `queue`: The expression to create the empty queue. If no queue is provided, then the expanded
74/// macro will use `Default::default()`.
75///
76/// - `return`: A nested tuple of identifiers that referr to previously defined nodes.
77///
78/// # Defining external routers
79/// Every node identifier can also be written like a macro invocation by appending a `!(AS_ID)`,
80/// where `AS_ID` is a literal number. In that case, this node will be trned into an external router
81/// that uses the given AS number. You only need to annotate an external router once!
82///
83/// # Example
84/// ```rust
85/// use bgpsim::prelude::*;
86///
87/// let (net, ((b0, b1), (e0, e1))) = net! {
88/// Prefix = Ipv4Prefix;
89/// links = {
90/// b0 -> r0: 1;
91/// r0 -> r1: 1;
92/// r1 -> b1: 1;
93/// };
94/// sessions = {
95/// b1 -> e1!(1);
96/// b0 -> e0!(2);
97/// r0 -> r1: peer;
98/// r0 -> b0: client;
99/// r1 -> b1: client;
100/// };
101/// routes = {
102/// e0 -> "10.0.0.0/8" as {path: [1, 3, 4], med: 100, community: 20};
103/// e1 -> "10.0.0.0/8" as {path: [2, 4]};
104/// };
105/// return ((b0, b1), (e0, e1))
106/// };
107/// ```
108///
109/// This example will be expanded into the following code. This code was cleaned-up, so the
110/// different parts can be seen better.
111///
112/// ```rust
113/// use bgpsim::prelude::*;
114/// // these imports are added for compactness
115/// use ipnet::Ipv4Net;
116/// use std::net::Ipv4Addr;
117///
118/// let (_net, ((b0, b1), (e0, e1))) = {
119/// let mut _net: Network<Ipv4Prefix, _> = Network::new(BasicEventQueue::default());
120/// let b0 = _net.add_router("b0");
121/// let b1 = _net.add_router("b1");
122/// let r0 = _net.add_router("r0");
123/// let r1 = _net.add_router("r1");
124/// let e0 = _net.add_external_router("e0", 2u32);
125/// let e1 = _net.add_external_router("e1", 1u32);
126///
127/// _net.add_link(b0, r0);
128/// _net.add_link(r1, b1);
129/// _net.add_link(r0, r1);
130/// _net.add_link(b1, e1);
131/// _net.add_link(b0, e0);
132///
133/// _net.set_link_weight(b0, r0, 1f64).unwrap();
134/// _net.set_link_weight(r0, b0, 1f64).unwrap();
135/// _net.set_link_weight(r1, b1, 1f64).unwrap();
136/// _net.set_link_weight(b1, r1, 1f64).unwrap();
137/// _net.set_link_weight(r0, r1, 1f64).unwrap();
138/// _net.set_link_weight(r1, r0, 1f64).unwrap();
139///
140/// _net.set_bgp_session(b0, e0, Some(BgpSessionType::EBgp)).unwrap();
141/// _net.set_bgp_session(r1, b1, Some(BgpSessionType::IBgpClient)).unwrap();
142/// _net.set_bgp_session(r0, r1, Some(BgpSessionType::IBgpPeer)).unwrap();
143/// _net.set_bgp_session(b1, e1, Some(BgpSessionType::EBgp)).unwrap();
144/// _net.set_bgp_session(r0, b0, Some(BgpSessionType::IBgpClient)).unwrap();
145///
146/// _net.advertise_external_route(
147/// e0,
148/// Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 0),8).unwrap(),
149/// [1, 3, 4],
150/// Some(100),
151/// [20],
152/// ).unwrap();
153/// _net.advertise_external_route(
154/// e1,
155/// Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 0),8).unwrap(),
156/// [2, 4],
157/// None,
158/// [],
159/// ).unwrap();
160/// (_net, ((b0, b1), (e0, e1)))
161/// };
162/// ```
163///
164/// ## Order or assigned Router-IDs
165///
166/// The router-IDs are assigned in order of their first occurrence. The first named router will be
167/// assigned id 0, the second 1, and so on. The first occurrence must not necessarily be in the
168/// `routers` block, but it also includes the mentioning of a router in a link or BGP session. Here
169/// is an example:
170///
171/// ```rust
172/// use bgpsim::prelude::*;
173///
174/// let (net, ((b0, b1), (r0, r1), (e0, e1))) = net! {
175/// Prefix = Ipv4Prefix;
176/// links = {
177/// b0 -> r0: 1;
178/// r0 -> r1: 1;
179/// r1 -> b1: 1;
180/// };
181/// sessions = {
182/// b1 -> e1!(1);
183/// b0 -> e0!(2);
184/// r0 -> r1: peer;
185/// r0 -> b0: client;
186/// r1 -> b1: client;
187/// };
188/// routes = {
189/// e0 -> "10.0.0.0/8" as {path: [1, 3, 4], med: 100, community: 20};
190/// e1 -> "10.0.0.0/8" as {path: [2, 4]};
191/// };
192/// return ((b0, b1), (r0, r1), (e0, e1))
193/// };
194///
195/// assert_eq!(b0.index(), 0);
196/// assert_eq!(r0.index(), 1);
197/// assert_eq!(r1.index(), 2);
198/// assert_eq!(b1.index(), 3);
199/// assert_eq!(e1.index(), 4);
200/// assert_eq!(e0.index(), 5);
201/// ```
202#[proc_macro]
203pub fn net(input: TokenStream) -> TokenStream {
204 // 1. Use syn to parse the input tokens into a syntax tree.
205 // 2. Use quote to generate new tokens based on what we parsed.
206 // 3. Return the generated tokens.
207 parse_macro_input!(input as Net).quote()
208}
209
210/// Create a `Prefix` from an [`ipnet::Ipv4Net`] string. If you provide an `as`, you can
211/// specify to which type the resulting `Ipv4Net` will be casted. If you omit the type parameter
212/// after `as`, then the macro will simply invoke `.into()` on the generated `IpvtNet`.
213///
214/// ```
215/// # use bgpsim_macros::*;
216/// # use ipnet::Ipv4Net as P;
217/// // `p` will be an `Ipv4Net`
218/// let p = prefix!("192.168.0.0/24");
219///
220/// // `p` will have type `P`, but `P` must implement `From<Ipv4Net>`.
221/// let p = prefix!("192.168.0.0/24" as P);
222/// let p: P = prefix!("192.168.0.0/24" as);
223/// ```
224#[proc_macro]
225pub fn prefix(input: TokenStream) -> TokenStream {
226 parse_macro_input!(input as PrefixInput).quote()
227}
228
229/// Automatically implement the NetworkFormatter for the given type. The strings are generated
230/// similar to the derived `std::fmt::Debug` implementation.
231///
232/// You can control the way in which individual fields are formatted. To do so, you can use the
233/// `#[formatter(...)]` attribute. You can use the following values:
234///
235/// - `skip` will skip that field entirely.
236/// - `fmt = ...` controls which function to use for the (single-line) formatting. You have the
237/// following options:
238/// - `path::to::fn`: A path to a function that takes a reference to the value and to the network
239/// the same function signature as `NetworkFormatter::fmt`. If you pick a
240/// custom function without specifying a `multiline` attribute, then the same function will be
241/// used when formatting the field for multiple lines.
242/// - `"fmt"`: The default (single-line) formatter (used by default). See `NetworkFormatter::fmt`.
243/// - `"fmt_set`: Format any iterable as a set. See `NetworkFormatterSequence::fmt_set`.
244/// - `"fmt_list`: Format any iterable as a list. See `NetworkFormatterSequence::fmt_list`.
245/// - `"fmt_path`: Format any iterable as a path, in the form of `a -> b -> c`. See
246/// `NetworkFormatterSequence::fmt_path`.
247/// - `"fmt_map`: Format the content as a mapping. See `NetworkFormatterMap::fmt_map`.
248/// - `"fmt_map`: Format the content as a mapping. See `NetworkFormatterMap::fmt_map`.
249/// - `"fmt_path_options`: Format a nested iterator as a path option set, in the form of `a -> b |
250/// a -> b -> c`. See `NetworkFormatterNestedSequence::fmt_path_options`.
251/// - `"fmt_path_set`: Format a nested iterator as a path option set, in the form of `{a -> b,
252/// a -> b -> c}`. See `NetworkFormatterNestedSequence::fmt_path_set`.
253/// - `"fmt_ext`: Format any iterable using the extension formatter, see
254/// `NetworkFormatterExt::fmt_ext`.
255/// - `multiline = ...` controls which function to use for the multiline formatting. By default, it
256/// will pick the multi-line variant of the `fmt` option (for instance, setting `fmt = "fmt_set"`
257/// will automatically configure `multiline = "fmt_set_multiline"`). In addition to those, you
258/// have the following options:
259/// - `path::to::fn`: A path to a function that takes a reference to the value, to the network,
260/// and an usize counting the current indentation level. It must have the same function
261/// signature as `NetworkFormatter::fmt_multiline_indent`.
262/// - `"fmt_multiline"`: The default multi-line formatter (used by default). See
263/// `NetworkFormatter::fmt_multiline_indent`.
264/// - `"fmt_set_multiline`: Format any iterable as a set. See
265/// `NetworkFormatterSequence::fmt_set_multiline`.
266/// - `"fmt_list_multiline`: Format any iterable as a list. See
267/// `NetworkFormatterSequence::fmt_list_multiline`.
268/// - `"fmt_map_multiline`: Format the content as a mapping. See
269/// `NetworkFormatterMap::fmt_map_multiline`.
270/// - `"fmt_path_multiline`: Format the content as a set of paths. See
271/// `NetworkFormatterNestedSequence::fmt_path_multiline`.
272///
273/// ```
274/// use bgpsim::prelude::*;
275/// # use std::collections::HashSet;
276///
277/// #[derive(NetworkFormatter)]
278/// struct Foo {
279/// /// Will be printed regularly
280/// counter: usize
281/// /// Will be hidden
282/// #[formatter(skip)]
283/// internal_counter: usize
284/// /// This will print a path instead of a list
285/// #[formatter(fmt = "fmt_path")]
286/// path: Vec<RouterId>,
287/// /// Do not print this field with multiple lines
288/// #[formatter(multiline = "fmt")]
289/// visited: HashSet<RouterId>,
290/// }
291/// ```
292#[proc_macro_derive(NetworkFormatter, attributes(formatter))]
293pub fn network_formatter_derive(input: TokenStream) -> TokenStream {
294 formatter::derive(input)
295}