From 5c239c128c04883dbed6d66f574edf8b48d11e11 Mon Sep 17 00:00:00 2001 From: Dimitri Staessens Date: Sun, 16 Aug 2026 18:55:15 +0000 Subject: lib: Replace shim IPCPs with points of attachment Removes the UDP and Ethernet shim IPCPs. The unicast and broadcast IPCPs can now directly attach to a "legacy" socket. We adopt Saltzer's Point-of-Attachment terminology, also advocated in Day's "Patterns in Network Architecture". The "poa" component manages these PoA's with one management thread, one link monitoring thread and one thread per attached point. For Ethernet PoA's the irm connect and enroll can resolve the destination IPCP or Layer name with a broadcast name query over the attached PoAs (first reply wins). UDP PoA's require a destination IP address or FQDN. attach to a local endpoint (required both server and client side): irm ipcp poa attach name a udp 10.0.0.1 irm ipcp poa attach name a udp 10.0.0.1:3435 irm ipcp poa attach name a udp [::1]:3435 irm ipcp poa attach name a eth dev eth0 irm ipcp poa attach name a eth dev eth0 ethertype 0xA000 release a PoA (refused while it carries a flow): irm ipcp poa detach name a udp 10.0.0.1:3435 irm ipcp poa detach name a eth eth0 list an IPCP's PoAs: irm ipcp poa list name a connect to a peer, by name or at an address: irm ipcp connect name b dst a irm ipcp connect name b dst a eth irm ipcp connect name b dst a eth dev eth0 irm ipcp connect name b dst a udp 10.0.0.1:3435 irm ipcp connect name b dst a udp peer.example.com:3435 disconnect by peer name, no address: irm ipcp disconnect name b dst a irm ipcp disconnect name b dst a component mgmt enroll has the same shape as connect: irm ipcp enroll name b layer lr autobind irm ipcp enroll name b layer lr autobind eth dev eth0 irm ipcp enroll name b layer lr autobind udp 10.0.0.1:3435 the IRMd config file attaches PoAs and names peers the same way: udp = [ "10.0.0.1", "10.0.0.1:3436" ] eth = [ "eth0", {dev="eth1", ethertype=0xA007} ] enrol={dst="LAN", eth={dev="eth0"}} conn=[{dst="lan3", eth={}}, {dst="lan4", udp="10.0.0.1:3435"}] Signed-off-by: Dimitri Staessens Signed-off-by: Sander Vrijders --- src/lib/poa/poa.h | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 src/lib/poa/poa.h (limited to 'src/lib/poa/poa.h') diff --git a/src/lib/poa/poa.h b/src/lib/poa/poa.h new file mode 100644 index 00000000..014986a3 --- /dev/null +++ b/src/lib/poa/poa.h @@ -0,0 +1,364 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Points of attachment (PoA) - internal API + * + * Dimitri Staessens + * Sander Vrijders + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_LIB_POA_POA_H +#define OUROBOROS_LIB_POA_POA_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define POA_MGMT_EID 0 /* reserved for the mgmt channel */ + +#define POA_FLOW_REQ 1 +#define POA_FLOW_REPLY 2 +#define POA_FLOW_UPDATE 3 +#define POA_NAME_QUERY 4 +#define POA_NAME_REPLY 5 + +#define POA_QUERY_HLEN 32 /* SHA3-256, the query hash algorithm */ + +/* Fits "udp6..", the longest display/RIB entry name. */ +#define POA_NAME_STRLEN 63 + +/* Management message; every transport frames it the same way. */ +struct poa_mgmt_msg { + uint8_t code; + uint8_t resv[3]; + uint32_t s_eid; + uint32_t d_eid; + int32_t response; + uint64_t bandwidth; + uint32_t delay; + uint32_t loss; + uint32_t ber; + uint32_t max_gap; + uint32_t timeout; + uint8_t availability; + uint8_t service; + uint16_t data_len; +} __attribute__((packed)); + +struct poa; +struct poa_flow; + +#ifdef PROC_FLOW_STATS +#define POA_STAT_BUMP(poa, field) FETCH_ADD_RELAXED(&(poa)->stat.field, 1) +#define POA_STAT_ADD(poa, field, v) FETCH_ADD_RELAXED(&(poa)->stat.field, (v)) +#define POA_STAT_SUB(poa, field, v) FETCH_SUB_RELAXED(&(poa)->stat.field, (v)) +#define POA_STAT_LOAD(poa, field) LOAD_RELAXED(&(poa)->stat.field) +#else +#define POA_STAT_BUMP(poa, field) ((void) (poa)) +#define POA_STAT_ADD(poa, field, v) ((void) (poa), (void) (v)) +#define POA_STAT_SUB(poa, field, v) ((void) (poa), (void) (v)) +#define POA_STAT_LOAD(poa, field) ((void) (poa), (size_t) 0) +#endif + +struct poa_stat { + size_t n_flows; /* gauge; the RIB reads it without poas.lock */ + size_t rx_pkts; /* packets delivered to a flow */ + size_t rx_bytes; /* payload bytes delivered */ + size_t tx_pkts; /* packets handed to the transport */ + size_t tx_bytes; /* payload bytes handed to the transport */ + size_t mgmt_rx; /* frames queued for the mgmt handler */ + size_t mgmt_tx; /* management frames sent */ + size_t bad_eid; /* no flow on the EID a peer sent */ + size_t dlv_fail; /* the rx ring above would not take it */ + size_t buf_fail; /* no buffer to receive into */ + size_t rcv_fail; /* transport read failed; the reader exits */ + size_t snd_fail; /* transport send failed */ + size_t qry_tx; /* name queries broadcast */ + size_t qry_rx; /* name queries received */ + size_t rep_tx; /* name replies sent, query matched */ + size_t rep_rx; /* name replies received */ +}; + +/* Spacing between transmit-depth samples; a depth costs a syscall. */ +#define POA_QLEN_GATE (100 * 1000) /* ns */ + + +/* Transport operations; public poa_X() dispatches to ops->poa_X. */ +struct poa_ops { + /* Parse own arm of the spec; validate; fill local and priv. */ + int (* poa_attach)(struct poa * poa, + const struct poa_spec * spec); + + void (* poa_detach)(struct poa * poa); + + int (* poa_start)(struct poa * poa); + + void (* poa_stop)(struct poa * poa); + + /* Full queue: -EAGAIN unless block; then wait, to abstime if set. */ + int (* poa_send)(struct poa * poa, + const struct poa_addr * dst, + uint32_t eid, + struct ssm_pk_buff * spb, + bool block, + const struct timespec * abstime); + + int (* poa_send_mgmt)(struct poa * poa, + const struct poa_addr * dst, + const uint8_t * buf, + size_t len); + + int (* poa_query)(const char * dst, + const struct timespec * timeo, + struct poa_addr * addr); + + uint32_t (* poa_mtu)(struct poa * poa, + const struct poa_addr * dst); + + /* Bytes queued in the transmit path of the PoA. */ + size_t (* poa_qlen)(struct poa * poa); + + /* Depth from the queue itself; NULL infers it from qlen. */ + int (* poa_qpkts)(struct poa * poa, + size_t * pkts, + size_t * byts); + + int (* poa_rib)(struct poa * poa, + char * buf, + size_t len); + + /* Identity as a spec, e.g. for poa_list. */ + void (* poa_spec)(const struct poa * poa, + struct poa_spec * spec); + + /* Same identity as spec? Caller matched poa->type already. */ + bool (* poa_has_id)(const struct poa * poa, + const struct poa_spec * spec); + + /* Carries dst? Caller matched poa->type already. */ + bool (* poa_match)(const struct poa * poa, + const struct poa_addr * dst); + + /* + * Flows ride the link this id names; NULL: no link events. + * Ids are meaningful only to the backend whose monitor + * produced them; a single backend owns the monitor. + */ + bool (* poa_link_match)(const struct poa * poa, + int id); + + /* Maximum packet lifetime in the transport, seconds. */ + time_t mpl; +}; +struct poa { + struct list_head next; + + enum poa_type type; + const struct poa_ops * ops; + void * priv; + + struct poa_addr local; /* what peers dial us on */ + + /* Display/RIB entry name for local, e.g. "udp4..". */ + char name[POA_NAME_STRLEN + 1]; + + time_t mpl; + + /* Identifies the transmit queue the flows on this PoA share. */ + int qid; + + /* Mean sent packet size (bytes), EWMA over the send path. */ + size_t avg_len; + /* Cost of one packet in the queue, in the transport's terms. */ + size_t avg_ovh; + size_t n_tx; + + /* Last queue depth read, and when, in the transport's terms. */ + size_t q_cache; + uint64_t q_time; + + /* Queued management frames, capped; poas.mgmt_mtx guards. */ + size_t n_mgmt; + +#ifdef PROC_FLOW_STATS + struct poa_stat stat; +#endif + + struct bmp * eids; + struct poa_flow ** eid_to_pf; + size_t n_eids; + + struct list_head flows; /* live flows, for repeats */ + + /* Keeps a flow and its ring alive under the receive path. */ + struct rcu_guard guard; +}; + +/* poa/poa.c is part of the dev.c translation unit. */ +int poa_init(const char * name); + +int poa_start(void); + +void poa_stop(void); + +void poa_fini(void); + +/* Also answer name queries for the layer once enrolled. */ +int poa_set_layer(const char * layer); + +/* Hash of a name this process answers queries for? */ +bool poa_has_name(const uint8_t * hash); + +int poa_flow_tx(struct poa_flow * pf, + struct ssm_pk_buff * spb, + bool block, + const struct timespec * abstime); + +size_t poa_flow_qlen(const struct poa_flow * pf); + +size_t poa_flow_qpkts(const struct poa_flow * pf); + +int poa_flow_qid(const struct poa_flow * pf); + +size_t poa_flow_mean_len(const struct poa_flow * pf); + +void poa_flow_attach(struct poa_flow * pf, + int flow_id, + struct ssm_rbuff * rx_rb); + +void poa_flow_ready(struct poa_flow * pf); + +void poa_flow_detach(struct poa_flow * pf); + +struct poa_flow * poa_flow_take_pending(int flow_id); + +/* Addresses and management messages (poa/addr.c). */ +int poa_addr_cmp(const struct poa_addr * a, + const struct poa_addr * b); + +/* Display/RIB entry name, e.g. "udp4..". */ +int poa_addr_name(const struct poa_addr * a, + char * buf, + size_t len); + +void poa_mgmt_msg_ser(struct poa_mgmt_msg * msg, + uint8_t code, + uint32_t s_eid, + uint32_t d_eid, + qosspec_t qs, + int response, + size_t data_len); + +void poa_mgmt_msg_qos(const struct poa_mgmt_msg * msg, + qosspec_t * qs); + +/* Called by the transports. */ +void poa_rx_pkt(struct poa * poa, + uint32_t eid, + struct ssm_pk_buff * spb); + +void poa_rx_mgmt(struct poa * poa, + const struct poa_addr * src, + const uint8_t * buf, + size_t len); + +/* Reserve a buffer for a received packet, with transport headroom. */ +int poa_spb_reserve(struct ssm_pk_buff ** spb, + size_t len); + +void poa_spb_release(struct ssm_pk_buff * spb); + +/* + * All flows on PoAs whose poa_link_match reports this link id go up + * or down with it. Returns the number of flows whose state changed. + */ +size_t poa_link_updown(int id, + bool up); + +/* + * Link monitor: one socket for the whole subsystem, opened by + * poa_start(). Returns -1 where the transport has no monitor. + */ +int poa_monitor_open(void); + +/* Reads one batch of link events; cancellation point. */ +void poa_monitor_read(int fd); + +/* Broadcast a mgmt frame on every PoA matching dst; # sent. */ +int poa_bcast_mgmt(const struct poa_addr * dst, + const uint8_t * buf, + size_t len); + +/* Transport op tables. */ +extern const struct poa_ops udp_poa_ops; +extern const struct poa_ops eth_poa_ops; + +/* + * Waits for a descriptor to take another packet, up to abstime. + * A NULL deadline waits indefinitely. Transports call this when + * their send reports the transmit queue full. + */ +static __inline__ int poa_wait_out(int fd, + const struct timespec * abstime) +{ + struct pollfd pfd; + struct timespec now; + long ms = -1; + bool clamped = false; + int ret; + + if (abstime != NULL) { + clock_gettime(PTHREAD_COND_CLOCK, &now); + + if (ts_diff_ns(abstime, &now) <= 0) + return -ETIMEDOUT; + + ms = ts_diff_ms(abstime, &now) + 1; /* sub-ms must wait */ + if (ms > INT_MAX) { /* poll takes an int */ + ms = INT_MAX; + clamped = true; + } + } + + pfd.fd = fd; + pfd.events = POLLOUT; + pfd.revents = 0; + + ret = poll(&pfd, 1, (int) ms); + if (ret < 0) + return errno == EINTR ? 0 : -EIO; + + if (ret == 0) + return clamped ? 0 : -ETIMEDOUT; /* clamped: retry */ + + return 0; +} + +#endif /* OUROBOROS_LIB_POA_POA_H */ -- cgit v1.2.3