Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f9bc169
.
Oct 16, 2025
83b3cee
Merge branch 'main' into proc_topology
Oct 16, 2025
3600e7a
wip
Oct 30, 2025
87cf158
Merge branch 'main' of https://github.com/microsoft/openvmm
damanm24 Oct 30, 2025
bedd466
Merge branch 'main' of https://github.com/microsoft/openvmm
damanm24 Nov 4, 2025
01fe341
Merge branch 'main' into consomme_ipv6
Nov 4, 2025
8acde53
wip
Nov 5, 2025
bc383e2
Finish UDP over IPv6 and DNS on unix
Nov 5, 2025
c60bd6d
Use AF_UNSPEC for 1 syscall instead
damanm24 Nov 6, 2025
116b411
Merge branch 'main' of https://github.com/microsoft/openvmm
damanm24 Nov 6, 2025
2bc543b
checkpt
damanm24 Nov 7, 2025
30dac28
Remove un-needed message handling for DHCPv6
damanm24 Nov 7, 2025
99b277d
checkpt
damanm24 Nov 7, 2025
441db93
Windows guest recognizes IPv6 capabilities
damanm24 Nov 7, 2025
f5b97f1
minor changes
damanm24 Nov 10, 2025
a77bea0
Merge branch 'main' of https://github.com/microsoft/openvmm
damanm24 Nov 10, 2025
2b57eea
Use already existing types
damanm24 Nov 11, 2025
cb23c36
checkpt
damanm24 Nov 11, 2025
dd84e24
bit of cleanup
damanm24 Nov 12, 2025
36c6cb5
Prepare for review
damanm24 Nov 12, 2025
31b7a60
More cleanup
damanm24 Nov 12, 2025
b439e0e
update comment with accurate default network state
damanm24 Nov 12, 2025
423a20b
Clippy + xtask fixes
Nov 12, 2025
235da5e
checkpt
damanm24 Nov 14, 2025
ab27f38
minor changes
damanm24 Nov 14, 2025
9121770
Merge branch 'main' of https://github.com/microsoft/openvmm
damanm24 Nov 14, 2025
4493f2b
Merge branch 'main' into consomme_ipv6
damanm24 Nov 14, 2025
6f56fbf
Merge branch 'consomme_ipv6' of https://github.com/damanm24/openvmm i…
damanm24 Nov 14, 2025
6bedac1
clippy fixes
damanm24 Nov 17, 2025
6a01be6
remove dhcproto dep
damanm24 Nov 17, 2025
1b78011
undo
damanm24 Nov 17, 2025
e1c2455
.
damanm24 Nov 17, 2025
2dc273d
.
damanm24 Nov 18, 2025
6fdce76
Merge branch 'main' into consomme_ipv6
damanm24 Dec 10, 2025
9577aa8
Address feedback
damanm24 Dec 15, 2025
9005164
Update bind_port fn
Dec 16, 2025
6e43b51
Address comments
Dec 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
407 changes: 401 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ criterion = { version = "0.7", default-features = false }
crossterm = { version = "0.29.0", default-features = false }
ctrlc = "3.4.0"
der = "0.7"
dhcproto = "0.14.0"
dirs = "6.0"
elfcore = "1.1.5"
embed-resource = "3.0.2"
Expand Down
3 changes: 2 additions & 1 deletion vm/devices/net/net_consomme/consomme/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ edition.workspace = true
rust-version.workspace = true

[dependencies]
dhcproto.workspace = true
inspect.workspace = true
inspect_counters.workspace = true
pal_async.workspace = true

futures.workspace = true
getrandom.workspace = true
smoltcp = { workspace = true, features = [ "proto-ipv4", "medium-ethernet", "socket-raw", "std", "proto-dhcpv4" ] }
smoltcp = { workspace = true, features = [ "proto-ipv4", "proto-ipv6", "medium-ethernet", "socket-raw", "std", "proto-dhcpv4" ] }
socket2.workspace = true
thiserror.workspace = true
tracing.workspace = true
Expand Down
6 changes: 5 additions & 1 deletion vm/devices/net/net_consomme/consomme/src/dhcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,16 @@ impl<T: Client> Access<'_, T> {
None
} else {
let mut dns_servers = [None; DHCP_MAX_DNS_SERVER_COUNT];
for (&s, d) in self
for (s, d) in self
.inner
.state
.params
.nameservers
.iter()
.filter_map(|ip| match ip {
IpAddress::Ipv4(addr) => Some(*addr),
_ => None,
})
.zip(&mut dns_servers)
{
*d = Some(s);
Expand Down
158 changes: 158 additions & 0 deletions vm/devices/net/net_consomme/consomme/src/dhcpv6.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::str::FromStr;

use super::Access;
use super::Client;
use super::DropReason;
use crate::ChecksumState;
use crate::MIN_MTU;
use dhcproto::Decodable;
use dhcproto::Encodable;
use dhcproto::v6;
use smoltcp::phy::ChecksumCapabilities;
use smoltcp::wire::EthernetFrame;
use smoltcp::wire::EthernetProtocol;
use smoltcp::wire::EthernetRepr;
use smoltcp::wire::IpAddress;
use smoltcp::wire::IpProtocol;
use smoltcp::wire::Ipv6Address;
use smoltcp::wire::Ipv6Packet;
use smoltcp::wire::Ipv6Repr;
use smoltcp::wire::UdpPacket;
use smoltcp::wire::UdpRepr;

impl<T: Client> Access<'_, T> {
pub(crate) fn handle_dhcpv6(
&mut self,
payload: &[u8],
client_ip: Option<Ipv6Address>,
) -> Result<(), DropReason> {
// Parse the DHCPv6 message
let msg = v6::Message::decode(&mut dhcproto::Decoder::new(payload)).map_err(|x| {
tracing::info!(
error = &x as &dyn std::error::Error,
"failed to decode DHCPv6 message"
);
DropReason::Packet(smoltcp::Error::Malformed)
})?;

match msg.msg_type() {
v6::MessageType::InformationRequest => {
// Handle InformationRequest message (stateless DHCPv6)
tracing::info!("Received DHCPv6 InformationRequest message");

// Build DHCPv6 Reply response
let mut reply = v6::Message::new(v6::MessageType::Reply);
reply.set_xid(msg.xid());

// Add Client Identifier option (echo back from the InformationRequest)
if let Some(v6::DhcpOption::ClientId(client_id)) =
msg.opts().get(v6::OptionCode::ClientId)
{
reply
.opts_mut()
.insert(v6::DhcpOption::ClientId(client_id.clone()));
}

// Add Server Identifier option
// Use DUID-LL (type 3: Link-layer address)
let gateway_mac = self.inner.state.params.gateway_mac_ipv6.0;
let mut duid_bytes = vec![0x00, 0x03, 0x00, 0x01]; // Type 3 (LL), Hardware type 1 (Ethernet)
duid_bytes.extend_from_slice(&gateway_mac);
reply
.opts_mut()
.insert(v6::DhcpOption::ServerId(duid_bytes));

// Add DNS Recursive Name Server option if we have nameservers
let dns_servers: Vec<std::net::Ipv6Addr> = self
.inner
.state
.params
.nameservers
.iter()
.filter_map(|ip| match ip {
IpAddress::Ipv6(addr) => Some(*addr),
_ => None,
})
.filter(|addr| {
!(addr.is_unspecified()
|| addr.is_loopback()
|| addr.is_link_local()
|| addr.is_multicast()
|| addr.0.starts_with(&[0xfc, 0x00])
|| addr.0.starts_with(&[0xfe, 0xc0]))
})
.map(|addr| addr.into())
.collect();

if !dns_servers.is_empty() {
reply
.opts_mut()
.insert(v6::DhcpOption::DomainNameServers(dns_servers));
}

let mut dhcpv6_buffer = Vec::new();
let mut encoder = dhcproto::Encoder::new(&mut dhcpv6_buffer);
reply.encode(&mut encoder).map_err(|x| {
tracing::error!(
error = &x as &dyn std::error::Error,
"failed to encode DHCPv6 message"
);
DropReason::Packet(smoltcp::Error::Malformed)
})?;

let resp_udp = UdpRepr {
src_port: v6::SERVER_PORT,
dst_port: v6::CLIENT_PORT,
};

let client_link_local =
client_ip.unwrap_or_else(|| Ipv6Address::from_str("ff02::1:2").unwrap());
let resp_ipv6 = Ipv6Repr {
src_addr: self.inner.state.params.gateway_link_local_ipv6,
dst_addr: client_link_local,
next_header: IpProtocol::Udp,
payload_len: resp_udp.header_len() + dhcpv6_buffer.len(),
hop_limit: 64,
};
let resp_eth = EthernetRepr {
src_addr: self.inner.state.params.gateway_mac_ipv6,
dst_addr: self.inner.state.params.client_mac,
ethertype: EthernetProtocol::Ipv6,
};

// Construct the complete packet
let mut buffer = [0; MIN_MTU];
let mut eth_frame = EthernetFrame::new_unchecked(&mut buffer);
resp_eth.emit(&mut eth_frame);

let mut ipv6_packet = Ipv6Packet::new_unchecked(eth_frame.payload_mut());
resp_ipv6.emit(&mut ipv6_packet);

let mut udp_packet = UdpPacket::new_unchecked(ipv6_packet.payload_mut());
resp_udp.emit(
&mut udp_packet,
&IpAddress::Ipv6(resp_ipv6.src_addr),
&IpAddress::Ipv6(resp_ipv6.dst_addr),
dhcpv6_buffer.len(),
|udp_payload| {
udp_payload[..dhcpv6_buffer.len()].copy_from_slice(&dhcpv6_buffer);
},
&ChecksumCapabilities::default(),
);

let total_len = resp_eth.buffer_len()
+ resp_ipv6.buffer_len()
+ resp_udp.header_len()
+ dhcpv6_buffer.len();

self.client.recv(&buffer[..total_len], &ChecksumState::NONE);
}
_ => return Err(DropReason::UnsupportedDhcpv6(msg.msg_type())),
}

Ok(())
}
}
9 changes: 6 additions & 3 deletions vm/devices/net/net_consomme/consomme/src/dns_unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the MIT License.

use resolv_conf::ScopedIp;
use smoltcp::wire::IpAddress;
use smoltcp::wire::Ipv4Address;
use smoltcp::wire::Ipv6Address;
use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -13,15 +15,16 @@ pub enum Error {
Parse(#[from] resolv_conf::ParseError),
}

pub fn nameservers() -> Result<Vec<Ipv4Address>, Error> {
pub fn nameservers() -> Result<Vec<IpAddress>, Error> {
let contents = std::fs::read("/etc/resolv.conf")?;
let config = resolv_conf::Config::parse(contents)?;
Ok(config
.nameservers
.iter()
.filter_map(|ns| match ns {
ScopedIp::V4(addr) => Some(Ipv4Address::from(*addr)),
ScopedIp::V6(_, _) => None,
ScopedIp::V4(addr) => Some(IpAddress::Ipv4(Ipv4Address::from(*addr))),
ScopedIp::V6(addr, None) => Some(IpAddress::Ipv6(Ipv6Address::from(*addr))),
ScopedIp::V6(_, Some(_)) => None,
})
.collect())
}
25 changes: 19 additions & 6 deletions vm/devices/net/net_consomme/consomme/src/dns_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
// UNSAFETY: Calling Win32 APIs to get DNS server information.
#![expect(unsafe_code)]

use smoltcp::wire::Ipv4Address;
use smoltcp::wire::IpAddress;
use std::alloc::Layout;
use std::io;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;
use std::ptr::NonNull;
use std::ptr::null_mut;
use thiserror::Error;
Expand All @@ -21,15 +22,18 @@ use windows_sys::Win32::NetworkManagement::IpHelper::GAA_FLAG_SKIP_UNICAST;
use windows_sys::Win32::NetworkManagement::IpHelper::GetAdaptersAddresses;
use windows_sys::Win32::NetworkManagement::IpHelper::IP_ADAPTER_ADDRESSES_LH;
use windows_sys::Win32::Networking::WinSock::AF_INET;
use windows_sys::Win32::Networking::WinSock::AF_INET6;
use windows_sys::Win32::Networking::WinSock::AF_UNSPEC;
use windows_sys::Win32::Networking::WinSock::SOCKADDR_IN;
use windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6;

#[derive(Debug, Error)]
pub enum Error {
#[error("failed to query adapter DNS addresses")]
AdapterAddresses(#[source] io::Error),
}

pub fn nameservers() -> Result<Vec<Ipv4Address>, Error> {
pub fn nameservers() -> Result<Vec<IpAddress>, Error> {
let flags = GAA_FLAG_SKIP_UNICAST
| GAA_FLAG_SKIP_ANYCAST
| GAA_FLAG_SKIP_MULTICAST
Expand All @@ -45,8 +49,13 @@ pub fn nameservers() -> Result<Vec<Ipv4Address>, Error> {
let mut addrs = Addresses::new(0);
loop {
let mut size = addrs.size();
let r =
GetAdaptersAddresses(AF_INET.into(), flags, null_mut(), addrs.as_ptr(), &mut size);
let r = GetAdaptersAddresses(
AF_UNSPEC.into(),
flags,
null_mut(),
addrs.as_ptr(),
&mut size,
);
match r {
ERROR_SUCCESS => break,
ERROR_BUFFER_OVERFLOW => {}
Expand All @@ -72,8 +81,12 @@ pub fn nameservers() -> Result<Vec<Ipv4Address>, Error> {
let dns_addr = &*dns.Address.lpSockaddr;
if dns_addr.sa_family == AF_INET {
let dns_addr = &*dns.Address.lpSockaddr.cast::<SOCKADDR_IN>();
dns_servers
.push(Ipv4Addr::from(u32::from_be(dns_addr.sin_addr.S_un.S_addr)).into());
let ipv4_addr = Ipv4Addr::from(u32::from_be(dns_addr.sin_addr.S_un.S_addr));
dns_servers.push(ipv4_addr.into());
} else if dns_addr.sa_family == AF_INET6 {
let dns_addr = &*dns.Address.lpSockaddr.cast::<SOCKADDR_IN6>();
let ipv6_addr = Ipv6Addr::from(u128::from_be_bytes(dns_addr.sin6_addr.u.Byte));
dns_servers.push(ipv6_addr.into());
}
dns_p = dns.Next;
}
Expand Down
15 changes: 6 additions & 9 deletions vm/devices/net/net_consomme/consomme/src/icmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use super::Access;
use super::Client;
use super::ConsommeState;
use super::DropReason;
use super::SocketAddress;
use crate::ChecksumState;
use crate::Ipv4Addresses;

Expand Down Expand Up @@ -36,13 +35,14 @@ use std::mem::MaybeUninit;
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::SocketAddr;
use std::net::SocketAddrV4;
use std::task::Context;
use std::task::Poll;

const ICMPV4_HEADER_LEN: usize = 8;

pub(crate) struct Icmp {
connections: HashMap<SocketAddress, IcmpConnection>,
connections: HashMap<SocketAddrV4, IcmpConnection>,
}

impl Icmp {
Expand All @@ -57,7 +57,7 @@ impl Inspect for Icmp {
fn inspect(&self, req: inspect::Request<'_>) {
let mut resp = req.respond();
for (addr, conn) in &self.connections {
resp.field(&format!("{}:{}", addr.ip, addr.port), conn);
resp.field(&format!("{}:{}", addr.ip(), addr.port()), conn);
}
}
}
Expand All @@ -83,7 +83,7 @@ impl IcmpConnection {
fn poll_conn(
&mut self,
cx: &mut Context<'_>,
dst_addr: &SocketAddress,
dst_addr: &SocketAddrV4,
state: &mut ConsommeState,
client: &mut impl Client,
) {
Expand All @@ -106,7 +106,7 @@ impl IcmpConnection {
eth.set_src_addr(state.params.gateway_mac);
eth.set_dst_addr(self.guest_mac);
let mut ipv4 = Ipv4Packet::new_unchecked(eth.payload_mut());
ipv4.set_dst_addr(dst_addr.ip);
ipv4.set_dst_addr((*dst_addr.ip()).into());
ipv4.fill_checksum();
let len = ETHERNET_HEADER_LEN + n;
client.recv(&eth.as_ref()[..len], &ChecksumState::IPV4_ONLY);
Expand Down Expand Up @@ -160,10 +160,7 @@ impl<T: Client> Access<'_, T> {
hop_limit: u8,
) -> Result<(), DropReason> {
let icmp_packet = smoltcp::wire::Icmpv4Packet::new_unchecked(payload);
let guest_addr = SocketAddress {
ip: addresses.src_addr,
port: 0,
};
let guest_addr = SocketAddrV4::new(addresses.src_addr.into(), 0);

let entry = self.inner.icmp.connections.entry(guest_addr);
let conn = match entry {
Expand Down
Loading
Loading