add dockerfile
This commit is contained in:
parent
23cdad095d
commit
c1d3112a45
Binary file not shown.
@ -1,7 +1,17 @@
|
|||||||
FROM rust:1.86-bookworm AS builder
|
FROM rust:1.89-bookworm AS builder
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
cmake \
|
||||||
|
pkg-config \
|
||||||
|
clang \
|
||||||
|
libclang-dev \
|
||||||
|
libssl-dev \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY Cargo.toml Cargo.lock ./
|
COPY Cargo.toml Cargo.lock ./
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
|
|
||||||
@ -31,4 +41,4 @@ ENV RPKI_RTR_ENABLE_TLS=false \
|
|||||||
|
|
||||||
EXPOSE 323 324
|
EXPOSE 323 324
|
||||||
|
|
||||||
CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/rpki-rtr.conf"]
|
CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/rpki-rtr.conf"]
|
||||||
24
deploy/Dockerfile.client
Normal file
24
deploy/Dockerfile.client
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
FROM rust:1.89-bookworm AS builder
|
||||||
|
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends clang libclang-dev pkg-config \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY Cargo.toml Cargo.lock ./
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
RUN cargo build --release --bin rtr_debug_client
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim AS runtime
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /build/target/release/rtr_debug_client /usr/local/bin/rtr_debug_client
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/rtr_debug_client"]
|
||||||
10
deploy/docker-compose.client.yml
Normal file
10
deploy/docker-compose.client.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
rtr-debug-client:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: deploy/Dockerfile.client
|
||||||
|
image: rpki-rtr-debug-client:latest
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
32
deploy/docker-compose.clients.yml
Normal file
32
deploy/docker-compose.clients.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
rtr-client-1:
|
||||||
|
image: rpki-rtr-debug-client:latest
|
||||||
|
network_mode: host
|
||||||
|
command: ["127.0.0.1:323", "2", "reset", "--keep-after-error", "--summary-only"]
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
rtr-client-2:
|
||||||
|
image: rpki-rtr-debug-client:latest
|
||||||
|
network_mode: host
|
||||||
|
command: ["127.0.0.1:323", "2", "reset", "--keep-after-error", "--summary-only"]
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
rtr-client-3:
|
||||||
|
image: rpki-rtr-debug-client:latest
|
||||||
|
network_mode: host
|
||||||
|
command: ["127.0.0.1:323", "2", "reset", "--keep-after-error", "--summary-only"]
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
rtr-client-4:
|
||||||
|
image: rpki-rtr-debug-client:latest
|
||||||
|
network_mode: host
|
||||||
|
command: ["127.0.0.1:323", "2", "reset", "--keep-after-error", "--summary-only"]
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
rtr-client-5:
|
||||||
|
image: rpki-rtr-debug-client:latest
|
||||||
|
network_mode: host
|
||||||
|
command: ["127.0.0.1:323", "2", "reset", "--keep-after-error", "--summary-only"]
|
||||||
|
restart: unless-stopped
|
||||||
@ -1,4 +1,5 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
use std::future::pending;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -27,6 +28,12 @@ impl<T> AsyncStream for T where T: AsyncRead + AsyncWrite + Unpin + Send {}
|
|||||||
type DynStream = Box<dyn AsyncStream>;
|
type DynStream = Box<dyn AsyncStream>;
|
||||||
type ClientWriter = WriteHalf<DynStream>;
|
type ClientWriter = WriteHalf<DynStream>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum OutputMode {
|
||||||
|
Verbose,
|
||||||
|
SummaryOnly,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> io::Result<()> {
|
async fn main() -> io::Result<()> {
|
||||||
let config = Config::from_args()?;
|
let config = Config::from_args()?;
|
||||||
@ -41,6 +48,7 @@ async fn main() -> io::Result<()> {
|
|||||||
config.default_poll_secs
|
config.default_poll_secs
|
||||||
);
|
);
|
||||||
println!("keep-after-error: {}", config.keep_after_error);
|
println!("keep-after-error: {}", config.keep_after_error);
|
||||||
|
println!("output : {}", config.output_mode.describe());
|
||||||
match &config.mode {
|
match &config.mode {
|
||||||
QueryMode::Reset => {
|
QueryMode::Reset => {
|
||||||
println!("mode : reset");
|
println!("mode : reset");
|
||||||
@ -59,10 +67,12 @@ async fn main() -> io::Result<()> {
|
|||||||
config.read_timeout_secs,
|
config.read_timeout_secs,
|
||||||
config.default_poll_secs,
|
config.default_poll_secs,
|
||||||
config.keep_after_error,
|
config.keep_after_error,
|
||||||
|
config.output_mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
let stdin = tokio::io::stdin();
|
let stdin = tokio::io::stdin();
|
||||||
let mut stdin_lines = BufReader::new(stdin).lines();
|
let mut stdin_lines = BufReader::new(stdin).lines();
|
||||||
|
let mut stdin_closed = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let stream = loop {
|
let stream = loop {
|
||||||
@ -89,7 +99,13 @@ async fn main() -> io::Result<()> {
|
|||||||
tokio::pin!(poll_sleep);
|
tokio::pin!(poll_sleep);
|
||||||
|
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
line = stdin_lines.next_line() => {
|
line = async {
|
||||||
|
if stdin_closed {
|
||||||
|
pending::<io::Result<Option<String>>>().await
|
||||||
|
} else {
|
||||||
|
stdin_lines.next_line().await
|
||||||
|
}
|
||||||
|
} => {
|
||||||
match line {
|
match line {
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
match handle_console_command(
|
match handle_console_command(
|
||||||
@ -111,7 +127,8 @@ async fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
println!("stdin closed, continue network loop.");
|
stdin_closed = true;
|
||||||
|
println!("stdin closed, disable console input.");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("read stdin failed: {}", err);
|
eprintln!("read stdin failed: {}", err);
|
||||||
@ -136,8 +153,11 @@ async fn main() -> io::Result<()> {
|
|||||||
) => {
|
) => {
|
||||||
match read_result {
|
match read_result {
|
||||||
Ok(Ok(pdu)) => {
|
Ok(Ok(pdu)) => {
|
||||||
print_raw_pdu(&pdu.header, &pdu.body);
|
state.observe_pdu(&pdu.header);
|
||||||
print_pdu(&pdu.header, &pdu.body);
|
if should_print_pdu(state.output_mode, &pdu.header) {
|
||||||
|
print_raw_pdu(&pdu.header, &pdu.body);
|
||||||
|
print_pdu(&pdu.header, &pdu.body);
|
||||||
|
}
|
||||||
match handle_incoming_pdu(&mut writer, &mut state, &pdu.header, &pdu.body).await {
|
match handle_incoming_pdu(&mut writer, &mut state, &pdu.header, &pdu.body).await {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(err) if should_reconnect(&err) => {
|
Err(err) if should_reconnect(&err) => {
|
||||||
@ -177,7 +197,13 @@ async fn main() -> io::Result<()> {
|
|||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = &mut reconnect_sleep => break,
|
_ = &mut reconnect_sleep => break,
|
||||||
line = stdin_lines.next_line() => {
|
line = async {
|
||||||
|
if stdin_closed {
|
||||||
|
pending::<io::Result<Option<String>>>().await
|
||||||
|
} else {
|
||||||
|
stdin_lines.next_line().await
|
||||||
|
}
|
||||||
|
} => {
|
||||||
match line {
|
match line {
|
||||||
Ok(Some(line)) => {
|
Ok(Some(line)) => {
|
||||||
match handle_console_command(&line, None, &mut state).await {
|
match handle_console_command(&line, None, &mut state).await {
|
||||||
@ -197,7 +223,8 @@ async fn main() -> io::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
println!("stdin closed, continue reconnect loop.");
|
stdin_closed = true;
|
||||||
|
println!("stdin closed, disable console input.");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("read stdin failed: {}", err);
|
eprintln!("read stdin failed: {}", err);
|
||||||
@ -326,6 +353,16 @@ async fn handle_incoming_pdu(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state.output_mode == OutputMode::SummaryOnly
|
||||||
|
&& state.skipped_payload_pdu_count_in_round > 0
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"summary : skipped {} payload PDUs in this response",
|
||||||
|
state.skipped_payload_pdu_count_in_round
|
||||||
|
);
|
||||||
|
state.skipped_payload_pdu_count_in_round = 0;
|
||||||
|
}
|
||||||
|
|
||||||
println!("received EndOfData, keep connection open.");
|
println!("received EndOfData, keep connection open.");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
@ -370,6 +407,7 @@ async fn handle_incoming_pdu(
|
|||||||
state.current_session_id = None;
|
state.current_session_id = None;
|
||||||
state.serial = None;
|
state.serial = None;
|
||||||
state.last_error_code = None;
|
state.last_error_code = None;
|
||||||
|
state.skipped_payload_pdu_count_in_round = 0;
|
||||||
send_reset_query(writer, state.version).await?;
|
send_reset_query(writer, state.version).await?;
|
||||||
state.schedule_next_poll();
|
state.schedule_next_poll();
|
||||||
println!();
|
println!();
|
||||||
@ -604,6 +642,21 @@ async fn handle_console_command(
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
["output"] => {
|
||||||
|
println!("current output mode: {}", state.output_mode.describe());
|
||||||
|
println!("skipped payload PDUs: {}", state.skipped_payload_pdu_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
["output", "verbose"] => {
|
||||||
|
state.output_mode = OutputMode::Verbose;
|
||||||
|
println!("updated output mode to verbose");
|
||||||
|
}
|
||||||
|
|
||||||
|
["output", "summary"] => {
|
||||||
|
state.output_mode = OutputMode::SummaryOnly;
|
||||||
|
println!("updated output mode to summary");
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
println!("unknown command: {}", line);
|
println!("unknown command: {}", line);
|
||||||
print_help();
|
print_help();
|
||||||
@ -629,6 +682,9 @@ fn print_help() {
|
|||||||
println!(" poll pause pause auto polling");
|
println!(" poll pause pause auto polling");
|
||||||
println!(" poll resume resume auto polling");
|
println!(" poll resume resume auto polling");
|
||||||
println!(" keep-after-error show current keep-after-error setting");
|
println!(" keep-after-error show current keep-after-error setting");
|
||||||
|
println!(" output show current output mode");
|
||||||
|
println!(" output verbose print all PDUs");
|
||||||
|
println!(" output summary suppress payload PDU details");
|
||||||
println!(" quit exit client");
|
println!(" quit exit client");
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
@ -648,6 +704,8 @@ fn print_state(state: &ClientState) {
|
|||||||
println!(" poll_source : {}", state.poll_interval_source());
|
println!(" poll_source : {}", state.poll_interval_source());
|
||||||
println!(" last_error_code : {:?}", state.last_error_code);
|
println!(" last_error_code : {:?}", state.last_error_code);
|
||||||
println!(" keep_after_error : {}", state.keep_after_error);
|
println!(" keep_after_error : {}", state.keep_after_error);
|
||||||
|
println!(" output_mode : {}", state.output_mode.describe());
|
||||||
|
println!(" skipped_payloads : {}", state.skipped_payload_pdu_count);
|
||||||
println!(" poll_paused : {}", state.poll_paused);
|
println!(" poll_paused : {}", state.poll_paused);
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
@ -664,6 +722,9 @@ struct ClientState {
|
|||||||
expire: Option<u32>,
|
expire: Option<u32>,
|
||||||
last_error_code: Option<u16>,
|
last_error_code: Option<u16>,
|
||||||
keep_after_error: bool,
|
keep_after_error: bool,
|
||||||
|
output_mode: OutputMode,
|
||||||
|
skipped_payload_pdu_count: u64,
|
||||||
|
skipped_payload_pdu_count_in_round: u64,
|
||||||
|
|
||||||
read_timeout_secs: u64,
|
read_timeout_secs: u64,
|
||||||
default_poll_secs: u64,
|
default_poll_secs: u64,
|
||||||
@ -679,6 +740,7 @@ impl ClientState {
|
|||||||
read_timeout_secs: u64,
|
read_timeout_secs: u64,
|
||||||
default_poll_secs: u64,
|
default_poll_secs: u64,
|
||||||
keep_after_error: bool,
|
keep_after_error: bool,
|
||||||
|
output_mode: OutputMode,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
version,
|
version,
|
||||||
@ -690,6 +752,9 @@ impl ClientState {
|
|||||||
expire: None,
|
expire: None,
|
||||||
last_error_code: None,
|
last_error_code: None,
|
||||||
keep_after_error,
|
keep_after_error,
|
||||||
|
output_mode,
|
||||||
|
skipped_payload_pdu_count: 0,
|
||||||
|
skipped_payload_pdu_count_in_round: 0,
|
||||||
read_timeout_secs,
|
read_timeout_secs,
|
||||||
default_poll_secs,
|
default_poll_secs,
|
||||||
next_poll_deadline: Instant::now() + Duration::from_secs(default_poll_secs),
|
next_poll_deadline: Instant::now() + Duration::from_secs(default_poll_secs),
|
||||||
@ -770,6 +835,14 @@ impl ClientState {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn observe_pdu(&mut self, header: &PduHeader) {
|
||||||
|
if self.output_mode == OutputMode::SummaryOnly && is_payload_pdu(header) {
|
||||||
|
self.skipped_payload_pdu_count = self.skipped_payload_pdu_count.saturating_add(1);
|
||||||
|
self.skipped_payload_pdu_count_in_round =
|
||||||
|
self.skipped_payload_pdu_count_in_round.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -781,6 +854,7 @@ struct Config {
|
|||||||
default_poll_secs: u64,
|
default_poll_secs: u64,
|
||||||
transport: TransportConfig,
|
transport: TransportConfig,
|
||||||
keep_after_error: bool,
|
keep_after_error: bool,
|
||||||
|
output_mode: OutputMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@ -791,6 +865,7 @@ impl Config {
|
|||||||
let mut read_timeout_secs = DEFAULT_READ_TIMEOUT_SECS;
|
let mut read_timeout_secs = DEFAULT_READ_TIMEOUT_SECS;
|
||||||
let mut default_poll_secs = DEFAULT_POLL_INTERVAL_SECS;
|
let mut default_poll_secs = DEFAULT_POLL_INTERVAL_SECS;
|
||||||
let mut keep_after_error = false;
|
let mut keep_after_error = false;
|
||||||
|
let mut output_mode = OutputMode::Verbose;
|
||||||
|
|
||||||
while let Some(arg) = args.next() {
|
while let Some(arg) = args.next() {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
@ -841,6 +916,9 @@ impl Config {
|
|||||||
"--keep-after-error" => {
|
"--keep-after-error" => {
|
||||||
keep_after_error = true;
|
keep_after_error = true;
|
||||||
}
|
}
|
||||||
|
"--summary-only" => {
|
||||||
|
output_mode = OutputMode::SummaryOnly;
|
||||||
|
}
|
||||||
_ if arg.starts_with("--") => {
|
_ if arg.starts_with("--") => {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidInput,
|
||||||
@ -924,10 +1002,34 @@ impl Config {
|
|||||||
default_poll_secs,
|
default_poll_secs,
|
||||||
transport,
|
transport,
|
||||||
keep_after_error,
|
keep_after_error,
|
||||||
|
output_mode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OutputMode {
|
||||||
|
fn describe(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Verbose => "verbose",
|
||||||
|
Self::SummaryOnly => "summary",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_payload_pdu(header: &PduHeader) -> bool {
|
||||||
|
matches!(
|
||||||
|
header.pdu_type(),
|
||||||
|
PduType::Ipv4Prefix | PduType::Ipv6Prefix | PduType::RouterKey | PduType::Aspa
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_print_pdu(output_mode: OutputMode, header: &PduHeader) -> bool {
|
||||||
|
match output_mode {
|
||||||
|
OutputMode::Verbose => true,
|
||||||
|
OutputMode::SummaryOnly => !is_payload_pdu(header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum TransportConfig {
|
enum TransportConfig {
|
||||||
Tcp,
|
Tcp,
|
||||||
|
|||||||
@ -668,13 +668,14 @@ where
|
|||||||
(
|
(
|
||||||
cache.session_id_for_version(version),
|
cache.session_id_for_version(version),
|
||||||
cache.serial_for_version(version),
|
cache.serial_for_version(version),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.write_cache_response(current_session).await?;
|
||||||
self.write_end_of_data(current_session, current_serial)
|
self.write_end_of_data(current_session, current_serial)
|
||||||
.await?;
|
.await?;
|
||||||
info!(
|
info!(
|
||||||
"RTR session replied EndOfData (up-to-date) to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, {}",
|
"RTR session replied CacheResponse+EndOfData (up-to-date) to Serial Query: client_session_id={}, client_serial={}, response_session_id={}, response_serial={}, {}",
|
||||||
client_session,
|
client_session,
|
||||||
client_serial,
|
client_serial,
|
||||||
current_session,
|
current_session,
|
||||||
|
|||||||
@ -268,6 +268,12 @@ pub struct AspaAssertion {
|
|||||||
|
|
||||||
impl AspaAssertion {
|
impl AspaAssertion {
|
||||||
fn validate(&self) -> Result<(), SlurmError> {
|
fn validate(&self) -> Result<(), SlurmError> {
|
||||||
|
if self.provider_asns.contains(&self.customer_asn) {
|
||||||
|
return Err(SlurmError::Invalid(
|
||||||
|
"aspaAssertion providerAsns must not contain customerAsn".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let providers = self
|
let providers = self
|
||||||
.provider_asns
|
.provider_asns
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@ -289,8 +289,9 @@ impl<'de> Deserialize<'de> for AspaAssertion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn decode_ski(input: &str) -> Result<Ski, SlurmError> {
|
fn decode_ski(input: &str) -> Result<Ski, SlurmError> {
|
||||||
let bytes = hex::decode(input)
|
let bytes = STANDARD_NO_PAD
|
||||||
.map_err(|err| SlurmError::Invalid(format!("invalid SKI '{}': {}", input, err)))?;
|
.decode(input)
|
||||||
|
.map_err(|err| SlurmError::Invalid(format!("invalid SKI base64 '{}': {}", input, err)))?;
|
||||||
if bytes.len() != 20 {
|
if bytes.len() != 20 {
|
||||||
return Err(SlurmError::Invalid(format!(
|
return Err(SlurmError::Invalid(format!(
|
||||||
"SKI must be exactly 20 bytes, got {}",
|
"SKI must be exactly 20 bytes, got {}",
|
||||||
|
|||||||
@ -20,9 +20,13 @@ fn sample_ski() -> [u8; 20] {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sample_ski_b64() -> String {
|
||||||
|
STANDARD_NO_PAD.encode(sample_ski())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_rfc8416_v1_slurm() {
|
fn parses_rfc8416_v1_slurm() {
|
||||||
let ski_hex = hex::encode(sample_ski());
|
let ski_b64 = sample_ski_b64();
|
||||||
let router_public_key = STANDARD_NO_PAD.encode(sample_spki());
|
let router_public_key = STANDARD_NO_PAD.encode(sample_spki());
|
||||||
let json = format!(
|
let json = format!(
|
||||||
r#"{{
|
r#"{{
|
||||||
@ -32,7 +36,7 @@ fn parses_rfc8416_v1_slurm() {
|
|||||||
{{ "prefix": "192.0.2.0/24", "asn": 64496, "comment": "drop roa" }}
|
{{ "prefix": "192.0.2.0/24", "asn": 64496, "comment": "drop roa" }}
|
||||||
],
|
],
|
||||||
"bgpsecFilters": [
|
"bgpsecFilters": [
|
||||||
{{ "asn": 64497, "SKI": "{ski_hex}" }}
|
{{ "asn": 64497, "SKI": "{ski_b64}" }}
|
||||||
]
|
]
|
||||||
}},
|
}},
|
||||||
"locallyAddedAssertions": {{
|
"locallyAddedAssertions": {{
|
||||||
@ -40,7 +44,7 @@ fn parses_rfc8416_v1_slurm() {
|
|||||||
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
||||||
],
|
],
|
||||||
"bgpsecAssertions": [
|
"bgpsecAssertions": [
|
||||||
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{router_public_key}" }}
|
{{ "asn": 64501, "SKI": "{ski_b64}", "routerPublicKey": "{router_public_key}" }}
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
}}"#
|
}}"#
|
||||||
@ -145,7 +149,7 @@ fn applies_filters_before_assertions_and_excludes_duplicates() {
|
|||||||
let ski = Ski::from_bytes(sample_ski());
|
let ski = Ski::from_bytes(sample_ski());
|
||||||
let spki = sample_spki();
|
let spki = sample_spki();
|
||||||
let spki_b64 = STANDARD_NO_PAD.encode(&spki);
|
let spki_b64 = STANDARD_NO_PAD.encode(&spki);
|
||||||
let ski_hex = hex::encode(sample_ski());
|
let ski_b64 = sample_ski_b64();
|
||||||
let json = format!(
|
let json = format!(
|
||||||
r#"{{
|
r#"{{
|
||||||
"slurmVersion": 2,
|
"slurmVersion": 2,
|
||||||
@ -154,7 +158,7 @@ fn applies_filters_before_assertions_and_excludes_duplicates() {
|
|||||||
{{ "prefix": "192.0.2.0/24", "asn": 64496 }}
|
{{ "prefix": "192.0.2.0/24", "asn": 64496 }}
|
||||||
],
|
],
|
||||||
"bgpsecFilters": [
|
"bgpsecFilters": [
|
||||||
{{ "SKI": "{ski_hex}" }}
|
{{ "SKI": "{ski_b64}" }}
|
||||||
],
|
],
|
||||||
"aspaFilters": [
|
"aspaFilters": [
|
||||||
{{ "customerAsn": 64496 }}
|
{{ "customerAsn": 64496 }}
|
||||||
@ -166,7 +170,7 @@ fn applies_filters_before_assertions_and_excludes_duplicates() {
|
|||||||
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
||||||
],
|
],
|
||||||
"bgpsecAssertions": [
|
"bgpsecAssertions": [
|
||||||
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{spki_b64}" }}
|
{{ "asn": 64501, "SKI": "{ski_b64}", "routerPublicKey": "{spki_b64}" }}
|
||||||
],
|
],
|
||||||
"aspaAssertions": [
|
"aspaAssertions": [
|
||||||
{{ "customerAsn": 64510, "providerAsns": [64511, 64512] }}
|
{{ "customerAsn": 64510, "providerAsns": [64511, 64512] }}
|
||||||
@ -216,6 +220,55 @@ fn applies_filters_before_assertions_and_excludes_duplicates() {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_hex_encoded_ski_and_aspa_customer_in_providers() {
|
||||||
|
let ski_hex = hex::encode(sample_ski());
|
||||||
|
let router_public_key = STANDARD_NO_PAD.encode(sample_spki());
|
||||||
|
let invalid_ski = format!(
|
||||||
|
r#"{{
|
||||||
|
"slurmVersion": 1,
|
||||||
|
"validationOutputFilters": {{
|
||||||
|
"prefixFilters": [],
|
||||||
|
"bgpsecFilters": [
|
||||||
|
{{ "SKI": "{ski_hex}" }}
|
||||||
|
]
|
||||||
|
}},
|
||||||
|
"locallyAddedAssertions": {{
|
||||||
|
"prefixAssertions": [],
|
||||||
|
"bgpsecAssertions": [
|
||||||
|
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{router_public_key}" }}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
}}"#
|
||||||
|
);
|
||||||
|
let ski_err = SlurmFile::from_slice(invalid_ski.as_bytes()).unwrap_err();
|
||||||
|
let ski_err_text = ski_err.to_string();
|
||||||
|
assert!(
|
||||||
|
ski_err_text.contains("invalid SKI base64")
|
||||||
|
|| ski_err_text.contains("SKI must be exactly 20 bytes")
|
||||||
|
);
|
||||||
|
|
||||||
|
let invalid_aspa = r#"{
|
||||||
|
"slurmVersion": 2,
|
||||||
|
"validationOutputFilters": {
|
||||||
|
"prefixFilters": [],
|
||||||
|
"bgpsecFilters": [],
|
||||||
|
"aspaFilters": []
|
||||||
|
},
|
||||||
|
"locallyAddedAssertions": {
|
||||||
|
"prefixAssertions": [],
|
||||||
|
"bgpsecAssertions": [],
|
||||||
|
"aspaAssertions": [
|
||||||
|
{ "customerAsn": 64500, "providerAsns": [64500, 64501] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
let aspa_err = SlurmFile::from_slice(invalid_aspa.as_bytes()).unwrap_err();
|
||||||
|
assert!(aspa_err
|
||||||
|
.to_string()
|
||||||
|
.contains("providerAsns must not contain customerAsn"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn merges_multiple_slurm_files_without_conflict() {
|
fn merges_multiple_slurm_files_without_conflict() {
|
||||||
let a = r#"{
|
let a = r#"{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user