1
1
use crate :: store:: { NetQuery , Query , QueryLimits , QueryResult , Store } ;
2
2
use axum:: body:: StreamBody ;
3
+ use axum:: extract:: FromRef ;
3
4
use axum:: extract:: { Query as AxumQuery , State } ;
4
5
use axum:: http:: StatusCode ;
5
6
use axum:: response:: { IntoResponse , Response } ;
@@ -10,7 +11,11 @@ use hickory_resolver::config::LookupIpStrategy;
10
11
use hickory_resolver:: TokioAsyncResolver ;
11
12
use ipnet:: IpNet ;
12
13
use log:: * ;
14
+ use regex:: Regex ;
15
+ use regex:: RegexSet ;
13
16
use serde:: { Deserialize , Serialize } ;
17
+ use std:: borrow:: Cow ;
18
+ use std:: collections:: HashMap ;
14
19
use std:: collections:: HashSet ;
15
20
use std:: convert:: Infallible ;
16
21
use std:: net:: IpAddr ;
@@ -21,6 +26,8 @@ use std::sync::Arc;
21
26
#[ cfg( feature = "embed-static" ) ]
22
27
static STATIC_DIR : include_dir:: Dir < ' _ > = include_dir:: include_dir!( "$CARGO_MANIFEST_DIR/static" ) ;
23
28
29
+ static COMMUNITIES_LIST : & [ u8 ] = include_bytes ! ( "communities.json" ) ;
30
+
24
31
fn default_asn_dns_zone ( ) -> Option < String > {
25
32
Some ( "as{}.asn.cymru.com." . to_string ( ) )
26
33
}
@@ -36,6 +43,8 @@ pub struct ApiServerConfig {
36
43
/// Dns zone used for ASN lookups
37
44
#[ serde( default = "default_asn_dns_zone" ) ]
38
45
pub asn_dns_zone : Option < String > ,
46
+ /// Path to alternative communities.json
47
+ communities_file : Option < String > ,
39
48
}
40
49
41
50
#[ derive( Debug , Clone , Serialize ) ]
@@ -49,6 +58,10 @@ pub enum ApiResult {
49
58
asn : u32 ,
50
59
asn_name : String ,
51
60
} ,
61
+ CommunityDescription {
62
+ community : String ,
63
+ community_description : String ,
64
+ } ,
52
65
}
53
66
54
67
// Make our own error that wraps `anyhow::Error`.
76
89
}
77
90
}
78
91
92
+ #[ derive( Clone ) ]
93
+ struct AppState < T : Clone > {
94
+ cfg : Arc < ApiServerConfig > ,
95
+ resolver : TokioAsyncResolver ,
96
+ community_lists : Arc < CompiledCommunitiesLists > ,
97
+ store : T ,
98
+ }
99
+
100
+ impl < T : Clone > FromRef < AppState < T > > for Arc < ApiServerConfig > {
101
+ fn from_ref ( app_state : & AppState < T > ) -> Self {
102
+ app_state. cfg . clone ( )
103
+ }
104
+ }
105
+
106
+ impl < T : Clone > FromRef < AppState < T > > for TokioAsyncResolver {
107
+ fn from_ref ( app_state : & AppState < T > ) -> Self {
108
+ app_state. resolver . clone ( )
109
+ }
110
+ }
111
+
112
+ impl < T : Clone > FromRef < AppState < T > > for Arc < CompiledCommunitiesLists > {
113
+ fn from_ref ( app_state : & AppState < T > ) -> Self {
114
+ app_state. community_lists . clone ( )
115
+ }
116
+ }
117
+
79
118
async fn parse_or_resolve ( resolver : & TokioAsyncResolver , name : String ) -> anyhow:: Result < IpNet > {
80
119
if let Ok ( net) = name. parse ( ) {
81
120
return Ok ( net) ;
@@ -93,8 +132,82 @@ async fn parse_or_resolve(resolver: &TokioAsyncResolver, name: String) -> anyhow
93
132
. into ( ) )
94
133
}
95
134
135
+ #[ derive( Deserialize ) ]
136
+ struct CommunitiesLists {
137
+ regular : CommunitiesList ,
138
+ large : CommunitiesList ,
139
+ }
140
+ impl CommunitiesLists {
141
+ fn compile ( self ) -> anyhow:: Result < CompiledCommunitiesLists > {
142
+ Ok ( CompiledCommunitiesLists {
143
+ regular : self . regular . compile ( ) ?,
144
+ large : self . large . compile ( ) ?,
145
+ } )
146
+ }
147
+ }
148
+
149
+ struct CompiledCommunitiesLists {
150
+ regular : CompiledCommunitiesList ,
151
+ large : CompiledCommunitiesList ,
152
+ }
153
+
154
+ #[ derive( Deserialize ) ]
155
+ struct CommunitiesList ( HashMap < String , String > ) ;
156
+
157
+ impl CommunitiesList {
158
+ fn compile ( self ) -> anyhow:: Result < CompiledCommunitiesList > {
159
+ let mut sorted = self . 0 . into_iter ( ) . collect :: < Vec < _ > > ( ) ;
160
+ sorted. sort_by ( |a, b| a. 0 . len ( ) . cmp ( & b. 0 . len ( ) ) ) ;
161
+ Ok ( CompiledCommunitiesList {
162
+ regex_set : RegexSet :: new ( sorted. iter ( ) . map ( |( regex, _desc) | format ! ( "^{}$" , regex) ) ) ?,
163
+ list : sorted
164
+ . into_iter ( )
165
+ . map ( |( key, value) | Ok ( ( Regex :: new ( & format ! ( "^{}$" , key) ) ?, value) ) )
166
+ . collect :: < anyhow:: Result < _ > > ( ) ?,
167
+ } )
168
+ }
169
+ }
170
+
171
+ struct CompiledCommunitiesList {
172
+ regex_set : RegexSet ,
173
+ list : Vec < ( Regex , String ) > ,
174
+ }
175
+ impl CompiledCommunitiesList {
176
+ fn lookup ( & self , community : & str ) -> Option < Cow < str > > {
177
+ self . regex_set
178
+ . matches ( community)
179
+ . iter ( )
180
+ . next ( )
181
+ . map ( |index| {
182
+ let ( regex, desc) = & self . list [ index] ;
183
+ let mut desc_templated: Cow < str > = desc. into ( ) ;
184
+ for ( i, subcapture) in regex
185
+ . captures ( community)
186
+ . unwrap ( )
187
+ . iter ( )
188
+ . skip ( 1 )
189
+ . enumerate ( )
190
+ {
191
+ if let Some ( subcapture) = subcapture {
192
+ let searchstr = format ! ( "${}" , i) ;
193
+ if desc_templated. contains ( & searchstr) {
194
+ desc_templated =
195
+ desc_templated. replace ( & searchstr, subcapture. into ( ) ) . into ( )
196
+ }
197
+ }
198
+ }
199
+ desc_templated
200
+ } )
201
+ }
202
+ }
203
+
96
204
async fn query < T : Store > (
97
- State ( ( cfg, resolver, store) ) : State < ( Arc < ApiServerConfig > , TokioAsyncResolver , T ) > ,
205
+ State ( AppState {
206
+ cfg,
207
+ resolver,
208
+ store,
209
+ community_lists,
210
+ } ) : State < AppState < T > > ,
98
211
AxumQuery ( query) : AxumQuery < Query < String > > ,
99
212
) -> Result < impl IntoResponse , AppError > {
100
213
trace ! ( "request: {}" , serde_json:: to_string_pretty( & query) . unwrap( ) ) ;
@@ -126,6 +239,8 @@ async fn query<T: Store>(
126
239
// for deduplicating the nexthop resolutions
127
240
let mut have_resolved = HashSet :: new ( ) ;
128
241
let mut have_asn = HashSet :: new ( ) ;
242
+ let mut have_community = HashSet :: new ( ) ;
243
+ let mut have_large_community = HashSet :: new ( ) ;
129
244
130
245
let stream = store
131
246
. get_routes ( query)
@@ -182,6 +297,35 @@ async fn query<T: Store>(
182
297
}
183
298
}
184
299
}
300
+ for community in route. attrs . communities . into_iter ( ) . flat_map ( |x| x) {
301
+ if have_community. insert ( community) {
302
+ let community_str = format ! ( "{}:{}" , community. 0 , community. 1 ) ;
303
+ if let Some ( lookup) = community_lists. regular . lookup ( & community_str) {
304
+ futures. push ( Box :: pin ( futures_util:: future:: ready ( Some (
305
+ ApiResult :: CommunityDescription {
306
+ community : community_str,
307
+ community_description : lookup. to_string ( ) ,
308
+ } ,
309
+ ) ) ) ) ;
310
+ }
311
+ }
312
+ }
313
+ for large_community in route. attrs . large_communities . into_iter ( ) . flat_map ( |x| x) {
314
+ if have_large_community. insert ( large_community) {
315
+ let large_community_str = format ! (
316
+ "{}:{}:{}" ,
317
+ large_community. 0 , large_community. 1 , large_community. 2
318
+ ) ;
319
+ if let Some ( lookup) = community_lists. large . lookup ( & large_community_str) {
320
+ futures. push ( Box :: pin ( futures_util:: future:: ready ( Some (
321
+ ApiResult :: CommunityDescription {
322
+ community : large_community_str,
323
+ community_description : lookup. to_string ( ) ,
324
+ } ,
325
+ ) ) ) ) ;
326
+ }
327
+ }
328
+ }
185
329
186
330
futures
187
331
} )
@@ -194,22 +338,35 @@ async fn query<T: Store>(
194
338
Ok ( StreamBody :: new ( stream) )
195
339
}
196
340
197
- async fn routers < T : Store > (
198
- State ( ( _, _, store) ) : State < ( Arc < ApiServerConfig > , TokioAsyncResolver , T ) > ,
199
- ) -> impl IntoResponse {
341
+ async fn routers < T : Store > ( State ( AppState { store, .. } ) : State < AppState < T > > ) -> impl IntoResponse {
200
342
serde_json:: to_string ( & store. get_routers ( ) ) . unwrap ( )
201
343
}
202
344
203
- fn make_api < T : Store > ( cfg : ApiServerConfig , store : T ) -> anyhow:: Result < Router > {
345
+ async fn make_api < T : Store > ( cfg : ApiServerConfig , store : T ) -> anyhow:: Result < Router > {
204
346
let resolver = {
205
347
let ( rcfg, mut ropts) = hickory_resolver:: system_conf:: read_system_conf ( ) ?;
206
348
ropts. ip_strategy = LookupIpStrategy :: Ipv6thenIpv4 ; // strange people set strange default settings
207
349
TokioAsyncResolver :: tokio ( rcfg, ropts)
208
350
} ;
351
+
352
+ let community_lists: CommunitiesLists = if let Some ( ref path) = cfg. communities_file {
353
+ let path = path. clone ( ) ;
354
+ serde_json:: from_slice ( & tokio:: task:: spawn_blocking ( move || std:: fs:: read ( path) ) . await ??) ?
355
+ } else {
356
+ serde_json:: from_slice ( COMMUNITIES_LIST ) ?
357
+ } ;
358
+
359
+ let community_lists = Arc :: new ( community_lists. compile ( ) ?) ;
360
+
209
361
Ok ( Router :: new ( )
210
362
. route ( "/query" , get ( query :: < T > ) )
211
363
. route ( "/routers" , get ( routers :: < T > ) )
212
- . with_state ( ( Arc :: new ( cfg) , resolver, store) ) )
364
+ . with_state ( AppState {
365
+ cfg : Arc :: new ( cfg) ,
366
+ resolver,
367
+ store,
368
+ community_lists,
369
+ } ) )
213
370
}
214
371
215
372
/// This handler serializes the metrics into a string for Prometheus to scrape
@@ -222,8 +379,8 @@ pub async fn get_metrics() -> (StatusCode, String) {
222
379
223
380
#[ cfg( feature = "embed-static" ) ]
224
381
async fn static_path ( axum:: extract:: Path ( path) : axum:: extract:: Path < String > ) -> impl IntoResponse {
225
- use axum:: body:: Full ;
226
382
use axum:: body:: Empty ;
383
+ use axum:: body:: Full ;
227
384
use axum:: http:: header;
228
385
use axum:: http:: header:: HeaderValue ;
229
386
@@ -259,11 +416,10 @@ pub async fn run_api_server<T: Store>(
259
416
}
260
417
261
418
router = router
262
- . nest ( "/api" , make_api ( cfg. clone ( ) , store) ?)
419
+ . nest ( "/api" , make_api ( cfg. clone ( ) , store) . await ?)
263
420
. route ( "/metrics" , get ( get_metrics) ) ;
264
421
265
- let make_service = router
266
- . into_make_service ( ) ;
422
+ let make_service = router. into_make_service ( ) ;
267
423
268
424
axum:: Server :: bind ( & cfg. bind )
269
425
. serve ( make_service)
0 commit comments