完善routerkey和aspa功能
This commit is contained in:
@@ -59,7 +59,7 @@ cargo run --bin rtr_debug_client -- <addr> <version> [reset|serial <session_id>
|
||||
```
|
||||
|
||||
默认值:
|
||||
- `addr`: `127.0.0.1:3323`
|
||||
- `addr`: `127.0.0.1:323`
|
||||
- `version`: `1`
|
||||
- `mode`: `reset`
|
||||
- `timeout`: `30`
|
||||
@@ -70,19 +70,19 @@ cargo run --bin rtr_debug_client -- <addr> <version> [reset|serial <session_id>
|
||||
发送 `Reset Query`:
|
||||
|
||||
```sh
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:3323 1 reset
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 reset
|
||||
```
|
||||
|
||||
发送 `Serial Query`:
|
||||
|
||||
```sh
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:3323 1 serial 42 100
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 serial 42 100
|
||||
```
|
||||
|
||||
持续观察错误路径:
|
||||
|
||||
```sh
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:3323 1 reset --keep-after-error
|
||||
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 reset --keep-after-error
|
||||
```
|
||||
|
||||
## TLS 示例
|
||||
@@ -91,7 +91,7 @@ cargo run --bin rtr_debug_client -- 127.0.0.1:3323 1 reset --keep-after-error
|
||||
|
||||
```sh
|
||||
cargo run --bin rtr_debug_client -- \
|
||||
127.0.0.1:3324 1 reset \
|
||||
127.0.0.1:324 1 reset \
|
||||
--tls \
|
||||
--ca-cert tests/fixtures/tls/client-ca.crt \
|
||||
--server-name localhost
|
||||
@@ -101,7 +101,7 @@ cargo run --bin rtr_debug_client -- \
|
||||
|
||||
```sh
|
||||
cargo run --bin rtr_debug_client -- \
|
||||
127.0.0.1:3324 1 reset \
|
||||
127.0.0.1:324 1 reset \
|
||||
--tls \
|
||||
--ca-cert tests/fixtures/tls/client-ca.crt \
|
||||
--server-name localhost \
|
||||
@@ -113,7 +113,7 @@ cargo run --bin rtr_debug_client -- \
|
||||
|
||||
```sh
|
||||
cargo run --bin rtr_debug_client -- \
|
||||
127.0.0.1:3324 1 reset \
|
||||
127.0.0.1:324 1 reset \
|
||||
--tls \
|
||||
--ca-cert tests/fixtures/tls/client-ca.crt \
|
||||
--server-name localhost \
|
||||
|
||||
@@ -16,7 +16,7 @@ mod protocol;
|
||||
|
||||
use crate::wire::{read_pdu, send_reset_query, send_serial_query};
|
||||
use crate::pretty::{
|
||||
parse_end_of_data_info, parse_serial_notify_serial, print_pdu,
|
||||
parse_end_of_data_info, parse_serial_notify_serial, print_pdu, print_raw_pdu,
|
||||
};
|
||||
use crate::protocol::{PduHeader, PduType, QueryMode};
|
||||
|
||||
@@ -53,10 +53,6 @@ async fn main() -> io::Result<()> {
|
||||
println!();
|
||||
print_help();
|
||||
|
||||
let stream = connect_stream(&config).await?;
|
||||
println!("connected to {}", config.addr);
|
||||
|
||||
let (mut reader, mut writer) = tokio_io::split(stream);
|
||||
let mut state = ClientState::new(
|
||||
config.version,
|
||||
config.read_timeout_secs,
|
||||
@@ -64,85 +60,168 @@ async fn main() -> io::Result<()> {
|
||||
config.keep_after_error,
|
||||
);
|
||||
|
||||
match config.mode {
|
||||
QueryMode::Reset => {
|
||||
send_reset_query(&mut writer, config.version).await?;
|
||||
println!("sent Reset Query");
|
||||
}
|
||||
QueryMode::Serial { session_id, serial } => {
|
||||
state.session_id = Some(session_id);
|
||||
state.serial = Some(serial);
|
||||
send_serial_query(&mut writer, config.version, session_id, serial).await?;
|
||||
println!("sent Serial Query");
|
||||
}
|
||||
}
|
||||
|
||||
state.schedule_next_poll();
|
||||
println!();
|
||||
|
||||
let stdin = tokio::io::stdin();
|
||||
let mut stdin_lines = BufReader::new(stdin).lines();
|
||||
|
||||
loop {
|
||||
let poll_sleep = tokio::time::sleep_until(state.poll_deadline());
|
||||
tokio::pin!(poll_sleep);
|
||||
let stream = loop {
|
||||
match connect_stream(&config).await {
|
||||
Ok(stream) => {
|
||||
println!("connected to {}", config.addr);
|
||||
break stream;
|
||||
}
|
||||
Err(err) => {
|
||||
let delay = state.reconnect_delay_secs();
|
||||
eprintln!(
|
||||
"connect failed: {}. retry after {}s",
|
||||
err,
|
||||
delay
|
||||
);
|
||||
tokio::time::sleep(Duration::from_secs(delay)).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
line = stdin_lines.next_line() => {
|
||||
match line {
|
||||
Ok(Some(line)) => {
|
||||
let should_quit = handle_console_command(
|
||||
&line,
|
||||
&mut writer,
|
||||
&mut state,
|
||||
).await?;
|
||||
let (mut reader, mut writer) = tokio_io::split(stream);
|
||||
send_resume_query(&mut writer, &mut state, &config.mode).await?;
|
||||
state.schedule_next_poll();
|
||||
println!();
|
||||
|
||||
if should_quit {
|
||||
println!("quit requested, closing client.");
|
||||
break;
|
||||
let reconnect = loop {
|
||||
let poll_sleep = tokio::time::sleep_until(state.poll_deadline());
|
||||
tokio::pin!(poll_sleep);
|
||||
|
||||
tokio::select! {
|
||||
line = stdin_lines.next_line() => {
|
||||
match line {
|
||||
Ok(Some(line)) => {
|
||||
match handle_console_command(
|
||||
&line,
|
||||
&mut writer,
|
||||
&mut state,
|
||||
).await {
|
||||
Ok(should_quit) => {
|
||||
if should_quit {
|
||||
println!("quit requested, closing client.");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(err) if should_reconnect(&err) => {
|
||||
eprintln!("command failed due to disconnected transport: {}", err);
|
||||
break true;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("stdin closed, continue network loop.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("read stdin failed: {}", err);
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("stdin closed, continue network loop.");
|
||||
}
|
||||
|
||||
_ = &mut poll_sleep => {
|
||||
match handle_poll_tick(&mut writer, &mut state).await {
|
||||
Ok(()) => state.schedule_next_poll(),
|
||||
Err(err) if should_reconnect(&err) => {
|
||||
eprintln!("auto poll failed due to disconnected transport: {}", err);
|
||||
break true;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("read stdin failed: {}", err);
|
||||
}
|
||||
|
||||
read_result = timeout(
|
||||
Duration::from_secs(state.read_timeout_secs),
|
||||
read_pdu(&mut reader)
|
||||
) => {
|
||||
match read_result {
|
||||
Ok(Ok(pdu)) => {
|
||||
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 {
|
||||
Ok(()) => {}
|
||||
Err(err) if should_reconnect(&err) => {
|
||||
eprintln!("connection dropped while handling incoming PDU: {}", err);
|
||||
break true;
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
eprintln!("read PDU failed: {}", err);
|
||||
if should_reconnect(&err) {
|
||||
break true;
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
Err(_) => {
|
||||
println!(
|
||||
"[timeout] no PDU received in {}s, connection kept open.",
|
||||
state.read_timeout_secs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_ = &mut poll_sleep => {
|
||||
handle_poll_tick(&mut writer, &mut state).await?;
|
||||
state.schedule_next_poll();
|
||||
}
|
||||
|
||||
read_result = timeout(
|
||||
Duration::from_secs(state.read_timeout_secs),
|
||||
read_pdu(&mut reader)
|
||||
) => {
|
||||
match read_result {
|
||||
Ok(Ok(pdu)) => {
|
||||
print_pdu(&pdu.header, &pdu.body);
|
||||
handle_incoming_pdu(&mut writer, &mut state, &pdu.header, &pdu.body).await?;
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
eprintln!("read PDU failed: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
Err(_) => {
|
||||
println!(
|
||||
"[timeout] no PDU received in {}s, connection kept open.",
|
||||
state.read_timeout_secs
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if reconnect {
|
||||
let delay = state.reconnect_delay_secs();
|
||||
state.current_session_id = None;
|
||||
println!(
|
||||
"[reconnect] transport disconnected, retry after {}s",
|
||||
delay
|
||||
);
|
||||
tokio::time::sleep(Duration::from_secs(delay)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_resume_query(
|
||||
writer: &mut ClientWriter,
|
||||
state: &mut ClientState,
|
||||
mode: &QueryMode,
|
||||
) -> io::Result<()> {
|
||||
match (state.session_id, state.serial) {
|
||||
(Some(session_id), Some(serial)) => {
|
||||
println!(
|
||||
"reconnected, send Serial Query with session_id={}, serial={}",
|
||||
session_id,
|
||||
serial
|
||||
);
|
||||
send_serial_query(writer, state.version, session_id, serial).await?;
|
||||
}
|
||||
_ => match mode {
|
||||
QueryMode::Reset => {
|
||||
send_reset_query(writer, state.version).await?;
|
||||
println!("sent Reset Query");
|
||||
}
|
||||
QueryMode::Serial { session_id, serial } => {
|
||||
state.session_id = Some(*session_id);
|
||||
state.serial = Some(*serial);
|
||||
send_serial_query(writer, state.version, *session_id, *serial).await?;
|
||||
println!("sent Serial Query");
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_reconnect(err: &io::Error) -> bool {
|
||||
matches!(
|
||||
err.kind(),
|
||||
io::ErrorKind::UnexpectedEof
|
||||
| io::ErrorKind::ConnectionAborted
|
||||
| io::ErrorKind::ConnectionReset
|
||||
| io::ErrorKind::BrokenPipe
|
||||
| io::ErrorKind::NotConnected
|
||||
)
|
||||
}
|
||||
|
||||
async fn handle_incoming_pdu(
|
||||
writer: &mut ClientWriter,
|
||||
state: &mut ClientState,
|
||||
@@ -590,6 +669,16 @@ impl ClientState {
|
||||
"default"
|
||||
}
|
||||
}
|
||||
|
||||
fn reconnect_delay_secs(&self) -> u64 {
|
||||
if self.should_prefer_retry_poll() {
|
||||
self.retry
|
||||
.map(|v| v as u64)
|
||||
.unwrap_or(self.default_poll_secs)
|
||||
} else {
|
||||
self.default_poll_secs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -615,7 +704,9 @@ impl Config {
|
||||
while let Some(arg) = args.next() {
|
||||
match arg.as_str() {
|
||||
"--tls" => {
|
||||
transport = TransportConfig::Tls(TlsConfig::default());
|
||||
if matches!(transport, TransportConfig::Tcp) {
|
||||
transport = TransportConfig::Tls(TlsConfig::default());
|
||||
}
|
||||
}
|
||||
"--ca-cert" => {
|
||||
let path = args.next().ok_or_else(|| {
|
||||
@@ -670,7 +761,7 @@ impl Config {
|
||||
|
||||
let addr = positional
|
||||
.next()
|
||||
.unwrap_or_else(|| "127.0.0.1:3323".to_string());
|
||||
.unwrap_or_else(|| "127.0.0.1:323".to_string());
|
||||
|
||||
let version = positional
|
||||
.next()
|
||||
|
||||
@@ -220,20 +220,18 @@ fn print_serial_query(header: &PduHeader, body: &[u8]) {
|
||||
println!("serial : {}", serial);
|
||||
}
|
||||
|
||||
fn print_router_key(header: &PduHeader, body: &[u8]) {
|
||||
println!("session_id : {}", header.session_id());
|
||||
|
||||
fn print_router_key(_header: &PduHeader, body: &[u8]) {
|
||||
if body.len() < ROUTER_KEY_FIXED_BODY_LEN {
|
||||
println!("invalid Router Key body length: {}", body.len());
|
||||
println!("raw body : {}", hex_bytes(body));
|
||||
return;
|
||||
}
|
||||
|
||||
let flags = body[0];
|
||||
let zero = body[1];
|
||||
let ski = &body[2..22];
|
||||
let asn = u32::from_be_bytes([body[22], body[23], body[24], body[25]]);
|
||||
let spki = &body[26..];
|
||||
let flags = _header.flags();
|
||||
let zero = _header.zero();
|
||||
let ski = &body[0..20];
|
||||
let asn = u32::from_be_bytes([body[20], body[21], body[22], body[23]]);
|
||||
let spki = &body[24..];
|
||||
|
||||
println!("flags : 0x{:02x} ({})", flags, flag_meaning(flags));
|
||||
println!("zero : {}", zero);
|
||||
@@ -241,6 +239,32 @@ fn print_router_key(header: &PduHeader, body: &[u8]) {
|
||||
println!("asn : {}", asn);
|
||||
println!("spki_len : {}", spki.len());
|
||||
println!("spki : {}", hex_bytes(spki));
|
||||
|
||||
if flags & !0x01 != 0 {
|
||||
println!("warning : Router Key flags use reserved bits");
|
||||
}
|
||||
if zero != 0 {
|
||||
println!("warning : Router Key reserved zero octet is non-zero");
|
||||
}
|
||||
if asn == 0 {
|
||||
println!("warning : Router Key ASN is AS0");
|
||||
}
|
||||
if spki.is_empty() {
|
||||
println!("warning : Router Key SPKI is empty");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_raw_pdu(header: &PduHeader, body: &[u8]) {
|
||||
let mut raw = Vec::with_capacity(8 + body.len());
|
||||
raw.push(header.version);
|
||||
raw.push(header.pdu_type_raw);
|
||||
raw.extend_from_slice(&header.field1.to_be_bytes());
|
||||
raw.extend_from_slice(&header.length.to_be_bytes());
|
||||
raw.extend_from_slice(body);
|
||||
|
||||
println!("--------------------------------------------------");
|
||||
println!("[raw] pdu_type : {}", header.pdu_type());
|
||||
println!("[raw] octets : {}", hex_bytes(&raw));
|
||||
}
|
||||
|
||||
fn error_code_name(code: u16) -> &'static str {
|
||||
@@ -271,26 +295,22 @@ fn parse_encapsulated_header(encapsulated: &[u8]) -> Option<PduHeader> {
|
||||
Some(PduHeader::from_bytes(header))
|
||||
}
|
||||
|
||||
fn print_aspa(header: &PduHeader, body: &[u8]) {
|
||||
println!("session_id : {}", header.session_id());
|
||||
|
||||
fn print_aspa(_header: &PduHeader, body: &[u8]) {
|
||||
if body.len() < ASPA_FIXED_BODY_LEN {
|
||||
println!("invalid ASPA body length: {}", body.len());
|
||||
println!("raw body : {}", hex_bytes(body));
|
||||
return;
|
||||
}
|
||||
|
||||
let flags = body[0];
|
||||
let zero1 = body[1];
|
||||
let zero2 = body[2];
|
||||
let zero3 = body[3];
|
||||
let customer_asn = u32::from_be_bytes([body[4], body[5], body[6], body[7]]);
|
||||
let flags = _header.flags();
|
||||
let zero = _header.zero();
|
||||
let customer_asn = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
|
||||
|
||||
println!("flags : 0x{:02x} ({})", flags, flag_meaning(flags));
|
||||
println!("reserved : [{}, {}, {}]", zero1, zero2, zero3);
|
||||
println!("reserved : {}", zero);
|
||||
println!("customer_as : {}", customer_asn);
|
||||
|
||||
let providers_raw = &body[8..];
|
||||
let providers_raw = &body[4..];
|
||||
if providers_raw.len() % 4 != 0 {
|
||||
println!("invalid ASPA providers length: {}", providers_raw.len());
|
||||
println!("providers : {}", hex_bytes(providers_raw));
|
||||
@@ -303,6 +323,34 @@ fn print_aspa(header: &PduHeader, body: &[u8]) {
|
||||
}
|
||||
|
||||
println!("providers : {:?}", providers);
|
||||
|
||||
if flags & !0x01 != 0 {
|
||||
println!("warning : ASPA flags use reserved bits");
|
||||
}
|
||||
if zero != 0 {
|
||||
println!("warning : ASPA reserved zero octet is non-zero");
|
||||
}
|
||||
if customer_asn == 0 {
|
||||
println!("warning : ASPA customer ASN is AS0");
|
||||
}
|
||||
|
||||
let is_announcement = flags & 0x01 == 0x01;
|
||||
if is_announcement {
|
||||
if providers.is_empty() {
|
||||
println!("warning : ASPA announcement has empty provider list");
|
||||
}
|
||||
} else if !providers.is_empty() {
|
||||
println!("warning : ASPA withdrawal must not contain providers");
|
||||
}
|
||||
|
||||
if providers.iter().any(|asn| *asn == 0) {
|
||||
println!("warning : ASPA provider list contains AS0");
|
||||
}
|
||||
|
||||
let strictly_increasing = providers.windows(2).all(|pair| pair[0] < pair[1]);
|
||||
if !strictly_increasing && providers.len() > 1 {
|
||||
println!("warning : ASPA providers are not in strictly increasing order");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_serial_notify_serial(body: &[u8]) -> Option<u32> {
|
||||
|
||||
@@ -2,14 +2,14 @@ use std::fmt;
|
||||
|
||||
pub const HEADER_LEN: usize = 8;
|
||||
pub const SERIAL_QUERY_LEN: usize = 12;
|
||||
pub const MAX_PDU_LEN: u32 = 1024 * 1024; // 1 MiB
|
||||
pub const MAX_PDU_LEN: u32 = 65_535;
|
||||
|
||||
pub const IPV4_PREFIX_BODY_LEN: usize = 12;
|
||||
pub const IPV6_PREFIX_BODY_LEN: usize = 24;
|
||||
pub const END_OF_DATA_V0_BODY_LEN: usize = 4;
|
||||
pub const END_OF_DATA_V1_BODY_LEN: usize = 16;
|
||||
pub const ROUTER_KEY_FIXED_BODY_LEN: usize = 26;
|
||||
pub const ASPA_FIXED_BODY_LEN: usize = 8;
|
||||
pub const ROUTER_KEY_FIXED_BODY_LEN: usize = 24;
|
||||
pub const ASPA_FIXED_BODY_LEN: usize = 4;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum QueryMode {
|
||||
@@ -126,6 +126,14 @@ impl PduHeader {
|
||||
pub fn error_code(&self) -> u16 {
|
||||
self.field1
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u8 {
|
||||
self.field1.to_be_bytes()[0]
|
||||
}
|
||||
|
||||
pub fn zero(&self) -> u8 {
|
||||
self.field1.to_be_bytes()[1]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
Reference in New Issue
Block a user