From 1ca09b9484050d3a39474ea937a1f43246c7fca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=AB=E7=84=9A=20=E5=AF=8C=E8=89=AF?= Date: Mon, 13 Mar 2023 17:29:10 +0800 Subject: [PATCH] Add support for default route (IPv4 and IPv6) (#2059) Based on code by Michael Birtwell --- artiq/firmware/bootloader/main.rs | 41 +++++++------ artiq/firmware/libboard_misoc/net_settings.rs | 55 +++++++++++------ artiq/firmware/runtime/dhcp.rs | 61 ++++++++++++++----- artiq/firmware/runtime/ip_addr_storage.rs | 14 +++-- artiq/firmware/runtime/main.rs | 15 ++++- artiq/firmware/runtime/sched.rs | 12 +++- doc/manual/installing.rst | 14 +++-- 7 files changed, 146 insertions(+), 66 deletions(-) diff --git a/artiq/firmware/bootloader/main.rs b/artiq/firmware/bootloader/main.rs index 89d2002e1..355b94076 100644 --- a/artiq/firmware/bootloader/main.rs +++ b/artiq/firmware/bootloader/main.rs @@ -18,8 +18,8 @@ use board_misoc::slave_fpga; use board_misoc::{clock, ethmac, net_settings}; use board_misoc::uart_console::Console; use riscv::register::{mcause, mepc, mtval}; -use smoltcp::iface::SocketStorage; -use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address}; +use smoltcp::iface::{Routes, SocketStorage}; +use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address, Ipv6Address}; fn check_integrity() -> bool { extern { @@ -411,28 +411,29 @@ fn network_boot() { println!("Network addresses: {}", net_addresses); let mut ip_addrs = [ IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0), - IpCidr::new(net_addresses.ipv6_ll_addr, 0), - IpCidr::new(net_addresses.ipv6_ll_addr, 0) + net_addresses.ipv6_ll_addr, + IpCidr::new(IpAddress::Ipv6(Ipv6Address::UNSPECIFIED), 0) ]; if let net_settings::Ipv4AddrConfig::Static(ipv4) = net_addresses.ipv4_addr { - ip_addrs[0] = IpCidr::new(IpAddress::Ipv4(ipv4), 0); + ip_addrs[0] = IpCidr::Ipv4(ipv4); } - let mut interface = match net_addresses.ipv6_addr { - Some(addr) => { - ip_addrs[2] = IpCidr::new(addr, 0); - smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..]) - .hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr)) - .ip_addrs(&mut ip_addrs[..]) - .neighbor_cache(neighbor_cache) - .finalize() - } - None => - smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..]) - .hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr)) - .ip_addrs(&mut ip_addrs[..2]) - .neighbor_cache(neighbor_cache) - .finalize() + if let Some(ipv6) = net_addresses.ipv6_addr { + ip_addrs[2] = IpCidr::Ipv6(ipv6); }; + let mut routes = [None; 2]; + let mut interface = smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..]) + .hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr)) + .ip_addrs(&mut ip_addrs[..]) + .neighbor_cache(neighbor_cache) + .routes(Routes::new(&mut routes[..])) + .finalize(); + + if let Some(default_route) = net_addresses.ipv4_default_route { + interface.routes_mut().add_default_ipv4_route(default_route).unwrap(); + } + if let Some(default_route) = net_addresses.ipv6_default_route { + interface.routes_mut().add_default_ipv6_route(default_route).unwrap(); + } let mut rx_storage = [0; 4096]; let mut tx_storage = [0; 128]; diff --git a/artiq/firmware/libboard_misoc/net_settings.rs b/artiq/firmware/libboard_misoc/net_settings.rs index 36c85319f..bab8fb28b 100644 --- a/artiq/firmware/libboard_misoc/net_settings.rs +++ b/artiq/firmware/libboard_misoc/net_settings.rs @@ -2,7 +2,7 @@ use core::fmt; use core::fmt::{Display, Formatter}; use core::str::FromStr; -use smoltcp::wire::{EthernetAddress, IpAddress, Ipv4Address}; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr, Ipv6Address, Ipv6Cidr}; use config; #[cfg(soc_platform = "kasli")] @@ -10,18 +10,22 @@ use i2c_eeprom; pub enum Ipv4AddrConfig { UseDhcp, - Static(Ipv4Address), + Static(Ipv4Cidr), } impl FromStr for Ipv4AddrConfig { type Err = (); fn from_str(s: &str) -> Result { - Ok(if s == "use_dhcp" { - Ipv4AddrConfig::UseDhcp + if s == "use_dhcp" { + Ok(Ipv4AddrConfig::UseDhcp) + } else if let Ok(cidr) = Ipv4Cidr::from_str(s) { + Ok(Ipv4AddrConfig::Static(cidr)) + } else if let Ok(addr) = Ipv4Address::from_str(s) { + Ok(Ipv4AddrConfig::Static(Ipv4Cidr::new(addr, 0))) } else { - Ipv4AddrConfig::Static(Ipv4Address::from_str(s)?) - }) + Err(()) + } } } @@ -38,8 +42,10 @@ impl Display for Ipv4AddrConfig { pub struct NetAddresses { pub hardware_addr: EthernetAddress, pub ipv4_addr: Ipv4AddrConfig, - pub ipv6_ll_addr: IpAddress, - pub ipv6_addr: Option + pub ipv6_ll_addr: IpCidr, + pub ipv6_addr: Option, + pub ipv4_default_route: Option, + pub ipv6_default_route: Option, } impl fmt::Display for NetAddresses { @@ -72,28 +78,39 @@ pub fn get_adresses() -> NetAddresses { } } - let ipv4_addr; - match config::read_str("ip", |r| r.map(|s| s.parse())) { - Ok(Ok(addr)) => ipv4_addr = addr, - _ => ipv4_addr = Ipv4AddrConfig::UseDhcp, - } + let ipv4_addr = match config::read_str("ip", |r| r.map(|s| s.parse())) { + Ok(Ok(addr)) => addr, + _ => Ipv4AddrConfig::UseDhcp, + }; - let ipv6_ll_addr = IpAddress::v6( + let ipv4_default_route = match config::read_str("ipv4_default_route", |r| r.map(|s| s.parse())) { + Ok(Ok(addr)) => Some(addr), + _ => None, + }; + + let ipv6_ll_addr = IpCidr::new(IpAddress::v6( 0xfe80, 0x0000, 0x0000, 0x0000, (((hardware_addr.0[0] ^ 0x02) as u16) << 8) | (hardware_addr.0[1] as u16), ((hardware_addr.0[2] as u16) << 8) | 0x00ff, 0xfe00 | (hardware_addr.0[3] as u16), - ((hardware_addr.0[4] as u16) << 8) | (hardware_addr.0[5] as u16)); + ((hardware_addr.0[4] as u16) << 8) | (hardware_addr.0[5] as u16)), 10); let ipv6_addr = match config::read_str("ip6", |r| r.map(|s| s.parse())) { Ok(Ok(addr)) => Some(addr), _ => None }; + let ipv6_default_route = match config::read_str("ipv6_default_route", |r| r.map(|s| s.parse())) { + Ok(Ok(addr)) => Some(addr), + _ => None, + }; + NetAddresses { - hardware_addr: hardware_addr, - ipv4_addr: ipv4_addr, - ipv6_ll_addr: ipv6_ll_addr, - ipv6_addr: ipv6_addr + hardware_addr, + ipv4_addr, + ipv6_ll_addr, + ipv6_addr, + ipv4_default_route, + ipv6_default_route, } } diff --git a/artiq/firmware/runtime/dhcp.rs b/artiq/firmware/runtime/dhcp.rs index 0e7d3e438..5b5fc6263 100644 --- a/artiq/firmware/runtime/dhcp.rs +++ b/artiq/firmware/runtime/dhcp.rs @@ -1,12 +1,25 @@ use board_misoc::clock; use sched; use sched::Dhcpv4Socket; -use smoltcp::socket::Dhcpv4Event; +use core::fmt::{Display, Formatter}; +use smoltcp::socket::{Dhcpv4Config, Dhcpv4Event}; use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; +struct OptionalIpAddressDisplay<'a> (&'a Option); + +impl<'a> Display for OptionalIpAddressDisplay<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self.0 { + Some(ip) => write!(f, "{}", ip), + None => write!(f, ""), + } + } +} + + pub fn dhcp_thread(io: sched::Io) { let mut socket = Dhcpv4Socket::new(&io); - let mut last_ip: Option = None; + let mut last_config: Option = None; let mut done_reset = false; let start_time = clock::get_ms(); @@ -15,8 +28,8 @@ pub fn dhcp_thread(io: sched::Io) { // by the server. This is likely to be because the ethernet device isn't quite // ready at the point that it is sent. The following makes recovery from // that faster. - if !done_reset && last_ip.is_none() && start_time + 1000 < clock::get_ms() { - info!("Didn't get initial IP in first second. Assuming packet loss, trying a reset."); + if !done_reset && last_config.is_none() && start_time + 1000 < clock::get_ms() { + info!("Didn't get initial config in first second. Assuming packet loss, trying a reset."); socket.reset(); done_reset = true; } @@ -26,27 +39,45 @@ pub fn dhcp_thread(io: sched::Io) { // Only compare the ip address in the config with previous config because we // ignore the rest of the config i.e. we don't do any DNS or require a default // gateway. - let changed = if let Some(last_ip) = last_ip { - if config.address != last_ip { - info!("IP address changed {} -> {}", last_ip, config.address); - true - } else { - false + let changed = if let Some(last_config) = last_config { + let mut changed = false; + if config.address != last_config.address { + info!("IP address changed {} -> {}", last_config.address, config.address); + changed = true; } + if config.router != last_config.router { + info!("Default route changed {} -> {}", + OptionalIpAddressDisplay(&last_config.router), + OptionalIpAddressDisplay(&config.router), + ); + changed = true; + } + changed } else { - info!("Acquired IP address: None -> {}", config.address); + info!("Acquired DHCP config IP address: None -> {} default route: None -> {}", + config.address, + OptionalIpAddressDisplay(&config.router), + ); true }; if changed { - last_ip = Some(config.address); + last_config = Some(config); io.set_ipv4_address(&config.address); + match config.router { + Some(route) => { io.set_ipv4_default_route(route).unwrap(); } + None => { io.remove_ipv4_default_route(); } + } } } Dhcpv4Event::Deconfigured => { - if let Some(ip) = last_ip { - info!("Lost IP address {} -> None", ip); + if let Some(config) = last_config { + info!("Lost DHCP config IP address {} -> None; default route {} -> None", + config.address, + OptionalIpAddressDisplay(&config.router), + ); io.set_ipv4_address(&Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); - last_ip = None; + io.remove_ipv4_default_route(); + last_config = None; } // We always get one of these events at the start, ignore that one } diff --git a/artiq/firmware/runtime/ip_addr_storage.rs b/artiq/firmware/runtime/ip_addr_storage.rs index 310c21941..e51c6e86f 100644 --- a/artiq/firmware/runtime/ip_addr_storage.rs +++ b/artiq/firmware/runtime/ip_addr_storage.rs @@ -1,6 +1,6 @@ use smoltcp::iface::{Interface, InterfaceBuilder}; use smoltcp::phy::Device; -use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; +use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr, Ipv6Cidr}; use board_misoc::net_settings::{Ipv4AddrConfig, NetAddresses}; @@ -16,14 +16,14 @@ pub trait InterfaceBuilderEx { impl<'a, DeviceT: for<'d> Device<'d>> InterfaceBuilderEx for InterfaceBuilder<'a, DeviceT> { fn init_ip_addrs(self, net_addresses: &NetAddresses) -> Self { let mut storage = [ - IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0); IP_ADDRESS_STORAGE_SIZE + IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 32); IP_ADDRESS_STORAGE_SIZE ]; if let Ipv4AddrConfig::Static(ipv4) = net_addresses.ipv4_addr { - storage[IPV4_INDEX] = IpCidr::new(IpAddress::Ipv4(ipv4), 0); + storage[IPV4_INDEX] = IpCidr::Ipv4(ipv4); } - storage[IPV6_LL_INDEX] = IpCidr::new(net_addresses.ipv6_ll_addr, 0); + storage[IPV6_LL_INDEX] = net_addresses.ipv6_ll_addr; if let Some(ipv6) = net_addresses.ipv6_addr { - storage[IPV6_INDEX] = IpCidr::new(ipv6, 0); + storage[IPV6_INDEX] = IpCidr::Ipv6(ipv6); } self.ip_addrs(storage) } @@ -31,10 +31,14 @@ impl<'a, DeviceT: for<'d> Device<'d>> InterfaceBuilderEx for InterfaceBuilder<'a pub trait InterfaceEx { fn update_ipv4_addr(&mut self, addr: &Ipv4Cidr); + fn update_ipv6_addr(&mut self, addr: &Ipv6Cidr); } impl<'a, DeviceT: for<'d> Device<'d>> InterfaceEx for Interface<'a, DeviceT> { fn update_ipv4_addr(&mut self, addr: &Ipv4Cidr) { self.update_ip_addrs(|storage| storage[IPV4_INDEX] = IpCidr::Ipv4(*addr)) } + fn update_ipv6_addr(&mut self, addr: &Ipv6Cidr) { + self.update_ip_addrs(|storage| storage[IPV6_INDEX] = IpCidr::Ipv6(*addr)) + } } diff --git a/artiq/firmware/runtime/main.rs b/artiq/firmware/runtime/main.rs index 6738d7c55..3ae50dcad 100644 --- a/artiq/firmware/runtime/main.rs +++ b/artiq/firmware/runtime/main.rs @@ -25,6 +25,7 @@ extern crate logger_artiq; extern crate proto_artiq; extern crate riscv; +use alloc::collections::BTreeMap; use core::cell::RefCell; use core::convert::TryFrom; use smoltcp::wire::HardwareAddress; @@ -42,6 +43,7 @@ use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_pro use proto_artiq::analyzer_proto; use riscv::register::{mcause, mepc, mtval}; +use smoltcp::iface::Routes; use ip_addr_storage::InterfaceBuilderEx; mod rtio_clocking; @@ -151,12 +153,23 @@ fn startup() { } else { false }; - let interface = smoltcp::iface::InterfaceBuilder::new(net_device, vec![]) + let mut interface = smoltcp::iface::InterfaceBuilder::new(net_device, vec![]) .hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr)) .init_ip_addrs(&net_addresses) .neighbor_cache(neighbor_cache) + .routes(Routes::new(BTreeMap::new())) .finalize(); + if !use_dhcp { + if let Some(ipv4_default_route) = net_addresses.ipv4_default_route { + interface.routes_mut().add_default_ipv4_route(ipv4_default_route).unwrap(); + } + } + + if let Some(ipv6_default_route) = net_addresses.ipv6_default_route { + interface.routes_mut().add_default_ipv6_route(ipv6_default_route).unwrap(); + } + #[cfg(has_drtio)] let drtio_routing_table = urc::Urc::new(RefCell::new( drtio_routing::config_routing_table(csr::DRTIO.len()))); diff --git a/artiq/firmware/runtime/sched.rs b/artiq/firmware/runtime/sched.rs index 1f9c79bc2..0adeaddd1 100644 --- a/artiq/firmware/runtime/sched.rs +++ b/artiq/firmware/runtime/sched.rs @@ -8,8 +8,8 @@ use fringe::OwnedStack; use fringe::generator::{Generator, Yielder, State as GeneratorState}; use smoltcp::time::Duration; use smoltcp::Error as NetworkError; -use smoltcp::wire::{IpEndpoint, Ipv4Cidr}; -use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::wire::{IpEndpoint, Ipv4Address, Ipv4Cidr}; +use smoltcp::iface::{Interface, Route, SocketHandle}; use io::{Read, Write}; use board_misoc::clock; @@ -278,6 +278,14 @@ impl<'a> Io<'a> { pub fn set_ipv4_address(&self, addr: &Ipv4Cidr) { self.network.borrow_mut().update_ipv4_addr(addr) } + + pub fn set_ipv4_default_route(&self, addr: Ipv4Address) -> Result, Error> { + Ok(self.network.borrow_mut().routes_mut().add_default_ipv4_route(addr)?) + } + + pub fn remove_ipv4_default_route(&self) -> Option { + self.network.borrow_mut().routes_mut().remove_default_ipv4_route() + } } #[derive(Clone)] diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 0c8aa9b85..002cb4004 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -284,19 +284,25 @@ If you purchased a Kasli device from M-Labs, it usually comes with the IP addres and then reboot the device (with ``artiq_flash start`` or a power cycle). -If the ip config field is not set, or set to "use_dhcp" then the device will attempt to obtain an IP address using -DHCP. If a static IP address is wanted, install OpenOCD as before, and flash the IP (and, if necessary, MAC) addresses -directly: :: +If the ``ip`` config field is not set, or set to ``use_dhcp`` then the device will +attempt to obtain an IP address and default gateway using DHCP. If a static IP +address is wanted, install OpenOCD as before, and flash the IP, default gateway +(and, if necessary, MAC and IPv6) addresses directly: :: - $ artiq_mkfs flash_storage.img -s mac xx:xx:xx:xx:xx:xx -s ip xx.xx.xx.xx + $ artiq_mkfs flash_storage.img -s mac xx:xx:xx:xx:xx:xx -s ip xx.xx.xx.xx/xx -s ipv4_default_route xx.xx.xx.xx -s ip6 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/xx -s ipv6_default_route xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx $ artiq_flash -t [board] -V [variant] -f flash_storage.img storage start For Kasli devices, flashing a MAC address is not necessary as they can obtain it from their EEPROM. +If you only want to access the core device from the same subnet you may +omit the default gateway and IPv4 prefix length: :: + + $ artiq_mkfs flash_storage.img -s mac xx:xx:xx:xx:xx:xx -s ip xx.xx.xx.xx If DHCP has been used the address can be found in the console output, which can be viewed using: :: $ python -m misoc.tools.flterm /dev/ttyUSB2 + Check that you can ping the device. If ping fails, check that the Ethernet link LED is ON - on Kasli, it is the LED next to the SFP0 connector. As a next step, look at the messages emitted on the UART during boot. Use a program such as flterm or PuTTY to connect to the device's serial port at 115200bps 8-N-1 and reboot the device. On Kasli, the serial port is on FTDI channel 2 with v1.1 hardware (with channel 0 being JTAG) and on FTDI channel 1 with v1.0 hardware. Note that on Windows you might need to install the `FTDI drivers `_ first. If you want to use IPv6, the device also has a link-local address that corresponds to its EUI-64, and an additional arbitrary IPv6 address can be defined by using the ``ip6`` configuration key. All IPv4 and IPv6 addresses can be used at the same time.