Skip to content

Commit 35f3c35

Browse files
committed
Add support for multiple interfaces
1 parent 05d619d commit 35f3c35

7 files changed

Lines changed: 174 additions & 30 deletions

File tree

.github/workflows/rust.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ jobs:
4747
sudo apt-get install -y dnsmasq qemu-system-x86 ovmf
4848
sudo mkdir -p /etc/qemu
4949
echo allow br-pixie | sudo tee /etc/qemu/bridge.conf
50+
echo allow br-pixie1 | sudo tee -a /etc/qemu/bridge.conf
5051
5152
- name: Install rust
5253
uses: actions-rust-lang/setup-rust-toolchain@v1

pixie-server/example.config.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
hosts:
2-
listen_on: 10.0.0.1
3-
dhcp: !static [10.187.100.1, 10.187.200.200]
4-
#dhcp: !proxy 192.168.1.100
2+
interfaces:
3+
- network: 10.0.0.1/8
4+
dhcp: !static [10.187.100.1, 10.187.200.200]
5+
#dhcp: !proxy 192.168.1.100
56
hostsfile: /etc/hosts
67
broadcast_speed: 52428800
78
http:

pixie-server/src/dnsmasq.rs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,51 @@ impl Drop for DnsmasqHandle {
3535
}
3636

3737
async fn write_config(state: &State) -> Result<()> {
38-
let (name, _) = find_network(state.config.hosts.listen_on)?;
39-
4038
let mut dnsmasq_conf = File::create(state.run_dir.join("dnsmasq.conf"))?;
4139

42-
let dhcp_dynamic_conf = match state.config.hosts.dhcp {
43-
DhcpMode::Static(low, high) => format!("dhcp-range=tag:netboot,{low},{high}"),
44-
DhcpMode::Proxy(ip) => format!("dhcp-range=tag:netboot,{ip},proxy"),
45-
};
46-
4740
let storage_str = state.storage_dir.to_str().unwrap();
4841
let run_str = state.run_dir.to_str().unwrap();
4942

43+
let interfaces_config = state
44+
.config
45+
.hosts
46+
.interfaces
47+
.iter()
48+
.map_while(|iface| {
49+
let name = match find_network(iface.network.addr()) {
50+
Ok((name, _)) => name,
51+
Err(_) => return None,
52+
};
53+
54+
let dhcp_dynamic_conf = match iface.dhcp {
55+
DhcpMode::Static(low, high) => format!("dhcp-range=tag:netboot,{low},{high}"),
56+
DhcpMode::Proxy(ip) => format!("dhcp-range=tag:netboot,{ip},proxy"),
57+
};
58+
59+
let netaddr = iface.network.network().to_string();
60+
let netmask = iface.network.netmask().to_string();
61+
62+
Some(format!(
63+
r#"
64+
## {name}
65+
dhcp-range=tag:!netboot,{netaddr},static,{netmask}
66+
{dhcp_dynamic_conf}
67+
interface={name}
68+
"#
69+
))
70+
})
71+
.collect::<Vec<_>>()
72+
.join("\n");
73+
5074
write!(
5175
dnsmasq_conf,
5276
r#"
5377
### Per-network configuration
5478
55-
## net0
56-
{dhcp_dynamic_conf}
57-
dhcp-range=tag:!netboot,10.0.0.0,static,255.0.0.0
79+
{interfaces_config}
80+
5881
dhcp-hostsfile={run_str}/hosts
5982
dhcp-boot=pixie-uefi.efi
60-
interface={name}
6183
except-interface=lo
6284
user=root
6385
group=root

pixie-server/src/udp.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
//! Handles [`UdpRequest`]
22
33
use crate::{
4-
find_mac, find_network,
4+
find_mac,
55
state::{State, UnitSelector},
66
};
77
use anyhow::{ensure, Context, Result};
8+
use futures::FutureExt;
9+
use ipnet::Ipv4Net;
810
use pixie_shared::{
911
chunk_codec::Encoder, ChunkHash, HintPacket, RegistrationInfo, UdpRequest, ACTION_PORT,
1012
CHUNKS_PORT, HINT_PORT, UDP_BODY_LEN,
1113
};
1214
use std::{
1315
collections::BTreeSet,
14-
net::{Ipv4Addr, SocketAddrV4},
16+
net::{IpAddr, Ipv4Addr, SocketAddrV4},
1517
ops::Bound,
1618
sync::Arc,
1719
};
@@ -165,13 +167,24 @@ async fn broadcast_hint(state: &State, socket: &UdpSocket, ip: Ipv4Addr) -> Resu
165167
Ok(())
166168
}
167169

168-
async fn handle_requests(state: &State, socket: &UdpSocket, tx: Sender<[u8; 32]>) -> Result<()> {
170+
async fn handle_requests(
171+
state: &State,
172+
socket: &UdpSocket,
173+
net_tx: Vec<(Ipv4Net, Sender<[u8; 32]>)>,
174+
) -> Result<()> {
169175
let mut buf = [0; UDP_BODY_LEN];
170176
loop {
171177
let (len, peer_addr) = tokio::select! {
172178
x = socket.recv_from(&mut buf) => x?,
173179
_ = state.cancel_token.cancelled() => break,
174180
};
181+
let peer_ip = match peer_addr.ip() {
182+
IpAddr::V4(ip) => ip,
183+
_ => panic!(),
184+
};
185+
let Some((_, tx)) = net_tx.iter().find(|(net, _)| net.contains(&peer_ip)) else {
186+
continue;
187+
};
175188
let req: postcard::Result<UdpRequest> = postcard::from_bytes(&buf[..len]);
176189
match req {
177190
Ok(UdpRequest::Discover) => {
@@ -201,18 +214,29 @@ async fn handle_requests(state: &State, socket: &UdpSocket, tx: Sender<[u8; 32]>
201214
}
202215

203216
pub async fn main(state: Arc<State>) -> Result<()> {
204-
let (_, network) = find_network(state.config.hosts.listen_on)?;
217+
let (net_tx, net_rx): (_, Vec<_>) = state
218+
.config
219+
.hosts
220+
.interfaces
221+
.iter()
222+
.map(|iface| {
223+
let (tx, rx) = mpsc::channel(128);
224+
((iface.network, tx), (iface.network, rx))
225+
})
226+
.unzip();
205227

206-
let (tx, rx) = mpsc::channel(128);
207228
let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, ACTION_PORT)).await?;
208229
log::info!("Listening on {}", socket.local_addr()?);
209230
socket.set_broadcast(true)?;
210231

211-
tokio::try_join!(
212-
broadcast_chunks(&state, &socket, network.broadcast(), rx),
213-
broadcast_hint(&state, &socket, network.broadcast()),
214-
handle_requests(&state, &socket, tx),
215-
)?;
232+
let mut tasks = vec![handle_requests(&state, &socket, net_tx).boxed()];
233+
234+
for (network, rx) in net_rx {
235+
tasks.push(broadcast_chunks(&state, &socket, network.broadcast(), rx).boxed());
236+
tasks.push(broadcast_hint(&state, &socket, network.broadcast()).boxed());
237+
}
238+
239+
futures::future::try_join_all(tasks).await?;
216240

217241
Ok(())
218242
}

pixie-shared/src/config.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{Action, Bijection};
22
use alloc::{string::String, vec::Vec};
3+
use ipnet::Ipv4Net;
34
use macaddr::MacAddr6;
45
use serde::{Deserialize, Serialize};
56
use std::{
@@ -18,16 +19,22 @@ pub enum DhcpMode {
1819
Proxy(Ipv4Addr),
1920
}
2021

22+
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)]
23+
pub struct InterfaceConfig {
24+
/// Listen on address
25+
pub network: Ipv4Net,
26+
/// DHCP server.
27+
pub dhcp: DhcpMode,
28+
}
29+
2130
/// Registered clients will always be assigned an IP in the form
2231
/// 10.{group_id}.{column_id}.{row_id}.
2332
/// Note that for this to work, the specified network interface must have an IP on the 10.0.0.0/8
2433
/// subnet; BEWARE that dnsmasq can be picky about the order of IP addresses.
2534
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone)]
2635
pub struct HostsConfig {
27-
/// Listen on address
28-
pub listen_on: Ipv4Addr,
29-
/// DHCP server.
30-
pub dhcp: DhcpMode,
36+
/// Interfaces to operate on.
37+
pub interfaces: Vec<InterfaceConfig>,
3138
/// Hosts file to use for DHCP hostnames.
3239
pub hostsfile: Option<PathBuf>,
3340
/// Speed in bytes/second used to broadcast chunks.

run_test.sh

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cleanup() {
2121
trap cleanup EXIT
2222

2323
cp -rv $1 $TEMPDIR
24+
cp test_config.yaml $TEMPDIR/storage/config.yaml
2425

2526
cat >$TEMPDIR/storage/registered.json <<EOF
2627
[
@@ -33,14 +34,30 @@ cat >$TEMPDIR/storage/registered.json <<EOF
3334
"curr_progress": null,
3435
"next_action": "shutdown",
3536
"image": "contestant"
37+
},
38+
{
39+
"mac": [82, 84, 0, 18, 52, 87],
40+
"group": 1,
41+
"row": 2,
42+
"col": 1,
43+
"curr_action": null,
44+
"curr_progress": null,
45+
"next_action": "shutdown",
46+
"image": "contestant"
3647
}
3748
]
3849
EOF
3950

4051
if ! ip link show br-pixie; then
4152
ip link add br-pixie type bridge
4253
ip link set dev br-pixie up
43-
ip addr add 10.0.0.1/8 brd 10.255.255.255 dev br-pixie
54+
ip addr add 10.0.0.1/16 dev br-pixie
55+
fi
56+
57+
if ! ip link show br-pixie1; then
58+
ip link add br-pixie1 type bridge
59+
ip link set dev br-pixie1 up
60+
ip addr add 10.1.0.1/16 dev br-pixie1
4461
fi
4562

4663
RUST_BACKTRACE=short RUST_LOG=debug RUST_LOG_STYLE=always LLVM_PROFILE_FILE=prof-out/pixie-server-%m-%p.profraw ./pixie-server/target/debug/pixie-server -s $TEMPDIR/storage &
@@ -67,7 +84,30 @@ run_qemu() {
6784
-nic bridge,mac=52:54:00:12:34:56,br=br-pixie,model=e1000
6885
}
6986

87+
run_qemu1() {
88+
OVMF=/usr/share/OVMF/OVMF_CODE_4M.fd
89+
if ! [ -e $OVMF ]; then
90+
OVMF=/usr/share/edk2/x64/OVMF_CODE.4m.fd
91+
fi
92+
FILE=prof-out/pixie-uefi-$RANDOM.profraw
93+
truncate -s 500M $FILE
94+
qemu-system-x86_64 \
95+
-nographic \
96+
-chardev stdio,id=char0,logfile=$1,signal=off \
97+
-serial chardev:char0 \
98+
-monitor none \
99+
-enable-kvm \
100+
-cpu host -smp cores=2 \
101+
-m 1G \
102+
-drive if=pflash,format=raw,file=$OVMF \
103+
-drive file=$TEMPDIR/disk1.img,if=none,id=nvm,format=raw \
104+
-drive file=$FILE,format=raw \
105+
-device nvme,serial=deadbeef,drive=nvm \
106+
-nic bridge,mac=52:54:00:12:34:57,br=br-pixie1,model=e1000
107+
}
108+
70109
truncate -s 8G $TEMPDIR/disk.img
110+
truncate -s 8G $TEMPDIR/disk1.img
71111
echo -e "label: gpt\n- 1GiB - -\n- 1GiB - -\n- - L -" | sfdisk $TEMPDIR/disk.img
72112
DEV=$(losetup --partscan --show --find $TEMPDIR/disk.img)
73113
mkswap ${DEV}p1
@@ -80,7 +120,7 @@ for PART in ${DEV}p{2..3}; do
80120
done
81121
losetup -d $DEV
82122

83-
curl 'http://localhost:8080/admin/curr_action/all/store'
123+
curl 'http://localhost:8080/admin/curr_action/52:54:00:12:34:56/store'
84124
run_qemu $TEMPDIR/store.log
85125

86126
# Check that we restore the original disk image.
@@ -90,6 +130,7 @@ truncate -s 8G $TEMPDIR/disk.img
90130

91131
curl 'http://localhost:8080/admin/curr_action/all/flash'
92132
run_qemu $TEMPDIR/flash-1.log
133+
run_qemu1 $TEMPDIR/flash1-1.log
93134

94135
DEV=$(losetup --partscan --show --find --read-only $TEMPDIR/disk.img)
95136
fsck -n ${DEV}p*
@@ -103,10 +144,23 @@ for PART in ${DEV}p{2..3}; do
103144
done
104145
losetup -d $DEV
105146

147+
DEV=$(losetup --partscan --show --find --read-only $TEMPDIR/disk1.img)
148+
fsck -n ${DEV}p*
149+
for PART in ${DEV}p{2..3}; do
150+
mount -o ro $PART $TEMPDIR/mnt
151+
if [ "$(md5sum $TEMPDIR/mnt/pixie-server | cut -f 1 -d ' ')" != "$(md5sum ./pixie-server/target/debug/pixie-server | cut -f 1 -d ' ')" ]; then
152+
echo "pixie-server does not contain the expected content"
153+
exit 1
154+
fi
155+
umount $TEMPDIR/mnt
156+
done
157+
losetup -d $DEV
158+
106159
# Check that we don't fetch any data if the disk contents have not changed.
107160

108161
curl 'http://localhost:8080/admin/curr_action/all/flash'
109162
run_qemu $TEMPDIR/flash-2.log
163+
run_qemu1 $TEMPDIR/flash1-2.log
110164

111165
DEV=$(losetup --partscan --show --find --read-only $TEMPDIR/disk.img)
112166
fsck -n ${DEV}p*
@@ -120,7 +174,24 @@ for PART in ${DEV}p{2..3}; do
120174
done
121175
losetup -d $DEV
122176

177+
DEV=$(losetup --partscan --show --find --read-only $TEMPDIR/disk1.img)
178+
fsck -n ${DEV}p*
179+
for PART in ${DEV}p{2..3}; do
180+
mount -o ro $PART $TEMPDIR/mnt
181+
if [ "$(md5sum $TEMPDIR/mnt/pixie-server | cut -f 1 -d ' ')" != "$(md5sum ./pixie-server/target/debug/pixie-server | cut -f 1 -d ' ')" ]; then
182+
echo "pixie-server does not contain the expected content"
183+
exit 1
184+
fi
185+
umount $TEMPDIR/mnt
186+
done
187+
losetup -d $DEV
188+
123189
if ! grep "Disk scanned; 0 chunks to fetch" $TEMPDIR/flash-2.log &>/dev/null; then
124190
echo "Data was re-fetched"
125191
exit 1
126192
fi
193+
194+
if ! grep "Disk scanned; 0 chunks to fetch" $TEMPDIR/flash1-2.log &>/dev/null; then
195+
echo "Data was re-fetched"
196+
exit 1
197+
fi

test_config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
hosts:
2+
interfaces:
3+
- network: 10.0.0.1/16
4+
dhcp: !static [10.0.100.1, 10.0.200.200]
5+
#dhcp: !proxy 192.168.1.100
6+
- network: 10.1.0.1/16
7+
dhcp: !static [10.1.100.1, 10.1.200.200]
8+
hostsfile: /etc/hosts
9+
broadcast_speed: 52428800
10+
http:
11+
listen_on: 0.0.0.0:8080
12+
#password: secret
13+
groups:
14+
- [room0, 0]
15+
- [room1, 1]
16+
images:
17+
- contestant
18+
- worker

0 commit comments

Comments
 (0)