فهرست منبع

Initial work with basic epoll/multicast discovery.

Kestrel 2 سال پیش
کامیت
1c3b99d1b0
17فایلهای تغییر یافته به همراه797 افزوده شده و 0 حذف شده
  1. 3 0
      .gitignore
  2. 242 0
      Cargo.lock
  3. 2 0
      Cargo.toml
  4. 3 0
      fleck/.gitignore
  5. 16 0
      fleck/Cargo.toml
  6. BIN
      fleck/examples/core
  7. 5 0
      fleck/examples/simple_node.rs
  8. 42 0
      fleck/src/io.rs
  9. 245 0
      fleck/src/io/linux.rs
  10. 98 0
      fleck/src/lib.rs
  11. 20 0
      fleck/src/msg.rs
  12. 8 0
      fleck/src/node.rs
  13. 56 0
      fleck/src/service.rs
  14. 43 0
      fleck/test-env.sh
  15. 1 0
      metashell/.gitignore
  16. 10 0
      metashell/Cargo.toml
  17. 3 0
      metashell/src/main.rs

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.*.sw?
+.sw?
+/target

+ 242 - 0
Cargo.lock

@@ -0,0 +1,242 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cc"
+version = "1.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "epoll"
+version = "4.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20df693c700404f7e19d4d6fae6b15215d2913c27955d2b9d6f2c0f537511cd0"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "errno"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fleck"
+version = "0.1.0"
+dependencies = [
+ "bincode",
+ "epoll",
+ "lazy_static",
+ "log",
+ "nix",
+ "serde",
+ "timerfd",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "metashell"
+version = "0.1.0"
+dependencies = [
+ "fleck",
+]
+
+[[package]]
+name = "nix"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
+dependencies = [
+ "autocfg",
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustix"
+version = "0.34.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c267b8394eb529872c3cf92e181c378b41fea36e68130357b52493701d2e"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "winapi",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "timerfd"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29f85a7c965b8e7136952f59f2a359694c78f105b2d2ff99cf6c2c404bf7e33f"
+dependencies = [
+ "rustix",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 2 - 0
Cargo.toml

@@ -0,0 +1,2 @@
+[workspace]
+members = ["fleck", "metashell"]

+ 3 - 0
fleck/.gitignore

@@ -0,0 +1,3 @@
+/target
+/Cargo.lock
+/.fleck*

+ 16 - 0
fleck/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "fleck"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+serde = { version = "1.0", features = ["derive"] }
+bincode = "1.3.3"
+epoll = "4.3.1"
+log = "0.4.17"
+lazy_static = "1.4.0"
+nix = { version = "0.25.0", default-features=false, features = ["socket", "net"] }
+timerfd = "1.3.0"

BIN
fleck/examples/core


+ 5 - 0
fleck/examples/simple_node.rs

@@ -0,0 +1,5 @@
+fn main() {
+    let mut fleck = fleck::Fleck::new();
+
+    fleck.run();
+}

+ 42 - 0
fleck/src/io.rs

@@ -0,0 +1,42 @@
+pub mod linux;
+
+lazy_static::lazy_static! {
+    pub static ref MULTICAST_ADDRESS : std::net::SocketAddrV4 =
+        std::net::SocketAddrV4::new(
+            std::net::Ipv4Addr::new(239, 0, 2, 35),
+            3535
+        );
+}
+
+pub struct Packet {
+    pub(crate) addr: Option<std::net::SocketAddr>,
+    pub(crate) data: Option<Vec<u8>>,
+    pub(crate) channel: Option<std::rc::Rc<dyn IOChannel>>,
+    pub msg: Option<crate::msg::Message>,
+}
+
+pub(crate) trait IOChannel {
+    fn send_packet(&self, packet: &mut Packet);
+}
+
+pub(crate) trait IOFeedback {
+    fn packet(&self, packet: Packet);
+    fn minor_tick(&self);
+    fn major_tick(&self);
+}
+
+pub(crate) trait FleckIO {
+    fn poll<'a>(&self, f: &dyn IOFeedback);
+
+    fn local(&self) -> std::rc::Rc<dyn IOChannel>;
+    fn global(&self) -> std::rc::Rc<dyn IOChannel>;
+}
+
+pub(crate) trait IOHandler {
+    fn minor(&self);
+}
+
+pub(crate) fn platform() -> std::rc::Rc<dyn FleckIO> {
+    std::rc::Rc::new(linux::LinuxIO::default())
+}
+

+ 245 - 0
fleck/src/io/linux.rs

@@ -0,0 +1,245 @@
+use std::cell::{RefCell, Cell};
+use std::collections::{HashMap,VecDeque};
+use std::io::Read;
+use std::net::SocketAddr;
+use std::os::unix::io::RawFd;
+use std::os::unix::prelude::AsRawFd;
+
+use crate::io::Packet;
+
+use super::{IOChannel, IOFeedback};
+
+struct Poller {
+    epoll: RawFd,
+    epoll_events: std::cell::RefCell<Vec<epoll::Event>>,
+}
+
+impl Default for Poller {
+    fn default() -> Self {
+        Self {
+            epoll: epoll::create(false).expect("couldn't create poller"),
+            epoll_events: Default::default(),
+        }
+    }
+}
+
+impl Drop for Poller {
+    fn drop(&mut self) {
+        epoll::close(self.epoll).expect("couldn't close epoll instance!");
+    }
+}
+
+impl Poller {
+    fn poll_event(&self) -> Option<epoll::Event> {
+        let mut evts = self.epoll_events.borrow_mut();
+        if !evts.is_empty() {
+            return evts.pop()
+        }
+
+        let mut new_evts = [epoll::Event::new(epoll::Events::empty(), 0); 4];
+        match epoll::wait(self.epoll, -1, &mut new_evts) {
+            Ok(count) => {
+                evts.extend_from_slice(&new_evts[0..count]);
+                drop(evts);
+                self.poll_event()
+            },
+            Err(_) => {
+                None
+            }
+        }
+    }
+
+    fn write_interest(&self, fd: RawFd) {
+        epoll::ctl(self.epoll, epoll::ControlOptions::EPOLL_CTL_MOD, fd,
+            epoll::Event::new(
+                epoll::Events::EPOLLIN | epoll::Events::EPOLLOUT,
+                fd as u64
+            )
+        ).expect("adding write interest failed?");
+    }
+
+    fn write_disinterest(&self, fd: RawFd) {
+        epoll::ctl(self.epoll, epoll::ControlOptions::EPOLL_CTL_MOD, fd,
+            epoll::Event::new(
+                epoll::Events::EPOLLIN,
+                fd as u64
+            )
+        ).expect("removing write interest failed?");
+    }
+
+    fn register_read(&self, fd: RawFd) {
+        epoll::ctl(self.epoll, epoll::ControlOptions::EPOLL_CTL_ADD, fd,
+            epoll::Event::new(
+                epoll::Events::EPOLLIN,
+                fd as u64
+            )
+        ).expect("adding read interest failed?");
+    }
+}
+
+struct Socket {
+    sock: std::net::UdpSocket,
+    poller: std::rc::Rc<Poller>,
+
+    send_queue: RefCell<VecDeque<super::Packet>>,
+    receive_buffer: RefCell<[u8; 2048]>,
+
+    is_writing: Cell<bool>,
+}
+
+impl Socket {
+    fn try_receive(&self) -> Option<Packet> {
+        let mut buf = self.receive_buffer.borrow_mut();
+        match self.sock.recv_from(buf.as_mut()) {
+            Ok((len, addr)) => {
+                println!("received packet {:?} from {:?}", &buf[0..len], addr);
+                Some(Packet {
+                    addr: Some(addr),
+                    data: Some(buf[0..len].into()),
+                    channel: None,
+                    msg: None,
+                })
+            },
+            Err(err) => {
+                None
+            }
+        }
+    }
+
+    fn queue_packet(&self, packet: super::Packet) {
+        self.send_queue.borrow_mut().push_back(packet);
+        if !self.is_writing.get() {
+            self.poller.write_interest(self.sock.as_raw_fd());
+            self.is_writing.set(true);
+        }
+    }
+
+    fn try_send(&self) {
+        if !self.is_writing.get() {
+            return
+        }
+
+        let mut sq = self.send_queue.borrow_mut();
+        while !sq.is_empty() {
+            let pkt = sq.front().unwrap();
+            match self.sock.send_to(pkt.data.as_ref().unwrap(), pkt.addr.as_ref().unwrap()) {
+                Ok(len) => {
+                    if len != pkt.data.as_ref().unwrap().len() {
+                        log::error!("Tried to send {}-byte packet, only sent {} bytes!", pkt.data.as_ref().unwrap().len(), len);
+                        return
+                    }
+                    else {
+                        sq.pop_front();
+                    }
+                },
+                Err(_) => {
+                    return
+                }
+            }
+        }
+
+        self.poller.write_disinterest(self.sock.as_raw_fd());
+        self.is_writing.set(false);
+    }
+}
+
+impl super::IOChannel for Socket {
+    fn send_packet(&self, packet: &mut super::Packet) {
+        match (packet.addr.take(), packet.data.take()) {
+            (Some(addr), Some(data)) => {
+                self.queue_packet(super::Packet {
+                    addr: Some(addr),
+                    data: Some(data),
+                    channel: None,
+                    msg: None,
+                });
+            },
+            _ => {
+                println!("")
+            }
+        };
+        
+    }
+}
+
+pub(crate) struct LinuxIO {
+    local_sockets: Vec<std::rc::Rc<Socket>>,
+    global_socket: std::rc::Rc<Socket>,
+    minor_timer: timerfd::TimerFd,
+    major_timer: timerfd::TimerFd,
+    poller: std::rc::Rc<Poller>,
+}
+
+impl Default for LinuxIO {
+    fn default() -> Self {
+        let poller : std::rc::Rc<Poller> = Default::default();
+        let global = std::net::UdpSocket::bind(SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)), 3535)).expect("couldn't bind on localhost:3536");
+
+        global.set_nonblocking(true).expect("couldn't set nonblocking mode?");
+
+        poller.register_read(global.as_raw_fd());
+
+        // try joining the relevant multicast group
+        nix::sys::socket::setsockopt(
+            global.as_raw_fd(),
+            nix::sys::socket::sockopt::IpAddMembership,
+            &nix::sys::socket::IpMembershipRequest::new(
+                *super::MULTICAST_ADDRESS.ip(), None)
+        ).expect("couldn't request multicast group join?");
+
+        let mut minor = timerfd::TimerFd::new().unwrap();
+        minor.set_state(timerfd::TimerState::Periodic{ current: std::time::Duration::new(1, 0), interval: std::time::Duration::new(1, 0) }, timerfd::SetTimeFlags::Default);
+
+        let mut major = timerfd::TimerFd::new().unwrap();
+        major.set_state(timerfd::TimerState::Periodic{ current: std::time::Duration::new(2, 0), interval: std::time::Duration::new(15, 0) }, timerfd::SetTimeFlags::Default);
+
+        poller.register_read(minor.as_raw_fd());
+        poller.register_read(major.as_raw_fd());
+
+        Self {
+            local_sockets: vec![],
+            global_socket: Socket {
+                sock: global,
+                send_queue: Default::default(),
+                poller: poller.clone(),
+                receive_buffer: [0; 2048].into(),
+                is_writing: false.into()
+            }.into(),
+            minor_timer: minor,
+            major_timer: major,
+            poller,
+        }
+    }
+}
+
+impl super::FleckIO for LinuxIO {
+    fn poll(&self, f: &dyn IOFeedback) {
+        loop {
+            if let Some(evt) = self.poller.poll_event() {
+                let ready_fd = evt.data as i32;
+                if ready_fd == self.global_socket.sock.as_raw_fd() {
+                    self.global_socket.try_receive().map(|p| f.packet(p));
+                    self.global_socket.try_send();
+                }
+                if ready_fd == self.minor_timer.as_raw_fd() {
+                    self.minor_timer.read();
+                    f.minor_tick();
+                }
+                if ready_fd == self.major_timer.as_raw_fd() {
+                    self.major_timer.read();
+                    f.major_tick();
+                }
+            }
+        }
+    }
+
+    fn local(&self) -> std::rc::Rc<dyn super::IOChannel> {
+        //let res : std::rc::Rc<dyn super::IOChannel> = self.
+        self.global()
+    }
+
+    fn global(&self) -> std::rc::Rc<dyn super::IOChannel> {
+        let res : std::rc::Rc<dyn super::IOChannel> = self.global_socket.clone();
+        res
+    }
+}

+ 98 - 0
fleck/src/lib.rs

@@ -0,0 +1,98 @@
+mod msg;
+mod node;
+
+mod io;
+mod service;
+
+pub mod prelude {
+    pub use crate::msg::Message;
+}
+
+use io::{FleckIO,IOChannel};
+
+pub trait ServiceInterface {
+    fn send(&self, packet: io::Packet);
+}
+
+pub trait FleckService {
+    fn process_incoming(&self, packet: &mut io::Packet) {}
+    fn process_outgoing(&self, packet: &mut io::Packet) {}
+    fn process_minor_tick(&self, si: &dyn ServiceInterface) {}
+    fn process_major_tick(&self, si: &dyn ServiceInterface) {}
+}
+
+pub struct Fleck {
+    io: std::rc::Rc<dyn FleckIO>,
+    services: Vec<std::rc::Rc<dyn FleckService>>,
+}
+
+impl Fleck {
+    pub fn new() -> Self {
+        let mut res = Self {
+            io: io::platform(),
+            services: Vec::new()
+        };
+
+        res.register_services();
+
+        res
+    }
+
+    /*pub fn new_with_io(io: std::rc::Rc<dyn FleckIO>) -> Self {
+        Self {
+            io,
+            services: Vec::new()
+        }
+    }*/
+
+    fn register_services(&mut self) {
+        // Inserted in processing order
+        // Local discovery
+        self.services.push(std::rc::Rc::new(service::LocalDiscovery::new(self.io.clone())));
+
+        // Finally, send the packet
+        self.services.push(std::rc::Rc::new(service::SendPacket::new(self.io.clone())));
+    }
+
+    pub fn register_service(&mut self, srv: std::rc::Rc<dyn FleckService>) {
+        self.services.push(srv);
+    }
+
+    pub fn run(&self) {
+        self.io.poll(self);
+    }
+}
+
+impl ServiceInterface for Fleck {
+     fn send(&self, mut packet: io::Packet) {
+        // process services in forwards order
+        for svc in self.services.iter() {
+            svc.process_outgoing(&mut packet);
+        }
+     }
+}
+
+impl io::IOFeedback for Fleck {
+    fn packet(&self, mut packet: io::Packet) {
+        println!("received a packet!");
+        // received a packet!
+        // process services in reverse order
+        for svc in self.services.iter().rev() {
+            svc.process_incoming(&mut packet);
+        }
+    }
+
+    fn minor_tick(&self) {
+        // process services in forwards order
+        for svc in self.services.iter() {
+            svc.process_minor_tick(self);
+        }
+    }
+
+    fn major_tick(&self) {
+        // process services in forwards order
+        for svc in self.services.iter() {
+            svc.process_major_tick(self);
+        }
+    }
+}

+ 20 - 0
fleck/src/msg.rs

@@ -0,0 +1,20 @@
+use serde::{Serialize,Deserialize};
+
+const MESSAGE_MAGIC: u64 = 0x1234123412341234;
+
+#[derive(Serialize,Deserialize)]
+pub enum HeaderType {
+    Hello,
+}
+
+#[derive(Serialize,Deserialize)]
+pub struct Message {
+    ht: HeaderType,
+    magic: u64,
+}
+
+impl Message {
+    pub fn build(ht: HeaderType) -> Self {
+        Self { ht, magic: MESSAGE_MAGIC }
+    }
+}

+ 8 - 0
fleck/src/node.rs

@@ -0,0 +1,8 @@
+pub struct NodeID(u64);
+pub struct NodeKey(Vec<u8>);
+
+pub struct FleckNode {
+    id: Option<NodeID>,
+    addr: Option<std::net::IpAddr>,
+    key: Option<NodeKey>,
+}

+ 56 - 0
fleck/src/service.rs

@@ -0,0 +1,56 @@
+use crate::io::{FleckIO, Packet};
+
+pub(crate) struct SendPacket {
+    io: std::rc::Rc<dyn FleckIO>,
+}
+
+impl SendPacket {
+    pub(crate) fn new(io: std::rc::Rc<dyn FleckIO>) -> Self {
+        Self { io }
+    }
+}
+
+impl crate::FleckService for SendPacket {
+    fn process_outgoing(&self, packet: &mut crate::io::Packet) {
+        // use default channel if not specified
+        if packet.channel.is_none() {
+            packet.channel = Some(self.io.global());
+        }
+
+        // serialize if needed
+        match (packet.data.as_ref().is_some(), packet.msg.take()) {
+            (false, Some(msg)) => {
+                packet.data = Some(bincode::serialize(&msg).expect("failed to serialize message"));
+            },
+            _ => {},
+        }
+
+        match &mut packet.channel {
+            Some(ch) => ch.clone().send_packet(packet),
+            _ => {
+                println!("tried to send packet with no channel?");
+            }
+        }
+    }
+}
+
+pub(crate) struct LocalDiscovery {
+    io: std::rc::Rc<dyn FleckIO>,
+}
+
+impl LocalDiscovery {
+    pub(crate) fn new(io: std::rc::Rc<dyn FleckIO>) -> Self {
+        Self { io }
+    }
+}
+
+impl crate::FleckService for LocalDiscovery {
+    fn process_major_tick(&self, si: &dyn super::ServiceInterface) {
+        si.send(Packet {
+            addr: Some(crate::io::MULTICAST_ADDRESS.clone().into()),
+            data: None,
+            channel: Some(self.io.local()),
+            msg: Some(crate::msg::Message::build(crate::msg::HeaderType::Hello)),
+        });
+    }
+}

+ 43 - 0
fleck/test-env.sh

@@ -0,0 +1,43 @@
+#!/bin/bash
+
+ENVNAME=$1
+LAST_OCTET=$2
+
+HASH=$(echo $ENVNAME | md5sum | head -c 6)
+
+IP_OCTET_3=$(printf "%d" 0x$(echo $HASH | head -c 2))
+IP_OCTET_4=$(printf "%d" 0x$(echo $HASH | tail -c 2))
+IP_ADDR=10.0.$IP_OCTET_3.$IP_OCTET_4
+
+# create root ns holder
+touch .fleck-lock
+mkdir -p .fleck-ns
+flock -Fn .fleck-lock -c 'exec unshare -mrnU sh -c "echo $$ > .fleck-lock; rm .fleck-ns/netns/*; mount --bind .fleck-ns /run; exec sleep infinity"' &
+sleep .1
+
+ROOT_PID=$(cat .fleck-lock)
+
+nsenter --preserve-credentials -U -m -n -t $ROOT_PID bash -x <<ROOT_NS
+# do initial setup?
+grep fleck-br <(ip link list) > /dev/null || (
+    ip link set lo up
+    ip link add name fleck-br type bridge
+    ip link set dev fleck-br up
+)
+
+# set up namespace?
+grep ^$ENVNAME$ <(ip netns list | awk '{print \$1}') > /dev/null || (
+    ip netns add $ENVNAME > /dev/null
+    ip link add $ENVNAME-root type veth peer name $ENVNAME-inner netns $ENVNAME
+    ip link set $ENVNAME-root up
+    ip link set $ENVNAME-root master fleck-br
+    ip netns exec $ENVNAME ip link set up lo
+    ip netns exec $ENVNAME ip link set up $ENVNAME-inner
+    ip netns exec $ENVNAME ip addr add $IP_ADDR/16 dev $ENVNAME-inner
+    ip netns exec $ENVNAME ip route add 224.0.0.0/4 dev $ENVNAME-inner
+)
+
+ROOT_NS
+
+# give a shell
+exec nsenter -w --preserve-credentials -U -m -n -t $ROOT_PID bash -i -c "DEBIAN_CHROOT=$ENVNAME ip netns exec $ENVNAME unshare --map-user=1024 $SHELL"

+ 1 - 0
metashell/.gitignore

@@ -0,0 +1 @@
+/target

+ 10 - 0
metashell/Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "metashell"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+fleck = { path = "../fleck/" }

+ 3 - 0
metashell/src/main.rs

@@ -0,0 +1,3 @@
+fn main() {
+    println!("Hello, world!");
+}