diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 72a26554..67b7e48f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -47,6 +47,7 @@ jobs: sudo apt-get install -y dnsmasq qemu-system-x86 ovmf sudo mkdir -p /etc/qemu echo allow br-pixie | sudo tee /etc/qemu/bridge.conf + echo allow br-pixie1 | sudo tee -a /etc/qemu/bridge.conf - name: Install rust uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/pixie-server/example.config.yaml b/pixie-server/example.config.yaml index 5e3859fe..2cef492a 100644 --- a/pixie-server/example.config.yaml +++ b/pixie-server/example.config.yaml @@ -1,9 +1,10 @@ hosts: - listen_on: 10.0.0.1 - dhcp: !static [10.187.100.1, 10.187.200.200] - #dhcp: !proxy 192.168.1.100 + interfaces: + - network: 10.0.0.1/8 + dhcp: !static [10.187.100.1, 10.187.200.200] + #dhcp: !proxy 192.168.1.100 + broadcast_speed: 52428800 hostsfile: /etc/hosts - broadcast_speed: 52428800 http: listen_on: 0.0.0.0:8080 #password: secret diff --git a/pixie-server/src/dnsmasq.rs b/pixie-server/src/dnsmasq.rs index a2b5be55..da962ef2 100644 --- a/pixie-server/src/dnsmasq.rs +++ b/pixie-server/src/dnsmasq.rs @@ -35,29 +35,48 @@ impl Drop for DnsmasqHandle { } async fn write_config(state: &State) -> Result<()> { - let (name, _) = find_network(state.config.hosts.listen_on)?; - let mut dnsmasq_conf = File::create(state.run_dir.join("dnsmasq.conf"))?; - let dhcp_dynamic_conf = match state.config.hosts.dhcp { - DhcpMode::Static(low, high) => format!("dhcp-range=tag:netboot,{low},{high}"), - DhcpMode::Proxy(ip) => format!("dhcp-range=tag:netboot,{ip},proxy"), - }; - let storage_str = state.storage_dir.to_str().unwrap(); let run_str = state.run_dir.to_str().unwrap(); + let interfaces_config = state + .config + .hosts + .interfaces + .iter() + .map(|iface| { + let name = find_network(iface.network.addr())?.0; + + let dhcp_dynamic_conf = match iface.dhcp { + DhcpMode::Static(low, high) => format!("dhcp-range=tag:netboot,{low},{high}"), + DhcpMode::Proxy(ip) => format!("dhcp-range=tag:netboot,{ip},proxy"), + }; + + let netaddr = iface.network.network().to_string(); + let netmask = iface.network.netmask().to_string(); + + Ok(format!( + r#" +## {name} +dhcp-range=tag:!netboot,{netaddr},static,{netmask} +{dhcp_dynamic_conf} +interface={name} +"# + )) + }) + .collect::>>()? + .join("\n"); + write!( dnsmasq_conf, r#" ### Per-network configuration -## net0 -{dhcp_dynamic_conf} -dhcp-range=tag:!netboot,10.0.0.0,static,255.0.0.0 +{interfaces_config} + dhcp-hostsfile={run_str}/hosts dhcp-boot=pixie-uefi.efi -interface={name} except-interface=lo user=root group=root diff --git a/pixie-server/src/udp.rs b/pixie-server/src/udp.rs index a1aef498..6a930556 100644 --- a/pixie-server/src/udp.rs +++ b/pixie-server/src/udp.rs @@ -1,17 +1,19 @@ //! Handles [`UdpRequest`] use crate::{ - find_mac, find_network, + find_mac, state::{State, UnitSelector}, }; use anyhow::{ensure, Context, Result}; +use futures::FutureExt; +use ipnet::Ipv4Net; use pixie_shared::{ - chunk_codec::Encoder, ChunkHash, HintPacket, RegistrationInfo, UdpRequest, ACTION_PORT, - CHUNKS_PORT, HINT_PORT, UDP_BODY_LEN, + chunk_codec::Encoder, ChunkHash, HintPacket, InterfaceConfig, RegistrationInfo, UdpRequest, + ACTION_PORT, CHUNKS_PORT, HINT_PORT, UDP_BODY_LEN, }; use std::{ collections::BTreeSet, - net::{Ipv4Addr, SocketAddrV4}, + net::{IpAddr, Ipv4Addr, SocketAddrV4}, ops::Bound, sync::Arc, }; @@ -24,7 +26,7 @@ use tokio::{ async fn broadcast_chunks( state: &State, socket: &UdpSocket, - ip: Ipv4Addr, + iface: &InterfaceConfig, mut rx: Receiver, ) -> Result<()> { let mut queue = BTreeSet::::new(); @@ -75,8 +77,7 @@ async fn broadcast_chunks( continue; }; - let hosts_cfg = &state.config.hosts; - let chunks_addr = SocketAddrV4::new(ip, CHUNKS_PORT); + let chunks_addr = SocketAddrV4::new(iface.network.broadcast(), CHUNKS_PORT); let mut encoder = Encoder::new(cdata); write_buf[..32].clone_from_slice(&index); @@ -85,7 +86,7 @@ async fn broadcast_chunks( let sent_len = socket.send_to(&write_buf[..32 + len], chunks_addr).await?; ensure!(sent_len == 32 + len, "Could not send packet"); - wait_for += 8 * (sent_len as u32) * Duration::from_secs(1) / hosts_cfg.broadcast_speed; + wait_for += 8 * (sent_len as u32) * Duration::from_secs(1) / iface.broadcast_speed; } } @@ -165,13 +166,24 @@ async fn broadcast_hint(state: &State, socket: &UdpSocket, ip: Ipv4Addr) -> Resu Ok(()) } -async fn handle_requests(state: &State, socket: &UdpSocket, tx: Sender<[u8; 32]>) -> Result<()> { +async fn handle_requests( + state: &State, + socket: &UdpSocket, + net_tx: Vec<(Ipv4Net, Sender<[u8; 32]>)>, +) -> Result<()> { let mut buf = [0; UDP_BODY_LEN]; loop { let (len, peer_addr) = tokio::select! { x = socket.recv_from(&mut buf) => x?, _ = state.cancel_token.cancelled() => break, }; + let peer_ip = match peer_addr.ip() { + IpAddr::V4(ip) => ip, + _ => panic!(), + }; + let Some((_, tx)) = net_tx.iter().find(|(net, _)| net.contains(&peer_ip)) else { + continue; + }; let req: postcard::Result = postcard::from_bytes(&buf[..len]); match req { Ok(UdpRequest::Discover) => { @@ -201,18 +213,29 @@ async fn handle_requests(state: &State, socket: &UdpSocket, tx: Sender<[u8; 32]> } pub async fn main(state: Arc) -> Result<()> { - let (_, network) = find_network(state.config.hosts.listen_on)?; + let (net_tx, net_rx): (_, Vec<_>) = state + .config + .hosts + .interfaces + .iter() + .map(|iface| { + let (tx, rx) = mpsc::channel(128); + ((iface.network, tx), (iface, rx)) + }) + .unzip(); - let (tx, rx) = mpsc::channel(128); let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, ACTION_PORT)).await?; log::info!("Listening on {}", socket.local_addr()?); socket.set_broadcast(true)?; - tokio::try_join!( - broadcast_chunks(&state, &socket, network.broadcast(), rx), - broadcast_hint(&state, &socket, network.broadcast()), - handle_requests(&state, &socket, tx), - )?; + let mut tasks = vec![handle_requests(&state, &socket, net_tx).boxed()]; + + for (iface, rx) in net_rx { + tasks.push(broadcast_chunks(&state, &socket, iface, rx).boxed()); + tasks.push(broadcast_hint(&state, &socket, iface.network.broadcast()).boxed()); + } + + futures::future::try_join_all(tasks).await?; Ok(()) } diff --git a/pixie-shared/src/config.rs b/pixie-shared/src/config.rs index be622856..86cf7bfc 100644 --- a/pixie-shared/src/config.rs +++ b/pixie-shared/src/config.rs @@ -1,5 +1,6 @@ use crate::{Action, Bijection}; use alloc::{string::String, vec::Vec}; +use ipnet::Ipv4Net; use macaddr::MacAddr6; use serde::{Deserialize, Serialize}; use std::{ @@ -18,20 +19,26 @@ pub enum DhcpMode { Proxy(Ipv4Addr), } +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)] +pub struct InterfaceConfig { + /// Listen on address + pub network: Ipv4Net, + /// DHCP server. + pub dhcp: DhcpMode, + /// Speed in bytes/second used to broadcast chunks. + pub broadcast_speed: u32, +} + /// Registered clients will always be assigned an IP in the form /// 10.{group_id}.{column_id}.{row_id}. /// Note that for this to work, the specified network interface must have an IP on the 10.0.0.0/8 /// subnet; BEWARE that dnsmasq can be picky about the order of IP addresses. #[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)] pub struct HostsConfig { - /// Listen on address - pub listen_on: Ipv4Addr, - /// DHCP server. - pub dhcp: DhcpMode, + /// Interfaces to operate on. + pub interfaces: Vec, /// Hosts file to use for DHCP hostnames. pub hostsfile: Option, - /// Speed in bytes/second used to broadcast chunks. - pub broadcast_speed: u32, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] diff --git a/run_test.sh b/run_test.sh index f3b6d7ff..6dece74b 100755 --- a/run_test.sh +++ b/run_test.sh @@ -21,6 +21,7 @@ cleanup() { trap cleanup EXIT cp -rv $1 $TEMPDIR +cp test_config.yaml $TEMPDIR/storage/config.yaml cat >$TEMPDIR/storage/registered.json <$TEMPDIR/storage/registered.json </dev/null; then echo "Data was re-fetched" exit 1 fi + +if ! grep "Disk scanned; 0 chunks to fetch" $TEMPDIR/flash1-2.log &>/dev/null; then + echo "Data was re-fetched" + exit 1 +fi diff --git a/test_config.yaml b/test_config.yaml new file mode 100644 index 00000000..522e31ea --- /dev/null +++ b/test_config.yaml @@ -0,0 +1,17 @@ +hosts: + interfaces: + - network: 10.0.0.1/16 + dhcp: !static [10.0.100.1, 10.0.200.200] + broadcast_speed: 52428800 + - network: 10.10.0.1/16 + dhcp: !static [10.10.100.1, 10.10.200.200] + broadcast_speed: 52428800 + hostsfile: /etc/hosts +http: + listen_on: 0.0.0.0:8080 +groups: + - [room0, 0] + - [room10, 10] +images: + - contestant + - worker