diff options
author | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-07-29 18:59:46 +0200 |
---|---|---|
committer | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-07-29 18:59:46 +0200 |
commit | c375d2ccc8ae29361926f3654517912ef0cd2caf (patch) | |
tree | e3962a2fd3405fd52167b4cf37cfa3328eae4dd9 | |
parent | 5b5e130cf9b2df9c35887260f65ab6b9ef95ff6c (diff) |
implement ipcp
-rw-r--r-- | Cargo.lock | 11 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/main.rs | 388 | ||||
-rw-r--r-- | src/state.rs | 20 |
4 files changed, 396 insertions, 26 deletions
@@ -625,7 +625,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppproperly" version = "0.1.0" -source = "git+https://github.com/rsdsl/ppproperly.git#d41dcb2599f5160b5158a9e9f0bcd786b2236784" +source = "git+https://github.com/rsdsl/ppproperly.git#e0b75cab5d6835946b790d9188e1d94fc0653f69" dependencies = [ "bitfield", "ppproperly_macros", @@ -747,16 +747,16 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "rsdsl_ip_config" -version = "0.1.0" -source = "git+https://github.com/rsdsl/ip_config.git#e13adb9cc9367ad93f1e947335b0d56108015999" +version = "0.2.1" +source = "git+https://github.com/rsdsl/ip_config.git#26c15096a1f4b69c6c06b5373b507c9dbbfc54c5" dependencies = [ "serde", ] [[package]] name = "rsdsl_netlinkd" -version = "0.3.2" -source = "git+https://github.com/rsdsl/netlinkd.git#e5641588803c096312fb271afa0c7f6ddc4516c0" +version = "0.4.0" +source = "git+https://github.com/rsdsl/netlinkd.git#5b1bde77629611ae44f7f0070b757ffba4564165" dependencies = [ "futures-util", "netlink-packet-route", @@ -776,6 +776,7 @@ dependencies = [ "md5", "ppproperly", "rand", + "rsdsl_ip_config", "rsdsl_netlinkd", "rsdsl_pppoe2_sys", "socket2 0.5.3", @@ -10,7 +10,8 @@ libc = "0.2.147" md5 = "0.7.0" ppproperly = { git = "https://github.com/rsdsl/ppproperly.git", version = "0.1.0" } rand = "0.8.5" -rsdsl_netlinkd = { git = "https://github.com/rsdsl/netlinkd.git", version = "0.3.2" } +rsdsl_ip_config = { git = "https://github.com/rsdsl/ip_config.git", version = "0.2.0" } +rsdsl_netlinkd = { git = "https://github.com/rsdsl/netlinkd.git", version = "0.4.0" } rsdsl_pppoe2_sys = { git = "https://github.com/rsdsl/pppoe2.git", version = "0.1.0" } socket2 = "0.5.3" thiserror = "1.0" diff --git a/src/main.rs b/src/main.rs index 8ba1e4b..6814afb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,19 @@ +use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::net::{Ipv4Addr, Ipv6Addr}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use ppproperly::{ - AuthProto, ChapAlgorithm, ChapData, ChapPkt, Deserialize, LcpData, LcpOpt, LcpPkt, MacAddr, - PapData, PapPkt, PppData, PppPkt, PppoeData, PppoePkt, PppoeVal, Serialize, + AuthProto, ChapAlgorithm, ChapData, ChapPkt, Deserialize, IpcpData, IpcpOpt, IpcpPkt, + Ipv6cpData, Ipv6cpOpt, Ipv6cpPkt, LcpData, LcpOpt, LcpPkt, MacAddr, PapData, PapPkt, PppData, + PppPkt, PppoeData, PppoePkt, PppoeVal, Serialize, }; +use rsdsl_ip_config::{Ipv4Config, Ipv6Config}; use rsdsl_netlinkd::link; -use rsdsl_pppoe2::{Ppp, Pppoe, Result}; +use rsdsl_pppoe2::{Ncp, Ppp, Pppoe, Result}; use rsdsl_pppoe2_sys::{new_discovery_socket, new_session}; use socket2::Socket; @@ -23,6 +27,12 @@ const MAX_STATUS_ATTEMPTS: usize = 2; static PPPOE_XMIT_INTERVAL: Duration = Duration::from_secs(3); static SESSION_INIT_GRACE_PERIOD: Duration = Duration::from_secs(1); +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +enum Network { + Ipv4, + Ipv6, +} + fn main() -> Result<()> { println!("wait for up {}", PPPOE_UPLINK); @@ -233,7 +243,7 @@ fn recv_discovery( println!(" <- [{}] padt, error: {}", pkt.src_mac, generic_error); } - _ => println!(" <- [{}] unsupported pkt {:?}", pkt.src_mac, pkt), + _ => println!(" <- [{}] unsupported pppoe pkt {:?}", pkt.src_mac, pkt), } } } @@ -253,12 +263,30 @@ fn session( let ppp_state = Arc::new(Mutex::new(Ppp::default())); + let ncp_states = Arc::new(Mutex::new(HashMap::new())); + + { + let mut ncps = ncp_states.lock().expect("ncp state mutex is poisoned"); + + ncps.insert(Network::Ipv4, Ncp::default()); + ncps.insert(Network::Ipv6, Ncp::default()); + } + + let config4 = Arc::new(Mutex::new(Ipv4Config::default())); + let config6 = Arc::new(Mutex::new(Ipv6Config::default())); + + let ctl2 = ctl.try_clone()?; let ppp_state2 = ppp_state.clone(); - let recv_sess = thread::spawn(move || match recv_session(ctl, ppp_state2.clone()) { - Ok(_) => Ok(()), - Err(e) => { - *ppp_state2.lock().expect("ppp state mutex is poisoned") = Ppp::Err; - Err(e) + let ncp_states2 = ncp_states.clone(); + let config42 = config4.clone(); + let config62 = config6.clone(); + let recv_sess = thread::spawn(move || { + match recv_session(ctl2, ppp_state2.clone(), ncp_states2, config42, config62) { + Ok(_) => Ok(()), + Err(e) => { + *ppp_state2.lock().expect("ppp state mutex is poisoned") = Ppp::Err; + Err(e) + } } }); @@ -324,6 +352,7 @@ fn session( } Ppp::SyncAcked(attempt) => { // Packet handler takes care of the rest. + if attempt >= MAX_ATTEMPTS { *ppp_state = Ppp::Terminate2( "Maximum number of Configure-Ack attempts exceeded".into(), @@ -361,7 +390,39 @@ fn session( *ppp_state = Ppp::Auth(auth_proto.clone(), attempt + 1); } - Ppp::Active => {} + Ppp::Active => { + let mut ncps = ncp_states.lock().expect("ncp state mutex is poisoned"); + for (ncp, state) in ncps.iter_mut() { + if *state == Ncp::Dead { + *state = Ncp::Configure(rand::random(), 0); + + let ctl2 = ctl.try_clone()?; + let ncp_states2 = ncp_states.clone(); + let config42 = config4.clone(); + let config62 = config6.clone(); + match *ncp { + Network::Ipv4 => { + thread::spawn(|| match ipcp(ctl2, ncp_states2, config42) { + Ok(_) => Ok(()), + Err(e) => { + eprintln!("{}", e); + Err(e) + } + }) + } + Network::Ipv6 => { + thread::spawn(|| match ipv6cp(ctl2, ncp_states2, config62) { + Ok(_) => Ok(()), + Err(e) => { + eprintln!("{}", e); + Err(e) + } + }) + } + }; + } + } + } Ppp::Terminate(ref reason, attempt) => { if attempt >= MAX_ATTEMPTS { *ppp_state = Ppp::Terminate2( @@ -424,7 +485,13 @@ fn session( Ok(()) } -fn recv_session(ctl: File, state: Arc<Mutex<Ppp>>) -> Result<()> { +fn recv_session( + ctl: File, + state: Arc<Mutex<Ppp>>, + ncp_states: Arc<Mutex<HashMap<Network, Ncp>>>, + config4: Arc<Mutex<Ipv4Config>>, + config6: Arc<Mutex<Ipv6Config>>, +) -> Result<()> { let mut ctl_r = BufReader::with_capacity(1500, ctl.try_clone()?); let mut ctl_w = BufWriter::with_capacity(1500, ctl); @@ -442,7 +509,21 @@ fn recv_session(ctl: File, state: Arc<Mutex<Ppp>>) -> Result<()> { PppData::Lcp(lcp) => handle_lcp(lcp, &mut ctl_w, state.clone(), &mut magic)?, PppData::Pap(pap) => handle_pap(pap, state.clone())?, PppData::Chap(chap) => handle_chap(chap, &mut ctl_w, state.clone())?, - _ => println!(" <- unhandled ppp {:?}", ppp), + PppData::Ipcp(ipcp) => handle_ipcp( + ipcp, + &mut ctl_w, + state.clone(), + ncp_states.clone(), + config4.clone(), + )?, + PppData::Ipv6cp(ipv6cp) => handle_ipv6cp( + ipv6cp, + &mut ctl_w, + state.clone(), + ncp_states.clone(), + config6.clone(), + )?, + _ => println!(" <- unsupported ppp pkt {:?}", ppp), } } @@ -561,7 +642,7 @@ fn handle_lcp( None } }) - .expect("receive configure-ack without magic number"); + .expect("receive lcp configure-ack without magic number"); let mut state = state.lock().expect("ppp state mutex is poisoned"); match *state { @@ -848,3 +929,284 @@ fn handle_chap(chap: ChapPkt, ctl_w: &mut BufWriter<File>, state: Arc<Mutex<Ppp> } } } + +fn ipcp( + ctl: File, + states: Arc<Mutex<HashMap<Network, Ncp>>>, + config: Arc<Mutex<Ipv4Config>>, +) -> Result<()> { + let mut ctl_w = BufWriter::with_capacity(1500, ctl); + + { + let mut config = config.lock().expect("ipv4 config mutex is poisoned"); + + config.addr = Ipv4Addr::UNSPECIFIED; + config.dns1 = Ipv4Addr::UNSPECIFIED; + config.dns2 = Ipv4Addr::UNSPECIFIED; + } + + loop { + { + let config = config.lock().expect("ipv4 config mutex is poisoned"); + + let mut states = states.lock().expect("ncp state mutex is poisoned"); + match states[&Network::Ipv4] { + Ncp::Dead => {} + Ncp::Configure(identifier, attempt) => { + if attempt >= MAX_ATTEMPTS { + *states.get_mut(&Network::Ipv4).expect("no ipv4 state") = Ncp::Failed; + continue; + } + + PppPkt::new_ipcp(IpcpPkt::new_configure_request( + identifier, + vec![IpcpOpt::IpAddr(config.addr.into()).into()], + )) + .serialize(&mut ctl_w)?; + ctl_w.flush()?; + + *states.get_mut(&Network::Ipv4).expect("no ipv4 state") = + Ncp::Configure(identifier, attempt + 1); + + println!(" -> ipcp configure-request {}/{}", attempt, MAX_ATTEMPTS); + } + Ncp::ConfAck(identifier, attempt) => { + if attempt >= MAX_ATTEMPTS { + *states.get_mut(&Network::Ipv4).expect("no ipv4 state") = Ncp::Failed; + continue; + } + + PppPkt::new_ipcp(IpcpPkt::new_configure_request( + identifier, + vec![IpcpOpt::IpAddr(config.addr.into()).into()], + )) + .serialize(&mut ctl_w)?; + ctl_w.flush()?; + + *states.get_mut(&Network::Ipv4).expect("no ipv4 state") = + Ncp::ConfAck(identifier, attempt + 1); + + println!(" -> ipcp configure-request {}/{}", attempt, MAX_ATTEMPTS); + } + Ncp::ConfAcked(attempt) => { + // Packet handler takes care of the rest. + + if attempt >= MAX_ATTEMPTS { + *states.get_mut(&Network::Ipv4).expect("no ipv4 state") = Ncp::Failed; + continue; + } + + *states.get_mut(&Network::Ipv4).expect("no ipv4 state") = + Ncp::ConfAcked(attempt + 1); + } + Ncp::Active => {} + Ncp::Failed => {} + } + } + + thread::sleep(PPPOE_XMIT_INTERVAL); + } +} + +fn ipv6cp( + ctl: File, + states: Arc<Mutex<HashMap<Network, Ncp>>>, + config: Arc<Mutex<Ipv6Config>>, +) -> Result<()> { + Ok(()) +} + +fn handle_ipcp( + ipcp: IpcpPkt, + ctl_w: &mut BufWriter<File>, + state: Arc<Mutex<Ppp>>, + ncp_states: Arc<Mutex<HashMap<Network, Ncp>>>, + config: Arc<Mutex<Ipv4Config>>, +) -> Result<()> { + if *state.lock().expect("ppp state mutex is poisoned") != Ppp::Active { + println!(" <- unexpected ipcp"); + return Ok(()); + } + + match ipcp.data { + IpcpData::ConfigureRequest(configure_request) => { + let compression = configure_request.options.iter().find_map(|opt| { + if let IpcpOpt::IpCompressionProtocol(compression) = &opt.value { + Some(compression) + } else { + None + } + }); + + if let Some(compression) = compression { + PppPkt::new_ipcp(IpcpPkt::new_configure_reject( + ipcp.identifier, + vec![IpcpOpt::IpCompressionProtocol(compression.clone()).into()], + )) + .serialize(ctl_w)?; + ctl_w.flush()?; + + println!( + " -> ipcp configure-reject {}, compression: {:?} -> None", + ipcp.identifier, compression + ); + return Ok(()); + } + + let mut ncp_states = ncp_states.lock().expect("ncp state mutex is poisoned"); + match ncp_states[&Network::Ipv4] { + Ncp::Configure(identifier, attempt) => { + *ncp_states.get_mut(&Network::Ipv4).expect("no ipv4 state") = + Ncp::ConfAck(identifier, attempt) + } + Ncp::ConfAck(..) => {} // Simply retransmit our previous ack. + Ncp::ConfAcked(..) => { + *ncp_states.get_mut(&Network::Ipv4).expect("no ipv4 state") = Ncp::Active + } + _ => { + println!(" <- unexpected ipcp configure-request {}", ipcp.identifier); + return Ok(()); + } + } + + PppPkt::new_ipcp(IpcpPkt::new_configure_ack( + ipcp.identifier, + configure_request.options, + )) + .serialize(ctl_w)?; + ctl_w.flush()?; + + println!(" <- ipcp configure-request {}", ipcp.identifier); + println!(" -> ipcp configure-ack {}", ipcp.identifier); + + Ok(()) + } + IpcpData::ConfigureAck(configure_ack) => { + let addr = configure_ack + .options + .iter() + .find_map(|opt| { + if let IpcpOpt::IpAddr(addr) = &opt.value { + Some(addr.0) + } else { + None + } + }) + .expect("receive ipcp configure-ack without ipv4 address"); + + let mut ncp_states = ncp_states.lock().expect("ncp state mutex is poisoned"); + match ncp_states[&Network::Ipv4] { + Ncp::Configure(identifier, attempt) if ipcp.identifier == identifier => { + *ncp_states.get_mut(&Network::Ipv4).expect("no ipv4 state") = + Ncp::ConfAcked(attempt) + } + Ncp::ConfAck(identifier, ..) if ipcp.identifier == identifier => { + *ncp_states.get_mut(&Network::Ipv4).expect("no ipv4 state") = Ncp::Active + } + _ => { + println!(" <- unexpected ipcp configure-ack {}", ipcp.identifier); + return Ok(()); + } + } + + config.lock().expect("ipv4 config mutex is poisoned").addr = addr; + + println!( + " <- ipcp configure-ack {}, address: {}", + ipcp.identifier, addr + ); + Ok(()) + } + IpcpData::ConfigureNak(configure_nak) => { + let addr = configure_nak + .options + .iter() + .find_map(|opt| { + if let IpcpOpt::IpAddr(addr) = &opt.value { + Some(addr.0) + } else { + None + } + }) + .expect("receive ipcp configure-nak without ipv4 address"); + + let mut ncp_states = ncp_states.lock().expect("ncp state mutex is poisoned"); + match ncp_states[&Network::Ipv4] { + Ncp::Configure(identifier, ..) if ipcp.identifier == identifier => {} + Ncp::ConfAck(identifier, ..) if ipcp.identifier == identifier => {} + _ => { + println!(" <- unexpected ipcp configure-nak {}", ipcp.identifier); + return Ok(()); + } + } + + config.lock().expect("ipv4 config mutex is poisoned").addr = addr; + + println!(" <- ipcp configure-nak {}", ipcp.identifier); + Ok(()) + } + IpcpData::ConfigureReject(..) => { + // None of our options can be unset. + // Ignore the packet and let the negotiation time out. + + match ncp_states.lock().expect("ncp state mutex is poisoned")[&Network::Ipv4] { + Ncp::Configure(..) => println!(" <- ipcp configure-reject {}", ipcp.identifier), + Ncp::ConfAck(..) => println!(" <- ipcp configure-reject {}", ipcp.identifier), + _ => println!(" <- unexpected ipcp configure-reject {}", ipcp.identifier), + } + + Ok(()) + } + IpcpData::TerminateRequest(terminate_request) => { + *ncp_states + .lock() + .expect("ncp state mutex is poisoned") + .get_mut(&Network::Ipv4) + .expect("no ipv4 state") = Ncp::Dead; + + PppPkt::new_ipcp(IpcpPkt::new_terminate_ack( + ipcp.identifier, + terminate_request.data.clone(), + )) + .serialize(ctl_w)?; + ctl_w.flush()?; + + let reason = String::from_utf8(terminate_request.data.clone()) + .unwrap_or(format!("{:?}", terminate_request.data)); + + println!( + " <- ipcp terminate-request {}, reason: {}", + ipcp.identifier, reason + ); + println!(" -> ipcp terminate-ack {}", ipcp.identifier); + + Ok(()) + } + IpcpData::TerminateAck(..) => { + // We never terminate NCPs + // so a Terminate-Ack will always be unexpected. + + println!(" <- unexpected lcp terminate-ack {}", ipcp.identifier); + Ok(()) + } + IpcpData::CodeReject(code_reject) => { + // Should never happen. + + println!( + " <- ipcp code-reject {}, packet: {:?}", + ipcp.identifier, code_reject.pkt + ); + Ok(()) + } + } +} + +fn handle_ipv6cp( + ipv6cp: Ipv6cpPkt, + ctl_w: &mut BufWriter<File>, + state: Arc<Mutex<Ppp>>, + ncp_states: Arc<Mutex<HashMap<Network, Ncp>>>, + config: Arc<Mutex<Ipv6Config>>, +) -> Result<()> { + Ok(()) +} diff --git a/src/state.rs b/src/state.rs index 696a4dd..e300e20 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,19 +1,14 @@ use ppproperly::{AuthProto, MacAddr}; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum Pppoe { + #[default] Init, Request(MacAddr, Option<Vec<u8>>, usize), Active(MacAddr), Err, } -impl Default for Pppoe { - fn default() -> Self { - Self::Init - } -} - #[derive(Clone, Debug, Eq, PartialEq)] pub enum Ppp { Synchronize(u8, u16, u32, usize), @@ -32,3 +27,14 @@ impl Default for Ppp { Self::Synchronize(1, 1492, rand::random(), 0) } } + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub enum Ncp { + #[default] + Dead, + Configure(u8, usize), + ConfAck(u8, usize), + ConfAcked(usize), + Active, + Failed, +} |