2
0
mirror of https://github.com/m-labs/artiq.git synced 2024-12-19 00:16:29 +08:00

Add support for default route (IPv4 and IPv6) (#2059)

Based on code by Michael Birtwell <michael.birtwell@oxionics.com>
This commit is contained in:
Egor Savkin 2023-03-13 17:29:10 +08:00 committed by GitHub
parent 84e7515721
commit 1ca09b9484
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 146 additions and 66 deletions

View File

@ -18,8 +18,8 @@ use board_misoc::slave_fpga;
use board_misoc::{clock, ethmac, net_settings}; use board_misoc::{clock, ethmac, net_settings};
use board_misoc::uart_console::Console; use board_misoc::uart_console::Console;
use riscv::register::{mcause, mepc, mtval}; use riscv::register::{mcause, mepc, mtval};
use smoltcp::iface::SocketStorage; use smoltcp::iface::{Routes, SocketStorage};
use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address}; use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address, Ipv6Address};
fn check_integrity() -> bool { fn check_integrity() -> bool {
extern { extern {
@ -411,28 +411,29 @@ fn network_boot() {
println!("Network addresses: {}", net_addresses); println!("Network addresses: {}", net_addresses);
let mut ip_addrs = [ let mut ip_addrs = [
IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0), IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0),
IpCidr::new(net_addresses.ipv6_ll_addr, 0), net_addresses.ipv6_ll_addr,
IpCidr::new(net_addresses.ipv6_ll_addr, 0) IpCidr::new(IpAddress::Ipv6(Ipv6Address::UNSPECIFIED), 0)
]; ];
if let net_settings::Ipv4AddrConfig::Static(ipv4) = net_addresses.ipv4_addr { 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 { if let Some(ipv6) = net_addresses.ipv6_addr {
Some(addr) => { ip_addrs[2] = IpCidr::Ipv6(ipv6);
ip_addrs[2] = IpCidr::new(addr, 0); };
smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..]) let mut routes = [None; 2];
let mut interface = smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr)) .hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
.ip_addrs(&mut ip_addrs[..]) .ip_addrs(&mut ip_addrs[..])
.neighbor_cache(neighbor_cache) .neighbor_cache(neighbor_cache)
.finalize() .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();
} }
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()
};
let mut rx_storage = [0; 4096]; let mut rx_storage = [0; 4096];
let mut tx_storage = [0; 128]; let mut tx_storage = [0; 128];

View File

@ -2,7 +2,7 @@ use core::fmt;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
use core::str::FromStr; use core::str::FromStr;
use smoltcp::wire::{EthernetAddress, IpAddress, Ipv4Address}; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr, Ipv6Address, Ipv6Cidr};
use config; use config;
#[cfg(soc_platform = "kasli")] #[cfg(soc_platform = "kasli")]
@ -10,18 +10,22 @@ use i2c_eeprom;
pub enum Ipv4AddrConfig { pub enum Ipv4AddrConfig {
UseDhcp, UseDhcp,
Static(Ipv4Address), Static(Ipv4Cidr),
} }
impl FromStr for Ipv4AddrConfig { impl FromStr for Ipv4AddrConfig {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(if s == "use_dhcp" { if s == "use_dhcp" {
Ipv4AddrConfig::UseDhcp 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 { } else {
Ipv4AddrConfig::Static(Ipv4Address::from_str(s)?) Err(())
}) }
} }
} }
@ -38,8 +42,10 @@ impl Display for Ipv4AddrConfig {
pub struct NetAddresses { pub struct NetAddresses {
pub hardware_addr: EthernetAddress, pub hardware_addr: EthernetAddress,
pub ipv4_addr: Ipv4AddrConfig, pub ipv4_addr: Ipv4AddrConfig,
pub ipv6_ll_addr: IpAddress, pub ipv6_ll_addr: IpCidr,
pub ipv6_addr: Option<IpAddress> pub ipv6_addr: Option<Ipv6Cidr>,
pub ipv4_default_route: Option<Ipv4Address>,
pub ipv6_default_route: Option<Ipv6Address>,
} }
impl fmt::Display for NetAddresses { impl fmt::Display for NetAddresses {
@ -72,28 +78,39 @@ pub fn get_adresses() -> NetAddresses {
} }
} }
let ipv4_addr; let ipv4_addr = match config::read_str("ip", |r| r.map(|s| s.parse())) {
match config::read_str("ip", |r| r.map(|s| s.parse())) { Ok(Ok(addr)) => addr,
Ok(Ok(addr)) => ipv4_addr = addr, _ => Ipv4AddrConfig::UseDhcp,
_ => ipv4_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, 0xfe80, 0x0000, 0x0000, 0x0000,
(((hardware_addr.0[0] ^ 0x02) as u16) << 8) | (hardware_addr.0[1] as u16), (((hardware_addr.0[0] ^ 0x02) as u16) << 8) | (hardware_addr.0[1] as u16),
((hardware_addr.0[2] as u16) << 8) | 0x00ff, ((hardware_addr.0[2] as u16) << 8) | 0x00ff,
0xfe00 | (hardware_addr.0[3] as u16), 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())) { let ipv6_addr = match config::read_str("ip6", |r| r.map(|s| s.parse())) {
Ok(Ok(addr)) => Some(addr), Ok(Ok(addr)) => Some(addr),
_ => None _ => 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 { NetAddresses {
hardware_addr: hardware_addr, hardware_addr,
ipv4_addr: ipv4_addr, ipv4_addr,
ipv6_ll_addr: ipv6_ll_addr, ipv6_ll_addr,
ipv6_addr: ipv6_addr ipv6_addr,
ipv4_default_route,
ipv6_default_route,
} }
} }

View File

@ -1,12 +1,25 @@
use board_misoc::clock; use board_misoc::clock;
use sched; use sched;
use sched::Dhcpv4Socket; use sched::Dhcpv4Socket;
use smoltcp::socket::Dhcpv4Event; use core::fmt::{Display, Formatter};
use smoltcp::socket::{Dhcpv4Config, Dhcpv4Event};
use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
struct OptionalIpAddressDisplay<'a> (&'a Option<Ipv4Address>);
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, "<not set>"),
}
}
}
pub fn dhcp_thread(io: sched::Io) { pub fn dhcp_thread(io: sched::Io) {
let mut socket = Dhcpv4Socket::new(&io); let mut socket = Dhcpv4Socket::new(&io);
let mut last_ip: Option<Ipv4Cidr> = None; let mut last_config: Option<Dhcpv4Config> = None;
let mut done_reset = false; let mut done_reset = false;
let start_time = clock::get_ms(); 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 // 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 // ready at the point that it is sent. The following makes recovery from
// that faster. // that faster.
if !done_reset && last_ip.is_none() && start_time + 1000 < clock::get_ms() { if !done_reset && last_config.is_none() && start_time + 1000 < clock::get_ms() {
info!("Didn't get initial IP in first second. Assuming packet loss, trying a reset."); info!("Didn't get initial config in first second. Assuming packet loss, trying a reset.");
socket.reset(); socket.reset();
done_reset = true; 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 // 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 // ignore the rest of the config i.e. we don't do any DNS or require a default
// gateway. // gateway.
let changed = if let Some(last_ip) = last_ip { let changed = if let Some(last_config) = last_config {
if config.address != last_ip { let mut changed = false;
info!("IP address changed {} -> {}", last_ip, config.address); if config.address != last_config.address {
true info!("IP address changed {} -> {}", last_config.address, config.address);
} else { changed = true;
false
} }
if config.router != last_config.router {
info!("Default route changed {} -> {}",
OptionalIpAddressDisplay(&last_config.router),
OptionalIpAddressDisplay(&config.router),
);
changed = true;
}
changed
} else { } else {
info!("Acquired IP address: None -> {}", config.address); info!("Acquired DHCP config IP address: None -> {} default route: None -> {}",
config.address,
OptionalIpAddressDisplay(&config.router),
);
true true
}; };
if changed { if changed {
last_ip = Some(config.address); last_config = Some(config);
io.set_ipv4_address(&config.address); 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 => { Dhcpv4Event::Deconfigured => {
if let Some(ip) = last_ip { if let Some(config) = last_config {
info!("Lost IP address {} -> None", ip); info!("Lost DHCP config IP address {} -> None; default route {} -> None",
config.address,
OptionalIpAddressDisplay(&config.router),
);
io.set_ipv4_address(&Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); 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 // We always get one of these events at the start, ignore that one
} }

View File

@ -1,6 +1,6 @@
use smoltcp::iface::{Interface, InterfaceBuilder}; use smoltcp::iface::{Interface, InterfaceBuilder};
use smoltcp::phy::Device; 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}; 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> { impl<'a, DeviceT: for<'d> Device<'d>> InterfaceBuilderEx for InterfaceBuilder<'a, DeviceT> {
fn init_ip_addrs(self, net_addresses: &NetAddresses) -> Self { fn init_ip_addrs(self, net_addresses: &NetAddresses) -> Self {
let mut storage = [ 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 { 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 { 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) self.ip_addrs(storage)
} }
@ -31,10 +31,14 @@ impl<'a, DeviceT: for<'d> Device<'d>> InterfaceBuilderEx for InterfaceBuilder<'a
pub trait InterfaceEx { pub trait InterfaceEx {
fn update_ipv4_addr(&mut self, addr: &Ipv4Cidr); 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> { impl<'a, DeviceT: for<'d> Device<'d>> InterfaceEx for Interface<'a, DeviceT> {
fn update_ipv4_addr(&mut self, addr: &Ipv4Cidr) { fn update_ipv4_addr(&mut self, addr: &Ipv4Cidr) {
self.update_ip_addrs(|storage| storage[IPV4_INDEX] = IpCidr::Ipv4(*addr)) 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))
}
} }

View File

@ -25,6 +25,7 @@ extern crate logger_artiq;
extern crate proto_artiq; extern crate proto_artiq;
extern crate riscv; extern crate riscv;
use alloc::collections::BTreeMap;
use core::cell::RefCell; use core::cell::RefCell;
use core::convert::TryFrom; use core::convert::TryFrom;
use smoltcp::wire::HardwareAddress; 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 proto_artiq::analyzer_proto;
use riscv::register::{mcause, mepc, mtval}; use riscv::register::{mcause, mepc, mtval};
use smoltcp::iface::Routes;
use ip_addr_storage::InterfaceBuilderEx; use ip_addr_storage::InterfaceBuilderEx;
mod rtio_clocking; mod rtio_clocking;
@ -151,12 +153,23 @@ fn startup() {
} else { } else {
false 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)) .hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
.init_ip_addrs(&net_addresses) .init_ip_addrs(&net_addresses)
.neighbor_cache(neighbor_cache) .neighbor_cache(neighbor_cache)
.routes(Routes::new(BTreeMap::new()))
.finalize(); .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)] #[cfg(has_drtio)]
let drtio_routing_table = urc::Urc::new(RefCell::new( let drtio_routing_table = urc::Urc::new(RefCell::new(
drtio_routing::config_routing_table(csr::DRTIO.len()))); drtio_routing::config_routing_table(csr::DRTIO.len())));

View File

@ -8,8 +8,8 @@ use fringe::OwnedStack;
use fringe::generator::{Generator, Yielder, State as GeneratorState}; use fringe::generator::{Generator, Yielder, State as GeneratorState};
use smoltcp::time::Duration; use smoltcp::time::Duration;
use smoltcp::Error as NetworkError; use smoltcp::Error as NetworkError;
use smoltcp::wire::{IpEndpoint, Ipv4Cidr}; use smoltcp::wire::{IpEndpoint, Ipv4Address, Ipv4Cidr};
use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::iface::{Interface, Route, SocketHandle};
use io::{Read, Write}; use io::{Read, Write};
use board_misoc::clock; use board_misoc::clock;
@ -278,6 +278,14 @@ impl<'a> Io<'a> {
pub fn set_ipv4_address(&self, addr: &Ipv4Cidr) { pub fn set_ipv4_address(&self, addr: &Ipv4Cidr) {
self.network.borrow_mut().update_ipv4_addr(addr) self.network.borrow_mut().update_ipv4_addr(addr)
} }
pub fn set_ipv4_default_route(&self, addr: Ipv4Address) -> Result<Option<Route>, Error> {
Ok(self.network.borrow_mut().routes_mut().add_default_ipv4_route(addr)?)
}
pub fn remove_ipv4_default_route(&self) -> Option<Route> {
self.network.borrow_mut().routes_mut().remove_default_ipv4_route()
}
} }
#[derive(Clone)] #[derive(Clone)]

View File

@ -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). 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 If the ``ip`` config field is not set, or set to ``use_dhcp`` then the device will
DHCP. If a static IP address is wanted, install OpenOCD as before, and flash the IP (and, if necessary, MAC) addresses attempt to obtain an IP address and default gateway using DHCP. If a static IP
directly: :: 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 $ 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. 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: :: 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 $ 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 <https://ftdichip.com/drivers/>`_ first. 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 <https://ftdichip.com/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. 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.