From 879193e573be55285d4412f2b87c57186ddede37 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 13 May 2024 14:11:56 +0200 Subject: [PATCH 001/112] allow running multiple transfers back-to-back split hercules into a server (C) and monitor (go) process --- Makefile | 7 + bitset.h | 16 +- bpf_prgm/redirect_userspace.c | 12 + cutils.go | 76 +- frame_queue.h | 14 +- go.mod | 2 +- hercules.c | 2639 +++++++++++++++++++++------------ hercules.go | 84 ++ hercules.h | 23 +- network.go | 56 - packet.h | 7 + pathinterface.go | 59 - pathmanager.go | 137 -- pathpicker.go | 201 --- pathstodestination.go | 230 --- send_queue.c | 2 + send_queue.h | 3 +- utils.h | 25 +- 18 files changed, 1910 insertions(+), 1683 deletions(-) delete mode 100644 network.go delete mode 100644 pathinterface.go delete mode 100644 pathmanager.go delete mode 100644 pathpicker.go delete mode 100644 pathstodestination.go diff --git a/Makefile b/Makefile index 55efbb1..4dd46e7 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,13 @@ hercules: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace. startupVersion=$$(git rev-parse --abbrev-ref HEAD)"-untagged-"$$(git describe --tags --dirty --always); \ docker exec hercules-builder go build -ldflags "-X main.startupVersion=$${startupVersion}" +herculesC: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf_prgm/pass.o bpf/src/libbpf.a + @# update modification dates in assembly, so that the new version gets loaded + @sed -i -e "s/\(load bpf_prgm_pass\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/pass.c | head -c 32)/g" bpf_prgms.s + @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s + docker exec hercules-builder cc -g -O0 -std=gnu99 -o runHercules -DDEBUG -D_GNU_SOURCE *.c bpf_prgms.s -Lbpf/src -lbpf -lm -lelf -pthread -lz + docker exec hercules-builder sh -c "cd monitor; go build -o ../runMonitor ." + bpf_prgm/%.ll: bpf_prgm/%.c builder docker exec hercules-builder clang -S -target bpf -D __BPF_TRACING__ -I. -Wall -O2 -emit-llvm -c -g -o $@ $< diff --git a/bitset.h b/bitset.h index 7966d88..f44e870 100644 --- a/bitset.h +++ b/bitset.h @@ -39,7 +39,7 @@ void bitset__create(struct bitset *s, u32 num); void bitset__destroy(struct bitset *s); // Returns true iff the bit at index i in bitmap is set -inline bool bitset__check(struct bitset *s, u32 i) +static inline bool bitset__check(struct bitset *s, u32 i) { assert(i < s->num); return (s->bitmap[i / HERCULES_BITSET_WORD_BITS]) & (1u << i % HERCULES_BITSET_WORD_BITS); @@ -47,7 +47,7 @@ inline bool bitset__check(struct bitset *s, u32 i) // set bit at index i in bitmap. // Returns the previous state of the bit. -inline bool bitset__set_mt_safe(struct bitset *s, u32 i) +static inline bool bitset__set_mt_safe(struct bitset *s, u32 i) { pthread_spin_lock(&s->lock); libbpf_smp_rmb(); @@ -72,7 +72,7 @@ inline bool bitset__set_mt_safe(struct bitset *s, u32 i) // set bit at index i in bitmap. // This function is not thread-safe. -inline bool bitset__set(struct bitset *s, u32 i) +static inline bool bitset__set(struct bitset *s, u32 i) { const bool prev = bitset__check(s, i); s->bitmap[i / HERCULES_BITSET_WORD_BITS] |= (1 << i % HERCULES_BITSET_WORD_BITS); @@ -88,7 +88,7 @@ inline bool bitset__set(struct bitset *s, u32 i) // unset bit at index i in bitmap. // Returns the previous state of the bit. -inline bool bitset__unset(struct bitset *s, u32 i) +static inline bool bitset__unset(struct bitset *s, u32 i) { const bool prev = bitset__check(s, i); s->bitmap[i / HERCULES_BITSET_WORD_BITS] &= ~(1u << i % HERCULES_BITSET_WORD_BITS); @@ -100,7 +100,7 @@ inline bool bitset__unset(struct bitset *s, u32 i) // Reset the bitmap // Unsets all entries in bitmap and reset the number of elements in the set -inline void bitset__reset(struct bitset *s) +static inline void bitset__reset(struct bitset *s) { // due to rounding, need to use the same formula as for allocation memset(s->bitmap, 0, @@ -111,7 +111,7 @@ inline void bitset__reset(struct bitset *s) // Find next entry in the set. // Returns lowest index i greater or equal than pos such that bit i is set, or // s->num if no such index exists. -inline u32 bitset__scan(struct bitset *s, u32 pos) +static inline u32 bitset__scan(struct bitset *s, u32 pos) { // TODO: profile the entire application and rewrite this function to use bitscan ops for(u32 i = pos; i < s->max_set; ++i) { @@ -125,7 +125,7 @@ inline u32 bitset__scan(struct bitset *s, u32 pos) // Find next entry NOT in the set. // Returns lowest index i greater or equal than pos such that bit i is NOT set, // or s->num if no such index exists. -inline u32 bitset__scan_neg(struct bitset *s, u32 pos) +static inline u32 bitset__scan_neg(struct bitset *s, u32 pos) { for(u32 i = pos; i < s->num; ++i) { if(!bitset__check(s, i)) { @@ -138,7 +138,7 @@ inline u32 bitset__scan_neg(struct bitset *s, u32 pos) // Find nth entry NOT in the set. // Returns nth lowest index i greater or equal than pos such that bit i is NOT set, // or s->num if no such index exists. -inline u32 bitset__scan_neg_n(struct bitset *s, u32 pos, u32 n) +static inline u32 bitset__scan_neg_n(struct bitset *s, u32 pos, u32 n) { for(u32 i = pos; i < s->num; ++i) { if(!bitset__check(s, i)) { diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index b6fa196..ab60569 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -2,6 +2,7 @@ // Copyright(c) 2017 - 2018 Intel Corporation. // Copyright(c) 2019 ETH Zurich. +#include #include #include #include @@ -131,6 +132,17 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) } offset += sizeof(struct udphdr); + const __u32 *idx = (__u32 *)(data+offset); + if (idx + 1 > (__u32 *)data_end){ + // bounds check for verifier + return XDP_PASS; + } + if (*idx == UINT_MAX){ + // Pass control packets so they end up in the control socket + // instead of an XDP socket + return XDP_PASS; + } + // write the payload offset to the first word, so that the user space program can continue from there. *(__u32 *)data = offset; diff --git a/cutils.go b/cutils.go index d40ffae..523adc0 100644 --- a/cutils.go +++ b/cutils.go @@ -14,7 +14,7 @@ package main -// #cgo CFLAGS: -O3 -Wall -DNDEBUG -D_GNU_SOURCE +// #cgo CFLAGS: -O1 -Wall -DDEBUG -D_GNU_SOURCE // #cgo LDFLAGS: ${SRCDIR}/bpf/src/libbpf.a -lm -lelf -pthread -lz // #pragma GCC diagnostic ignored "-Wunused-variable" // Hide warning in cgo-gcc-prolog // #include "hercules.h" @@ -38,6 +38,7 @@ import ( log "github.com/inconshreveable/log15" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" "github.com/scionproto/scion/private/topology" "github.com/vishvananda/netlink" ) @@ -57,6 +58,10 @@ type HerculesSession struct { session *C.struct_hercules_session } +type HerculesServer struct { + server *C.struct_hercules_server +} + type pathStats struct { statsBuffer *C.struct_path_stats } @@ -70,13 +75,27 @@ func herculesInit(interfaces []*net.Interface, local *snet.UDPAddr, queue int, M for _, iface := range interfaces { ifIndices = append(ifIndices, iface.Index) } - ifacesC := toCIntArray(ifIndices) + fmt.Println("init: ifindices", ifIndices); + // ifacesC := toCIntArray(ifIndices) herculesSession := &HerculesSession{ - session: C.hercules_init(&ifacesC[0], C.int(len(interfaces)), toCAddr(local), C.int(queue), C.int(MTU)), + // session: C.hercules_init(&ifacesC[0], C.int(len(interfaces)), toCAddr(local), C.int(queue), C.int(MTU)), } return herculesSession } +func herculesInitServer(interfaces []*net.Interface, local *snet.UDPAddr, queue int, MTU int) *HerculesServer { + var ifIndices []int + for _, iface := range interfaces { + ifIndices = append(ifIndices, iface.Index) + } + ifacesC := toCIntArray(ifIndices) + // fmt.Println("init: ifindices", ifIndices); + herculesServer := &HerculesServer{ + server: C.hercules_init_server(&ifacesC[0], C.int(len(interfaces)), toCAddr(local), C.int(queue), C.int(MTU)), + } + return herculesServer +} + func herculesTx(session *HerculesSession, filename string, offset int, length int, destinations []*Destination, pm *PathManager, maxRateLimit int, enablePCC bool, xdpMode int, numThreads int) herculesStats { cFilename := C.CString(filename) @@ -102,6 +121,17 @@ func herculesTx(session *HerculesSession, filename string, offset int, length in C.int(numThreads), ), nil) } +func herculesMainServer(server *HerculesServer, filename string, offset int, length int, destinations []*Destination, + pm *PathManager, maxRateLimit int, enablePCC bool, xdpMode int, numThreads int) { + cFilename := C.CString(filename) + defer C.free(unsafe.Pointer(cFilename)) + + cDests := make([]C.struct_hercules_app_addr, len(destinations)) + for d, dest := range destinations { + cDests[d] = toCAddr(dest.hostAddr) + } + C.hercules_main_sender(server.server, C.int(xdpMode), &cDests[0], &pm.cStruct.pathsPerDest[0], C.int(len(destinations)), &pm.cStruct.numPathsPerDst[0], pm.cStruct.maxNumPathsPerDst, C.int(maxRateLimit), C.bool(enablePCC)) +} func herculesRx(session *HerculesSession, filename string, xdpMode int, numThreads int, configureQueues bool, acceptTimeout int, isPCCBenchmark bool) herculesStats { cFilename := C.CString(filename) @@ -112,8 +142,13 @@ func herculesRx(session *HerculesSession, filename string, xdpMode int, numThrea ) } +func herculesMain(server *HerculesServer, filename string, xdpMode int, numThreads int, configureQueues bool, acceptTimeout int) { + // XXX ignoring arguments + C.hercules_main(server.server, C.int(xdpMode)) +} + func herculesClose(session *HerculesSession) { - C.hercules_close(session.session) + // C.hercules_close(session.session) } func makePerPathStatsBuffer(numPaths int) *pathStats { @@ -228,6 +263,8 @@ func getReplyPathHeader(buf []byte) (*HerculesPathHeader, error) { sourcePkt.Path = replyPath } + sourcePkt.Path = path.Empty{} + udpPkt, ok := sourcePkt.Payload.(snet.UDPPayload) if !ok { return nil, errors.New("error decoding SCION/UDP") @@ -295,6 +332,18 @@ func toCPath(iface *net.Interface, from *HerculesPathHeader, to *C.struct_hercul to.enabled = C.atomic_bool(enabled) } +func SerializePath(from *HerculesPathHeader) []byte { + out := make([]byte, 0, 1500); + fmt.Println(out) + out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) + fmt.Println(out) + out = append(out, from.Header...) + fmt.Println(out) + out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) + fmt.Println(out) + return out; +} + func toCAddr(addr *snet.UDPAddr) C.struct_hercules_app_addr { out := C.struct_hercules_app_addr{} bufIA := toCIA(addr.IA) @@ -305,6 +354,7 @@ func toCAddr(addr *snet.UDPAddr) C.struct_hercules_app_addr { C.memcpy(unsafe.Pointer(&out.ia), unsafe.Pointer(&bufIA), C.sizeof_ia) C.memcpy(unsafe.Pointer(&out.ip), unsafe.Pointer(&bufIP[0]), 4) C.memcpy(unsafe.Pointer(&out.port), unsafe.Pointer(&bufPort[0]), 2) + fmt.Println(out) return out } @@ -574,6 +624,24 @@ func (pm *PathManager) pushPaths(session *HerculesSession) { //C.push_hercules_tx_paths(herculesSession.session) not needed atm } +func (pm *PathManager) pushPathsServer(server *HerculesServer) { + C.acquire_path_lock() + defer C.free_path_lock() + syncTime := time.Now() + + // prepare and copy header to C + for d, dst := range pm.dsts { + if pm.syncTime.After(dst.modifyTime) { + continue + } + + dst.pushPaths(d, d*pm.numPathSlotsPerDst) + } + + pm.syncTime = syncTime + //C.push_hercules_tx_paths(herculesSession.session) not needed atm +} + // TODO move back to pathstodestination.go func (ptd *PathsToDestination) pushPaths(pwdIdx, firstSlot int) { n := 0 diff --git a/frame_queue.h b/frame_queue.h index 9e07c61..70cc3fa 100644 --- a/frame_queue.h +++ b/frame_queue.h @@ -26,7 +26,7 @@ struct frame_queue { u16 index_mask; }; -inline int frame_queue__init(struct frame_queue *fq, u16 size) +static inline int frame_queue__init(struct frame_queue *fq, u16 size) { if((size == 0) || ((size & (size - 1)) != 0)) { return EINVAL; // size is zero or not a power of two @@ -41,32 +41,32 @@ inline int frame_queue__init(struct frame_queue *fq, u16 size) return EXIT_SUCCESS; } -inline u16 frame_queue__prod_reserve(struct frame_queue *fq, u16 num) +static inline u16 frame_queue__prod_reserve(struct frame_queue *fq, u16 num) { return umin16(atomic_load(&fq->cons) - fq->prod, num); } -inline void frame_queue__prod_fill(struct frame_queue *fq, u16 offset, u64 addr) +static inline void frame_queue__prod_fill(struct frame_queue *fq, u16 offset, u64 addr) { fq->addrs[(fq->prod + offset) & fq->index_mask] = addr >> XSK_UMEM__DEFAULT_FRAME_SHIFT; } -inline void frame_queue__push(struct frame_queue *fq, u16 num) +static inline void frame_queue__push(struct frame_queue *fq, u16 num) { atomic_fetch_add(&fq->prod, num); } -inline u16 frame_queue__cons_reserve(struct frame_queue *fq, u16 num) +static inline u16 frame_queue__cons_reserve(struct frame_queue *fq, u16 num) { return umin16(atomic_load(&fq->prod) - fq->cons + fq->size, num); } -inline u64 frame_queue__cons_fetch(struct frame_queue *fq, u16 offset) +static inline u64 frame_queue__cons_fetch(struct frame_queue *fq, u16 offset) { return fq->addrs[(fq->cons + offset) & fq->index_mask] << XSK_UMEM__DEFAULT_FRAME_SHIFT; } -inline void frame_queue__pop(struct frame_queue *fq, u16 num) +static inline void frame_queue__pop(struct frame_queue *fq, u16 num) { atomic_fetch_add(&fq->cons, num); } diff --git a/go.mod b/go.mod index f926032..ad71a2b 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/inconshreveable/log15 v2.16.0+incompatible github.com/scionproto/scion v0.10.0 github.com/vishvananda/netlink v1.2.1-beta.2 - go.uber.org/atomic v1.11.0 ) require ( @@ -40,6 +39,7 @@ require ( github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/vishvananda/netns v0.0.4 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.18.0 // indirect diff --git a/hercules.c b/hercules.c index efea8df..b896bae 100644 --- a/hercules.c +++ b/hercules.c @@ -31,11 +31,12 @@ #include #include #include +#include #include #include #include #include -#include +#include "linux/bpf_util.h" #include "bpf/src/libbpf.h" #include "bpf/src/bpf.h" @@ -71,6 +72,7 @@ static const u64 tx_handshake_retry_after = 1e9; static const u64 tx_handshake_timeout = 5e9; #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path +int usock; // exported from hercules.go extern int HerculesGetReplyPath(const char *packetPtr, int length, struct hercules_path *reply_path); @@ -117,18 +119,50 @@ struct hercules_config { int ether_size; }; -struct hercules_session { - struct hercules_config config; - struct receiver_state *rx_state; - struct sender_state *tx_state; - bool is_running; - bool is_closed; +enum session_state { + SESSION_STATE_NONE, //< no session + SESSION_STATE_PENDING, //< Need to send HS and repeat until TO + SESSION_STATE_CREATED, //< + SESSION_STATE_READY, //< Receiver setup ready, need to send HS and CTS + SESSION_STATE_WAIT_HS, //< Waiting for HS + SESSION_STATE_WAIT_CTS, //< Waiting for CTS + SESSION_STATE_RUNNING, //< Transfer in progress + SESSION_STATE_DONE, //< Transfer done (or cancelled with error) +}; - // State for stat dump - size_t rx_npkts; - size_t tx_npkts; +struct hercules_session { + struct receiver_state *rx_state; + struct sender_state *tx_state; + enum session_state state; + bool is_running; + bool is_closed; + struct send_queue *send_queue; + + // State for stat dump + /* size_t rx_npkts; */ + /* size_t tx_npkts; */ +}; +struct hercules_server { + struct hercules_config config; int control_sockfd; + int *ifindices; + struct hercules_app_addr local_addr; + int queue; + int mtu; + struct hercules_session *session_tx; //Current TX session + struct hercules_session *session_rx; //Current RX session + struct hercules_app_addr *destinations; + struct hercules_path *paths_per_dest; + int num_dests; + int *num_paths; + int max_paths; + int rate_limit; + bool enable_pcc; + int n_pending; + char pending[5][20]; + struct xsk_umem_info *umem; + struct xsk_socket_info *xsk; int num_ifaces; struct hercules_interface ifaces[]; }; @@ -186,7 +220,6 @@ struct sender_state_per_receiver { struct sender_state { struct hercules_session *session; - struct send_queue *send_queue; // State for transmit rate control size_t tx_npkts_queued; @@ -267,16 +300,16 @@ static void set_rx_sample(struct receiver_state *rx_state, int ifid, const char memcpy(rx_state->rx_sample_buf, pkt, len); } -static void remove_xdp_program(struct hercules_session *session) +static void remove_xdp_program(struct hercules_server *server) { - for(int i = 0; i < session->num_ifaces; i++) { + for(int i = 0; i < server->num_ifaces; i++) { u32 curr_prog_id = 0; - if(bpf_get_link_xdp_id(session->ifaces[i].ifid, &curr_prog_id, session->config.xdp_flags)) { + if(bpf_get_link_xdp_id(server->ifaces[i].ifid, &curr_prog_id, server->config.xdp_flags)) { printf("bpf_get_link_xdp_id failed\n"); exit(EXIT_FAILURE); } - if(session->ifaces[i].prog_id == curr_prog_id) - bpf_set_link_xdp_fd(session->ifaces[i].ifid, -1, session->config.xdp_flags); + if(server->ifaces[i].prog_id == curr_prog_id) + bpf_set_link_xdp_fd(server->ifaces[i].ifid, -1, server->config.xdp_flags); else if(!curr_prog_id) printf("couldn't find a prog id on a given interface\n"); else @@ -284,14 +317,14 @@ static void remove_xdp_program(struct hercules_session *session) } } -static int unconfigure_rx_queues(struct hercules_session *session); +static int unconfigure_rx_queues(struct hercules_server *server); -static void __exit_with_error(struct hercules_session *session, int error, const char *file, const char *func, int line) +static void __exit_with_error(struct hercules_server *server, int error, const char *file, const char *func, int line) { fprintf(stderr, "%s:%s:%i: errno: %d/\"%s\"\n", file, func, line, error, strerror(error)); - if(session) { - remove_xdp_program(session); - unconfigure_rx_queues(session); + if(server) { + remove_xdp_program(server); + unconfigure_rx_queues(server); } exit(EXIT_FAILURE); } @@ -305,11 +338,11 @@ static void close_xsk(struct xsk_socket_info *xsk) free(xsk); } -static inline struct hercules_interface *get_interface_by_id(struct hercules_session *session, int ifid) +static inline struct hercules_interface *get_interface_by_id(struct hercules_server *server, int ifid) { - for(int i = 0; i < session->num_ifaces; i++) { - if(session->ifaces[i].ifid == ifid) { - return &session->ifaces[i]; + for(int i = 0; i < server->num_ifaces; i++) { + if(server->ifaces[i].ifid == ifid) { + return &server->ifaces[i]; } } return NULL; @@ -421,7 +454,7 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec // check SCION-UDP checksum if set. // sets scionaddrh_o to SCION address header, if provided // return rbudp-packet (i.e. SCION/UDP packet payload) -static const char *parse_pkt(const struct hercules_session *session, const char *pkt, size_t length, bool check, +static const char *parse_pkt(const struct hercules_server *server, const char *pkt, size_t length, bool check, const struct scionaddrhdr_ipv4 **scionaddrh_o, const struct udphdr **udphdr_o) { // Parse Ethernet frame @@ -443,11 +476,11 @@ static const char *parse_pkt(const struct hercules_session *session, const char } const struct iphdr *iph = (const struct iphdr *)(pkt + offset); if(iph->protocol != IPPROTO_UDP) { - debug_printf("not UDP: %u, %zu", iph->protocol, offset); + /* debug_printf("not UDP: %u, %zu", iph->protocol, offset); */ return NULL; } - if(iph->daddr != session->config.local_addr.ip) { - debug_printf("not addressed to us (IP overlay)"); + if(iph->daddr != server->config.local_addr.ip) { + /* debug_printf("not addressed to us (IP overlay)"); */ return NULL; } offset += iph->ihl * 4u; // IHL is header length, in number of 32-bit words. @@ -510,13 +543,13 @@ static const char *parse_pkt(const struct hercules_session *session, const char } const struct scionaddrhdr_ipv4 *scionaddrh = (const struct scionaddrhdr_ipv4 *)(pkt + offset + sizeof(struct scionhdr)); - if(scionaddrh->dst_ia != session->config.local_addr.ia) { + if(scionaddrh->dst_ia != server->config.local_addr.ia) { debug_printf("not addressed to us (IA)"); return NULL; } - if(scionaddrh->dst_ip != session->config.local_addr.ip) { + if(scionaddrh->dst_ip != server->config.local_addr.ip) { debug_printf("not addressed to us (IP in SCION hdr), expect %x, have %x, remote %x", - session->config.local_addr.ip, scionaddrh->dst_ip, session->tx_state->receiver[0].addr.ip); + server->config.local_addr.ip, scionaddrh->dst_ip, 0xFF); return NULL; } @@ -529,7 +562,7 @@ static const char *parse_pkt(const struct hercules_session *session, const char } const struct udphdr *l4udph = (const struct udphdr *)(pkt + offset); - if(l4udph->dest != session->config.local_addr.port) { + if(l4udph->dest != server->config.local_addr.port) { debug_printf("not addressed to us (L4 UDP port): %u", ntohs(l4udph->dest)); return NULL; } @@ -544,61 +577,69 @@ static const char *parse_pkt(const struct hercules_session *session, const char return parse_pkt_fast_path(pkt, length, check, offset); } -static bool recv_rbudp_control_pkt(struct hercules_session *session, char *buf, size_t buflen, - const char **payload, int *payloadlen, const struct scionaddrhdr_ipv4 **scionaddrh, - const struct udphdr **udphdr, u8 *path, int *ifid) -{ - struct sockaddr_ll addr; - socklen_t addr_size = sizeof(addr); - ssize_t len = recvfrom(session->control_sockfd, buf, buflen, 0, (struct sockaddr *) &addr, - &addr_size); // XXX set timeout - if(len == -1) { - if(errno == EAGAIN || errno == EINTR) { - return false; - } - exit_with_error(session, errno); // XXX: are there situations where we want to try again? - } - - if(get_interface_by_id(session, addr.sll_ifindex) == NULL) { - return false; // wrong interface, ignore packet - } - - const char *rbudp_pkt = parse_pkt(session, buf, len, true, scionaddrh, udphdr); - if(rbudp_pkt == NULL) { - return false; - } - - const size_t rbudp_len = len - (rbudp_pkt - buf); - if(rbudp_len < sizeof(u32)) { - return false; - } - u32 chunk_idx; - memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); - if(chunk_idx != UINT_MAX) { - return false; - } - - *payload = rbudp_pkt + rbudp_headerlen; - *payloadlen = rbudp_len - rbudp_headerlen; - u32 path_idx; - memcpy(&path_idx, rbudp_pkt + sizeof(u32), sizeof(*path)); - if(path != NULL) { - *path = path_idx; - } - if(ifid != NULL) { - *ifid = addr.sll_ifindex; - } - - atomic_fetch_add(&session->rx_npkts, 1); - if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { - atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); - } - return true; -} +/* static bool recv_rbudp_control_pkt(struct hercules_session *session, char *buf, size_t buflen, */ +/* const char **payload, int *payloadlen, const struct scionaddrhdr_ipv4 **scionaddrh, */ +/* const struct udphdr **udphdr, u8 *path, int *ifid) */ +/* { */ +/* /\* debug_printf("receive rbudp control pkt..."); *\/ */ +/* struct sockaddr_ll addr; */ +/* socklen_t addr_size = sizeof(addr); */ +/* ssize_t len = recvfrom(session->control_sockfd, buf, buflen, 0, (struct sockaddr *) &addr, */ +/* &addr_size); // XXX set timeout */ +/* if(len == -1) { */ +/* if(errno == EAGAIN || errno == EINTR) { */ +/* /\* debug_printf("eagain/intr"); *\/ */ +/* return false; */ +/* } */ +/* exit_with_error(session, errno); // XXX: are there situations where we want to try again? */ +/* } */ + +/* if(get_interface_by_id(session, addr.sll_ifindex) == NULL) { */ +/* return false; // wrong interface, ignore packet */ +/* } */ + +/* const char *rbudp_pkt = parse_pkt(session, buf, len, true, scionaddrh, udphdr); */ +/* if(rbudp_pkt == NULL) { */ +/* debug_printf("Ignoring, failed parse"); */ +/* return false; */ +/* } */ +/* print_rbudp_pkt(rbudp_pkt, true); */ + +/* const size_t rbudp_len = len - (rbudp_pkt - buf); */ +/* if(rbudp_len < sizeof(u32)) { */ +/* debug_printf("Ignoring, length too short"); */ +/* return false; */ +/* } */ +/* u32 chunk_idx; */ +/* memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); */ +/* if(chunk_idx != UINT_MAX) { */ +/* debug_printf("Ignoring, chunk_idx != UINT_MAX"); */ +/* return false; */ +/* } */ + +/* *payload = rbudp_pkt + rbudp_headerlen; */ +/* *payloadlen = rbudp_len - rbudp_headerlen; */ +/* u32 path_idx; */ +/* memcpy(&path_idx, rbudp_pkt + sizeof(u32), sizeof(*path)); */ +/* if(path != NULL) { */ +/* *path = path_idx; */ +/* debug_printf("Path idx %u", path_idx); */ +/* } */ +/* if(ifid != NULL) { */ +/* *ifid = addr.sll_ifindex; */ +/* } */ + +/* atomic_fetch_add(&session->rx_npkts, 1); */ +/* if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { */ +/* atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); */ +/* } */ +/* return true; */ +/* } */ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { if(length < rbudp_headerlen + rx_state->chunklen) { + debug_printf("packet too short"); return false; } @@ -663,51 +704,80 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p } -static struct xsk_umem_info *xsk_configure_umem(struct hercules_session *session, u32 ifidx, void *buffer, u64 size) +static struct xsk_umem_info *xsk_configure_umem(struct hercules_server *server, u32 ifidx, void *buffer, u64 size) { + debug_printf("xsk_configure_umem"); struct xsk_umem_info *umem; int ret; umem = calloc(1, sizeof(*umem)); if(!umem) - exit_with_error(session, errno); + exit_with_error(server, errno); ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, NULL); if(ret) - exit_with_error(session, -ret); + exit_with_error(server, -ret); umem->buffer = buffer; - umem->iface = &session->ifaces[ifidx]; + umem->iface = &server->ifaces[ifidx]; // The number of slots in the umem->available_frames queue needs to be larger than the number of frames in the loop, // pushed in submit_initial_tx_frames() (assumption in pop_completion_ring() and handle_send_queue_unit()) ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS); if(ret) - exit_with_error(session, ret); + exit_with_error(server, ret); pthread_spin_init(&umem->lock, 0); return umem; } -static void kick_tx(struct hercules_session *session, struct xsk_socket_info *xsk) +static struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, u32 ifidx, void *buffer, u64 size) { + debug_printf("xsk_configure_umem_server"); + struct xsk_umem_info *umem; int ret; + + umem = calloc(1, sizeof(*umem)); + if(!umem) + exit_with_error(NULL, errno); + + ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, + NULL); + if(ret) + exit_with_error(NULL, -ret); + + umem->buffer = buffer; + umem->iface = &server->ifaces[ifidx]; + // The number of slots in the umem->available_frames queue needs to be larger than the number of frames in the loop, + // pushed in submit_initial_tx_frames() (assumption in pop_completion_ring() and handle_send_queue_unit()) + ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS); + if(ret) + exit_with_error(NULL, ret); + pthread_spin_init(&umem->lock, 0); + return umem; +} + +static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) +{ + int ret; + /* debug_printf("kicking tx"); */ do { ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); } while(ret < 0 && errno == EAGAIN); if(ret < 0 && errno != ENOBUFS && errno != EBUSY) { - exit_with_error(session, errno); + exit_with_error(server, errno); } + /* debug_printf("kicked"); */ } -static void kick_all_tx(struct hercules_session *session, struct hercules_interface *iface) +static void kick_all_tx(struct hercules_server *server, struct hercules_interface *iface) { for(u32 s = 0; s < iface->num_sockets; s++) { - kick_tx(session, iface->xsks[s]); + kick_tx(server, iface->xsks[s]); } } -static void submit_initial_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem) +static void submit_initial_rx_frames(struct hercules_server *server, struct xsk_umem_info *umem) { int initial_kernel_rx_frame_count = XSK_RING_PROD__DEFAULT_NUM_DESCS - BATCH_SIZE; u32 idx; @@ -715,14 +785,14 @@ static void submit_initial_rx_frames(struct hercules_session *session, struct xs initial_kernel_rx_frame_count, &idx); if(ret != initial_kernel_rx_frame_count) - exit_with_error(session, -ret); + exit_with_error(server, -ret); for(int i = 0; i < initial_kernel_rx_frame_count; i++) *xsk_ring_prod__fill_addr(&umem->fq, idx++) = (XSK_RING_PROD__DEFAULT_NUM_DESCS + i) * XSK_UMEM__DEFAULT_FRAME_SIZE; xsk_ring_prod__submit(&umem->fq, initial_kernel_rx_frame_count); } -static void submit_initial_tx_frames(struct hercules_session *session, struct xsk_umem_info *umem) +static void submit_initial_tx_frames(struct hercules_server *server, struct xsk_umem_info *umem) { // This number needs to be smaller than the number of slots in the umem->available_frames queue (initialized in // xsk_configure_umem(); assumption in pop_completion_ring() and handle_send_queue_unit()) @@ -730,7 +800,7 @@ static void submit_initial_tx_frames(struct hercules_session *session, struct xs int avail = frame_queue__prod_reserve(&umem->available_frames, initial_tx_frames); if(initial_tx_frames > avail) { debug_printf("trying to push %d initial frames, but only %d slots available", initial_tx_frames, avail); - exit_with_error(session, EINVAL); + exit_with_error(server, EINVAL); } for(int i = 0; i < avail; i++) { frame_queue__prod_fill(&umem->available_frames, i, i * XSK_UMEM__DEFAULT_FRAME_SIZE); @@ -738,7 +808,7 @@ static void submit_initial_tx_frames(struct hercules_session *session, struct xs frame_queue__push(&umem->available_frames, avail); } -static struct xsk_socket_info *xsk_configure_socket(struct hercules_session *session, int ifidx, +static struct xsk_socket_info *xsk_configure_socket(struct hercules_server *server, int ifidx, struct xsk_umem_info *umem, int queue, int libbpf_flags, int bind_flags) { @@ -746,42 +816,42 @@ static struct xsk_socket_info *xsk_configure_socket(struct hercules_session *ses struct xsk_socket_info *xsk; int ret; - if(session->ifaces[ifidx].ifid != umem->iface->ifid) { - debug_printf("cannot configure XSK on interface %d with queue on interface %d", session->ifaces[ifidx].ifid, umem->iface->ifid); - exit_with_error(session, EINVAL); + if(server->ifaces[ifidx].ifid != umem->iface->ifid) { + debug_printf("cannot configure XSK on interface %d with queue on interface %d", server->ifaces[ifidx].ifid, umem->iface->ifid); + exit_with_error(server, EINVAL); } xsk = calloc(1, sizeof(*xsk)); if(!xsk) - exit_with_error(session, errno); + exit_with_error(server, errno); xsk->umem = umem; cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; cfg.libbpf_flags = libbpf_flags; - cfg.xdp_flags = session->config.xdp_flags; + cfg.xdp_flags = server->config.xdp_flags; cfg.bind_flags = bind_flags; - ret = xsk_socket__create_shared(&xsk->xsk, session->ifaces[ifidx].ifname, queue, umem->umem, &xsk->rx, &xsk->tx, + ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[ifidx].ifname, queue, umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); if(ret) - exit_with_error(session, -ret); + exit_with_error(server, -ret); - ret = bpf_get_link_xdp_id(session->ifaces[ifidx].ifid, &session->ifaces[ifidx].prog_id, session->config.xdp_flags); + ret = bpf_get_link_xdp_id(server->ifaces[ifidx].ifid, &server->ifaces[ifidx].prog_id, server->config.xdp_flags); if(ret) - exit_with_error(session, -ret); + exit_with_error(server, -ret); return xsk; } -static struct xsk_umem_info *create_umem(struct hercules_session *session, u32 ifidx) +static struct xsk_umem_info *create_umem(struct hercules_server *server, u32 ifidx) { void *bufs; int ret = posix_memalign(&bufs, getpagesize(), /* PAGE_SIZE aligned */ NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); if(ret) - exit_with_error(session, ret); + exit_with_error(server, ret); struct xsk_umem_info *umem; - umem = xsk_configure_umem(session, ifidx, bufs, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + umem = xsk_configure_umem(server, ifidx, bufs, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); return umem; } @@ -793,7 +863,7 @@ static void destroy_umem(struct xsk_umem_info *umem) } // Pop entries from completion ring and store them in umem->available_frames. -static void pop_completion_ring(struct hercules_session *session, struct xsk_umem_info *umem) +static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_info *umem) { u32 idx; size_t entries = xsk_ring_cons__peek(&umem->cq, SIZE_MAX, &idx); @@ -801,21 +871,21 @@ static void pop_completion_ring(struct hercules_session *session, struct xsk_ume u16 num = frame_queue__prod_reserve(&umem->available_frames, entries); if(num < entries) { // there are less frames in the loop than the number of slots in frame_queue debug_printf("trying to push %ld frames, only got %d slots in frame_queue", entries, num); - exit_with_error(session, EINVAL); + exit_with_error(server, EINVAL); } for(u16 i = 0; i < num; i++) { frame_queue__prod_fill(&umem->available_frames, i, *xsk_ring_cons__comp_addr(&umem->cq, idx + i)); } frame_queue__push(&umem->available_frames, num); xsk_ring_cons__release(&umem->cq, entries); - atomic_fetch_add(&session->tx_npkts, entries); + /* atomic_fetch_add(&server->tx_npkts, entries); */ } } -static inline void pop_completion_rings(struct hercules_session *session) +static inline void pop_completion_rings(struct hercules_server *server) { - for(int i = 0; i < session->num_ifaces; i++) { - pop_completion_ring(session, session->ifaces[i].umem); + for(int i = 0; i < server->num_ifaces; i++) { + pop_completion_ring(server, server->ifaces[i].umem); } } @@ -877,7 +947,7 @@ static bool has_more_nacks(sequence_number curr, struct bitset *seqs) return end < seqs->num; } -static void send_eth_frame(struct hercules_session *session, const struct hercules_path *path, void *buf) +static void send_eth_frame(struct hercules_server *server, const struct hercules_path *path, void *buf) { struct sockaddr_ll addr; // Index of the network device @@ -887,9 +957,9 @@ static void send_eth_frame(struct hercules_session *session, const struct hercul // Destination MAC; extracted from ethernet header memcpy(addr.sll_addr, buf, ETH_ALEN); - ssize_t ret = sendto(session->control_sockfd, buf, path->framelen, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_ll)); + ssize_t ret = sendto(server->control_sockfd, buf, path->framelen, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_ll)); if(ret == -1) { - exit_with_error(session, errno); + exit_with_error(server, errno); } } @@ -1032,7 +1102,7 @@ static void pcc_monitor(struct sender_state *tx_state) u64 now = get_nsecs(); if(cc_state->mi_end == 0) { // TODO should not be necessary fprintf(stderr, "Assumption violated.\n"); - exit_with_error(tx_state->session, EINVAL); + exit_with_error(NULL, EINVAL); cc_state->mi_end = now; } u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI @@ -1114,88 +1184,88 @@ bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct s return updated; } -static void tx_recv_control_messages(struct sender_state *tx_state) -{ - struct timeval to = {.tv_sec = 0, .tv_usec = 100}; - setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); - char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; - - // packet receive timeouts - u64 last_pkt_rcvd[tx_state->num_receivers]; - for(u32 r = 0; r < tx_state->num_receivers; r++) { - // tolerate some delay for first ACK - last_pkt_rcvd[r] = get_nsecs() - + 2 * tx_state->receiver[r].handshake_rtt // at startup, tolerate two additional RTTs - + 100 * ACK_RATE_TIME_MS * 1e6; // some drivers experience a short outage after activating XDP - } - - while(tx_state->session->is_running && !tx_acked_all(tx_state)) { - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(!tx_state->receiver[r].finished && last_pkt_rcvd[r] + 8 * ACK_RATE_TIME_MS * 1e6 < get_nsecs()) { - // Abort transmission after timeout. - debug_printf("receiver %d timed out: last %fs, now %fs", r, last_pkt_rcvd[r] / 1.e9, - get_nsecs() / 1.e9); - // XXX: this aborts all transmissions, as soon as one times out - exit_with_error(tx_state->session, ETIMEDOUT); - } - } - - const char *payload; - int payloadlen; - const struct scionaddrhdr_ipv4 *scionaddrhdr; - const struct udphdr *udphdr; - u8 path_idx; - if(recv_rbudp_control_pkt(tx_state->session, buf, sizeof buf, &payload, &payloadlen, - &scionaddrhdr, &udphdr, &path_idx, NULL)) { - const struct hercules_control_packet *control_pkt = (const struct hercules_control_packet *) payload; - if((u32) payloadlen < sizeof(control_pkt->type)) { - debug_printf("control packet too short"); - } else { - u32 control_pkt_payloadlen = payloadlen - sizeof(control_pkt->type); - u32 rcvr_idx = rcvr_by_src_address(tx_state, scionaddrhdr, udphdr); - if(rcvr_idx < tx_state->num_receivers) { - last_pkt_rcvd[rcvr_idx] = umax64(last_pkt_rcvd[rcvr_idx], get_nsecs()); - switch(control_pkt->type) { - case CONTROL_PACKET_TYPE_ACK: - if(control_pkt_payloadlen >= ack__len(&control_pkt->payload.ack)) { - struct rbudp_ack_pkt ack; - memcpy(&ack, &control_pkt->payload.ack, ack__len(&control_pkt->payload.ack)); - tx_register_acks(&ack, &tx_state->receiver[rcvr_idx]); - } - break; - case CONTROL_PACKET_TYPE_NACK: - if(tx_state->receiver[0].cc_states != NULL && - control_pkt_payloadlen >= ack__len(&control_pkt->payload.ack)) { - struct rbudp_ack_pkt nack; - memcpy(&nack, &control_pkt->payload.ack, ack__len(&control_pkt->payload.ack)); - nack_trace_push(nack.timestamp, nack.ack_nr); - tx_register_nacks(&nack, &tx_state->receiver[rcvr_idx].cc_states[path_idx]); - } - break; - case CONTROL_PACKET_TYPE_INITIAL: - if(control_pkt_payloadlen >= sizeof(control_pkt->payload.initial)) { - struct rbudp_initial_pkt initial; - memcpy(&initial, &control_pkt->payload.initial, sizeof(control_pkt->payload.initial)); - struct sender_state_per_receiver *receiver = &tx_state->receiver[rcvr_idx]; - if(tx_handle_handshake_reply(&initial, receiver)) { - debug_printf("[receiver %d] [path %d] handshake_rtt: %fs, MI: %fs", rcvr_idx, - initial.path_index, receiver->cc_states[initial.path_index].rtt, - receiver->cc_states[initial.path_index].pcc_mi_duration); - } - } - break; - default: - debug_printf("received a control packet of unknown type %d", control_pkt->type); - } - } - } - } - - if(tx_state->receiver[0].cc_states) { - pcc_monitor(tx_state); - } - } -} +/* static void tx_recv_control_messages(struct sender_state *tx_state) */ +/* { */ +/* struct timeval to = {.tv_sec = 0, .tv_usec = 100}; */ +/* setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ +/* char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ + +/* // packet receive timeouts */ +/* u64 last_pkt_rcvd[tx_state->num_receivers]; */ +/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ +/* // tolerate some delay for first ACK */ +/* last_pkt_rcvd[r] = get_nsecs() */ +/* + 2 * tx_state->receiver[r].handshake_rtt // at startup, tolerate two additional RTTs */ +/* + 100 * ACK_RATE_TIME_MS * 1e6; // some drivers experience a short outage after activating XDP */ +/* } */ + +/* while(tx_state->session->is_running && !tx_acked_all(tx_state)) { */ +/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ +/* if(!tx_state->receiver[r].finished && last_pkt_rcvd[r] + 8 * ACK_RATE_TIME_MS * 1e6 < get_nsecs()) { */ +/* // Abort transmission after timeout. */ +/* debug_printf("receiver %d timed out: last %fs, now %fs", r, last_pkt_rcvd[r] / 1.e9, */ +/* get_nsecs() / 1.e9); */ +/* // XXX: this aborts all transmissions, as soon as one times out */ +/* exit_with_error(tx_state->session, ETIMEDOUT); */ +/* } */ +/* } */ + +/* const char *payload; */ +/* int payloadlen; */ +/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ +/* const struct udphdr *udphdr; */ +/* u8 path_idx; */ +/* if(recv_rbudp_control_pkt(tx_state->session, buf, sizeof buf, &payload, &payloadlen, */ +/* &scionaddrhdr, &udphdr, &path_idx, NULL)) { */ +/* const struct hercules_control_packet *control_pkt = (const struct hercules_control_packet *) payload; */ +/* if((u32) payloadlen < sizeof(control_pkt->type)) { */ +/* debug_printf("control packet too short"); */ +/* } else { */ +/* u32 control_pkt_payloadlen = payloadlen - sizeof(control_pkt->type); */ +/* u32 rcvr_idx = rcvr_by_src_address(tx_state, scionaddrhdr, udphdr); */ +/* if(rcvr_idx < tx_state->num_receivers) { */ +/* last_pkt_rcvd[rcvr_idx] = umax64(last_pkt_rcvd[rcvr_idx], get_nsecs()); */ +/* switch(control_pkt->type) { */ +/* case CONTROL_PACKET_TYPE_ACK: */ +/* if(control_pkt_payloadlen >= ack__len(&control_pkt->payload.ack)) { */ +/* struct rbudp_ack_pkt ack; */ +/* memcpy(&ack, &control_pkt->payload.ack, ack__len(&control_pkt->payload.ack)); */ +/* tx_register_acks(&ack, &tx_state->receiver[rcvr_idx]); */ +/* } */ +/* break; */ +/* case CONTROL_PACKET_TYPE_NACK: */ +/* if(tx_state->receiver[0].cc_states != NULL && */ +/* control_pkt_payloadlen >= ack__len(&control_pkt->payload.ack)) { */ +/* struct rbudp_ack_pkt nack; */ +/* memcpy(&nack, &control_pkt->payload.ack, ack__len(&control_pkt->payload.ack)); */ +/* nack_trace_push(nack.timestamp, nack.ack_nr); */ +/* tx_register_nacks(&nack, &tx_state->receiver[rcvr_idx].cc_states[path_idx]); */ +/* } */ +/* break; */ +/* case CONTROL_PACKET_TYPE_INITIAL: */ +/* if(control_pkt_payloadlen >= sizeof(control_pkt->payload.initial)) { */ +/* struct rbudp_initial_pkt initial; */ +/* memcpy(&initial, &control_pkt->payload.initial, sizeof(control_pkt->payload.initial)); */ +/* struct sender_state_per_receiver *receiver = &tx_state->receiver[rcvr_idx]; */ +/* if(tx_handle_handshake_reply(&initial, receiver)) { */ +/* debug_printf("[receiver %d] [path %d] handshake_rtt: %fs, MI: %fs", rcvr_idx, */ +/* initial.path_index, receiver->cc_states[initial.path_index].rtt, */ +/* receiver->cc_states[initial.path_index].pcc_mi_duration); */ +/* } */ +/* } */ +/* break; */ +/* default: */ +/* debug_printf("received a control packet of unknown type %d", control_pkt->type); */ +/* } */ +/* } */ +/* } */ +/* } */ + +/* if(tx_state->receiver[0].cc_states) { */ +/* pcc_monitor(tx_state); */ +/* } */ +/* } */ +/* } */ static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t payloadlen, u32 rcvr) { @@ -1210,103 +1280,105 @@ static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t return false; } -static bool tx_await_cts(struct sender_state *tx_state) -{ - // count received CTS - u32 received = 0; - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(tx_state->receiver[r].cts_received) { - received++; - } - } - - // Set timeout on the socket - struct timeval to = {.tv_sec = 1, .tv_usec = 0}; - setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); - - char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; - const char *payload; - int payloadlen; - const struct scionaddrhdr_ipv4 *scionaddrhdr; - const struct udphdr *udphdr; - // Wait up to 20 seconds for the receiver to get ready - for(u64 start = get_nsecs(); start + 300e9l > get_nsecs();) { - if(recv_rbudp_control_pkt(tx_state->session, buf, sizeof buf, &payload, &payloadlen, &scionaddrhdr, &udphdr, NULL, NULL)) { - if(tx_handle_cts(tx_state, payload, payloadlen, rcvr_by_src_address(tx_state, scionaddrhdr, udphdr))) { - received++; - if(received >= tx_state->num_receivers) { - return true; - } - } - } - } - return false; -} - -static void tx_send_handshake_ack(struct sender_state *tx_state, u32 rcvr) -{ - char buf[tx_state->session->config.ether_size]; - struct hercules_path *path = &tx_state->receiver[rcvr].paths[0]; - void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); - - struct rbudp_ack_pkt ack; - ack.num_acks = 0; - - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&ack, ack__len(&ack), path->payloadlen); - stitch_checksum(path, path->header.checksum, buf); - - send_eth_frame(tx_state->session, path, buf); - atomic_fetch_add(&tx_state->session->tx_npkts, 1); -} - -static bool tx_await_rtt_ack(struct sender_state *tx_state, char *buf, size_t buflen, const struct scionaddrhdr_ipv4 **scionaddrhdr, const struct udphdr **udphdr) -{ - const struct scionaddrhdr_ipv4 *scionaddrhdr_fallback; - if(scionaddrhdr == NULL) { - scionaddrhdr = &scionaddrhdr_fallback; - } - - const struct udphdr *udphdr_fallback; - if(udphdr == NULL) { - udphdr = &udphdr_fallback; - } - - // Set 0.1 second timeout on the socket - struct timeval to = {.tv_sec = 0, .tv_usec = 100e3}; - setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); - - const char *payload; - int payloadlen; - if(recv_rbudp_control_pkt(tx_state->session, buf, buflen, &payload, &payloadlen, scionaddrhdr, udphdr, NULL, NULL)) { - struct rbudp_initial_pkt parsed_pkt; - u32 rcvr = rcvr_by_src_address(tx_state, *scionaddrhdr, *udphdr); - if(rbudp_parse_initial(payload, payloadlen, &parsed_pkt)) { - if(rcvr < tx_state->num_receivers && tx_state->receiver[rcvr].handshake_rtt == 0) { - tx_state->receiver[rcvr].handshake_rtt = (u64)(get_nsecs() - parsed_pkt.timestamp); - if(parsed_pkt.filesize != tx_state->filesize || - parsed_pkt.chunklen != tx_state->chunklen) { - debug_printf("Receiver disagrees " - "on transfer parameters:\n" - "filesize: %llu\nchunklen: %u", - parsed_pkt.filesize, - parsed_pkt.chunklen); - return false; - } - tx_send_handshake_ack(tx_state, rcvr); - } - return true; - } else { - tx_handle_cts(tx_state, payload, payloadlen, rcvr); - } - } - return false; -} +/* static bool tx_await_cts(struct sender_state *tx_state) */ +/* { */ +/* // count received CTS */ +/* u32 received = 0; */ +/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ +/* if(tx_state->receiver[r].cts_received) { */ +/* received++; */ +/* } */ +/* } */ + +/* // Set timeout on the socket */ +/* struct timeval to = {.tv_sec = 1, .tv_usec = 0}; */ +/* setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ + +/* char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ +/* const char *payload; */ +/* int payloadlen; */ +/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ +/* const struct udphdr *udphdr; */ +/* // Wait up to 20 seconds for the receiver to get ready */ +/* for(u64 start = get_nsecs(); start + 300e9l > get_nsecs();) { */ +/* if(recv_rbudp_control_pkt(tx_state->session, buf, sizeof buf, &payload, &payloadlen, &scionaddrhdr, &udphdr, NULL, NULL)) { */ +/* if(tx_handle_cts(tx_state, payload, payloadlen, rcvr_by_src_address(tx_state, scionaddrhdr, udphdr))) { */ +/* received++; */ +/* if(received >= tx_state->num_receivers) { */ +/* return true; */ +/* } */ +/* } */ +/* } */ +/* } */ +/* return false; */ +/* } */ + +/* static void tx_send_handshake_ack(struct sender_state *tx_state, u32 rcvr) */ +/* { */ +/* debug_printf("send HS ack"); */ +/* char buf[tx_state->session->config.ether_size]; */ +/* struct hercules_path *path = &tx_state->receiver[rcvr].paths[0]; */ +/* void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); */ + +/* struct rbudp_ack_pkt ack; */ +/* ack.num_acks = 0; */ + +/* fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&ack, ack__len(&ack), path->payloadlen); */ +/* stitch_checksum(path, path->header.checksum, buf); */ + +/* send_eth_frame(tx_state->session, path, buf); */ +/* atomic_fetch_add(&tx_state->session->tx_npkts, 1); */ +/* } */ + +/* static bool tx_await_rtt_ack(struct sender_state *tx_state, char *buf, size_t buflen, const struct scionaddrhdr_ipv4 **scionaddrhdr, const struct udphdr **udphdr) */ +/* { */ +/* const struct scionaddrhdr_ipv4 *scionaddrhdr_fallback; */ +/* if(scionaddrhdr == NULL) { */ +/* scionaddrhdr = &scionaddrhdr_fallback; */ +/* } */ + +/* const struct udphdr *udphdr_fallback; */ +/* if(udphdr == NULL) { */ +/* udphdr = &udphdr_fallback; */ +/* } */ + +/* // Set 0.1 second timeout on the socket */ +/* struct timeval to = {.tv_sec = 0, .tv_usec = 100e3}; */ +/* setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ + +/* const char *payload; */ +/* int payloadlen; */ +/* if(recv_rbudp_control_pkt(tx_state->session, buf, buflen, &payload, &payloadlen, scionaddrhdr, udphdr, NULL, NULL)) { */ +/* struct rbudp_initial_pkt parsed_pkt; */ +/* u32 rcvr = rcvr_by_src_address(tx_state, *scionaddrhdr, *udphdr); */ +/* if(rbudp_parse_initial(payload, payloadlen, &parsed_pkt)) { */ +/* if(rcvr < tx_state->num_receivers && tx_state->receiver[rcvr].handshake_rtt == 0) { */ +/* tx_state->receiver[rcvr].handshake_rtt = (u64)(get_nsecs() - parsed_pkt.timestamp); */ +/* if(parsed_pkt.filesize != tx_state->filesize || */ +/* parsed_pkt.chunklen != tx_state->chunklen) { */ +/* debug_printf("Receiver disagrees " */ +/* "on transfer parameters:\n" */ +/* "filesize: %llu\nchunklen: %u", */ +/* parsed_pkt.filesize, */ +/* parsed_pkt.chunklen); */ +/* return false; */ +/* } */ +/* tx_send_handshake_ack(tx_state, rcvr); */ +/* } */ +/* return true; */ +/* } else { */ +/* tx_handle_cts(tx_state, payload, payloadlen, rcvr); */ +/* } */ +/* } */ +/* return false; */ +/* } */ static void -tx_send_initial(struct hercules_session *session, const struct hercules_path *path, size_t filesize, u32 chunklen, +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path) { - char buf[session->config.ether_size]; + debug_printf("Sending initial"); + char buf[server->config.ether_size]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); struct hercules_control_packet pld = { @@ -1323,46 +1395,46 @@ tx_send_initial(struct hercules_session *session, const struct hercules_path *pa path->payloadlen); stitch_checksum(path, path->header.checksum, buf); - send_eth_frame(session, path, buf); - atomic_fetch_add(&session->tx_npkts, 1); -} - -static bool tx_handshake(struct sender_state *tx_state) -{ - bool succeeded[tx_state->num_receivers]; - memset(succeeded, 0, sizeof(succeeded)); - for(u64 start = get_nsecs(); start >= get_nsecs() - tx_handshake_timeout;) { - int await = 0; - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(!succeeded[r]) { - unsigned long timestamp = get_nsecs(); - tx_send_initial(tx_state->session, &tx_state->receiver[r].paths[0], tx_state->filesize, - tx_state->chunklen, timestamp, 0, true); - await++; - } - } - - char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; - const struct scionaddrhdr_ipv4 *scionaddrhdr; - const struct udphdr *udphdr; - for(u64 start_wait = get_nsecs(); get_nsecs() < start_wait + tx_handshake_retry_after;) { - if(tx_await_rtt_ack(tx_state, buf, sizeof buf, &scionaddrhdr, &udphdr)) { - u32 rcvr = rcvr_by_src_address(tx_state, scionaddrhdr, udphdr); - if(rcvr < tx_state->num_receivers && !succeeded[rcvr]) { - tx_state->receiver[rcvr].paths[0].next_handshake_at = UINT64_MAX; - succeeded[rcvr] = true; - await--; - if(await == 0) { - return true; - } - } - } - } - debug_printf("Timeout, retry."); - } - fprintf(stderr, "ERR: timeout during handshake. Gave up after %.0f seconds.\n", tx_handshake_timeout / 1e9); - return false; -} + send_eth_frame(server, path, buf); + /* atomic_fetch_add(&server->tx_npkts, 1); */ +} + +/* static bool tx_handshake(struct sender_state *tx_state) */ +/* { */ +/* bool succeeded[tx_state->num_receivers]; */ +/* memset(succeeded, 0, sizeof(succeeded)); */ +/* for(u64 start = get_nsecs(); start >= get_nsecs() - tx_handshake_timeout;) { */ +/* int await = 0; */ +/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ +/* if(!succeeded[r]) { */ +/* unsigned long timestamp = get_nsecs(); */ +/* tx_send_initial(tx_state->session, &tx_state->receiver[r].paths[0], tx_state->filesize, */ +/* tx_state->chunklen, timestamp, 0, true); */ +/* await++; */ +/* } */ +/* } */ + +/* char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ +/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ +/* const struct udphdr *udphdr; */ +/* for(u64 start_wait = get_nsecs(); get_nsecs() < start_wait + tx_handshake_retry_after;) { */ +/* if(tx_await_rtt_ack(tx_state, buf, sizeof buf, &scionaddrhdr, &udphdr)) { */ +/* u32 rcvr = rcvr_by_src_address(tx_state, scionaddrhdr, udphdr); */ +/* if(rcvr < tx_state->num_receivers && !succeeded[rcvr]) { */ +/* tx_state->receiver[rcvr].paths[0].next_handshake_at = UINT64_MAX; */ +/* succeeded[rcvr] = true; */ +/* await--; */ +/* if(await == 0) { */ +/* return true; */ +/* } */ +/* } */ +/* } */ +/* } */ +/* debug_printf("Timeout, retry."); */ +/* } */ +/* fprintf(stderr, "ERR: timeout during handshake. Gave up after %.0f seconds.\n", tx_handshake_timeout / 1e9); */ +/* return false; */ +/* } */ static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) { @@ -1378,7 +1450,7 @@ static void stitch_checksum(const struct hercules_path *path, u16 precomputed_ch mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); } -static void rx_handle_initial(struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, +static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, int ifid, const char *payload, int payloadlen); static void @@ -1404,12 +1476,15 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_info *xsk) { + /* debug_printf("receive batch"); */ u32 idx_rx = 0; int ignored = 0; size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); - if(!rcvd) + if(!rcvd){ return; + } + debug_printf("received %ld", rcvd); // optimistically update receive timestamp u64 now = get_nsecs(); @@ -1426,20 +1501,22 @@ static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_ const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); if(rbudp_pkt) { + print_rbudp_pkt(rbudp_pkt, true); if(!handle_rbudp_data_pkt(rx_state, rbudp_pkt, len - (rbudp_pkt - pkt))) { struct rbudp_initial_pkt initial; if(rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len, &initial)) { - rx_handle_initial(rx_state, &initial, pkt, xsk->umem->iface->ifid, rbudp_pkt, (int) len - (int) (rbudp_pkt - pkt)); + /* rx_handle_initial(rx_state, &initial, pkt, xsk->umem->iface->ifid, rbudp_pkt, (int) len - (int) (rbudp_pkt - pkt)); */ } else { ignored++; } } } else { + debug_printf("ignoring packet failed parse"); ignored++; } } xsk_ring_cons__release(&xsk->rx, rcvd); - atomic_fetch_add(&rx_state->session->rx_npkts, (rcvd - ignored)); + /* atomic_fetch_add(&rx_state->session->rx_npkts, (rcvd - ignored)); */ submit_rx_frames(rx_state->session, xsk->umem, frame_addrs, rcvd); } @@ -1480,6 +1557,7 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence if(sizeof(chunk_idx) + sizeof(path_idx) + n < payloadlen) { memset(start_pad, 0, payloadlen - sizeof(chunk_idx) - sizeof(path_idx) - n); } + print_rbudp_pkt(rbudp_pkt, false); } @@ -1505,6 +1583,7 @@ void push_hercules_tx_paths(struct hercules_session *session) static void update_hercules_tx_paths(struct sender_state *tx_state) { + return; // FIXME HACK acquire_path_lock(); tx_state->has_new_paths = false; u64 now = get_nsecs(); @@ -1571,35 +1650,39 @@ static void update_hercules_tx_paths(struct sender_state *tx_state) void send_path_handshakes(struct sender_state *tx_state) { - u64 now = get_nsecs(); + return; + /* u64 now = get_nsecs(); */ for(u32 r = 0; r < tx_state->num_receivers; r++) { struct sender_state_per_receiver *rcvr = &tx_state->receiver[r]; for(u32 p = 0; p < rcvr->num_paths; p++) { struct hercules_path *path = &rcvr->paths[p]; if(path->enabled) { - u64 handshake_at = atomic_load(&path->next_handshake_at); - if(handshake_at < now) { - if(atomic_compare_exchange_strong(&path->next_handshake_at, &handshake_at, - now + PATH_HANDSHAKE_TIMEOUT_NS)) { - tx_send_initial(tx_state->session, path, tx_state->filesize, tx_state->chunklen, get_nsecs(), p, - p == rcvr->return_path_idx); - } - } + /* u64 handshake_at = atomic_load(&path->next_handshake_at); */ + /* if(handshake_at < now) { */ + /* if(atomic_compare_exchange_strong(&path->next_handshake_at, &handshake_at, */ + /* now + PATH_HANDSHAKE_TIMEOUT_NS)) { */ + /* debug_printf("sending hs"); */ + /* tx_send_initial(tx_state->session, path, tx_state->filesize, tx_state->chunklen, get_nsecs(), p, */ + /* p == rcvr->return_path_idx); */ + /* } */ + /* } */ } } } } -static void claim_tx_frames(struct hercules_session *session, struct hercules_interface *iface, u64 *addrs, size_t num_frames) +static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { pthread_spin_lock(&iface->umem->lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); while(reserved != num_frames) { // When we're not getting any frames, we might need to... - kick_all_tx(session, iface); + kick_all_tx(server, iface); reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); - if(!session->is_running) { + /* debug_printf("reserved %ld, wanted %ld", reserved, num_frames); */ + // XXX FIXME + if(!server->session_tx) { pthread_spin_unlock(&iface->umem->lock); return; } @@ -1639,11 +1722,12 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s num_chunks_in_unit++; } } + debug_printf("handling %d chunks in unit", num_chunks_in_unit); u32 idx; if(xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != num_chunks_in_unit) { // As there are less frames in the loop than slots in the TX ring, this should not happen - exit_with_error(tx_state->session, EINVAL); + exit_with_error(NULL, EINVAL); } int current_frame = 0; @@ -1680,33 +1764,38 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s stitch_checksum(path, path->header.checksum, pkt); } + debug_printf("submitting %d chunks in unit", num_chunks_in_unit); xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); + __sync_synchronize(); + debug_printf("submit returns"); } -static inline void tx_handle_send_queue_unit(struct sender_state *tx_state, struct xsk_socket_info *xsks[], +static inline void tx_handle_send_queue_unit(struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit) { - for(int i = 0; i < tx_state->session->num_ifaces; i++) { - tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], tx_state->session->ifaces[i].ifid, frame_addrs[i], unit); + + for(int i = 0; i < server->num_ifaces; i++) { + tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit); } } static void -produce_batch(struct sender_state *tx_state, const u8 *path_by_rcvr, const u32 *chunks, +produce_batch(struct hercules_server *server, struct hercules_session *session, struct sender_state *tx_state, const u8 *path_by_rcvr, const u32 *chunks, const u8 *rcvr_by_chunk, u32 num_chunks) { + (void)tx_state; // FIXME u32 chk; u32 num_chunks_in_unit; struct send_queue_unit *unit = NULL; for(chk = 0; chk < num_chunks; chk++) { if(unit == NULL) { - unit = send_queue_reserve(tx_state->send_queue); + unit = send_queue_reserve(session->send_queue); num_chunks_in_unit = 0; if(unit == NULL) { // send_queue is full, make sure that the frame_queue does not drain in the meantime - for(int i = 0; i < tx_state->session->num_ifaces; i++) { - pop_completion_ring(tx_state->session, tx_state->session->ifaces[i].umem); + for(int i = 0; i < server->num_ifaces; i++) { + pop_completion_ring(server, server->ifaces[i].umem); } chk--; // retry with same chunk continue; @@ -1722,23 +1811,23 @@ produce_batch(struct sender_state *tx_state, const u8 *path_by_rcvr, const u32 * if(num_chunks_in_unit < SEND_QUEUE_ENTRIES_PER_UNIT) { unit->paths[num_chunks_in_unit] = UINT8_MAX; } - send_queue_push(tx_state->send_queue); + send_queue_push(session->send_queue); unit = NULL; } } } -static inline void allocate_tx_frames(struct hercules_session *session, +static inline void allocate_tx_frames(struct hercules_server *server, u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT]) { - for(int i = 0; i < session->num_ifaces; i++) { + for(int i = 0; i < server->num_ifaces; i++) { int num_frames; for(num_frames = 0; num_frames < SEND_QUEUE_ENTRIES_PER_UNIT; num_frames++) { if(frame_addrs[i][num_frames] != (u64) -1) { break; } } - claim_tx_frames(session, &session->ifaces[i], frame_addrs[i], num_frames); + claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); } } @@ -1748,37 +1837,26 @@ struct tx_send_p_args { }; static void tx_send_p(void *arg) { - struct tx_send_p_args *args = arg; - struct hercules_session *session = args->tx_state->session; - struct send_queue *send_queue = args->tx_state->send_queue; - - u64 frame_addrs[session->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; - memset(frame_addrs, 0xFF, sizeof(frame_addrs)); - allocate_tx_frames(session, frame_addrs); - - struct send_queue_unit unit; - send_queue_pop_wait(send_queue, &unit, &args->tx_state->session->is_running); - int units_in_batch = 0; - while(true) { - tx_handle_send_queue_unit(args->tx_state, args->xsks, frame_addrs, &unit); - allocate_tx_frames(session, frame_addrs); - if(!send_queue_pop(send_queue, &unit)) { // queue currently empty - for(int i = 0; i < args->tx_state->session->num_ifaces; i++) { - kick_tx(args->tx_state->session, args->xsks[i]); - } - units_in_batch = 0; - while(!send_queue_pop(send_queue, &unit)) { - if(!atomic_load(&session->is_running)) { - return; - } - } - } else if(++units_in_batch == 5) { - for(int i = 0; i < args->tx_state->session->num_ifaces; i++) { - kick_tx(args->tx_state->session, args->xsks[i]); - } - units_in_batch = 0; - } - } + struct hercules_server *server = arg; + while (1) { + struct hercules_session *session_tx = atomic_load(&server->session_tx); + if (session_tx == NULL || session_tx->state != SESSION_STATE_RUNNING) { + /* debug_printf("Invalid session, dropping sendq unit"); */ + continue; + } + struct send_queue_unit unit; + int ret = send_queue_pop(session_tx->send_queue, &unit); + if (!ret) { + kick_tx(server, &server->xsk[0]); + continue; + } + u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; + memset(frame_addrs, 0xFF, sizeof(frame_addrs)); + allocate_tx_frames(server, frame_addrs); + tx_handle_send_queue_unit(server, session_tx->tx_state, &server->xsk, + frame_addrs, &unit); + kick_tx(server, &server->xsk[0]); + } } // Collect path rate limits @@ -1884,10 +1962,13 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 struct sender_state_per_receiver *rcvr = &tx_state->receiver[rcvr_idx]; u32 num_chunks_prepared = 0; u32 chunk_idx = rcvr->prev_chunk_idx; + debug_printf("n chunks %d", num_chunks); for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { + debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); chunk_idx = bitset__scan_neg(&rcvr->acked_chunks, chunk_idx); if(chunk_idx == tx_state->total_chunks) { if(rcvr->prev_chunk_idx == 0) { // this receiver has finished + debug_printf("receiver has finished"); rcvr->finished = true; break; } @@ -1907,11 +1988,14 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 const u64 prev_transmit = umin64(rcvr->prev_round_start + rcvr->prev_slope * chunk_idx, rcvr->prev_round_end); const u64 ack_due = prev_transmit + rcvr->ack_wait_duration; // 0 for first round if(now >= ack_due) { // add the chunk to the current batch + /* debug_printf("now >= ack due, adding chunk"); */ + debug_printf("adding chunk idx %d", chunk_idx); *chunks = chunk_idx++; *chunk_rcvr = rcvr_idx; chunks++; chunk_rcvr++; } else { // no chunk to send - skip this receiver in the current batch + debug_printf("now < ack due, no chunk to send"); (*wait_until) = ack_due; break; } @@ -1920,7 +2004,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 return num_chunks_prepared; } -inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) +static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) { return cc_state->state != pcc_terminated && cc_state->state != pcc_uninitialized && @@ -1955,7 +2039,7 @@ inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) * This assumes a uniform send rate and that chunks that need to be retransmitted (i.e. losses) * occur uniformly. */ -static void tx_only(struct sender_state *tx_state) +static void tx_only(struct hercules_server *server, struct sender_state *tx_state) { debug_printf("Start transmit round for all receivers"); tx_state->prev_rate_check = get_nsecs(); @@ -1966,7 +2050,7 @@ static void tx_only(struct sender_state *tx_state) u32 max_chunks_per_rcvr[tx_state->num_receivers]; while(tx_state->session->is_running && finished_count < tx_state->num_receivers) { - pop_completion_rings(tx_state->session); + pop_completion_rings(server); send_path_handshakes(tx_state); u64 next_ack_due = 0; u32 num_chunks_per_rcvr[tx_state->num_receivers]; @@ -2021,7 +2105,7 @@ static void tx_only(struct sender_state *tx_state) if(num_chunks > 0) { u8 rcvr_path[tx_state->num_receivers]; prepare_rcvr_paths(tx_state, rcvr_path); - produce_batch(tx_state, rcvr_path, chunks, chunk_rcvr, num_chunks); + produce_batch(server, NULL, tx_state, rcvr_path, chunks, chunk_rcvr, num_chunks); tx_state->tx_npkts_queued += num_chunks; rate_limit_tx(tx_state); @@ -2063,6 +2147,9 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i exit(1); } + debug_printf("Sending a file consisting of %d chunks (ANY KEY TO CONTINUE)", total_chunks); + getchar(); + struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); tx_state->session = session; tx_state->filesize = filesize; @@ -2079,18 +2166,13 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i tx_state->shd_num_paths = num_paths; tx_state->has_new_paths = false; - int err = posix_memalign((void **)&tx_state->send_queue, CACHELINE_SIZE, sizeof(*tx_state->send_queue)); - if(err != 0) { - exit_with_error(session, err); - } - for(u32 d = 0; d < num_dests; d++) { struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; bitset__create(&receiver->acked_chunks, tx_state->total_chunks); receiver->path_index = 0; receiver->handshake_rtt = 0; receiver->num_paths = num_paths[d]; - receiver->paths = calloc(tx_state->max_paths_per_rcvr, sizeof(struct hercules_path)); + receiver->paths = paths; receiver->addr = dests[d]; receiver->cts_received = false; } @@ -2108,44 +2190,27 @@ static void destroy_tx_state(struct sender_state *tx_state) free(tx_state); } -static struct receiver_state *make_rx_state(struct hercules_session *session, size_t filesize, int chunklen, - bool is_pcc_benchmark) -{ - struct receiver_state *rx_state; - rx_state = calloc(1, sizeof(*rx_state)); - rx_state->session = session; - rx_state->filesize = filesize; - rx_state->chunklen = chunklen; - rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; - bitset__create(&rx_state->received_chunks, rx_state->total_chunks); - rx_state->start_time = 0; - rx_state->end_time = 0; - rx_state->handshake_rtt = 0; - rx_state->is_pcc_benchmark = is_pcc_benchmark; - return rx_state; -} - -static char *rx_mmap(struct hercules_session *session, const char *pathname, size_t filesize) +static char *rx_mmap(struct hercules_server *server, const char *pathname, size_t filesize) { int ret; /*ret = unlink(pathname); if(ret && errno != ENOENT) { - exit_with_error(session, errno); + exit_with_error(server, errno); }*/ int f = open(pathname, O_RDWR | O_CREAT | O_EXCL, 0664); if(f == -1 && errno == EEXIST) { f = open(pathname, O_RDWR | O_EXCL); } if(f == -1) { - exit_with_error(session, errno); + exit_with_error(server, errno); } ret = fallocate(f, 0, 0, filesize); // Will fail on old filesystems (ext3) if(ret) { - exit_with_error(session, errno); + exit_with_error(server, errno); } char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); if(mem == MAP_FAILED) { - exit_with_error(session, errno); + exit_with_error(server, errno); } close(f); // fault and dirty the pages @@ -2158,20 +2223,67 @@ static char *rx_mmap(struct hercules_session *session, const char *pathname, siz return mem; } +static struct receiver_state *make_rx_state(struct hercules_session *session, size_t filesize, int chunklen, + bool is_pcc_benchmark) +{ + struct receiver_state *rx_state; + rx_state = calloc(1, sizeof(*rx_state)); + rx_state->session = session; + rx_state->filesize = filesize; + rx_state->chunklen = chunklen; + rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; + bitset__create(&rx_state->received_chunks, rx_state->total_chunks); + rx_state->start_time = 0; + rx_state->end_time = 0; + rx_state->handshake_rtt = 0; + rx_state->is_pcc_benchmark = is_pcc_benchmark; + rx_state->mem = rx_mmap(NULL, "outfile", filesize); + return rx_state; +} + static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt) { struct hercules_control_packet control_pkt; memcpy(&control_pkt, pkt, umin32(sizeof(control_pkt), len)); if(control_pkt.type != CONTROL_PACKET_TYPE_INITIAL) { + debug_printf("Packet type not INITIAL"); return false; } if(len < sizeof(control_pkt.type) + sizeof(*parsed_pkt)) { + debug_printf("Packet too short"); return false; } memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt)); return true; } +static int get_reply_path_socket(char *rx_sample_buf, int rx_sample_len, + struct hercules_path *path) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + debug_printf("sending %d bytes", rx_sample_len); + sendto(usock, rx_sample_buf, rx_sample_len, 0, &monitor, sizeof(monitor)); + char buf[2000]; + memset(&buf, 0, 2000); + int n = recv(usock, &buf, sizeof(buf), 0); + if (n > 0) { + debug_printf("Read %d bytes", n); + u32 headerlen = *(u32 *)buf; + path->headerlen = headerlen; + memcpy(path->header.header, buf+4, headerlen); + memcpy(&path->header.checksum, buf+4+headerlen, 2); + path->enabled = true; + path->replaced = false; + path->payloadlen = 1200 - headerlen; + path->framelen = 1200; + path->ifid = 3; + return 0; + } + assert(false); + return 1; +} + static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) { // Get reply path for sending ACKs: @@ -2188,7 +2300,7 @@ static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_p char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); - int ret = HerculesGetReplyPath(rx_sample_buf, rx_sample_len, path); + int ret = get_reply_path_socket(rx_sample_buf, rx_sample_len, path); if(ret) { return false; } @@ -2196,111 +2308,114 @@ static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_p return true; } -static void rx_send_rtt_ack(struct receiver_state *rx_state, struct rbudp_initial_pkt *pld) +static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *pld) { struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { + debug_printf("no return path"); return; } - char buf[rx_state->session->config.ether_size]; + char buf[server->config.ether_size]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); struct hercules_control_packet control_pkt = { .type = CONTROL_PACKET_TYPE_INITIAL, .payload.initial = *pld, }; + control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); - send_eth_frame(rx_state->session, &path, buf); - atomic_fetch_add(&rx_state->session->tx_npkts, 1); + send_eth_frame(server, &path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ } -static void rx_handle_initial(struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, +static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, int ifid, const char *payload, int payloadlen) { const int headerlen = (int)(payload - buf); if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + debug_printf("setting rx sample"); set_rx_sample(rx_state, ifid, buf, headerlen + payloadlen); } - rx_send_rtt_ack(rx_state, initial); // echo back initial pkt to ACK filesize + rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize rx_state->cts_sent_at = get_nsecs(); } -static struct receiver_state *rx_accept(struct hercules_session *session, int timeout, bool is_pcc_benchmark) -{ - char buf[session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; - __u64 start_wait = get_nsecs(); - struct timeval to = {.tv_sec = 1, .tv_usec = 0}; - setsockopt(session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); - - // Wait for well formed startup packet - while(timeout == 0 || start_wait + timeout * 1e9 > get_nsecs()) { - const char *payload; - int payloadlen; - int ifid; - if(recv_rbudp_control_pkt(session, buf, sizeof buf, &payload, &payloadlen, NULL, NULL, NULL, &ifid)) { - struct rbudp_initial_pkt parsed_pkt; - if(rbudp_parse_initial(payload, payloadlen, &parsed_pkt)) { - struct receiver_state *rx_state = make_rx_state(session, parsed_pkt.filesize, parsed_pkt.chunklen, - is_pcc_benchmark); - rx_handle_initial(rx_state, &parsed_pkt, buf, ifid, payload, payloadlen); - return rx_state; - } - } - } - return NULL; -} - -static void rx_get_rtt_estimate(void *arg) -{ - struct receiver_state *rx_state = arg; - char buf[rx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; - const char *payload; - int payloadlen; - const struct scionaddrhdr_ipv4 *scionaddrhdr; - const struct udphdr *udphdr; - for(u64 timeout = get_nsecs() + 5e9; timeout > get_nsecs();) { - if(recv_rbudp_control_pkt(rx_state->session, buf, sizeof buf, &payload, &payloadlen, - &scionaddrhdr, &udphdr, NULL, NULL)) { - u64 now = get_nsecs(); - rx_state->handshake_rtt = (now - rx_state->cts_sent_at) / 1000; - return; - } - } - exit_with_error(rx_state->session, ETIMEDOUT); -} - -static void configure_rx_queues(struct hercules_session *session) -{ - for(int i = 0; i < session->num_ifaces; i++) { +/* static struct receiver_state *rx_accept(struct hercules_server *server, int timeout, bool is_pcc_benchmark) */ +/* { */ +/* char buf[server->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ +/* __u64 start_wait = get_nsecs(); */ +/* struct timeval to = {.tv_sec = 1, .tv_usec = 0}; */ +/* setsockopt(server->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ + +/* // Wait for well formed startup packet */ +/* while(timeout == 0 || start_wait + timeout * 1e9 > get_nsecs()) { */ +/* const char *payload; */ +/* int payloadlen; */ +/* int ifid; */ +/* if(recv_rbudp_control_pkt(server, buf, sizeof buf, &payload, &payloadlen, NULL, NULL, NULL, &ifid)) { */ +/* struct rbudp_initial_pkt parsed_pkt; */ +/* if(rbudp_parse_initial(payload, payloadlen, &parsed_pkt)) { */ +/* struct receiver_state *rx_state = make_rx_state(server, parsed_pkt.filesize, parsed_pkt.chunklen, */ +/* is_pcc_benchmark); */ +/* rx_handle_initial(rx_state, &parsed_pkt, buf, ifid, payload, payloadlen); */ +/* return rx_state; */ +/* } */ +/* } */ +/* } */ +/* return NULL; */ +/* } */ + +/* static void rx_get_rtt_estimate(void *arg) */ +/* { */ +/* struct receiver_state *rx_state = arg; */ +/* char buf[rx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ +/* const char *payload; */ +/* int payloadlen; */ +/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ +/* const struct udphdr *udphdr; */ +/* for(u64 timeout = get_nsecs() + 5e9; timeout > get_nsecs();) { */ +/* if(recv_rbudp_control_pkt(rx_state->session, buf, sizeof buf, &payload, &payloadlen, */ +/* &scionaddrhdr, &udphdr, NULL, NULL)) { */ +/* u64 now = get_nsecs(); */ +/* rx_state->handshake_rtt = (now - rx_state->cts_sent_at) / 1000; */ +/* return; */ +/* } */ +/* } */ +/* exit_with_error(rx_state->session, ETIMEDOUT); */ +/* } */ + +static void configure_rx_queues(struct hercules_server *server) +{ + for(int i = 0; i < server->num_ifaces; i++) { debug_printf("map UDP4 flow to %d.%d.%d.%d to queue %d on interface %s", - (u8) (session->config.local_addr.ip), - (u8) (session->config.local_addr.ip >> 8u), - (u8) (session->config.local_addr.ip >> 16u), - (u8) (session->config.local_addr.ip >> 24u), - session->ifaces[i].queue, - session->ifaces[i].ifname + (u8) (server->config.local_addr.ip), + (u8) (server->config.local_addr.ip >> 8u), + (u8) (server->config.local_addr.ip >> 16u), + (u8) (server->config.local_addr.ip >> 24u), + server->ifaces[i].queue, + server->ifaces[i].ifname ); char cmd[1024]; int cmd_len = snprintf(cmd, 1024, "ethtool -N %s flow-type udp4 dst-ip %d.%d.%d.%d action %d", - session->ifaces[i].ifname, - (u8) (session->config.local_addr.ip), - (u8) (session->config.local_addr.ip >> 8u), - (u8) (session->config.local_addr.ip >> 16u), - (u8) (session->config.local_addr.ip >> 24u), - session->ifaces[i].queue + server->ifaces[i].ifname, + (u8) (server->config.local_addr.ip), + (u8) (server->config.local_addr.ip >> 8u), + (u8) (server->config.local_addr.ip >> 16u), + (u8) (server->config.local_addr.ip >> 24u), + server->ifaces[i].queue ); if(cmd_len > 1023) { fprintf(stderr, "could not configure queue %d on interface %s - command too long, abort\n", - session->ifaces[i].queue, session->ifaces[i].ifname); - unconfigure_rx_queues(session); - exit_with_error(session, EXIT_FAILURE); + server->ifaces[i].queue, server->ifaces[i].ifname); + unconfigure_rx_queues(server); + exit_with_error(server, EXIT_FAILURE); } FILE *proc = popen(cmd, "r"); @@ -2308,33 +2423,33 @@ static void configure_rx_queues(struct hercules_session *session) int num_parsed = fscanf(proc, "Added rule with ID %d", &rule_id); int ret = pclose(proc); if(ret != 0) { - fprintf(stderr, "could not configure queue %d on interface %s, abort\n", session->ifaces[i].queue, - session->ifaces[i].ifname); - unconfigure_rx_queues(session); - exit_with_error(session, ret); + fprintf(stderr, "could not configure queue %d on interface %s, abort\n", server->ifaces[i].queue, + server->ifaces[i].ifname); + unconfigure_rx_queues(server); + exit_with_error(server, ret); } if(num_parsed != 1) { - fprintf(stderr, "could not configure queue %d on interface %s, abort\n", session->ifaces[i].queue, - session->ifaces[i].ifname); - unconfigure_rx_queues(session); - exit_with_error(session, EXIT_FAILURE); + fprintf(stderr, "could not configure queue %d on interface %s, abort\n", server->ifaces[i].queue, + server->ifaces[i].ifname); + unconfigure_rx_queues(server); + exit_with_error(server, EXIT_FAILURE); } - session->ifaces[i].ethtool_rule = rule_id; + server->ifaces[i].ethtool_rule = rule_id; } } -static int unconfigure_rx_queues(struct hercules_session *session) +static int unconfigure_rx_queues(struct hercules_server *server) { int error = 0; - for(int i = 0; i < session->num_ifaces; i++) { - if(session->ifaces[i].ethtool_rule >= 0) { + for(int i = 0; i < server->num_ifaces; i++) { + if(server->ifaces[i].ethtool_rule >= 0) { char cmd[1024]; - int cmd_len = snprintf(cmd, 1024, "ethtool -N %s delete %d", session->ifaces[i].ifname, - session->ifaces[i].ethtool_rule); - session->ifaces[i].ethtool_rule = -1; + int cmd_len = snprintf(cmd, 1024, "ethtool -N %s delete %d", server->ifaces[i].ifname, + server->ifaces[i].ethtool_rule); + server->ifaces[i].ethtool_rule = -1; if(cmd_len > 1023) { // This will never happen as the command to configure is strictly longer than this one fprintf(stderr, "could not delete ethtool rule on interface %s - command too long\n", - session->ifaces[i].ifname); + server->ifaces[i].ifname); error = EXIT_FAILURE; continue; } @@ -2349,21 +2464,23 @@ static int unconfigure_rx_queues(struct hercules_session *session) static void rx_rtt_and_configure(void *arg) { - struct receiver_state *rx_state = arg; - rx_get_rtt_estimate(arg); + (void)arg; + /* struct receiver_state *rx_state = arg; */ + /* rx_get_rtt_estimate(arg); */ // as soon as we got the RTT estimate, we are ready to set up the queues - configure_rx_queues(rx_state->session); + /* configure_rx_queues(rx_state->session); */ } -static void rx_send_cts_ack(struct receiver_state *rx_state) +static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) { + debug_printf("Send CTS ACK"); struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { debug_printf("no reply path"); return; } - char buf[rx_state->session->config.ether_size]; + char buf[server->config.ether_size]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); struct hercules_control_packet control_pkt = { @@ -2375,25 +2492,26 @@ static void rx_send_cts_ack(struct receiver_state *rx_state) sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); - send_eth_frame(rx_state->session, &path, buf); - atomic_fetch_add(&rx_state->session->tx_npkts, 1); + send_eth_frame(server, &path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ } -static void rx_send_ack_pkt(struct receiver_state *rx_state, struct hercules_control_packet *control_pkt, +static void rx_send_ack_pkt(struct hercules_server *server, struct hercules_control_packet *control_pkt, struct hercules_path *path) { - char buf[rx_state->session->config.ether_size]; + char buf[server->config.ether_size]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), path->payloadlen); stitch_checksum(path, path->header.checksum, buf); - send_eth_frame(rx_state->session, path, buf); - atomic_fetch_add(&rx_state->session->tx_npkts, 1); + send_eth_frame(server, path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ } -static void rx_send_acks(struct receiver_state *rx_state) +static void rx_send_acks(struct hercules_server *server, struct receiver_state *rx_state) { + /* debug_printf("rx_send_acks"); */ struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { debug_printf("no reply path"); @@ -2409,30 +2527,45 @@ static void rx_send_acks(struct receiver_state *rx_state) // send an empty ACK to keep connection alive until first packet arrives u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries); - rx_send_ack_pkt(rx_state, &control_pkt, &path); + rx_send_ack_pkt(server, &control_pkt, &path); for(; curr < rx_state->total_chunks;) { curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries); if(control_pkt.payload.ack.num_acks == 0) break; - rx_send_ack_pkt(rx_state, &control_pkt, &path); - } -} - -static void rx_trickle_acks(struct receiver_state *rx_state) -{ - // XXX: data races in access to shared rx_state! - atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); - while(rx_state->session->is_running && !rx_received_all(rx_state)) { - if(atomic_load(&rx_state->last_pkt_rcvd) + umax64(100 * ACK_RATE_TIME_MS * 1e6, 3 * rx_state->handshake_rtt) < - get_nsecs()) { - // Transmission timed out - exit_with_error(rx_state->session, ETIMEDOUT); - } - rx_send_acks(rx_state); - sleep_nsecs(ACK_RATE_TIME_MS * 1e6); - } + rx_send_ack_pkt(server, &control_pkt, &path); + } +} + +static void rx_trickle_acks(void *arg) { + struct hercules_server *server = arg; + while (1) { + __sync_synchronize(); + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && + session_rx->state == SESSION_STATE_RUNNING) { + struct receiver_state *rx_state = session_rx->rx_state; + // XXX: data races in access to shared rx_state! + atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); + if (atomic_load(&rx_state->last_pkt_rcvd) + + umax64(100 * ACK_RATE_TIME_MS * 1e6, + 3 * rx_state->handshake_rtt) < + get_nsecs()) { + // Transmission timed out + exit_with_error(server, ETIMEDOUT); + } + rx_send_acks(server, rx_state); + if (rx_received_all(rx_state)) { + debug_printf("Received all, done."); + session_rx->state = SESSION_STATE_DONE; + rx_send_acks(server, rx_state); + server->session_rx = NULL; // FIXME leak + atomic_store(&server->session_rx, NULL); + } + } + sleep_nsecs(ACK_RATE_TIME_MS * 1e6); + } } -static void rx_send_path_nacks(struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr) +static void rx_send_path_nacks(struct hercules_server *server, struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr) { struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { @@ -2440,7 +2573,7 @@ static void rx_send_path_nacks(struct receiver_state *rx_state, struct receiver_ return; } - char buf[rx_state->session->config.ether_size]; + char buf[server->config.ether_size]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); // XXX: could write ack payload directly to buf, but @@ -2475,38 +2608,47 @@ static void rx_send_path_nacks(struct receiver_state *rx_state, struct receiver_ sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); - send_eth_frame(rx_state->session, &path, buf); - atomic_fetch_add(&rx_state->session->tx_npkts, 1); + send_eth_frame(server, &path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ } libbpf_smp_wmb(); - pthread_spin_unlock(&path_state->seq_rcvd.lock); + /* assert(&path_state->seq_rcvd.lock != NULL); */ + /* assert(path_state->seq_rcvd.lock != NULL); */ + /* pthread_spin_unlock(&path_state->seq_rcvd.lock); */ path_state->nack_end = nack_end; } // sends the NACKs used for congestion control by the sender -static void rx_send_nacks(struct receiver_state *rx_state, u64 time, u32 nr) +static void rx_send_nacks(struct hercules_server *server, struct receiver_state *rx_state, u64 time, u32 nr) { u8 num_paths = atomic_load(&rx_state->num_tracked_paths); for(u8 p = 0; p < num_paths; p++) { - rx_send_path_nacks(rx_state, &rx_state->path_state[p], p, time, nr); - } -} - -static void rx_trickle_nacks(void *arg) -{ - u32 ack_nr = 0; - struct receiver_state *rx_state = arg; - while(rx_state->session->is_running && !rx_received_all(rx_state)) { - u64 ack_round_start = get_nsecs(); - rx_send_nacks(rx_state, ack_round_start, ack_nr); - u64 ack_round_end = get_nsecs(); - if(ack_round_end > ack_round_start + rx_state->handshake_rtt * 1000 / 4) { - //fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", ack_round_end - ack_round_start, rx_state->handshake_rtt * 1000 / 4); - } else { - sleep_until(ack_round_start + rx_state->handshake_rtt * 1000 / 4); - } - ack_nr++; - } + rx_send_path_nacks(server, rx_state, &rx_state->path_state[p], p, time, nr); + } +} + +static void rx_trickle_nacks(void *arg) { + struct hercules_server *server = arg; + while (1) { + if (true) { + u32 ack_nr = 0; + struct receiver_state *rx_state = server->session_tx->rx_state; + while (rx_state->session->is_running && !rx_received_all(rx_state)) { + u64 ack_round_start = get_nsecs(); + rx_send_nacks(server, rx_state, ack_round_start, ack_nr); + u64 ack_round_end = get_nsecs(); + if (ack_round_end > + ack_round_start + rx_state->handshake_rtt * 1000 / 4) { + // fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", + // ack_round_end - ack_round_start, rx_state->handshake_rtt * 1000 / + // 4); + } else { + sleep_until(ack_round_start + rx_state->handshake_rtt * 1000 / 4); + } + ack_nr++; + } + } + } } struct rx_p_args { @@ -2514,14 +2656,253 @@ struct rx_p_args { struct xsk_socket_info *xsks[]; }; -static void *rx_p(void *arg) -{ - struct rx_p_args *args = arg; - int num_ifaces = args->rx_state->session->num_ifaces; - for(int i = 0; args->rx_state->session->is_running && !rx_received_all(args->rx_state); i++) { - rx_receive_batch(args->rx_state, args->xsks[i % num_ifaces]); - } - return NULL; +static void *rx_p(void *arg) { + debug_printf("rx_p"); + struct hercules_server *server = arg; + /* int num_ifaces = server->num_ifaces; */ + while (1) { + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + rx_receive_batch(session_rx->rx_state, server->xsk); + } + } + return NULL; +} + +static void events_p(void *arg) { + debug_printf("event listener thread started"); + struct hercules_server *server = arg; + + // Read a packet from the control socket and act accordingly. + struct sockaddr_ll addr; + socklen_t addr_size = sizeof(addr); + char buf[3000]; + const struct scionaddrhdr_ipv4 *scionaddrhdr; + const struct udphdr *udphdr; + /* u8 path_idx; */ + /* const char *payload; */ + /* int payloadlen; */ + + while (1) { // event handler thread loop + ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), 0, + (struct sockaddr *)&addr, + &addr_size); // XXX set timeout + if (len == -1) { + if (errno == EAGAIN || errno == EINTR) { + continue; + } + exit_with_error( + NULL, + errno); // XXX: are there situations where we want to try again? + } + + /* if (get_interface_by_id(session, addr.sll_ifindex) == NULL) { */ + /* continue; */ + /* } */ + const char *rbudp_pkt = + parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr); + if (rbudp_pkt == NULL) { + /* debug_printf("Ignoring, failed parse"); */ + continue; + } + + const size_t rbudp_len = len - (rbudp_pkt - buf); + if (rbudp_len < sizeof(u32)) { + debug_printf("Ignoring, length too short"); + continue; + } + u32 chunk_idx; + memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); + if (chunk_idx != UINT_MAX) { + debug_printf("Ignoring, chunk_idx != UINT_MAX"); + continue; + } + print_rbudp_pkt(rbudp_pkt, true); + struct hercules_header *h = (struct hercules_header *)rbudp_pkt; + if (h->chunk_idx == UINT_MAX) { + const char *pl = rbudp_pkt + rbudp_headerlen; + struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; + switch (cp->type) { + case CONTROL_PACKET_TYPE_INITIAL: + ; + struct rbudp_initial_pkt parsed_pkt; + rbudp_parse_initial((const char *)cp, rbudp_len - rbudp_headerlen, + &parsed_pkt); + if (parsed_pkt.flags & HANDSHAKE_FLAG_HS_CONFIRM) { + // This is a confirmation for a handshake packet + // we sent out earlier + debug_printf("HS confirm packet"); + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_WAIT_HS) { + server->session_tx->state = SESSION_STATE_WAIT_CTS; + } + break; // Make sure we don't process this further + } + // If a transfer is already running, ignore + // XXX breaks multipath, for now (until we split path and transfer HS) + if (server->session_rx == NULL) { + debug_printf("No current rx session"); + if (parsed_pkt.flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + struct hercules_session *session = make_session(server); + server->session_rx = session; + session->state = SESSION_STATE_CREATED; + struct receiver_state *rx_state = make_rx_state( + session, parsed_pkt.filesize, parsed_pkt.chunklen, false); + session->rx_state = rx_state; + /* session->state = SESSION_STATE_READY; */ + rx_handle_initial(server, rx_state, &parsed_pkt, buf, 3, + rbudp_pkt + rbudp_headerlen, len); + rx_send_cts_ack(server, rx_state); + server->session_rx->state = SESSION_STATE_RUNNING; + } + } + break; + case CONTROL_PACKET_TYPE_ACK: + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_WAIT_CTS) { + // TODO check this is actually a CTS ack + debug_printf("CTS received"); + server->session_tx->state = SESSION_STATE_RUNNING; + } + __sync_synchronize(); + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_RUNNING) { + tx_register_acks(&cp->payload.ack, + &server->session_tx->tx_state->receiver[0]); + if (tx_acked_all(server->session_tx->tx_state)) { + debug_printf("TX done, received all acks"); + server->session_tx = NULL; // FIXME leak + } + } + break; + case CONTROL_PACKET_TYPE_NACK: + break; + default: + break; + } + /* debug_printf("done with packet"); */ + } else { + debug_printf("Non-control packet received on control socket"); + } + + /* *payload = rbudp_pkt + rbudp_headerlen; */ + /* *payloadlen = rbudp_len - rbudp_headerlen; */ + /* u32 path_idx; */ + /* memcpy(&path_idx, rbudp_pkt + sizeof(u32), sizeof(*path)); */ + /* if (path != NULL) { */ + /* *path = path_idx; */ + /* debug_printf("Path idx %u", path_idx); */ + /* } */ + } +} + +/* #define DEBUG_PRINT_PKTS */ +#ifdef DEBUG_PRINT_PKTS +void print_rbudp_pkt(const char *pkt, bool recv) { + struct hercules_header *h = (struct hercules_header *)pkt; + const char *prefix = (recv) ? "RX->" : "<-TX"; + printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, h->path, + h->seqno); + if (h->chunk_idx == UINT_MAX) { + // Control packets + const char *pl = pkt + 9; + struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; + switch (cp->type) { + case CONTROL_PACKET_TYPE_INITIAL: + printf("%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx %u, Flags " + "0x%x\n", prefix, + cp->payload.initial.filesize, cp->payload.initial.chunklen, + cp->payload.initial.timestamp, cp->payload.initial.path_index, + cp->payload.initial.flags); + break; + case CONTROL_PACKET_TYPE_ACK: + printf("%s ACK ", prefix); + for (int r = 0; r < cp->payload.ack.num_acks; r++){ + printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); + } + printf("\n"); + break; + case CONTROL_PACKET_TYPE_NACK: + printf("%s NACK \n", prefix); + for (int r = 0; r < cp->payload.ack.num_acks; r++){ + printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); + } + printf("\n"); + break; + default: + printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); + break; + } + } else { + printf("%s ** PAYLOAD **\n", prefix); + } +} +#else +void print_rbudp_pkt(const char * pkt, bool recv){ + return; +} +#endif + +struct hercules_session *make_session(struct hercules_server *server) { + struct hercules_session *s; + s = calloc(1, sizeof(*s)); + assert(s); + s->is_running = true; + s->state = SESSION_STATE_NONE; + int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, + sizeof(*s->send_queue)); + if (err != 0) { + exit_with_error(NULL, err); + } + init_send_queue(s->send_queue, BATCH_SIZE); + + return s; +} + +static struct receiver_state *rx_receive_hs(struct hercules_server *server, + struct xsk_socket_info *xsk) { + + u32 idx_rx = 0; + int ignored = 0; + size_t rcvd = xsk_ring_cons__peek(&xsk->rx, 1, &idx_rx); + if (!rcvd) + return NULL; + debug_printf("Received %lu pkts", rcvd); + u64 frame_addrs[BATCH_SIZE]; + for (size_t i = 0; i < rcvd; i++) { + u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; + frame_addrs[i] = addr; + u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; + const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); + const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); + if (rbudp_pkt) { + print_rbudp_pkt(rbudp_pkt, true); + struct rbudp_initial_pkt parsed_pkt; + if (rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len - rbudp_headerlen, &parsed_pkt)) { + debug_printf("parsed initial"); + struct hercules_session *session = make_session(server); + struct receiver_state *rx_state = make_rx_state( + session, parsed_pkt.filesize, parsed_pkt.chunklen, false); + session->rx_state = rx_state; + /* rx_handle_initial(rx_state, &parsed_pkt, pkt, 3, rbudp_pkt + rbudp_headerlen, len); */ + xsk_ring_cons__release(&xsk->rx, rcvd); + struct hercules_session s; + s.is_running = true; + submit_rx_frames(&s, xsk->umem, frame_addrs, rcvd); + return rx_state; + } else { + debug_printf("failed parse_initial"); + } + } else { + debug_printf("Dropping unparseable pkt"); + ignored++; + } + } + xsk_ring_cons__release(&xsk->rx, rcvd); + struct hercules_session s; + s.is_running = true; + submit_rx_frames(&s, xsk->umem, frame_addrs, rcvd); + return NULL; } // Helper function: open a AF_PACKET socket. @@ -2568,40 +2949,42 @@ static int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj return prog_fd; } -static void set_bpf_prgm_active(struct hercules_session *session, struct hercules_interface *iface, int prog_fd) +static void set_bpf_prgm_active(struct hercules_server *server, struct hercules_interface *iface, int prog_fd) { - int err = bpf_set_link_xdp_fd(iface->ifid, prog_fd, session->config.xdp_flags); + int err = bpf_set_link_xdp_fd(iface->ifid, prog_fd, server->config.xdp_flags); if(err) { - exit_with_error(session, -err); + exit_with_error(server, -err); } - int ret = bpf_get_link_xdp_id(iface->ifid, &iface->prog_id, session->config.xdp_flags); + int ret = bpf_get_link_xdp_id(iface->ifid, &iface->prog_id, server->config.xdp_flags); if(ret) { - exit_with_error(session, -ret); + exit_with_error(server, -ret); } } + + // XXX Workaround: the i40e driver (in zc mode) does not seem to allow sending if no program is loaded. // Load an XDP program that just passes all packets (i.e. does the same thing as no program). -static int load_xsk_pass(struct hercules_session *session) +static int load_xsk_pass(struct hercules_server *server) { int prog_fd; - for(int i = 0; i < session->num_ifaces; i++) { + for(int i = 0; i < server->num_ifaces; i++) { prog_fd = load_bpf(bpf_prgm_pass, bpf_prgm_pass_size, NULL); if(prog_fd < 0) { - exit_with_error(session, -prog_fd); + exit_with_error(server, -prog_fd); } - set_bpf_prgm_active(session, &session->ifaces[i], prog_fd); + set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); } return 0; } -static void xsk_map__add_xsk(struct hercules_session *session, xskmap map, int index, struct xsk_socket_info *xsk) +static void xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, struct xsk_socket_info *xsk) { int xsk_fd = xsk_socket__fd(xsk->xsk); if(xsk_fd < 0) { - exit_with_error(session, -xsk_fd); + exit_with_error(server, -xsk_fd); } bpf_map_update_elem(map, &index, &xsk_fd, 0); } @@ -2609,212 +2992,283 @@ static void xsk_map__add_xsk(struct hercules_session *session, xskmap map, int i /* * Load a BPF program redirecting IP traffic to the XSK. */ -static void load_xsk_redirect_userspace(struct hercules_session *session, struct rx_p_args *args[], int num_threads) +static void load_xsk_redirect_userspace(struct hercules_server *server, int num_threads) { - for(int i = 0; i < session->num_ifaces; i++) { + debug_printf("Loading XDP program for redirection"); + for(int i = 0; i < server->num_ifaces; i++) { struct bpf_object *obj; int prog_fd = load_bpf(bpf_prgm_redirect_userspace, bpf_prgm_redirect_userspace_size, &obj); if(prog_fd < 0) { - exit_with_error(session, prog_fd); + exit_with_error(server, prog_fd); } // push XSKs int xsks_map_fd = bpf_object__find_map_fd_by_name(obj, "xsks_map"); if(xsks_map_fd < 0) { - exit_with_error(session, -xsks_map_fd); + exit_with_error(server, -xsks_map_fd); } for(int s = 0; s < num_threads; s++) { - xsk_map__add_xsk(session, xsks_map_fd, s, args[s]->xsks[i]); + xsk_map__add_xsk(server, xsks_map_fd, s, server->xsk); } // push XSKs meta int zero = 0; int num_xsks_fd = bpf_object__find_map_fd_by_name(obj, "num_xsks"); if(num_xsks_fd < 0) { - exit_with_error(session, -num_xsks_fd); + exit_with_error(server, -num_xsks_fd); } bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); // push local address int local_addr_fd = bpf_object__find_map_fd_by_name(obj, "local_addr"); if(local_addr_fd < 0) { - exit_with_error(session, -local_addr_fd); - } - bpf_map_update_elem(local_addr_fd, &zero, &session->config.local_addr, 0); - - set_bpf_prgm_active(session, &session->ifaces[i], prog_fd); - } -} - -static void *tx_p(void *arg) -{ - struct sender_state *tx_state = arg; - load_xsk_pass(tx_state->session); - tx_only(tx_state); - - return NULL; -} - -struct hercules_session *hercules_init(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, - int queue, int mtu) -{ - struct hercules_session *session; - int err = posix_memalign((void **) &session, CACHELINE_SIZE, - sizeof(*session) + num_ifaces * sizeof(*session->ifaces)); - if(err != 0) { - exit_with_error(NULL, err); - } - memset(session, 0, sizeof(*session)); - session->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; - if(HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)mtu) { - printf("MTU too small (min: %lu, given: %d)", - HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen, - mtu - ); - exit_with_error(session, EINVAL); - } - session->config.ether_size = mtu; - session->config.local_addr = local_addr; - session->num_ifaces = num_ifaces; + exit_with_error(server, -local_addr_fd); + } + bpf_map_update_elem(local_addr_fd, &zero, &server->config.local_addr, 0); + + set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); + } +} + +static void *tx_p(void *arg) { + struct hercules_server *server = arg; + while (1) { + /* pthread_spin_lock(&server->biglock); */ + __sync_synchronize(); // XXX HACK + struct hercules_session *session_tx = atomic_load(&server->session_tx); + if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { + struct sender_state *tx_state = session_tx->tx_state; + /* tx_only(server, tx_state); */ + u32 chunks[BATCH_SIZE]; + u8 chunk_rcvr[BATCH_SIZE]; + u32 max_chunks_per_rcvr[1] = {BATCH_SIZE}; + + pop_completion_rings(server); + u64 next_ack_due = 0; + u32 total_chunks = BATCH_SIZE; + const u64 now = get_nsecs(); + u32 num_chunks = 0; + struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; + u64 ack_due = 0; + u32 cur_num_chunks = prepare_rcvr_chunks( + tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, + &ack_due, max_chunks_per_rcvr[0]); + debug_printf("prepared %d chunks", cur_num_chunks); + num_chunks += cur_num_chunks; + if (num_chunks > 0){ + u8 rcvr_path[1] = {0}; + produce_batch(server, session_tx, tx_state, rcvr_path, chunks, chunk_rcvr, num_chunks); + tx_state->tx_npkts_queued += num_chunks; + } + /* sleep_nsecs(1e9); // XXX FIXME */ + } + } + + return NULL; +} + +/* struct hercules_session *hercules_init(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, */ +/* int queue, int mtu) */ +/* { */ +/* struct hercules_session *session; */ +/* int err = posix_memalign((void **) &session, CACHELINE_SIZE, */ +/* sizeof(*session) + num_ifaces * sizeof(*session->ifaces)); */ +/* if(err != 0) { */ +/* exit_with_error(NULL, err); */ +/* } */ +/* memset(session, 0, sizeof(*session)); */ +/* session->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; */ +/* if(HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)mtu) { */ +/* printf("MTU too small (min: %lu, given: %d)", */ +/* HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen, */ +/* mtu */ +/* ); */ +/* exit_with_error(session, EINVAL); */ +/* } */ +/* session->config.ether_size = mtu; */ +/* session->config.local_addr = local_addr; */ +/* session->num_ifaces = num_ifaces; */ + +/* for(int i = 0; i < num_ifaces; i++) { */ +/* session->ifaces[i] = (struct hercules_interface) { */ +/* .queue = queue, */ +/* .ifid = ifindices[i], */ +/* .ethtool_rule = -1, */ +/* }; */ +/* if_indextoname(ifindices[i], session->ifaces[i].ifname); */ +/* debug_printf("using queue %d on interface %s", session->ifaces[i].queue, session->ifaces[i].ifname); */ + +/* // Open RAW socket to receive and send control messages on */ +/* // Note: at the receiver, this socket will not receive any packets once the BPF has been */ +/* // activated, which will then redirect packets to one of the XSKs. */ +/* session->control_sockfd = open_control_socket(); */ +/* if(session->control_sockfd < 0) { */ +/* exit_with_error(session, -session->control_sockfd); */ +/* } */ +/* } */ + +/* struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; */ +/* setlocale(LC_ALL, ""); */ +/* if(setrlimit(RLIMIT_MEMLOCK, &r)) { */ +/* fprintf(stderr, "ERROR: setrlimit(RLIMIT_MEMLOCK) \"%s\"\n", */ +/* strerror(errno)); */ +/* exit(EXIT_FAILURE); */ +/* } */ +/* return session; */ +/* } */ + +struct hercules_server *hercules_init_server(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, int queue, int mtu){ + debug_printf("init_server, %d interfaces, queue %d, mtu %d", num_ifaces, queue, mtu); + struct hercules_server *server; + server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); + if (server == NULL){ + debug_quit("init calloc"); + } + printf("local %llx %x %x\n", local_addr.ia, local_addr.ip, local_addr.port); + server->ifindices = ifindices; + server->num_ifaces = num_ifaces; + server->local_addr = local_addr; + server->queue = queue; + server->mtu = mtu; + server->session_rx = NULL; + server->session_tx = NULL; for(int i = 0; i < num_ifaces; i++) { - session->ifaces[i] = (struct hercules_interface) { + debug_printf("iter %d", i); + server->ifaces[i] = (struct hercules_interface) { .queue = queue, .ifid = ifindices[i], .ethtool_rule = -1, }; - if_indextoname(ifindices[i], session->ifaces[i].ifname); - debug_printf("using queue %d on interface %s", session->ifaces[i].queue, session->ifaces[i].ifname); - - // Open RAW socket to receive and send control messages on - // Note: at the receiver, this socket will not receive any packets once the BPF has been - // activated, which will then redirect packets to one of the XSKs. - session->control_sockfd = open_control_socket(); - if(session->control_sockfd < 0) { - exit_with_error(session, -session->control_sockfd); - } + if_indextoname(ifindices[i], server->ifaces[i].ifname); + debug_printf("using queue %d on interface %s", server->ifaces[i].queue, server->ifaces[i].ifname); } - struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; - setlocale(LC_ALL, ""); - if(setrlimit(RLIMIT_MEMLOCK, &r)) { - fprintf(stderr, "ERROR: setrlimit(RLIMIT_MEMLOCK) \"%s\"\n", - strerror(errno)); - exit(EXIT_FAILURE); - } - return session; + server->config.ether_size = mtu; + server->config.local_addr = local_addr; + server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + assert(server->control_sockfd != -1); + + debug_printf("init complete"); + return server; } + + + struct path_stats *make_path_stats_buffer(int num_paths) { struct path_stats *path_stats = calloc(1, sizeof(*path_stats) + num_paths * sizeof(path_stats->paths[0])); path_stats->num_paths = num_paths; return path_stats; } -static struct hercules_stats tx_stats(struct sender_state *tx_state, struct path_stats* path_stats) -{ - if(path_stats != NULL && tx_state->receiver[0].cc_states != NULL) { - if(path_stats->num_paths < tx_state->num_receivers * tx_state->max_paths_per_rcvr) { - fprintf(stderr,"stats buffer not large enough: %d given, %d required\n", path_stats->num_paths, - tx_state->num_receivers * tx_state->max_paths_per_rcvr); - exit_with_error(tx_state->session, EINVAL); - } - for(u32 r = 0; r < tx_state->num_receivers; r++) { - const struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; - for(u32 p = 0; p < receiver->num_paths; p++) { - path_stats->paths[r * tx_state->max_paths_per_rcvr + p].pps_target = receiver->cc_states[p].curr_rate; - path_stats->paths[r * tx_state->max_paths_per_rcvr + p].total_packets = receiver->cc_states[p].total_tx_npkts; - } - memset(&path_stats->paths[r * tx_state->max_paths_per_rcvr + receiver->num_paths], 0, - sizeof(path_stats->paths[0]) * (tx_state->max_paths_per_rcvr - receiver->num_paths)); - } - } - u32 completed_chunks = 0; - u64 rate_limit = 0; - for(u32 r = 0; r < tx_state->num_receivers; r++) { - const struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; - completed_chunks += tx_state->receiver[r].acked_chunks.num_set; - for(u8 p = 0; p < receiver->num_paths; p++) { - if(receiver->cc_states == NULL) { // no path-specific rate-limit - rate_limit += tx_state->rate_limit; - } else { // PCC provided limit - rate_limit += receiver->cc_states[p].curr_rate; - } - } - } - return (struct hercules_stats){ - .start_time = tx_state->start_time, - .end_time = tx_state->end_time, - .now = get_nsecs(), - .tx_npkts = tx_state->session->tx_npkts, - .rx_npkts = tx_state->session->rx_npkts, - .filesize = tx_state->filesize, - .framelen = tx_state->session->config.ether_size, - .chunklen = tx_state->chunklen, - .total_chunks = tx_state->total_chunks * tx_state->num_receivers, - .completed_chunks = completed_chunks, - .rate_limit = umin64(tx_state->rate_limit, rate_limit), - }; -} - -static struct hercules_stats rx_stats(struct receiver_state *rx_state, struct path_stats* path_stats) -{ - if(path_stats != NULL) { - if(path_stats->num_paths < rx_state->num_tracked_paths) { - fprintf(stderr,"stats buffer not large enough: %d given, %d required\n", path_stats->num_paths, - rx_state->num_tracked_paths); - exit_with_error(rx_state->session, EINVAL); - } - for(u32 p = 0; p < rx_state->num_tracked_paths; p++) { - path_stats->paths[p].total_packets = rx_state->path_state[p].rx_npkts; - } - } - return (struct hercules_stats){ - .start_time = rx_state->start_time, - .end_time = rx_state->end_time, - .now = get_nsecs(), - .tx_npkts = rx_state->session->tx_npkts, - .rx_npkts = rx_state->session->rx_npkts, - .filesize = rx_state->filesize, - .framelen = rx_state->session->config.ether_size, - .chunklen = rx_state->chunklen, - .total_chunks = rx_state->total_chunks, - .completed_chunks = rx_state->received_chunks.num_set, - .rate_limit = 0 - }; -} +/* static struct hercules_stats tx_stats(struct sender_state *tx_state, struct path_stats* path_stats) */ +/* { */ +/* if(path_stats != NULL && tx_state->receiver[0].cc_states != NULL) { */ +/* if(path_stats->num_paths < tx_state->num_receivers * tx_state->max_paths_per_rcvr) { */ +/* fprintf(stderr,"stats buffer not large enough: %d given, %d required\n", path_stats->num_paths, */ +/* tx_state->num_receivers * tx_state->max_paths_per_rcvr); */ +/* exit_with_error(tx_state->session, EINVAL); */ +/* } */ +/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ +/* const struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; */ +/* for(u32 p = 0; p < receiver->num_paths; p++) { */ +/* path_stats->paths[r * tx_state->max_paths_per_rcvr + p].pps_target = receiver->cc_states[p].curr_rate; */ +/* path_stats->paths[r * tx_state->max_paths_per_rcvr + p].total_packets = receiver->cc_states[p].total_tx_npkts; */ +/* } */ +/* memset(&path_stats->paths[r * tx_state->max_paths_per_rcvr + receiver->num_paths], 0, */ +/* sizeof(path_stats->paths[0]) * (tx_state->max_paths_per_rcvr - receiver->num_paths)); */ +/* } */ +/* } */ +/* u32 completed_chunks = 0; */ +/* u64 rate_limit = 0; */ +/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ +/* const struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; */ +/* completed_chunks += tx_state->receiver[r].acked_chunks.num_set; */ +/* for(u8 p = 0; p < receiver->num_paths; p++) { */ +/* if(receiver->cc_states == NULL) { // no path-specific rate-limit */ +/* rate_limit += tx_state->rate_limit; */ +/* } else { // PCC provided limit */ +/* rate_limit += receiver->cc_states[p].curr_rate; */ +/* } */ +/* } */ +/* } */ +/* return (struct hercules_stats){ */ +/* .start_time = tx_state->start_time, */ +/* .end_time = tx_state->end_time, */ +/* .now = get_nsecs(), */ +/* .tx_npkts = tx_state->session->tx_npkts, */ +/* .rx_npkts = tx_state->session->rx_npkts, */ +/* .filesize = tx_state->filesize, */ +/* .framelen = tx_state->session->config.ether_size, */ +/* .chunklen = tx_state->chunklen, */ +/* .total_chunks = tx_state->total_chunks * tx_state->num_receivers, */ +/* .completed_chunks = completed_chunks, */ +/* .rate_limit = umin64(tx_state->rate_limit, rate_limit), */ +/* }; */ +/* } */ + +/* static struct hercules_stats rx_stats(struct receiver_state *rx_state, struct path_stats* path_stats) */ +/* { */ +/* if(path_stats != NULL) { */ +/* if(path_stats->num_paths < rx_state->num_tracked_paths) { */ +/* fprintf(stderr,"stats buffer not large enough: %d given, %d required\n", path_stats->num_paths, */ +/* rx_state->num_tracked_paths); */ +/* exit_with_error(rx_state->session, EINVAL); */ +/* } */ +/* for(u32 p = 0; p < rx_state->num_tracked_paths; p++) { */ +/* path_stats->paths[p].total_packets = rx_state->path_state[p].rx_npkts; */ +/* } */ +/* } */ +/* return (struct hercules_stats){ */ +/* .start_time = rx_state->start_time, */ +/* .end_time = rx_state->end_time, */ +/* .now = get_nsecs(), */ +/* .tx_npkts = rx_state->session->tx_npkts, */ +/* .rx_npkts = rx_state->session->rx_npkts, */ +/* .filesize = rx_state->filesize, */ +/* .framelen = rx_state->session->config.ether_size, */ +/* .chunklen = rx_state->chunklen, */ +/* .total_chunks = rx_state->total_chunks, */ +/* .completed_chunks = rx_state->received_chunks.num_set, */ +/* .rate_limit = 0 */ +/* }; */ +/* } */ struct hercules_stats hercules_get_stats(struct hercules_session *session, struct path_stats* path_stats) { libbpf_smp_rmb(); - if(!session->tx_state && !session->rx_state) { - return (struct hercules_stats){ - .start_time = 0 - }; - } + (void)session; + (void)path_stats; + return (struct hercules_stats){.start_time = 0}; + /* if(!session->tx_state && !session->rx_state) { */ + /* return (struct hercules_stats){ */ + /* .start_time = 0 */ + /* }; */ + /* } */ - if(session->tx_state) { - return tx_stats(session->tx_state, path_stats); - } else { - return rx_stats(session->rx_state, path_stats); - } + /* if(session->tx_state) { */ + /* return tx_stats(session->tx_state, path_stats); */ + /* } else { */ + /* return rx_stats(session->rx_state, path_stats); */ + /* } */ } -static pthread_t start_thread(struct hercules_session *session, void *(start_routine), void *arg) +static pthread_t start_thread(struct hercules_server *server, void *(start_routine), void *arg) { pthread_t pt; int ret = pthread_create(&pt, NULL, start_routine, arg); if(ret) - exit_with_error(session, ret); + exit_with_error(server, ret); return pt; } -static void join_thread(struct hercules_session *session, pthread_t pt) +static void join_thread(struct hercules_server *server, pthread_t pt) { int ret = pthread_join(pt, NULL); if(ret) { - exit_with_error(session, ret); + exit_with_error(server, ret); } } @@ -2823,246 +3277,493 @@ hercules_tx(struct hercules_session *session, const char *filename, int offset, const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc, int xdp_mode, int num_threads) { - // Open mmaped send file - int f = open(filename, O_RDONLY); - if(f == -1) { - exit_with_error(session, errno); - } - - struct stat stat; - int ret = fstat(f, &stat); - if(ret) { - exit_with_error(session, errno); - } - const size_t filesize = length == -1 ? stat.st_size : length; - offset = offset < 0 ? 0 : offset; - - if(offset + filesize > (size_t)stat.st_size) { - fprintf(stderr, "ERR: offset + length > filesize. Out of bounds\n"); - exit_with_error(session, EINVAL); - } - - char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE -#ifndef NO_PRELOAD - | MAP_POPULATE -#endif - , f, offset); - if(mem == MAP_FAILED) { - fprintf(stderr, "ERR: memory mapping failed\n"); - exit_with_error(session, errno); - } - close(f); - - u32 chunklen = paths_per_dest[0].payloadlen - rbudp_headerlen; - for(int d = 0; d < num_dests; d++) { - for(int p = 0; p < num_paths[d]; p++) { - chunklen = umin32(chunklen, paths_per_dest[d * max_paths + p].payloadlen - rbudp_headerlen); - } - } - struct sender_state *tx_state = init_tx_state(session, filesize, chunklen, max_rate_limit, mem, destinations, - paths_per_dest, num_dests, num_paths, max_paths); - libbpf_smp_rmb(); - session->tx_state = tx_state; - libbpf_smp_wmb(); - - if(!tx_handshake(tx_state)) { - exit_with_error(session, ETIMEDOUT); - } - - if(enable_pcc) { - u64 now = get_nsecs(); - for(int d = 0; d < num_dests; d++) { - struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; - receiver->cc_states = init_ccontrol_state( - max_rate_limit, - tx_state->total_chunks, - *num_paths, - max_paths, - max_paths * num_dests - ); - ccontrol_update_rtt(&receiver->cc_states[0], receiver->handshake_rtt); - fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: %fs, MI: %fs\n", - d, receiver->handshake_rtt / 1e9, receiver->cc_states[0].pcc_mi_duration); - - // make sure tx_only() performs RTT estimation on every enabled path - for(u32 p = 1; p < receiver->num_paths; p++) { - receiver->paths[p].next_handshake_at = now; - } - } - } - - tx_state->rate_limit = max_rate_limit; - - // Wait for CTS from receiver - printf("Waiting for receiver to get ready..."); fflush(stdout); - if(!tx_await_cts(tx_state)) { - exit_with_error(session, ETIMEDOUT); - } - printf(" OK\n"); - - init_send_queue(tx_state->send_queue, BATCH_SIZE); - - struct tx_send_p_args *args[num_threads]; - for(int i = 0; i < session->num_ifaces; i++) { - session->ifaces[i].xsks = calloc(num_threads, sizeof(*session->ifaces[i].xsks)); - session->ifaces[i].umem = create_umem(session, i); - submit_initial_tx_frames(session, session->ifaces[i].umem); - submit_initial_rx_frames(session, session->ifaces[i].umem); - } - - pthread_t senders[num_threads]; - session->is_running = true; - for(int t = 0; t < num_threads; t++) { - args[t] = malloc(sizeof(*args[t]) + session->num_ifaces * sizeof(*args[t]->xsks)); - args[t]->tx_state = tx_state; - for(int i = 0; i < session->num_ifaces; i++) { - args[t]->xsks[i] = xsk_configure_socket(session, i, session->ifaces[i].umem, session->ifaces[i].queue, - XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, xdp_mode); - session->ifaces[i].xsks[t] = args[t]->xsks[i]; - } - senders[t] = start_thread(session, tx_send_p, args[t]); - } - - tx_state->start_time = get_nsecs(); - pthread_t worker = start_thread(session, tx_p, tx_state); - - tx_recv_control_messages(tx_state); - - tx_state->end_time = get_nsecs(); - session->is_running = false; - join_thread(session, worker); - - if(!session->is_closed) { - session->is_closed = true; - remove_xdp_program(session); - } - for(int t = 0; t < num_threads; t++) { - join_thread(session, senders[t]); - for(int i = 0; i < session->num_ifaces; i++) { - close_xsk(args[t]->xsks[i]); - } - } - for(int i = 0; i < session->num_ifaces; i++) { - destroy_umem(session->ifaces[i].umem); - } - destroy_send_queue(tx_state->send_queue); - - struct hercules_stats stats = tx_stats(tx_state, NULL); - - if(enable_pcc) { - for(int d = 0; d < num_dests; d++) { - destroy_ccontrol_state(tx_state->receiver[d].cc_states, num_paths[d]); - } - } - close(session->control_sockfd); - destroy_tx_state(tx_state); - session->tx_state = NULL; - return stats; + (void)session; + (void)filename; + (void)offset; + (void)length; + (void)destinations; + (void)paths_per_dest; + (void)num_dests; + (void)num_paths; + (void)max_paths; + (void)max_rate_limit; + (void)enable_pcc; + (void)xdp_mode; + (void)num_threads; + return (struct hercules_stats){}; + /* // Open mmaped send file */ +/* int f = open(filename, O_RDONLY); */ +/* if(f == -1) { */ +/* exit_with_error(session, errno); */ +/* } */ + +/* struct stat stat; */ +/* int ret = fstat(f, &stat); */ +/* if(ret) { */ +/* exit_with_error(session, errno); */ +/* } */ +/* const size_t filesize = length == -1 ? stat.st_size : length; */ +/* offset = offset < 0 ? 0 : offset; */ + +/* if(offset + filesize > (size_t)stat.st_size) { */ +/* fprintf(stderr, "ERR: offset + length > filesize. Out of bounds\n"); */ +/* exit_with_error(session, EINVAL); */ +/* } */ + +/* char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE */ +/* #ifndef NO_PRELOAD */ +/* | MAP_POPULATE */ +/* #endif */ +/* , f, offset); */ +/* if(mem == MAP_FAILED) { */ +/* fprintf(stderr, "ERR: memory mapping failed\n"); */ +/* exit_with_error(session, errno); */ +/* } */ +/* close(f); */ + +/* u32 chunklen = paths_per_dest[0].payloadlen - rbudp_headerlen; */ +/* for(int d = 0; d < num_dests; d++) { */ +/* for(int p = 0; p < num_paths[d]; p++) { */ +/* chunklen = umin32(chunklen, paths_per_dest[d * max_paths + p].payloadlen - rbudp_headerlen); */ +/* } */ +/* } */ +/* struct sender_state *tx_state = init_tx_state(session, filesize, chunklen, max_rate_limit, mem, destinations, */ +/* paths_per_dest, num_dests, num_paths, max_paths); */ +/* libbpf_smp_rmb(); */ +/* session->tx_state = tx_state; */ +/* libbpf_smp_wmb(); */ + +/* if(!tx_handshake(tx_state)) { */ +/* exit_with_error(session, ETIMEDOUT); */ +/* } */ + +/* if(enable_pcc) { */ +/* u64 now = get_nsecs(); */ +/* for(int d = 0; d < num_dests; d++) { */ +/* struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; */ +/* receiver->cc_states = init_ccontrol_state( */ +/* max_rate_limit, */ +/* tx_state->total_chunks, */ +/* *num_paths, */ +/* max_paths, */ +/* max_paths * num_dests */ +/* ); */ +/* ccontrol_update_rtt(&receiver->cc_states[0], receiver->handshake_rtt); */ +/* fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: %fs, MI: %fs\n", */ +/* d, receiver->handshake_rtt / 1e9, receiver->cc_states[0].pcc_mi_duration); */ + +/* // make sure tx_only() performs RTT estimation on every enabled path */ +/* for(u32 p = 1; p < receiver->num_paths; p++) { */ +/* receiver->paths[p].next_handshake_at = now; */ +/* } */ +/* } */ +/* } */ + +/* tx_state->rate_limit = max_rate_limit; */ + +/* // Wait for CTS from receiver */ +/* printf("Waiting for receiver to get ready..."); fflush(stdout); */ +/* if(!tx_await_cts(tx_state)) { */ +/* exit_with_error(session, ETIMEDOUT); */ +/* } */ +/* printf(" OK\n"); */ + +/* init_send_queue(tx_state->send_queue, BATCH_SIZE); */ + +/* struct tx_send_p_args *args[num_threads]; */ +/* for(int i = 0; i < session->num_ifaces; i++) { */ +/* session->ifaces[i].xsks = calloc(num_threads, sizeof(*session->ifaces[i].xsks)); */ +/* session->ifaces[i].umem = create_umem(session, i); */ +/* submit_initial_tx_frames(session, session->ifaces[i].umem); */ +/* submit_initial_rx_frames(session, session->ifaces[i].umem); */ +/* } */ + +/* pthread_t senders[num_threads]; */ +/* session->is_running = true; */ +/* for(int t = 0; t < num_threads; t++) { */ +/* args[t] = malloc(sizeof(*args[t]) + session->num_ifaces * sizeof(*args[t]->xsks)); */ +/* args[t]->tx_state = tx_state; */ +/* for(int i = 0; i < session->num_ifaces; i++) { */ +/* args[t]->xsks[i] = xsk_configure_socket(session, i, session->ifaces[i].umem, session->ifaces[i].queue, */ +/* XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, xdp_mode); */ +/* session->ifaces[i].xsks[t] = args[t]->xsks[i]; */ +/* } */ +/* senders[t] = start_thread(session, tx_send_p, args[t]); */ +/* } */ + +/* tx_state->start_time = get_nsecs(); */ +/* pthread_t worker = start_thread(session, tx_p, tx_state); */ + +/* tx_recv_control_messages(tx_state); */ + +/* tx_state->end_time = get_nsecs(); */ +/* session->is_running = false; */ +/* join_thread(session, worker); */ + +/* if(!session->is_closed) { */ +/* session->is_closed = true; */ +/* remove_xdp_program(session); */ +/* } */ +/* for(int t = 0; t < num_threads; t++) { */ +/* join_thread(session, senders[t]); */ +/* for(int i = 0; i < session->num_ifaces; i++) { */ +/* close_xsk(args[t]->xsks[i]); */ +/* } */ +/* } */ +/* for(int i = 0; i < session->num_ifaces; i++) { */ +/* destroy_umem(session->ifaces[i].umem); */ +/* } */ +/* destroy_send_queue(tx_state->send_queue); */ + +/* struct hercules_stats stats = tx_stats(tx_state, NULL); */ + +/* if(enable_pcc) { */ +/* for(int d = 0; d < num_dests; d++) { */ +/* destroy_ccontrol_state(tx_state->receiver[d].cc_states, num_paths[d]); */ +/* } */ +/* } */ +/* close(session->control_sockfd); */ +/* destroy_tx_state(tx_state); */ +/* session->tx_state = NULL; */ +/* return stats; */ } struct hercules_stats hercules_rx(struct hercules_session *session, const char *filename, int xdp_mode, bool configure_queues, int accept_timeout, int num_threads, bool is_pcc_benchmark) { - struct receiver_state *rx_state = rx_accept(session, accept_timeout, is_pcc_benchmark); - if(rx_state == NULL) { - exit_with_error(session, ETIMEDOUT); - } - libbpf_smp_rmb(); - session->rx_state = rx_state; - libbpf_smp_wmb(); - - pthread_t rtt_estimator; - if(configure_queues) { - rtt_estimator = start_thread(session, rx_rtt_and_configure, rx_state); - } else { - rtt_estimator = start_thread(session, rx_get_rtt_estimate, rx_state); - } - debug_printf("Filesize %lu Bytes, %u total chunks of size %u.", - rx_state->filesize, rx_state->total_chunks, rx_state->chunklen); - printf("Preparing file for receive..."); - fflush(stdout); - rx_state->mem = rx_mmap(session, filename, rx_state->filesize); - printf(" OK\n"); - join_thread(session, rtt_estimator); - debug_printf("cts_rtt: %fs", rx_state->handshake_rtt / 1e6); - - struct rx_p_args *worker_args[num_threads]; - for(int i = 0; i < session->num_ifaces; i++) { - session->ifaces[i].xsks = calloc(num_threads, sizeof(*session->ifaces[i].xsks)); - session->ifaces[i].umem = create_umem(session, i); - submit_initial_tx_frames(session, session->ifaces[i].umem); - submit_initial_rx_frames(session, session->ifaces[i].umem); - } - for(int t = 0; t < num_threads; t++) { - worker_args[t] = malloc(sizeof(*worker_args) + session->num_ifaces * sizeof(*worker_args[t]->xsks)); - worker_args[t]->rx_state = rx_state; - for(int i = 0; i < session->num_ifaces; i++) { - worker_args[t]->xsks[i] = xsk_configure_socket(session, i, session->ifaces[i].umem, - session->ifaces[i].queue, - XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, xdp_mode); - session->ifaces[i].xsks[t] = worker_args[t]->xsks[i]; - } - } - - load_xsk_redirect_userspace(session, worker_args, num_threads); - if(configure_queues) { - configure_rx_queues(session); - } - - rx_state->start_time = get_nsecs(); - session->is_running = true; - - pthread_t worker[num_threads]; - for(int t = 0; t < num_threads; t++) { - worker[t] = start_thread(session, rx_p, worker_args[t]); - } - - rx_send_cts_ack(rx_state); // send Clear To Send ACK - pthread_t trickle_nacks = start_thread(session, rx_trickle_nacks, rx_state); - rx_trickle_acks(rx_state); - rx_send_acks(rx_state); - - rx_state->end_time = get_nsecs(); - session->is_running = false; - - join_thread(session, trickle_nacks); - for(int q = 0; q < num_threads; q++) { - join_thread(session, worker[q]); - } - - struct hercules_stats stats = rx_stats(rx_state, NULL); - - for(int i = 0; i < session->num_ifaces; i++) { - for(int t = 0; t < num_threads; t++) { - close_xsk(worker_args[t]->xsks[i]); - } - destroy_umem(session->ifaces[i].umem); - } - if(!session->is_closed) { - session->is_closed = true; - unconfigure_rx_queues(session); - remove_xdp_program(session); - } - bitset__destroy(&rx_state->received_chunks); - close(session->control_sockfd); - return stats; -} + (void)session; + (void)filename; + (void)xdp_mode; + (void)configure_queues; + (void)accept_timeout; + (void)num_threads; + (void)is_pcc_benchmark; + return (struct hercules_stats){}; + /* struct receiver_state *rx_state = rx_accept(session, accept_timeout, is_pcc_benchmark); */ + /* if(rx_state == NULL) { */ + /* exit_with_error(session, ETIMEDOUT); */ + /* } */ + /* libbpf_smp_rmb(); */ + /* session->rx_state = rx_state; */ + /* libbpf_smp_wmb(); */ + + /* pthread_t rtt_estimator; */ + /* if(configure_queues) { */ + /* rtt_estimator = start_thread(session, rx_rtt_and_configure, rx_state); */ + /* } else { */ + /* rtt_estimator = start_thread(session, rx_get_rtt_estimate, rx_state); */ + /* } */ + /* debug_printf("Filesize %lu Bytes, %u total chunks of size %u.", */ + /* rx_state->filesize, rx_state->total_chunks, rx_state->chunklen); */ + /* printf("Preparing file for receive..."); */ + /* fflush(stdout); */ + /* rx_state->mem = rx_mmap(session, filename, rx_state->filesize); */ + /* printf(" OK\n"); */ + /* join_thread(session, rtt_estimator); */ + /* debug_printf("cts_rtt: %fs", rx_state->handshake_rtt / 1e6); */ + + /* struct rx_p_args *worker_args[num_threads]; */ + /* for(int i = 0; i < session->num_ifaces; i++) { */ + /* session->ifaces[i].xsks = calloc(num_threads, sizeof(*session->ifaces[i].xsks)); */ + /* session->ifaces[i].umem = create_umem(session, i); */ + /* submit_initial_tx_frames(session, session->ifaces[i].umem); */ + /* submit_initial_rx_frames(session, session->ifaces[i].umem); */ + /* } */ + /* for(int t = 0; t < num_threads; t++) { */ + /* worker_args[t] = malloc(sizeof(*worker_args) + session->num_ifaces * sizeof(*worker_args[t]->xsks)); */ + /* worker_args[t]->rx_state = rx_state; */ + /* for(int i = 0; i < session->num_ifaces; i++) { */ + /* worker_args[t]->xsks[i] = xsk_configure_socket(session, i, session->ifaces[i].umem, */ + /* session->ifaces[i].queue, */ + /* XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, xdp_mode); */ + /* session->ifaces[i].xsks[t] = worker_args[t]->xsks[i]; */ + /* } */ + /* } */ + + /* load_xsk_redirect_userspace(session, worker_args, num_threads); */ + /* if(configure_queues) { */ + /* configure_rx_queues(session); */ + /* } */ + + /* rx_state->start_time = get_nsecs(); */ + /* session->is_running = true; */ + + /* pthread_t worker[num_threads]; */ + /* for(int t = 0; t < num_threads; t++) { */ + /* worker[t] = start_thread(session, rx_p, worker_args[t]); */ + /* } */ + /* debug_printf("Started worker threads"); */ + + /* rx_send_cts_ack(rx_state); // send Clear To Send ACK */ + /* pthread_t trickle_nacks = start_thread(session, rx_trickle_nacks, rx_state); */ + /* debug_printf("Started trickle NACK thread"); */ + /* rx_trickle_acks(rx_state); */ + /* rx_send_acks(rx_state); */ + + /* rx_state->end_time = get_nsecs(); */ + /* session->is_running = false; */ + + /* debug_printf("Joining threads"); */ + /* join_thread(session, trickle_nacks); */ + /* for(int q = 0; q < num_threads; q++) { */ + /* join_thread(session, worker[q]); */ + /* } */ + + /* struct hercules_stats stats = rx_stats(rx_state, NULL); */ + + /* for(int i = 0; i < session->num_ifaces; i++) { */ + /* for(int t = 0; t < num_threads; t++) { */ + /* close_xsk(worker_args[t]->xsks[i]); */ + /* } */ + /* destroy_umem(session->ifaces[i].umem); */ + /* } */ + /* if(!session->is_closed) { */ + /* session->is_closed = true; */ + /* unconfigure_rx_queues(session); */ + /* remove_xdp_program(session); */ + /* } */ + /* bitset__destroy(&rx_state->received_chunks); */ + /* close(session->control_sockfd); */ + /* return stats; */ +} + +void hercules_close(struct hercules_server *server) +{ + (void)server; + return; + /* if(!session->is_closed) { */ + /* // Only essential cleanup. */ + /* session->is_closed = true; */ + /* session->is_running = false; // stop it, if not already stopped (benchmark mode) */ + /* remove_xdp_program(session); */ + /* unconfigure_rx_queues(session); */ + /* } */ + /* if(session->rx_state) { */ + /* free(session->rx_state); */ + /* session->rx_state = NULL; */ + /* } */ + /* if(session->tx_state) { */ + /* destroy_tx_state(session->tx_state); */ + /* session->tx_state = NULL; */ + /* } */ +} + +static void socket_handler(void *arg) { + /* struct hercules_server *server = arg; */ + char buf[1024]; + while (1) { + /* memset(&buf, 0, 1024); */ + /* struct sockaddr_un client; */ + /* socklen_t clen = sizeof(client); */ + /* int n = recvfrom(usock, &buf, 1000, 0, &client, &clen); */ + /* if (n > 0) { */ + /* printf("--> [%s] %s\n", client.sun_path, buf); */ + /* sendto(usock, "OK", 3, 0, &client, sizeof(client)); */ + /* } */ + } +} + +static void xdp_setup(struct hercules_server *server, int xdp_mode){ + // Prepare UMEM for XSK socket + void *umem_buf; + int ret = posix_memalign(&umem_buf, getpagesize(), + NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + if (ret) { + debug_quit("Allocating umem buffer"); + } + debug_printf("Allocated umem buffer"); + struct xsk_umem_info *umem = xsk_configure_umem_server( + server, 0, umem_buf, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + debug_printf("Configured umem"); + server->ifaces[0].xsks = calloc(1, sizeof(*server->ifaces[0].xsks)); + server->ifaces[0].umem = umem; + submit_initial_tx_frames(NULL, umem); + submit_initial_rx_frames(NULL, umem); + debug_printf("umem created"); + debug_printf("umem interface %d %s, queue %d", umem->iface->ifid, + umem->iface->ifname, umem->iface->queue); + + // Create XSK socket + struct xsk_socket_info *xsk; + xsk = calloc(1, sizeof(*xsk)); + if (!xsk) { + debug_quit("xsk calloc"); + } + xsk->umem = umem; + + assert(server->ifaces[0].ifid == umem->iface->ifid); + struct xsk_socket_config cfg; + cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; + cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; + cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; + cfg.xdp_flags = server->config.xdp_flags; + cfg.bind_flags = xdp_mode; + ret = xsk_socket__create_shared(&xsk->xsk, "ens5f0", 0, umem->umem, &xsk->rx, + &xsk->tx, &umem->fq, &umem->cq, &cfg); + if (ret) { + debug_quit("xsk socket create"); + } + server->xsk = xsk; + debug_printf("xsk socket created"); + load_xsk_redirect_userspace(server, 1); + debug_printf("XSK stuff complete"); + // SETUP COMPLETE + server->ifaces[0].xsks = &xsk; + server->umem = umem; +} + +void hercules_main(struct hercules_server *server, int xdp_mode) { + debug_printf("Hercules main"); + debug_printf("#ifaces %d, mtu %d, queue %d", server->num_ifaces, server->mtu, + server->queue); + + xdp_setup(server, xdp_mode); + + + // Start socket handler thread +/* debug_printf("Starting socket thread"); */ +/* pthread_t socket_handler_thread = start_thread(NULL, socket_handler, server); */ + + + // Start event receiver thread +debug_printf("Starting event receiver thread"); + pthread_t events = start_thread(NULL, events_p, server); + + // XXX only needed for PCC + /* // Start the NACK sender thread */ + /* debug_printf("starting NACK trickle thread"); */ + /* pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); */ + + // Start the ACK sender thread + debug_printf("starting ACK trickle thread"); + pthread_t trickle_acks = start_thread(NULL, rx_trickle_acks, server); + + + // Start the RX worker thread + debug_printf("starting thread rx_p"); + pthread_t rx_p_thread = start_thread(NULL, rx_p, server); + + // Start the TX worker thread + debug_printf("starting thread tx_send_p"); + pthread_t tx_send_p_thread = start_thread(NULL, tx_send_p, server); + + // Start the TX scheduler thread + debug_printf("starting thread tx_p"); + pthread_t tx_p_thread = start_thread(NULL, tx_p, server); + + while (1) { + // XXX STOP HERE + // Get to here without crashing and wait + if (server->n_pending > 0) { + if (server->session_tx == NULL) { + debug_printf("Starting new TX"); + int f = open(server->pending[server->n_pending - 1], O_RDONLY); + if (f == -1) { + exit_with_error(NULL, errno); + } -void hercules_close(struct hercules_session *session) -{ - if(!session->is_closed) { - // Only essential cleanup. - session->is_closed = true; - session->is_running = false; // stop it, if not already stopped (benchmark mode) - remove_xdp_program(session); - unconfigure_rx_queues(session); - } - if(session->rx_state) { - free(session->rx_state); - session->rx_state = NULL; + struct stat stat; + int ret = fstat(f, &stat); + if (ret) { + exit_with_error(NULL, errno); + } + const size_t filesize = stat.st_size; + char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, f, 0); + if (mem == MAP_FAILED) { + fprintf(stderr, "ERR: memory mapping failed\n"); + exit_with_error(NULL, errno); + } + close(f); + + u32 chunklen = server->paths_per_dest[0].payloadlen - rbudp_headerlen; + /* pthread_spin_lock(&server->biglock); */ + server->session_tx = make_session(server); + struct sender_state *tx_state = init_tx_state( + server->session_tx, filesize, chunklen, server->rate_limit, mem, + server->destinations, server->paths_per_dest, server->num_dests, + server->num_paths, server->max_paths); + server->session_tx->tx_state = tx_state; + unsigned long timestamp = get_nsecs(); + tx_send_initial(server, &tx_state->receiver[0].paths[0], + tx_state->filesize, tx_state->chunklen, timestamp, 0, + true); + server->session_tx->state = SESSION_STATE_WAIT_HS; + + /* pthread_spin_unlock(&server->biglock); */ + server->n_pending--; + sleep_nsecs(2e9); + } } - if(session->tx_state) { - destroy_tx_state(session->tx_state); - session->tx_state = NULL; - } + } + + /* while(1){ */ + /* // XXX */ + /* } */ +} + +int main(int argc, char *argv[]) { + usock = socket(AF_UNIX, SOCK_DGRAM, 0); + assert(usock > 0); + struct sockaddr_un name; + name.sun_family = AF_UNIX; + strcpy(name.sun_path, "/var/hercules.sock"); + unlink("/var/hercules.sock"); + int ret = bind(usock, &name, sizeof(name)); + assert(!ret); + + int ifindices[1] = {3}; + int num_ifaces = 1; + struct hercules_app_addr local_addr = { + .ia = 0xe20f0100aaff1100ULL, .ip = 0x232a8c0UL, .port = 0x7b00}; + struct hercules_app_addr receiver = local_addr; + int queue = 0; + int mtu = 1200; + if (argc == 2 && strcmp(argv[1], "TX") == 0) { + // Sender + // scionlab1, 192.168.50.1:123 + local_addr.ip = 0x132a8c0UL; + } + struct hercules_server *server = + hercules_init_server(ifindices, num_ifaces, local_addr, queue, mtu); + if (argc == 2 && strcmp(argv[1], "TX") == 0) { + // Sender + server->n_pending = 1; + strncpy(server->pending[0], "hercules", 20); + strncpy(server->pending[1], "smallfile", 20); + server->destinations = &receiver; + server->num_dests = 1; + int np = 1; + server->num_paths = &np; // num paths per dest + server->max_paths = 1; // max paths per dest + server->rate_limit = 10e6; + server->enable_pcc = false; + + struct hercules_path path; + debug_printf("path pointer %p", &path); + char buf[2000]; + buf[0] = 0x00; + + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + int len = 1; + debug_printf("sending %d bytes", len); + sendto(usock, buf, len, 0, &monitor, sizeof(monitor)); + char recvbuf[2000]; + memset(&recvbuf, 0, 2000); + int n = recv(usock, &recvbuf, sizeof(recvbuf), 0); + debug_printf("receive %d bytes", n); + u32 headerlen = *(u32 *)recvbuf; + path.headerlen = headerlen; + memcpy(path.header.header, recvbuf + 4, headerlen); + memcpy(&path.header.checksum, recvbuf + 4 + headerlen, 2); + path.enabled = true; + path.replaced = false; + path.payloadlen = 1200 - headerlen; + path.framelen = 1200; + path.ifid = 3; + server->paths_per_dest = &path; + } + + hercules_main(server, XDP_COPY); } diff --git a/hercules.go b/hercules.go index 675b7f5..5606b73 100644 --- a/hercules.go +++ b/hercules.go @@ -18,6 +18,7 @@ import ( "errors" "flag" "fmt" + "net" "os" "os/signal" "strings" @@ -126,6 +127,30 @@ func realMain() error { os.Exit(0) } + etherLen = 1200; + /// XXX BREAK + daemon, err := net.ResolveUnixAddr("unixgram", "/var/hercules.sock") + fmt.Println(daemon, err) + local, err := net.ResolveUnixAddr("unixgram", "/var/herculesmon.sock") + fmt.Println(local, err) + os.Remove("/var/herculesmon.sock") + usock, err := net.ListenUnixgram("unixgram", local) + fmt.Println(usock, err) + + for { + buf := make([]byte, 2000) + fmt.Println("read...") + n, a, err := usock.ReadFromUnix(buf) + fmt.Println(n, a, err) + if n > 0 { + replyPath, err := getReplyPathHeader(buf) + fmt.Println(replyPath, err) + // the interface index is handled C-internally + b := SerializePath(replyPath) + usock.WriteToUnix(b, a) + } + } + if err := configureLogger(flags.verbose); err != nil { return err } @@ -245,6 +270,7 @@ func configureLogger(verbosity string) error { // Assumes config to be strictly valid. func mainTx(config *HerculesSenderConfig) (err error) { + mainServerSend(config) // since config is valid, there can be no errors here: etherLen = config.MTU localAddress, _ := snet.ParseUDPAddr(config.LocalAddress) @@ -283,6 +309,7 @@ func mainTx(config *HerculesSenderConfig) (err error) { // Assumes config to be strictly valid. func mainRx(config *HerculesReceiverConfig) error { + return mainServer(config) // since config is valid, there can be no errors here: etherLen = config.MTU interfaces, _ := config.interfaces() @@ -306,6 +333,63 @@ func mainRx(config *HerculesReceiverConfig) error { return nil } +func mainServer(config *HerculesReceiverConfig) error { + // since config is valid, there can be no errors here: + etherLen = config.MTU + interfaces, _ := config.interfaces() + localAddr, _ := snet.ParseUDPAddr(config.LocalAddress) + server := herculesInitServer(interfaces, localAddr, config.Queue, config.MTU) + + fmt.Println("mainServer(go)", etherLen, interfaces, localAddr) + + // aggregateStats := aggregateStats{} + done := make(chan struct{}, 1) + // go statsDumper(session, false, config.DumpInterval, &aggregateStats, config.PerPathStatsFile, config.ExpectNumPaths, done, config.PCCBenchMarkDuration) + // go cleanupOnSignal(session) + herculesMain(server, config.OutputFile, config.getXDPMode(), config.NumThreads, config.ConfigureQueues, config.AcceptTimeout) + done <- struct{}{} + // printSummary(stats, aggregateStats) + <-done // wait for path stats to be flushed + // herculesClose(server) + return nil +} +func mainServerSend(config *HerculesSenderConfig) (err error) { + // since config is valid, there can be no errors here: + etherLen = config.MTU + localAddress, _ := snet.ParseUDPAddr(config.LocalAddress) + interfaces, _ := config.interfaces() + destinations := config.destinations() + + pm, err := initNewPathManager( + interfaces, + destinations, + localAddress, + uint64(config.RateLimit)*uint64(config.MTU)) + if err != nil { + return err + } + + pm.choosePaths() + server := herculesInitServer(interfaces, localAddress, config.Queue, config.MTU) + pm.pushPathsServer(server) + if !pm.canSendToAllDests() { + return errors.New("some destinations are unreachable, abort") + } + + // aggregateStats := aggregateStats{} + done := make(chan struct{}, 1) + // go statsDumper(session, true, config.DumpInterval, &aggregateStats, config.PerPathStatsFile, config.NumPathsPerDest*len(config.Destinations), done, config.PCCBenchMarkDuration) + // go cleanupOnSignal(session) + herculesMainServer(server, config.TransmitFile, config.FileOffset, config.FileLength, + destinations, pm, config.RateLimit, config.EnablePCC, config.getXDPMode(), + config.NumThreads) + done <- struct{}{} + // printSummary(stats, aggregateStats) + <-done // wait for path stats to be flushed + // herculesClose(session) + return nil +} + func cleanupOnSignal(session *HerculesSession) { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) diff --git a/hercules.h b/hercules.h index 4d6fd4d..03c05c5 100644 --- a/hercules.h +++ b/hercules.h @@ -18,10 +18,12 @@ #include #include #include +#include #define MAX_NUM_SOCKETS 256 #define HERCULES_MAX_HEADERLEN 256 + struct hercules_path_header { const char header[HERCULES_MAX_HEADERLEN]; //!< headerlen bytes __u16 checksum; //SCION L4 checksum over header with 0 payload @@ -41,6 +43,7 @@ struct hercules_path { atomic_bool replaced; }; + // Connection information struct hercules_app_addr { /** SCION IA. In network byte order. */ @@ -54,8 +57,9 @@ struct hercules_app_addr { typedef __u64 ia; +struct hercules_server; struct hercules_session *hercules_init(int *ifindices, int num_ifaces, struct hercules_app_addr local_addr, int queue, int mtu); -void hercules_close(struct hercules_session *session); +void hercules_close(struct hercules_server *server); struct path_stats_path { __u64 total_packets; @@ -104,8 +108,25 @@ hercules_tx(struct hercules_session *session, const char *filename, int offset, const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc, int xdp_mode, int num_threads); +struct receiver_state; // Initiate receiver, waiting for a transmitter to initiate the file transfer. struct hercules_stats hercules_rx(struct hercules_session *session, const char *filename, int xdp_mode, bool configure_queues, int accept_timeout, int num_threads, bool is_pcc_benchmark); + +struct rx_print_args{ + struct hercules_session *session; + struct xsk_socket_info *xsk; + struct send_queue *rxq; +}; + +struct hercules_server * +hercules_init_server(int *ifindices, int num_ifaces, + const struct hercules_app_addr local_addr, int queue, + int mtu); +void hercules_main(struct hercules_server *server, int xdp_mode); +void hercules_main_sender(struct hercules_server *server, int xdp_mode, const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc); +void print_rbudp_pkt(const char *pkt, bool recv); +struct hercules_session *make_session(struct hercules_server *server); + #endif // __HERCULES_H__ diff --git a/network.go b/network.go deleted file mode 100644 index 4e5fa0b..0000000 --- a/network.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2023 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "fmt" - "github.com/scionproto/scion/pkg/daemon" - "github.com/scionproto/scion/pkg/snet" - "os" -) - -func exit(err error) { - fmt.Fprintf(os.Stderr, "Error initializing SCION network: %v\n", err) - os.Exit(1) -} - -func newDaemonConn(ctx context.Context) (daemon.Connector, error) { - address, ok := os.LookupEnv("SCION_DAEMON_ADDRESS") - if !ok { - address = daemon.DefaultAPIAddress - } - daemonConn, err := daemon.NewService(address).Connect(ctx) - if err != nil { - return nil, fmt.Errorf("unable to connect to SCIOND at %s (override with SCION_DAEMON_ADDRESS): %w", address, err) - } - return daemonConn, nil -} - -func newPathQuerier() snet.PathQuerier { - ctx := context.Background() - daemonConn, err := newDaemonConn(ctx) - if err != nil { - exit(err) - } - localIA, err := daemonConn.LocalIA(ctx) - if err != nil { - exit(err) - } - return daemon.Querier{ - Connector: daemonConn, - IA: localIA, - } -} diff --git a/packet.h b/packet.h index 08b0137..931af05 100644 --- a/packet.h +++ b/packet.h @@ -63,6 +63,12 @@ struct scionaddrhdr_ipv4 { __u32 src_ip; }; +struct hercules_header { + __u32 chunk_idx; + __u8 path; + __u32 seqno; +}; + // Structure of first RBUDP packet sent by sender. // Integers all transmitted in little endian (host endianness). struct rbudp_initial_pkt { @@ -74,6 +80,7 @@ struct rbudp_initial_pkt { }; #define HANDSHAKE_FLAG_SET_RETURN_PATH 0x1u +#define HANDSHAKE_FLAG_HS_CONFIRM (0x1u << 1) // Structure of ACK RBUDP packets sent by the receiver. // Integers all transmitted in little endian (host endianness). diff --git a/pathinterface.go b/pathinterface.go deleted file mode 100644 index 35bd00e..0000000 --- a/pathinterface.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "bytes" - "fmt" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/snet" - "strconv" -) - -type PathInterface struct { - ia addr.IA - ifId uint64 -} - -func (iface *PathInterface) ID() uint64 { - return iface.ifId -} - -func (iface *PathInterface) IA() addr.IA { - return iface.ia -} - -func (iface *PathInterface) UnmarshalText(text []byte) error { - parts := bytes.Split(text, []byte{' '}) - if len(parts) > 2 { - return fmt.Errorf("cannot unmarshal \"%s\" as PathInterface: contains too many spaces", text) - } - if len(parts) > 1 { - ifId, err := strconv.ParseInt(string(parts[1]), 10, 64) - if err != nil { - return err - } - iface.ifId = uint64(ifId) - } - ret := iface.ia.UnmarshalText(parts[0]) - return ret -} - -func (iface *PathInterface) match(pathIface snet.PathInterface) bool { - if iface.ifId == 0 { - return iface.IA() == pathIface.IA - } - return iface.ID() == uint64(pathIface.ID) && iface.IA() == pathIface.IA -} diff --git a/pathmanager.go b/pathmanager.go deleted file mode 100644 index f60bcac..0000000 --- a/pathmanager.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - "github.com/scionproto/scion/pkg/snet" - "github.com/vishvananda/netlink" - "net" - "time" -) - -type Destination struct { - hostAddr *snet.UDPAddr - pathSpec *[]PathSpec - numPaths int -} - -type PathManager struct { - numPathSlotsPerDst int - interfaces map[int]*net.Interface - dsts []*PathsToDestination - src *snet.UDPAddr - syncTime time.Time - maxBps uint64 - cStruct CPathManagement -} - -type PathWithInterface struct { - path snet.Path - iface *net.Interface -} - -type AppPathSet map[snet.PathFingerprint]PathWithInterface - -const numPathsResolved = 20 - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func initNewPathManager(interfaces []*net.Interface, dsts []*Destination, src *snet.UDPAddr, maxBps uint64) (*PathManager, error) { - ifMap := make(map[int]*net.Interface) - for _, iface := range interfaces { - ifMap[iface.Index] = iface - } - - numPathsPerDst := 0 - pm := &PathManager{ - interfaces: ifMap, - src: src, - dsts: make([]*PathsToDestination, 0, len(dsts)), - syncTime: time.Unix(0, 0), - maxBps: maxBps, - } - - for _, dst := range dsts { - var dstState *PathsToDestination - if src.IA == dst.hostAddr.IA { - dstState = initNewPathsToDestinationWithEmptyPath(pm, dst) - } else { - var err error - dstState, err = initNewPathsToDestination(pm, src, dst) - if err != nil { - return nil, err - } - } - pm.dsts = append(pm.dsts, dstState) - numPathsPerDst = max(numPathsPerDst, dst.numPaths) - } - - // allocate memory to pass paths to C - pm.numPathSlotsPerDst = numPathsPerDst - pm.cStruct.initialize(len(dsts), numPathsPerDst) - return pm, nil -} - -func (pm *PathManager) canSendToAllDests() bool { - for _, dst := range pm.dsts { - if !dst.hasUsablePaths() { - return false - } - } - return true -} - -func (pm *PathManager) choosePaths() bool { - updated := false - for _, dst := range pm.dsts { - if dst.choosePaths() { - updated = true - } - } - return updated -} - -func (pm *PathManager) filterPathsByActiveInterfaces(pathsAvail []snet.Path) AppPathSet { - pathsFiltered := make(AppPathSet) - for _, path := range pathsAvail { - iface, err := pm.interfaceForRoute(path.UnderlayNextHop().IP) - if err != nil { - } else { - pathsFiltered[snet.Fingerprint(path)] = PathWithInterface{path, iface} - } - } - return pathsFiltered -} - -func (pm *PathManager) interfaceForRoute(ip net.IP) (*net.Interface, error) { - routes, err := netlink.RouteGet(ip) - if err != nil { - return nil, fmt.Errorf("could not find route for destination %s: %s", ip, err) - } - - for _, route := range routes { - if iface, ok := pm.interfaces[route.LinkIndex]; ok { - fmt.Printf("sending via #%d (%s) to %s\n", route.LinkIndex, pm.interfaces[route.LinkIndex].Name, ip) - return iface, nil - } - } - return nil, fmt.Errorf("no interface active for sending to %s", ip) -} diff --git a/pathpicker.go b/pathpicker.go deleted file mode 100644 index 315a702..0000000 --- a/pathpicker.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "github.com/scionproto/scion/pkg/snet" -) - -type PathSpec []PathInterface - -type PathPickDescriptor struct { - ruleIndex int - pathIndex int -} - -type PathPicker struct { - pathSpec *[]PathSpec - availablePaths []snet.Path - currentPathPick []PathPickDescriptor -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func makePathPicker(spec *[]PathSpec, pathSet *AppPathSet, numPaths int) *PathPicker { - if len(*spec) == 0 { - defaultSpec := make([]PathSpec, numPaths) - spec = &defaultSpec - } - paths := make([]snet.Path, 0, len(*pathSet)) - for _, path := range *pathSet { - paths = append(paths, path.path) - } - picker := &PathPicker{ - pathSpec: spec, - availablePaths: paths, - } - picker.reset(numPaths) - return picker -} - -func (picker *PathPicker) reset(numPaths int) { - descriptor := make([]PathPickDescriptor, numPaths) - for i := range descriptor { - descriptor[i].ruleIndex = -1 - descriptor[i].pathIndex = -1 - } - picker.currentPathPick = descriptor -} - -func (picker *PathPicker) maxRuleIdx() int { - // rule indices are sorted ascending - for idx := len(picker.currentPathPick) - 1; idx >= 0; idx++ { - if picker.currentPathPick[idx].ruleIndex != -1 { - return picker.currentPathPick[idx].ruleIndex - } - } - return -1 -} - -func (picker *PathPicker) numPaths() int { - numPaths := 0 - for _, pick := range picker.currentPathPick { - if pick.pathIndex != -1 { - numPaths++ - } - } - return numPaths -} - -// Iterates to the next set of rules. Returns false, if no set of the appropriate size exists. -func (picker *PathPicker) nextRuleSet() bool { - ret := picker.nextRuleSetIterate(len(picker.currentPathPick) - 1) - if ret { - for i := range picker.currentPathPick { - picker.currentPathPick[i].pathIndex = -1 - } - } - return ret -} - -func (picker *PathPicker) nextRuleSetIterate(idx int) bool { - if idx > 0 && picker.currentPathPick[idx].ruleIndex == -1 { - if !picker.nextRuleSetIterate(idx - 1) { - return false - } - picker.currentPathPick[idx].ruleIndex = picker.currentPathPick[idx-1].ruleIndex - } - ruleIdx := picker.currentPathPick[idx].ruleIndex + 1 - for true { - if ruleIdx < len(*picker.pathSpec) { - picker.currentPathPick[idx].ruleIndex = ruleIdx - return true - } - // overflow - if idx > 0 { - if !picker.nextRuleSetIterate(idx - 1) { - picker.currentPathPick[idx].ruleIndex = -1 - return false - } - ruleIdx = picker.currentPathPick[idx-1].ruleIndex + 1 - } else { - break // cannot overflow, abort - } - } - return false -} - -// Iterates to the next allowed choice of paths. Returns false, if no next pick exists. -func (picker *PathPicker) nextPick() bool { - return picker.nextPickIterate(len(picker.currentPathPick) - 1) -} - -func (picker *PathPicker) nextPickIterate(idx int) bool { - if idx > 0 && picker.currentPathPick[idx-1].pathIndex == -1 { - if !picker.nextPickIterate(idx - 1) { - return false - } - } - for true { - for pathIdx := picker.currentPathPick[idx].pathIndex + 1; pathIdx < len(picker.availablePaths); pathIdx++ { - if !picker.isInUse(pathIdx, idx) && picker.matches(pathIdx, picker.currentPathPick[idx].ruleIndex) { - picker.currentPathPick[idx].pathIndex = pathIdx - return true - } - } - // overflow - if idx > 0 { - picker.currentPathPick[idx].pathIndex = -1 - if !picker.nextPickIterate(idx - 1) { - return false - } - } else { - break // cannot overflow, abort - } - } - return false -} - -func (picker *PathPicker) matches(pathIdx, ruleIdx int) bool { - pathSpec := (*picker.pathSpec)[ruleIdx] - pathInterfaces := picker.availablePaths[pathIdx].Metadata().Interfaces - idx := 0 - for _, iface := range pathSpec { - for len(pathInterfaces) > idx && !iface.match(pathInterfaces[idx]) { - idx++ - } - if idx >= len(pathInterfaces) { - return false - } - } - return true -} - -func (picker *PathPicker) isInUse(pathIdx, idx int) bool { - for i, pick := range picker.currentPathPick { - if i > idx { - return false - } - if pick.pathIndex == pathIdx { - return true - } - } - return false -} - -func (picker *PathPicker) disjointnessScore() int { - interfaces := map[snet.PathInterface]int{} - score := 0 - for _, pick := range picker.currentPathPick { - for _, path := range picker.availablePaths[pick.pathIndex].Metadata().Interfaces { - score -= interfaces[path] - interfaces[path]++ - } - } - return score -} - -func (picker *PathPicker) getPaths() []snet.Path { - paths := make([]snet.Path, 0, len(picker.currentPathPick)) - for _, pick := range picker.currentPathPick { - paths = append(paths, picker.availablePaths[pick.pathIndex]) - } - return paths -} diff --git a/pathstodestination.go b/pathstodestination.go deleted file mode 100644 index 100223b..0000000 --- a/pathstodestination.go +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "fmt" - log "github.com/inconshreveable/log15" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/private/topology" - "go.uber.org/atomic" - "net" - "time" -) - -type PathsToDestination struct { - pm *PathManager - dst *Destination - modifyTime time.Time - ExtnUpdated atomic.Bool - allPaths []snet.Path - paths []PathMeta // nil indicates that the destination is in the same AS as the sender and we can use an empty path - canSendLocally bool // (only if destination in same AS) indicates if we can send packets -} - -type PathMeta struct { - path snet.Path - fingerprint snet.PathFingerprint - iface *net.Interface - enabled bool // Indicates whether this path can be used at the moment - updated bool // Indicates whether this path needs to be synced to the C path -} - -type HerculesPathHeader struct { - Header []byte //!< C.HERCULES_MAX_HEADERLEN bytes - PartialChecksum uint16 //SCION L4 checksum over header with 0 payload -} - -func initNewPathsToDestinationWithEmptyPath(pm *PathManager, dst *Destination) *PathsToDestination { - return &PathsToDestination{ - pm: pm, - dst: dst, - paths: nil, - modifyTime: time.Now(), - } -} - -func initNewPathsToDestination(pm *PathManager, src *snet.UDPAddr, dst *Destination) (*PathsToDestination, error) { - paths, err := newPathQuerier().Query(context.Background(), dst.hostAddr.IA) - if err != nil { - return nil, err - } - return &PathsToDestination{ - pm: pm, - dst: dst, - allPaths: paths, - paths: make([]PathMeta, dst.numPaths), - modifyTime: time.Unix(0, 0), - }, nil -} - -func (ptd *PathsToDestination) hasUsablePaths() bool { - if ptd.paths == nil { - return ptd.canSendLocally - } - for _, path := range ptd.paths { - if path.enabled { - return true - } - } - return false -} - -func (ptd *PathsToDestination) choosePaths() bool { - if ptd.allPaths == nil { - return false - } - - if ptd.modifyTime.After(time.Unix(0, 0)) { // TODO this chooses paths only once - if ptd.ExtnUpdated.Swap(false) { - ptd.modifyTime = time.Now() - return true - } - return false - } - - availablePaths := ptd.pm.filterPathsByActiveInterfaces(ptd.allPaths) - if len(availablePaths) == 0 { - log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) - } - - previousPathAvailable := make([]bool, ptd.dst.numPaths) - updated := ptd.choosePreviousPaths(&previousPathAvailable, &availablePaths) - - if ptd.disableVanishedPaths(&previousPathAvailable) { - updated = true - } - // Note: we keep vanished paths around until they can be replaced or re-enabled - - if ptd.chooseNewPaths(&previousPathAvailable, &availablePaths) { - updated = true - } - - if ptd.ExtnUpdated.Swap(false) || updated { - ptd.modifyTime = time.Now() - return true - } - return false -} - -func (ptd *PathsToDestination) choosePreviousPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { - updated := false - for newFingerprint := range *availablePaths { - for i := range ptd.paths { - pathMeta := &ptd.paths[i] - if newFingerprint == pathMeta.fingerprint { - if !pathMeta.enabled { - log.Info(fmt.Sprintf("[Destination %s] re-enabling path %d\n", ptd.dst.hostAddr.IA, i)) - pathMeta.enabled = true - updated = true - } - (*previousPathAvailable)[i] = true - break - } - } - } - return updated -} - -func (ptd *PathsToDestination) disableVanishedPaths(previousPathAvailable *[]bool) bool { - updated := false - for i, inUse := range *previousPathAvailable { - pathMeta := &ptd.paths[i] - if inUse == false && pathMeta.enabled { - log.Info(fmt.Sprintf("[Destination %s] disabling path %d\n", ptd.dst.hostAddr.IA, i)) - pathMeta.enabled = false - updated = true - } - } - return updated -} - -func (ptd *PathsToDestination) chooseNewPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { - updated := false - // XXX for now, we do not support replacing vanished paths - // check that no previous path available - for _, prev := range *previousPathAvailable { - if prev { - return false - } - } - - // pick paths - picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) - var pathSet []snet.Path - disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) - maxRuleIdx := 0 // the highest index of a PathSpec that is used (to be minimized) - for i := ptd.dst.numPaths; i > 0; i-- { - picker.reset(i) - for picker.nextRuleSet() { // iterate through different choices of PathSpecs to use - if pathSet != nil && maxRuleIdx < picker.maxRuleIdx() { // ignore rule set, if path set with lower maxRuleIndex is known - continue // due to the iteration order, we cannot break here - } - for picker.nextPick() { // iterate through different choices of paths obeying the rules of the current set of PathSpecs - curDisjointness := picker.disjointnessScore() - if pathSet == nil || disjointness < curDisjointness { // maximize disjointness - disjointness = curDisjointness - maxRuleIdx = picker.maxRuleIdx() - pathSet = picker.getPaths() - } - } - } - if pathSet != nil { // if no path set of size i found, try with i-1 - break - } - } - - log.Info(fmt.Sprintf("[Destination %s] using %d paths:", ptd.dst.hostAddr.IA, len(pathSet))) - for i, path := range pathSet { - log.Info(fmt.Sprintf("\t%s", path)) - fingerprint := snet.Fingerprint(path) - ptd.paths[i].path = path - ptd.paths[i].fingerprint = fingerprint - ptd.paths[i].enabled = true - ptd.paths[i].updated = true - ptd.paths[i].iface = (*availablePaths)[fingerprint].iface - updated = true - } - return updated -} - -func (ptd *PathsToDestination) preparePath(p *PathMeta) (*HerculesPathHeader, error) { - var err error - var iface *net.Interface - curDst := ptd.dst.hostAddr - if (*p).path == nil { - // in order to use a static empty path, we need to set the next hop on dst - curDst.NextHop = &net.UDPAddr{ - IP: ptd.dst.hostAddr.Host.IP, - Port: topology.EndhostPort, - } - iface, err = ptd.pm.interfaceForRoute(ptd.dst.hostAddr.Host.IP) - if err != nil { - return nil, err - } - } else { - curDst.Path = (*p).path.Dataplane() - - curDst.NextHop = (*p).path.UnderlayNextHop() - iface = p.iface - } - - path, err := prepareSCIONPacketHeader(ptd.pm.src, curDst, iface) - if err != nil { - return nil, err - } - return path, nil -} diff --git a/send_queue.c b/send_queue.c index a008557..6248b55 100644 --- a/send_queue.c +++ b/send_queue.c @@ -14,11 +14,13 @@ #include "send_queue.h" #include "hercules.h" +#include #include #include void init_send_queue(struct send_queue *queue, u32 num_entries) { + assert(sizeof(struct send_queue_unit) == 64); queue->units_base = (struct send_queue_unit *)calloc(num_entries + 1, sizeof(struct send_queue_unit)); // make sure units are aligned to cache lines u32 offset = (size_t)queue->units_base % CACHELINE_SIZE; diff --git a/send_queue.h b/send_queue.h index 23e0642..29ec002 100644 --- a/send_queue.h +++ b/send_queue.h @@ -28,7 +28,8 @@ struct send_queue_unit { u32 chunk_idx[SEND_QUEUE_ENTRIES_PER_UNIT]; u8 rcvr[SEND_QUEUE_ENTRIES_PER_UNIT]; u8 paths[SEND_QUEUE_ENTRIES_PER_UNIT]; - char a[CACHELINE_SIZE - SEND_QUEUE_ENTRIES_PER_UNIT * SEND_QUEUE_ENTRY_SIZE]; // force padding to 64 bytes + u32 ts; + char a[CACHELINE_SIZE - 6 - SEND_QUEUE_ENTRIES_PER_UNIT * SEND_QUEUE_ENTRY_SIZE]; // force padding to 64 bytes }; // single producer, multi consumer queue diff --git a/utils.h b/utils.h index 7db29cf..a5a508b 100644 --- a/utils.h +++ b/utils.h @@ -27,46 +27,53 @@ typedef __u8 u8; #ifndef NDEBUG #define debug_printf(fmt, ...) printf("DEBUG: %s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define debug_quit(fmt, ...) \ + do { \ + printf("DEBUG: %s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, \ + ##__VA_ARGS__); \ + exit_with_error(NULL, 1); \ + } while (0); #else #define debug_printf(...) ; +#define debug_quit(...) ; #endif #ifndef likely # define likely(x) __builtin_expect(!!(x), 1) #endif -inline u16 umin16(u16 a, u16 b) +static inline u16 umin16(u16 a, u16 b) { return (a < b) ? a : b; } -inline u16 umax16(u16 a, u16 b) +static inline u16 umax16(u16 a, u16 b) { return (a > b) ? a : b; } -inline u32 umin32(u32 a, u32 b) +static inline u32 umin32(u32 a, u32 b) { return (a < b) ? a : b; } -inline u32 umax32(u32 a, u32 b) +static inline u32 umax32(u32 a, u32 b) { return (a > b) ? a : b; } -inline u64 umin64(u64 a, u64 b) +static inline u64 umin64(u64 a, u64 b) { return (a < b) ? a : b; } -inline u64 umax64(u64 a, u64 b) +static inline u64 umax64(u64 a, u64 b) { return (a > b) ? a : b; } -inline u64 get_nsecs(void) +static inline u64 get_nsecs(void) { struct timespec ts; @@ -74,7 +81,7 @@ inline u64 get_nsecs(void) return ts.tv_sec * 1000000000UL + ts.tv_nsec; } -inline void sleep_until(u64 ns) +static inline void sleep_until(u64 ns) { struct timespec req; req.tv_sec = (time_t)(ns / 1000000000UL); @@ -82,7 +89,7 @@ inline void sleep_until(u64 ns) clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &req, NULL); } -inline void sleep_nsecs(u64 ns) +static inline void sleep_nsecs(u64 ns) { // Use clock_nanosleep to avoid drift by repeated interrupts. See NOTES in man(2) nanosleep. sleep_until(get_nsecs() + ns); From a049a76bac6d00b11026e085397327e7247fc7fe Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 21 May 2024 14:00:18 +0200 Subject: [PATCH 002/112] monitor messages, multiple transfers ok --- frame_queue.h | 5 + hercules.c | 258 +++++++------- monitor.c | 99 ++++++ monitor.h | 96 ++++++ monitor/go.mod | 64 ++++ monitor/go.sum | 618 ++++++++++++++++++++++++++++++++++ monitor/monitor.go | 491 +++++++++++++++++++++++++++ monitor/network.go | 56 +++ monitor/pathinterface.go | 59 ++++ monitor/pathmanager.go | 137 ++++++++ monitor/pathpicker.go | 201 +++++++++++ monitor/pathstodestination.go | 236 +++++++++++++ packet.h | 2 + send_queue.c | 1 - send_queue.h | 1 + 15 files changed, 2197 insertions(+), 127 deletions(-) create mode 100644 monitor.c create mode 100644 monitor.h create mode 100644 monitor/go.mod create mode 100644 monitor/go.sum create mode 100644 monitor/monitor.go create mode 100644 monitor/network.go create mode 100644 monitor/pathinterface.go create mode 100644 monitor/pathmanager.go create mode 100644 monitor/pathpicker.go create mode 100644 monitor/pathstodestination.go diff --git a/frame_queue.h b/frame_queue.h index 70cc3fa..5f03e07 100644 --- a/frame_queue.h +++ b/frame_queue.h @@ -43,6 +43,7 @@ static inline int frame_queue__init(struct frame_queue *fq, u16 size) static inline u16 frame_queue__prod_reserve(struct frame_queue *fq, u16 num) { + debug_printf("Empty slots in fq %llu", fq->cons - fq->prod); return umin16(atomic_load(&fq->cons) - fq->prod, num); } @@ -54,10 +55,14 @@ static inline void frame_queue__prod_fill(struct frame_queue *fq, u16 offset, u6 static inline void frame_queue__push(struct frame_queue *fq, u16 num) { atomic_fetch_add(&fq->prod, num); + u64 now = atomic_load(&fq->prod) - atomic_load(&fq->cons) + fq->size; + debug_printf("Frames in fq after push %llu", now); } static inline u16 frame_queue__cons_reserve(struct frame_queue *fq, u16 num) { + /* debug_printf("Reserve prod %llu, cons %llu", fq->prod, fq->cons); */ + debug_printf("Frames in fq %llu", fq->prod - fq->cons + fq->size); return umin16(atomic_load(&fq->prod) - fq->cons + fq->size, num); } diff --git a/hercules.c b/hercules.c index b896bae..354024e 100644 --- a/hercules.c +++ b/hercules.c @@ -50,6 +50,7 @@ #include "utils.h" #include "send_queue.h" #include "bpf_prgms.h" +#include "monitor.h" #define MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE 128 // E.g., SCION SPAO header added by LightningFilter @@ -72,7 +73,9 @@ static const u64 tx_handshake_retry_after = 1e9; static const u64 tx_handshake_timeout = 5e9; #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path +// TODO move and see if we can't use this from only 1 thread so no locks int usock; +pthread_spinlock_t usock_lock; // exported from hercules.go extern int HerculesGetReplyPath(const char *packetPtr, int length, struct hercules_path *reply_path); @@ -151,6 +154,7 @@ struct hercules_server { int queue; int mtu; struct hercules_session *session_tx; //Current TX session + u64 session_tx_counter; struct hercules_session *session_rx; //Current RX session struct hercules_app_addr *destinations; struct hercules_path *paths_per_dest; @@ -723,7 +727,7 @@ static struct xsk_umem_info *xsk_configure_umem(struct hercules_server *server, umem->iface = &server->ifaces[ifidx]; // The number of slots in the umem->available_frames queue needs to be larger than the number of frames in the loop, // pushed in submit_initial_tx_frames() (assumption in pop_completion_ring() and handle_send_queue_unit()) - ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS); + ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS*2); if(ret) exit_with_error(server, ret); pthread_spin_init(&umem->lock, 0); @@ -1374,7 +1378,7 @@ static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t /* } */ static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, size_t filesize, u32 chunklen, +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path) { debug_printf("Sending initial"); @@ -1389,9 +1393,12 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path .timestamp = timestamp, .path_index = path_index, .flags = set_return_path ? HANDSHAKE_FLAG_SET_RETURN_PATH : 0, + .name_len = strlen(filename), }, }; - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, sizeof(pld.type) + sizeof(pld.payload.initial), + assert(strlen(filename) < 100); // TODO + strncpy(pld.payload.initial.name, filename, 100); + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, sizeof(pld.type) + sizeof(pld.payload.initial) + pld.payload.initial.name_len, path->payloadlen); stitch_checksum(path, path->header.checksum, buf); @@ -1674,6 +1681,7 @@ void send_path_handshakes(struct sender_state *tx_state) static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { + /* assert(num_frames == 7); */ pthread_spin_lock(&iface->umem->lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); while(reserved != num_frames) { @@ -1682,7 +1690,9 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_inte reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); /* debug_printf("reserved %ld, wanted %ld", reserved, num_frames); */ // XXX FIXME - if(!server->session_tx) { + struct hercules_session *s = atomic_load(&server->session_tx); + if(!s || atomic_load(&s->state) != SESSION_STATE_RUNNING) { + debug_printf("STOP"); pthread_spin_unlock(&iface->umem->lock); return; } @@ -1719,9 +1729,11 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s struct sender_state_per_receiver *rcvr = &tx_state->receiver[unit->rcvr[i]]; struct hercules_path *path = &rcvr->paths[unit->paths[i]]; if(path->ifid == ifid) { + debug_printf("%d, %d", path->ifid, ifid); num_chunks_in_unit++; } } + // We may have claimed more frames than we need, if the unit is not full debug_printf("handling %d chunks in unit", num_chunks_in_unit); u32 idx; @@ -1766,6 +1778,9 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s debug_printf("submitting %d chunks in unit", num_chunks_in_unit); xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); + if (num_chunks_in_unit != SEND_QUEUE_ENTRIES_PER_UNIT){ + debug_printf("Submitting only %d frames", num_chunks_in_unit); + } __sync_synchronize(); debug_printf("submit returns"); } @@ -1827,6 +1842,7 @@ static inline void allocate_tx_frames(struct hercules_server *server, break; } } + debug_printf("claiming %d frames", num_frames); claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); } } @@ -1840,8 +1856,9 @@ static void tx_send_p(void *arg) { struct hercules_server *server = arg; while (1) { struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx == NULL || session_tx->state != SESSION_STATE_RUNNING) { + if (session_tx == NULL || atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { /* debug_printf("Invalid session, dropping sendq unit"); */ + kick_tx(server, &server->xsk[0]); continue; } struct send_queue_unit unit; @@ -1850,11 +1867,29 @@ static void tx_send_p(void *arg) { kick_tx(server, &server->xsk[0]); continue; } + u32 num_chunks_in_unit = 0; + for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { + if(unit.paths[i] == UINT8_MAX) { + break; + } + num_chunks_in_unit++; + } + debug_printf("unit has %d chunks", num_chunks_in_unit); u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; memset(frame_addrs, 0xFF, sizeof(frame_addrs)); + for (int i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ + if (i >= num_chunks_in_unit){ + debug_printf("not using unit slot %d", i); + frame_addrs[0][i] = 0; + } + } + debug_printf("allocating frames"); allocate_tx_frames(server, frame_addrs); + debug_printf("done allocating frames"); + // At this point we claimed 7 frames (as many as can fit in a sendq unit) tx_handle_send_queue_unit(server, session_tx->tx_state, &server->xsk, frame_addrs, &unit); + /* assert(num_chunks_in_unit == 7); */ kick_tx(server, &server->xsk[0]); } } @@ -2147,7 +2182,7 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i exit(1); } - debug_printf("Sending a file consisting of %d chunks (ANY KEY TO CONTINUE)", total_chunks); + debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); getchar(); struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); @@ -2192,6 +2227,7 @@ static void destroy_tx_state(struct sender_state *tx_state) static char *rx_mmap(struct hercules_server *server, const char *pathname, size_t filesize) { + debug_printf("mmap file: %s", pathname); int ret; /*ret = unlink(pathname); if(ret && errno != ENOENT) { @@ -2223,7 +2259,7 @@ static char *rx_mmap(struct hercules_server *server, const char *pathname, size_ return mem; } -static struct receiver_state *make_rx_state(struct hercules_session *session, size_t filesize, int chunklen, +static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t filesize, int chunklen, bool is_pcc_benchmark) { struct receiver_state *rx_state; @@ -2237,7 +2273,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, si rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; - rx_state->mem = rx_mmap(NULL, "outfile", filesize); + rx_state->mem = rx_mmap(NULL, filename, filesize); return rx_state; } @@ -2253,36 +2289,10 @@ static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initia debug_printf("Packet too short"); return false; } - memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt)); + memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt) + control_pkt.payload.initial.name_len); return true; } -static int get_reply_path_socket(char *rx_sample_buf, int rx_sample_len, - struct hercules_path *path) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - debug_printf("sending %d bytes", rx_sample_len); - sendto(usock, rx_sample_buf, rx_sample_len, 0, &monitor, sizeof(monitor)); - char buf[2000]; - memset(&buf, 0, 2000); - int n = recv(usock, &buf, sizeof(buf), 0); - if (n > 0) { - debug_printf("Read %d bytes", n); - u32 headerlen = *(u32 *)buf; - path->headerlen = headerlen; - memcpy(path->header.header, buf+4, headerlen); - memcpy(&path->header.checksum, buf+4+headerlen, 2); - path->enabled = true; - path->replaced = false; - path->payloadlen = 1200 - headerlen; - path->framelen = 1200; - path->ifid = 3; - return 0; - } - assert(false); - return 1; -} static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) { @@ -2300,8 +2310,10 @@ static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_p char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); - int ret = get_reply_path_socket(rx_sample_buf, rx_sample_len, path); - if(ret) { + pthread_spin_lock(&usock_lock); + int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, path); + pthread_spin_unlock(&usock_lock); + if(!ret) { return false; } path->ifid = rx_state->rx_sample_ifid; @@ -2630,9 +2642,10 @@ static void rx_send_nacks(struct hercules_server *server, struct receiver_state static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; while (1) { - if (true) { + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { u32 ack_nr = 0; - struct receiver_state *rx_state = server->session_tx->rx_state; + struct receiver_state *rx_state = session_rx->rx_state; while (rx_state->session->is_running && !rx_received_all(rx_state)) { u64 ack_round_start = get_nsecs(); rx_send_nacks(server, rx_state, ack_round_start, ack_nr); @@ -2682,8 +2695,62 @@ static void events_p(void *arg) { /* u8 path_idx; */ /* const char *payload; */ /* int payloadlen; */ + u64 lastpoll = get_nsecs(); while (1) { // event handler thread loop + + // XXX monitor poll + if (atomic_load(&server->session_tx) == NULL) { + if (get_nsecs() > lastpoll + 1e9) { + lastpoll = get_nsecs(); + // every 1s + char fname[1000]; + memset(fname, 0, 1000); + int count; + pthread_spin_lock(&usock_lock); + int ret = monitor_get_new_job(usock, fname); + pthread_spin_unlock(&usock_lock); + if (ret) { + debug_printf("new job: %s", fname); + debug_printf("Starting new TX"); + int f = open(fname, O_RDONLY); + if (f == -1) { + exit_with_error(NULL, errno); + } + + struct stat stat; + int ret = fstat(f, &stat); + if (ret) { + exit_with_error(NULL, errno); + } + const size_t filesize = stat.st_size; + char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, f, 0); + if (mem == MAP_FAILED) { + fprintf(stderr, "ERR: memory mapping failed\n"); + exit_with_error(NULL, errno); + } + close(f); + + u32 chunklen = server->paths_per_dest[0].payloadlen - rbudp_headerlen; + atomic_store(&server->session_tx, make_session(server)); + atomic_fetch_add(&server->session_tx_counter, 1); + struct sender_state *tx_state = init_tx_state( + server->session_tx, filesize, chunklen, server->rate_limit, mem, + server->destinations, server->paths_per_dest, server->num_dests, + server->num_paths, server->max_paths); + server->session_tx->tx_state = tx_state; + unsigned long timestamp = get_nsecs(); + tx_send_initial(server, &tx_state->receiver[0].paths[0], fname, + tx_state->filesize, tx_state->chunklen, timestamp, 0, + true); + atomic_store(&server->session_tx->state, SESSION_STATE_WAIT_HS); + } else { + debug_printf("no new job."); + } + } + } + // XXX + ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addr_size); // XXX set timeout @@ -2723,8 +2790,7 @@ static void events_p(void *arg) { const char *pl = rbudp_pkt + rbudp_headerlen; struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; switch (cp->type) { - case CONTROL_PACKET_TYPE_INITIAL: - ; + case CONTROL_PACKET_TYPE_INITIAL:; struct rbudp_initial_pkt parsed_pkt; rbudp_parse_initial((const char *)cp, rbudp_len - rbudp_headerlen, &parsed_pkt); @@ -2739,7 +2805,7 @@ static void events_p(void *arg) { break; // Make sure we don't process this further } // If a transfer is already running, ignore - // XXX breaks multipath, for now (until we split path and transfer HS) + // XXX breaks multipath, for now if (server->session_rx == NULL) { debug_printf("No current rx session"); if (parsed_pkt.flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { @@ -2747,7 +2813,7 @@ static void events_p(void *arg) { server->session_rx = session; session->state = SESSION_STATE_CREATED; struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt.filesize, parsed_pkt.chunklen, false); + session, parsed_pkt.name, parsed_pkt.filesize, parsed_pkt.chunklen, false); session->rx_state = rx_state; /* session->state = SESSION_STATE_READY; */ rx_handle_initial(server, rx_state, &parsed_pkt, buf, 3, @@ -2762,16 +2828,17 @@ static void events_p(void *arg) { server->session_tx->state == SESSION_STATE_WAIT_CTS) { // TODO check this is actually a CTS ack debug_printf("CTS received"); - server->session_tx->state = SESSION_STATE_RUNNING; + atomic_store(&server->session_tx->state, SESSION_STATE_RUNNING); } - __sync_synchronize(); + __sync_synchronize(); if (server->session_tx != NULL && server->session_tx->state == SESSION_STATE_RUNNING) { tx_register_acks(&cp->payload.ack, &server->session_tx->tx_state->receiver[0]); if (tx_acked_all(server->session_tx->tx_state)) { debug_printf("TX done, received all acks"); - server->session_tx = NULL; // FIXME leak + /* server->session_tx = NULL; // FIXME leak */ + atomic_store(&server->session_tx, NULL); } } break; @@ -2810,10 +2877,10 @@ void print_rbudp_pkt(const char *pkt, bool recv) { switch (cp->type) { case CONTROL_PACKET_TYPE_INITIAL: printf("%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx %u, Flags " - "0x%x\n", prefix, + "0x%x, Name length %u [%s]\n", prefix, cp->payload.initial.filesize, cp->payload.initial.chunklen, cp->payload.initial.timestamp, cp->payload.initial.path_index, - cp->payload.initial.flags); + cp->payload.initial.flags, cp->payload.initial.name_len, cp->payload.initial.name); break; case CONTROL_PACKET_TYPE_ACK: printf("%s ACK ", prefix); @@ -2839,6 +2906,8 @@ void print_rbudp_pkt(const char *pkt, bool recv) { } #else void print_rbudp_pkt(const char * pkt, bool recv){ + (void)pkt; + (void)recv; return; } #endif @@ -2882,7 +2951,7 @@ static struct receiver_state *rx_receive_hs(struct hercules_server *server, debug_printf("parsed initial"); struct hercules_session *session = make_session(server); struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt.filesize, parsed_pkt.chunklen, false); + session, NULL, parsed_pkt.filesize, parsed_pkt.chunklen, false); session->rx_state = rx_state; /* rx_handle_initial(rx_state, &parsed_pkt, pkt, 3, rbudp_pkt + rbudp_headerlen, len); */ xsk_ring_cons__release(&xsk->rx, rcvd); @@ -3034,16 +3103,15 @@ static void *tx_p(void *arg) { struct hercules_server *server = arg; while (1) { /* pthread_spin_lock(&server->biglock); */ - __sync_synchronize(); // XXX HACK + pop_completion_rings(server); struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { + if (session_tx != NULL && atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { struct sender_state *tx_state = session_tx->tx_state; /* tx_only(server, tx_state); */ u32 chunks[BATCH_SIZE]; u8 chunk_rcvr[BATCH_SIZE]; u32 max_chunks_per_rcvr[1] = {BATCH_SIZE}; - pop_completion_rings(server); u64 next_ack_due = 0; u32 total_chunks = BATCH_SIZE; const u64 now = get_nsecs(); @@ -3132,6 +3200,7 @@ struct hercules_server *hercules_init_server(int *ifindices, int num_ifaces, con server->mtu = mtu; server->session_rx = NULL; server->session_tx = NULL; + server->session_tx_counter = 0; for(int i = 0; i < num_ifaces; i++) { debug_printf("iter %d", i); @@ -3144,6 +3213,8 @@ struct hercules_server *hercules_init_server(int *ifindices, int num_ifaces, con debug_printf("using queue %d on interface %s", server->ifaces[i].queue, server->ifaces[i].ifname); } + pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); + server->config.ether_size = mtu; server->config.local_addr = local_addr; server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); @@ -3630,9 +3701,9 @@ debug_printf("Starting event receiver thread"); pthread_t events = start_thread(NULL, events_p, server); // XXX only needed for PCC - /* // Start the NACK sender thread */ - /* debug_printf("starting NACK trickle thread"); */ - /* pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); */ + // Start the NACK sender thread + debug_printf("starting NACK trickle thread"); + pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); // Start the ACK sender thread debug_printf("starting ACK trickle thread"); @@ -3653,52 +3724,7 @@ debug_printf("Starting event receiver thread"); while (1) { // XXX STOP HERE - // Get to here without crashing and wait - if (server->n_pending > 0) { - if (server->session_tx == NULL) { - debug_printf("Starting new TX"); - int f = open(server->pending[server->n_pending - 1], O_RDONLY); - if (f == -1) { - exit_with_error(NULL, errno); - } - - struct stat stat; - int ret = fstat(f, &stat); - if (ret) { - exit_with_error(NULL, errno); - } - const size_t filesize = stat.st_size; - char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, f, 0); - if (mem == MAP_FAILED) { - fprintf(stderr, "ERR: memory mapping failed\n"); - exit_with_error(NULL, errno); - } - close(f); - - u32 chunklen = server->paths_per_dest[0].payloadlen - rbudp_headerlen; - /* pthread_spin_lock(&server->biglock); */ - server->session_tx = make_session(server); - struct sender_state *tx_state = init_tx_state( - server->session_tx, filesize, chunklen, server->rate_limit, mem, - server->destinations, server->paths_per_dest, server->num_dests, - server->num_paths, server->max_paths); - server->session_tx->tx_state = tx_state; - unsigned long timestamp = get_nsecs(); - tx_send_initial(server, &tx_state->receiver[0].paths[0], - tx_state->filesize, tx_state->chunklen, timestamp, 0, - true); - server->session_tx->state = SESSION_STATE_WAIT_HS; - - /* pthread_spin_unlock(&server->biglock); */ - server->n_pending--; - sleep_nsecs(2e9); - } - } } - - /* while(1){ */ - /* // XXX */ - /* } */ } int main(int argc, char *argv[]) { @@ -3736,33 +3762,13 @@ int main(int argc, char *argv[]) { server->num_paths = &np; // num paths per dest server->max_paths = 1; // max paths per dest server->rate_limit = 10e6; - server->enable_pcc = false; + server->enable_pcc = true; - struct hercules_path path; - debug_printf("path pointer %p", &path); - char buf[2000]; - buf[0] = 0x00; - - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - int len = 1; - debug_printf("sending %d bytes", len); - sendto(usock, buf, len, 0, &monitor, sizeof(monitor)); - char recvbuf[2000]; - memset(&recvbuf, 0, 2000); - int n = recv(usock, &recvbuf, sizeof(recvbuf), 0); - debug_printf("receive %d bytes", n); - u32 headerlen = *(u32 *)recvbuf; - path.headerlen = headerlen; - memcpy(path.header.header, recvbuf + 4, headerlen); - memcpy(&path.header.checksum, recvbuf + 4 + headerlen, 2); - path.enabled = true; - path.replaced = false; - path.payloadlen = 1200 - headerlen; - path.framelen = 1200; - path.ifid = 3; - server->paths_per_dest = &path; + struct hercules_path *path; + int n_paths; + monitor_get_paths(usock, 1, &n_paths, &path); + + server->paths_per_dest = path; } hercules_main(server, XDP_COPY); diff --git a/monitor.c b/monitor.c new file mode 100644 index 0000000..40e4728 --- /dev/null +++ b/monitor.c @@ -0,0 +1,99 @@ +#include "monitor.h" +#include "hercules.h" +#include "utils.h" +#include +#include +#include +#include +#include + +bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, + struct hercules_path *path) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + + struct hercules_sockmsg_Q *msg; + size_t msg_len = sizeof(*msg) + rx_sample_len; + msg = calloc(1, msg_len); + assert(msg); + + msg->msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; + msg->payload.reply_path.sample_len = rx_sample_len; + memcpy(msg->payload.reply_path.sample, rx_sample_buf, rx_sample_len); + debug_printf("sending %ld bytes", msg_len); + sendto(sockfd, msg, msg_len, 0, &monitor, sizeof(monitor)); // TODO return val + free(msg); + + char buf[2000]; + memset(&buf, 0, 2000); + int n = recv(sockfd, &buf, sizeof(buf), 0); + debug_printf("Read %d bytes", n); + if (n <= 0) { + return false; + } + struct hercules_sockmsg_A *reply = buf; + path->headerlen = reply->payload.reply_path.headerlen; + memcpy(&path->header.header, reply->payload.reply_path.header, + path->headerlen); + path->header.checksum = reply->payload.reply_path.chksum; + + path->enabled = true; + path->replaced = false; + path->payloadlen = 1200 - path->headerlen; + path->framelen = 1200; + path->ifid = 3; + return true; +} + +bool monitor_get_paths(int sockfd, int job_id, int *n_paths, + struct hercules_path **paths) { + struct hercules_path *path = calloc(1, sizeof(*path)); + assert(path); + + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_PATHS}; + int len = sizeof(msg); + debug_printf("sending %d bytes", len); + sendto(sockfd, &msg, len, 0, &monitor, sizeof(monitor)); + char recvbuf[2000]; + memset(&recvbuf, 0, 2000); + int n = recv(sockfd, &recvbuf, sizeof(recvbuf), 0); + debug_printf("receive %d bytes", n); + struct sockmsg_reply_path_A *reply = recvbuf + 2; + path->headerlen = reply->headerlen; + memcpy(path->header.header, reply->header, reply->headerlen); + path->header.checksum = reply->chksum; + + path->enabled = true; + path->replaced = false; + path->payloadlen = 1200 - path->headerlen; + path->framelen = 1200; + path->ifid = 3; + + *n_paths = 1; + *paths = path; + return true; +} + +bool monitor_get_new_job(int sockfd, char *name) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; + int len = sizeof(msg); + debug_printf("sending %d bytes", len); + sendto(sockfd, &msg, len, 0, &monitor, sizeof(monitor)); + char recvbuf[2000]; + memset(&recvbuf, 0, 2000); + int n = recv(sockfd, &recvbuf, sizeof(recvbuf), 0); + debug_printf("receive %d bytes", n); + struct sockmsg_new_job_A *reply = recvbuf; + if (!reply->has_job){ + return false; + } + strncpy(name, reply->filename, reply->filename_len); + return true; +} diff --git a/monitor.h b/monitor.h new file mode 100644 index 0000000..7ae6021 --- /dev/null +++ b/monitor.h @@ -0,0 +1,96 @@ +#ifndef HERCULES_MONITOR_H_ +#define HERCULES_MONITOR_H_ +#include "hercules.h" +#include +#include + +// Get a reply path from the monitor. The reversed path will be written to +// *path. Returns false in case of error. +bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, + struct hercules_path *path); + +// Get SCION paths from the monitor. The caller is responsible for freeing +// **paths. +bool monitor_get_paths(int sockfd, int job_id, int *n_paths, + struct hercules_path **paths); + +// Check if the monitor has a new job available +// TODO +bool monitor_get_new_job(int sockfd, char *name); + +// Inform the monitor about a transfer's (new) status +// TODO +bool monitor_update_job(int sockfd, int job_id); + +// Messages used for communication between the Hercules daemon and monitor +// via unix socket. Queries are sent by the daemon, Replies by the monitor. +#pragma pack(push) +#pragma pack(1) + +// Ask the monitor for a reply path by sending it a received header. +// The monitor will return the appropriate header, along with its partial +// checksum +#define SOCKMSG_TYPE_GET_REPLY_PATH (1) +struct sockmsg_reply_path_Q { + uint16_t sample_len; + uint8_t sample[]; +}; +struct sockmsg_reply_path_A { + uint16_t chksum; + uint32_t headerlen; + uint8_t header[]; +}; + +// Ask the monitor for new transfer jobs. +// The answer contains at most one new job, if one was queued at the monitor. +#define SOCKMSG_TYPE_GET_NEW_JOB (2) +struct sockmsg_new_job_Q {}; +struct sockmsg_new_job_A { + uint8_t has_job; // The other fields are only valid if this is set to 1 + uint16_t job_id; + uint16_t filename_len; + uint8_t filename[]; +}; + +// Get paths to use for a given job ID +#define SOCKMSG_TYPE_GET_PATHS (3) +struct sockmsg_paths_Q { + uint16_t job_id; +}; +struct sockmsg_paths_A { + uint16_t n_paths; + uint8_t paths[]; // This should be a concatenation of n_paths many paths, each + // laid out as struct sockmsg_reply_path_A above. +}; + +// Inform the monitor about a job's status +#define SOCKMSG_TYPE_UPDATE_JOB (4) +struct sockmsg_update_job_Q { + uint16_t job_id; + uint32_t status; // One of enum session_state + uint32_t error; // One of enum session_error +}; +struct sockmsg_update_job_A {}; + +struct hercules_sockmsg_Q { + uint16_t msgtype; + union { + struct sockmsg_reply_path_Q reply_path; + struct sockmsg_paths_Q paths; + struct sockmsg_new_job_Q newjob; + struct sockmsg_update_job_Q job_update; + } payload; +}; + +struct hercules_sockmsg_A { + union { + struct sockmsg_reply_path_A reply_path; + struct sockmsg_paths_A paths; + struct sockmsg_new_job_A newjob; + struct sockmsg_update_job_A job_update; + } payload; +}; + +#pragma pack(pop) + +#endif // HERCULES_MONITOR_H_ diff --git a/monitor/go.mod b/monitor/go.mod new file mode 100644 index 0000000..857af6e --- /dev/null +++ b/monitor/go.mod @@ -0,0 +1,64 @@ +module monitor + + +go 1.21 + +toolchain go1.21.6 + +require ( + github.com/google/gopacket v1.1.19 + github.com/inconshreveable/log15 v2.16.0+incompatible + github.com/scionproto/scion v0.10.0 + github.com/vishvananda/netlink v1.2.1-beta.2 + go.uber.org/atomic v1.9.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dchest/cmac v1.0.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.0.0+incompatible // indirect + github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect + go.uber.org/multierr v1.8.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/term v0.11.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect + google.golang.org/grpc v1.57.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.24.0 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) diff --git a/monitor/go.sum b/monitor/go.sum new file mode 100644 index 0000000..701b91f --- /dev/null +++ b/monitor/go.sum @@ -0,0 +1,618 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/cmac v1.0.0 h1:Vaorm9FVpO2P+YmRdH0RVCUB1XF3Ge1yg9scPvJphyk= +github.com/dchest/cmac v1.0.0/go.mod h1:0zViPqHm8iZwwMl1cuK3HqK7Tu4Q7DV4EuMIOUwBVQ0= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw= +github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/scionproto/scion v0.10.0 h1:OcjLpOaaT8uoTGsOAj1TRcqz4BJzLw/uKcWLNRfrUWY= +github.com/scionproto/scion v0.10.0/go.mod h1:N5p5gAbL5is+q85ohxSjo+WzFn8u5NM0Y0YwocXRF7U= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw= +github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.57.2 h1:uw37EN34aMFFXB2QPW7Tq6tdTbind1GpRxw5aOX3a5k= +google.golang.org/grpc v1.57.2/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc/examples v0.0.0-20230222033013-5353eaa44095 h1:ijVKWXLMbG/RK63KfOQ1lEVpEApj174fkw073gxZf3w= +google.golang.org/grpc/examples v0.0.0-20230222033013-5353eaa44095/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI= +modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/monitor/monitor.go b/monitor/monitor.go new file mode 100644 index 0000000..c29363a --- /dev/null +++ b/monitor/monitor.go @@ -0,0 +1,491 @@ +package main + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "sync" + "syscall" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + // "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/topology" + "github.com/vishvananda/netlink" +) + +// #include "../monitor.h" +import "C" + +// TODO should not be here +const etherLen = 1200 + +type layerWithOpts struct { + Layer gopacket.SerializableLayer + Opts gopacket.SerializeOptions +} + +func prepareUnderlayPacketHeader(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, dstPort uint16) ([]byte, error) { + ethHeader := 14 + ipHeader := 20 + udpHeader := 8 + + eth := layers.Ethernet{ + SrcMAC: srcMAC, + DstMAC: dstMAC, + EthernetType: layers.EthernetTypeIPv4, + } + + ip := layers.IPv4{ + Version: 4, + IHL: 5, // Computed at serialization when FixLengths option set + TOS: 0x0, + Length: uint16(etherLen - ethHeader), // Computed at serialization when FixLengths option set + Id: 0, + Flags: layers.IPv4DontFragment, + FragOffset: 0, + TTL: 0xFF, + Protocol: layers.IPProtocolUDP, + //Checksum: 0, // Set at serialization with the ComputeChecksums option + SrcIP: srcIP, + DstIP: dstIP, + Options: nil, + } + + srcPort := uint16(topology.EndhostPort) + udp := layers.UDP{ + SrcPort: layers.UDPPort(srcPort), + DstPort: layers.UDPPort(dstPort), + Length: uint16(etherLen - ethHeader - ipHeader), + Checksum: 0, + } + + buf := gopacket.NewSerializeBuffer() + serializeOpts := gopacket.SerializeOptions{ + FixLengths: false, + ComputeChecksums: false, + } + serializeOptsChecked := gopacket.SerializeOptions{ + FixLengths: false, + ComputeChecksums: true, + } + err := serializeLayersWOpts(buf, + layerWithOpts{ð, serializeOpts}, + layerWithOpts{&ip, serializeOptsChecked}, + layerWithOpts{&udp, serializeOpts}) + if err != nil { + return nil, err + } + + // return only the header + return buf.Bytes()[:ethHeader+ipHeader+udpHeader], nil +} + +func serializeLayersWOpts(w gopacket.SerializeBuffer, layersWOpts ...layerWithOpts) error { + err := w.Clear() + if err != nil { + return err + } + for i := len(layersWOpts) - 1; i >= 0; i-- { + layerWOpt := layersWOpts[i] + err := layerWOpt.Layer.SerializeTo(w, layerWOpt.Opts) + if err != nil { + return err + } + w.PushLayer(layerWOpt.Layer.LayerType()) + } + return nil +} + +func getReplyPathHeader(buf []byte) (*HerculesPathHeader, error) { + packet := gopacket.NewPacket(buf, layers.LayerTypeEthernet, gopacket.Default) + if err := packet.ErrorLayer(); err != nil { + return nil, fmt.Errorf("error decoding some part of the packet: %v", err) + } + eth := packet.Layer(layers.LayerTypeEthernet) + if eth == nil { + return nil, errors.New("error decoding ETH layer") + } + dstMAC, srcMAC := eth.(*layers.Ethernet).SrcMAC, eth.(*layers.Ethernet).DstMAC + + ip4 := packet.Layer(layers.LayerTypeIPv4) + if ip4 == nil { + return nil, errors.New("error decoding IPv4 layer") + } + dstIP, srcIP := ip4.(*layers.IPv4).SrcIP, ip4.(*layers.IPv4).DstIP + + udp := packet.Layer(layers.LayerTypeUDP) + if udp == nil { + return nil, errors.New("error decoding IPv4/UDP layer") + } + udpPayload := udp.(*layers.UDP).Payload + udpDstPort := udp.(*layers.UDP).SrcPort + + if len(udpPayload) < 8 { // Guard against bug in ParseScnPkt + return nil, errors.New("error decoding SCION packet: payload too small") + } + + sourcePkt := snet.Packet{ + Bytes: udpPayload, + } + if err := sourcePkt.Decode(); err != nil { + return nil, fmt.Errorf("error decoding SCION packet: %v", err) + } + + rpath, ok := sourcePkt.Path.(snet.RawPath) + if !ok { + return nil, fmt.Errorf("error decoding SCION packet: unexpected dataplane path type") + } + if len(rpath.Raw) != 0 { + replyPath, err := snet.DefaultReplyPather{}.ReplyPath(rpath) + if err != nil { + return nil, fmt.Errorf("failed to reverse SCION path: %v", err) + } + sourcePkt.Path = replyPath + } + + sourcePkt.Path = path.Empty{} + + udpPkt, ok := sourcePkt.Payload.(snet.UDPPayload) + if !ok { + return nil, errors.New("error decoding SCION/UDP") + } + + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort)) + if err != nil { + return nil, err + } + + payload := snet.UDPPayload{ + SrcPort: udpPkt.DstPort, + DstPort: udpPkt.SrcPort, + Payload: nil, + } + + destPkt := &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Destination: sourcePkt.Source, + Source: sourcePkt.Destination, + Path: sourcePkt.Path, + Payload: payload, + }, + } + + if err = destPkt.Serialize(); err != nil { + return nil, err + } + scionHeaderLen := len(destPkt.Bytes) + payloadLen := etherLen - len(underlayHeader) - scionHeaderLen + payload.Payload = make([]byte, payloadLen) + destPkt.Payload = payload + + if err = destPkt.Serialize(); err != nil { + return nil, err + } + scionHeader := destPkt.Bytes[:scionHeaderLen] + scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) + headerBuf := append(underlayHeader, scionHeader...) + herculesPath := HerculesPathHeader{ + Header: headerBuf, + PartialChecksum: scionChecksum, + } + return &herculesPath, nil +} + +func SerializePath(from *HerculesPathHeader) []byte { + fmt.Println("serialize") + out := make([]byte, 0, 1500) + fmt.Println(out) + out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) + fmt.Println(out) + out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) + fmt.Println(out) + out = append(out, from.Header...) + fmt.Println(out) + return out +} + +// getAddrs returns dstMAC, srcMAC and srcIP for a packet to be sent over interface to destination. +func getAddrs(iface *net.Interface, destination net.IP) (dstMAC, srcMAC net.HardwareAddr, err error) { + + srcMAC = iface.HardwareAddr + + // Get destination MAC (address of either destination or gateway) using netlink + // n is the handle (i.e. the main entrypoint) for netlink + n, err := netlink.NewHandle() + if err != nil { + return + } + defer n.Delete() + + routes, err := n.RouteGet(destination) + if err != nil { + return + } + route := routes[0] + for _, r := range routes { + if r.LinkIndex == iface.Index { + route = r + break + } + } + if route.LinkIndex != iface.Index { + err = errors.New("no route found to destination on specified interface") + } + + dstIP := destination + if route.Gw != nil { + dstIP = route.Gw + } + dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) + if err != nil { + if err.Error() == "missing ARP entry" { + // Handle missing ARP entry + fmt.Printf("Sending ICMP echo to %v over %v and retrying...\n", dstIP, iface.Name) + + // Send ICMP + if err = sendICMP(iface, route.Src, dstIP); err != nil { + return + } + // Poll for 3 seconds + for start := time.Now(); time.Since(start) < time.Duration(3)*time.Second; { + dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) + if err == nil { + break + } + } + } + if err != nil { + return + } + } + + return +} + +func sendICMP(iface *net.Interface, srcIP net.IP, dstIP net.IP) (err error) { + icmp := layers.ICMPv4{ + TypeCode: layers.ICMPv4TypeEchoRequest, + } + buf := gopacket.NewSerializeBuffer() + serializeOpts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + err = gopacket.SerializeLayers(buf, serializeOpts, &icmp) + if err != nil { + return err + } + + fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP) + if err != nil { + fmt.Println("Creating raw socket failed.") + return err + } + defer syscall.Close(fd) + dstIPRaw := [4]byte{} + copy(dstIPRaw[:4], dstIP.To4()) + ipSockAddr := syscall.SockaddrInet4{ + Port: 0, + Addr: dstIPRaw, + } + if err = syscall.Sendto(fd, buf.Bytes(), 0, &ipSockAddr); err != nil { + fmt.Printf("Sending ICMP echo to %v over %v failed.\n", dstIP, iface.Name) + return err + } + return nil +} + +// getNeighborMAC returns the HardwareAddr for the neighbor (ARP table entry) with the given IP +func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAddr, error) { + neighbors, err := n.NeighList(linkIndex, netlink.FAMILY_ALL) + if err != nil { + return nil, err + } + for _, neigh := range neighbors { + if neigh.IP.Equal(ip) && neigh.HardwareAddr != nil { + return neigh.HardwareAddr, nil + } + } + return nil, errors.New("missing ARP entry") +} + +func getNewHeader() HerculesPathHeader { + srcAddr := addr.MustParseAddr("17-ffaa:1:fe2,192.168.50.1") + srcIP := net.ParseIP(srcAddr.Host.String()) + dstAddr := addr.MustParseAddr("17-ffaa:1:fe2,192.168.50.2") + dstIP := net.ParseIP(dstAddr.Host.String()) + // querier := newPathQuerier() + // path, err := querier.Query(context.Background(), dest) + // fmt.Println(path, err) + emptyPath := path.Empty{} + + iface, err := net.InterfaceByName("ens5f0") + fmt.Println(iface, err) + dstMAC, srcMAC, err := getAddrs(iface, dstIP) + fmt.Println(dstMAC, srcMAC, err) + fmt.Println(dstIP, srcIP) + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(30041)) + fmt.Println(underlayHeader, err) + + payload := snet.UDPPayload{ + SrcPort: 123, + DstPort: 123, + Payload: nil, + } + + destPkt := &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Destination: dstAddr, + Source: srcAddr, + Path: emptyPath, + Payload: payload, + }, + } + + if err = destPkt.Serialize(); err != nil { + fmt.Println("serializer err") + } + scionHeaderLen := len(destPkt.Bytes) + payloadLen := etherLen - len(underlayHeader) - scionHeaderLen + payload.Payload = make([]byte, payloadLen) + destPkt.Payload = payload + + if err = destPkt.Serialize(); err != nil { + fmt.Println("serrializer err2") + } + scionHeader := destPkt.Bytes[:scionHeaderLen] + scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) + headerBuf := append(underlayHeader, scionHeader...) + herculesPath := HerculesPathHeader{ + Header: headerBuf, + PartialChecksum: scionChecksum, + } + fmt.Println(herculesPath) + return herculesPath +} + +type HerculesTransfer struct { + id int + status int + file string + dest snet.UDPAddr +} + +var transfersLock sync.Mutex +var transfers = map[int]*HerculesTransfer{} +var nextID int = 1 + +// GET params: +// file (File to transfer) +// dest (Destination IA+Host) +func httpreq(w http.ResponseWriter, r *http.Request) { + fmt.Println(r) + if !r.URL.Query().Has("file") || !r.URL.Query().Has("dest") { + io.WriteString(w, "missing parameter") + return + } + file := r.URL.Query().Get("file") + dest := r.URL.Query().Get("dest") + fmt.Println(file, dest) + destParsed, err := snet.ParseUDPAddr(dest) + if err != nil { + io.WriteString(w, "parse err") + return + } + fmt.Println(destParsed) + transfersLock.Lock() + transfers[nextID] = &HerculesTransfer{ + id: nextID, + status: 0, + file: file, + dest: *destParsed, + } + nextID += 1 + transfersLock.Unlock() + + io.WriteString(w, "OK") +} + +func main() { + daemon, err := net.ResolveUnixAddr("unixgram", "/var/hercules.sock") + fmt.Println(daemon, err) + local, err := net.ResolveUnixAddr("unixgram", "/var/herculesmon.sock") + fmt.Println(local, err) + os.Remove("/var/herculesmon.sock") + usock, err := net.ListenUnixgram("unixgram", local) + fmt.Println(usock, err) + + http.HandleFunc("/", httpreq) + go http.ListenAndServe(":8000", nil) + + for { + buf := make([]byte, 2000) + fmt.Println("read...") + n, a, err := usock.ReadFromUnix(buf) + fmt.Println(n, a, err, buf) + if n > 0 { + id := binary.LittleEndian.Uint16(buf[:2]) + buf = buf[2:] + switch id { + case C.SOCKMSG_TYPE_GET_REPLY_PATH: + fmt.Println("reply path") + sample_len := binary.LittleEndian.Uint16(buf[:2]) + buf = buf[2:] + replyPath, err := getReplyPathHeader(buf[:sample_len]) + fmt.Println(replyPath, err) + b := SerializePath(replyPath) + usock.WriteToUnix(b, a) + + case C.SOCKMSG_TYPE_GET_NEW_JOB: + transfersLock.Lock() + var selectedJob *HerculesTransfer = nil + for _, job := range transfers { + if job.status == 0 { + selectedJob = job + job.status = 1 + break + } + } + transfersLock.Unlock() + var b []byte + if selectedJob != nil { + fmt.Println("sending file to daemon", selectedJob.file) + // TODO Conversion between go and C strings? + strlen := len(selectedJob.file) + b = append(b, 1) + b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) + b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) + b = append(b, []byte(selectedJob.file)...) + fmt.Println(b) + } else { + fmt.Println("no new jobs") + b = append(b, 0) + } + usock.WriteToUnix(b, a) + + case C.SOCKMSG_TYPE_GET_PATHS: + fmt.Println("fetch path") + header := getNewHeader() + b := binary.LittleEndian.AppendUint16(nil, uint16(1)) + b = append(b, SerializePath(&header)...) + usock.WriteToUnix(b, a) + + case C.SOCKMSG_TYPE_UPDATE_JOB: + fallthrough + + default: + fmt.Println("unknown message?") + } + } + } +} diff --git a/monitor/network.go b/monitor/network.go new file mode 100644 index 0000000..4e5fa0b --- /dev/null +++ b/monitor/network.go @@ -0,0 +1,56 @@ +// Copyright 2023 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "github.com/scionproto/scion/pkg/daemon" + "github.com/scionproto/scion/pkg/snet" + "os" +) + +func exit(err error) { + fmt.Fprintf(os.Stderr, "Error initializing SCION network: %v\n", err) + os.Exit(1) +} + +func newDaemonConn(ctx context.Context) (daemon.Connector, error) { + address, ok := os.LookupEnv("SCION_DAEMON_ADDRESS") + if !ok { + address = daemon.DefaultAPIAddress + } + daemonConn, err := daemon.NewService(address).Connect(ctx) + if err != nil { + return nil, fmt.Errorf("unable to connect to SCIOND at %s (override with SCION_DAEMON_ADDRESS): %w", address, err) + } + return daemonConn, nil +} + +func newPathQuerier() snet.PathQuerier { + ctx := context.Background() + daemonConn, err := newDaemonConn(ctx) + if err != nil { + exit(err) + } + localIA, err := daemonConn.LocalIA(ctx) + if err != nil { + exit(err) + } + return daemon.Querier{ + Connector: daemonConn, + IA: localIA, + } +} diff --git a/monitor/pathinterface.go b/monitor/pathinterface.go new file mode 100644 index 0000000..35bd00e --- /dev/null +++ b/monitor/pathinterface.go @@ -0,0 +1,59 @@ +// Copyright 2019 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "fmt" + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/snet" + "strconv" +) + +type PathInterface struct { + ia addr.IA + ifId uint64 +} + +func (iface *PathInterface) ID() uint64 { + return iface.ifId +} + +func (iface *PathInterface) IA() addr.IA { + return iface.ia +} + +func (iface *PathInterface) UnmarshalText(text []byte) error { + parts := bytes.Split(text, []byte{' '}) + if len(parts) > 2 { + return fmt.Errorf("cannot unmarshal \"%s\" as PathInterface: contains too many spaces", text) + } + if len(parts) > 1 { + ifId, err := strconv.ParseInt(string(parts[1]), 10, 64) + if err != nil { + return err + } + iface.ifId = uint64(ifId) + } + ret := iface.ia.UnmarshalText(parts[0]) + return ret +} + +func (iface *PathInterface) match(pathIface snet.PathInterface) bool { + if iface.ifId == 0 { + return iface.IA() == pathIface.IA + } + return iface.ID() == uint64(pathIface.ID) && iface.IA() == pathIface.IA +} diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go new file mode 100644 index 0000000..d52a1ed --- /dev/null +++ b/monitor/pathmanager.go @@ -0,0 +1,137 @@ +// Copyright 2019 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "github.com/scionproto/scion/pkg/snet" + "github.com/vishvananda/netlink" + "net" + "time" +) + +type Destination struct { + hostAddr *snet.UDPAddr + pathSpec *[]PathSpec + numPaths int +} + +type PathManager struct { + numPathSlotsPerDst int + interfaces map[int]*net.Interface + dsts []*PathsToDestination + src *snet.UDPAddr + syncTime time.Time + maxBps uint64 + // cStruct CPathManagement +} + +type PathWithInterface struct { + path snet.Path + iface *net.Interface +} + +type AppPathSet map[snet.PathFingerprint]PathWithInterface + +const numPathsResolved = 20 + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func initNewPathManager(interfaces []*net.Interface, dsts []*Destination, src *snet.UDPAddr, maxBps uint64) (*PathManager, error) { + ifMap := make(map[int]*net.Interface) + for _, iface := range interfaces { + ifMap[iface.Index] = iface + } + + numPathsPerDst := 0 + pm := &PathManager{ + interfaces: ifMap, + src: src, + dsts: make([]*PathsToDestination, 0, len(dsts)), + syncTime: time.Unix(0, 0), + maxBps: maxBps, + } + + for _, dst := range dsts { + var dstState *PathsToDestination + if src.IA == dst.hostAddr.IA { + dstState = initNewPathsToDestinationWithEmptyPath(pm, dst) + } else { + var err error + dstState, err = initNewPathsToDestination(pm, src, dst) + if err != nil { + return nil, err + } + } + pm.dsts = append(pm.dsts, dstState) + numPathsPerDst = max(numPathsPerDst, dst.numPaths) + } + + // allocate memory to pass paths to C + pm.numPathSlotsPerDst = numPathsPerDst + // pm.cStruct.initialize(len(dsts), numPathsPerDst) + return pm, nil +} + +func (pm *PathManager) canSendToAllDests() bool { + for _, dst := range pm.dsts { + if !dst.hasUsablePaths() { + return false + } + } + return true +} + +func (pm *PathManager) choosePaths() bool { + updated := false + for _, dst := range pm.dsts { + if dst.choosePaths() { + updated = true + } + } + return updated +} + +func (pm *PathManager) filterPathsByActiveInterfaces(pathsAvail []snet.Path) AppPathSet { + pathsFiltered := make(AppPathSet) + for _, path := range pathsAvail { + iface, err := pm.interfaceForRoute(path.UnderlayNextHop().IP) + if err != nil { + } else { + pathsFiltered[snet.Fingerprint(path)] = PathWithInterface{path, iface} + } + } + return pathsFiltered +} + +func (pm *PathManager) interfaceForRoute(ip net.IP) (*net.Interface, error) { + routes, err := netlink.RouteGet(ip) + if err != nil { + return nil, fmt.Errorf("could not find route for destination %s: %s", ip, err) + } + + for _, route := range routes { + if iface, ok := pm.interfaces[route.LinkIndex]; ok { + fmt.Printf("sending via #%d (%s) to %s\n", route.LinkIndex, pm.interfaces[route.LinkIndex].Name, ip) + return iface, nil + } + } + return nil, fmt.Errorf("no interface active for sending to %s", ip) +} diff --git a/monitor/pathpicker.go b/monitor/pathpicker.go new file mode 100644 index 0000000..315a702 --- /dev/null +++ b/monitor/pathpicker.go @@ -0,0 +1,201 @@ +// Copyright 2019 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "github.com/scionproto/scion/pkg/snet" +) + +type PathSpec []PathInterface + +type PathPickDescriptor struct { + ruleIndex int + pathIndex int +} + +type PathPicker struct { + pathSpec *[]PathSpec + availablePaths []snet.Path + currentPathPick []PathPickDescriptor +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func makePathPicker(spec *[]PathSpec, pathSet *AppPathSet, numPaths int) *PathPicker { + if len(*spec) == 0 { + defaultSpec := make([]PathSpec, numPaths) + spec = &defaultSpec + } + paths := make([]snet.Path, 0, len(*pathSet)) + for _, path := range *pathSet { + paths = append(paths, path.path) + } + picker := &PathPicker{ + pathSpec: spec, + availablePaths: paths, + } + picker.reset(numPaths) + return picker +} + +func (picker *PathPicker) reset(numPaths int) { + descriptor := make([]PathPickDescriptor, numPaths) + for i := range descriptor { + descriptor[i].ruleIndex = -1 + descriptor[i].pathIndex = -1 + } + picker.currentPathPick = descriptor +} + +func (picker *PathPicker) maxRuleIdx() int { + // rule indices are sorted ascending + for idx := len(picker.currentPathPick) - 1; idx >= 0; idx++ { + if picker.currentPathPick[idx].ruleIndex != -1 { + return picker.currentPathPick[idx].ruleIndex + } + } + return -1 +} + +func (picker *PathPicker) numPaths() int { + numPaths := 0 + for _, pick := range picker.currentPathPick { + if pick.pathIndex != -1 { + numPaths++ + } + } + return numPaths +} + +// Iterates to the next set of rules. Returns false, if no set of the appropriate size exists. +func (picker *PathPicker) nextRuleSet() bool { + ret := picker.nextRuleSetIterate(len(picker.currentPathPick) - 1) + if ret { + for i := range picker.currentPathPick { + picker.currentPathPick[i].pathIndex = -1 + } + } + return ret +} + +func (picker *PathPicker) nextRuleSetIterate(idx int) bool { + if idx > 0 && picker.currentPathPick[idx].ruleIndex == -1 { + if !picker.nextRuleSetIterate(idx - 1) { + return false + } + picker.currentPathPick[idx].ruleIndex = picker.currentPathPick[idx-1].ruleIndex + } + ruleIdx := picker.currentPathPick[idx].ruleIndex + 1 + for true { + if ruleIdx < len(*picker.pathSpec) { + picker.currentPathPick[idx].ruleIndex = ruleIdx + return true + } + // overflow + if idx > 0 { + if !picker.nextRuleSetIterate(idx - 1) { + picker.currentPathPick[idx].ruleIndex = -1 + return false + } + ruleIdx = picker.currentPathPick[idx-1].ruleIndex + 1 + } else { + break // cannot overflow, abort + } + } + return false +} + +// Iterates to the next allowed choice of paths. Returns false, if no next pick exists. +func (picker *PathPicker) nextPick() bool { + return picker.nextPickIterate(len(picker.currentPathPick) - 1) +} + +func (picker *PathPicker) nextPickIterate(idx int) bool { + if idx > 0 && picker.currentPathPick[idx-1].pathIndex == -1 { + if !picker.nextPickIterate(idx - 1) { + return false + } + } + for true { + for pathIdx := picker.currentPathPick[idx].pathIndex + 1; pathIdx < len(picker.availablePaths); pathIdx++ { + if !picker.isInUse(pathIdx, idx) && picker.matches(pathIdx, picker.currentPathPick[idx].ruleIndex) { + picker.currentPathPick[idx].pathIndex = pathIdx + return true + } + } + // overflow + if idx > 0 { + picker.currentPathPick[idx].pathIndex = -1 + if !picker.nextPickIterate(idx - 1) { + return false + } + } else { + break // cannot overflow, abort + } + } + return false +} + +func (picker *PathPicker) matches(pathIdx, ruleIdx int) bool { + pathSpec := (*picker.pathSpec)[ruleIdx] + pathInterfaces := picker.availablePaths[pathIdx].Metadata().Interfaces + idx := 0 + for _, iface := range pathSpec { + for len(pathInterfaces) > idx && !iface.match(pathInterfaces[idx]) { + idx++ + } + if idx >= len(pathInterfaces) { + return false + } + } + return true +} + +func (picker *PathPicker) isInUse(pathIdx, idx int) bool { + for i, pick := range picker.currentPathPick { + if i > idx { + return false + } + if pick.pathIndex == pathIdx { + return true + } + } + return false +} + +func (picker *PathPicker) disjointnessScore() int { + interfaces := map[snet.PathInterface]int{} + score := 0 + for _, pick := range picker.currentPathPick { + for _, path := range picker.availablePaths[pick.pathIndex].Metadata().Interfaces { + score -= interfaces[path] + interfaces[path]++ + } + } + return score +} + +func (picker *PathPicker) getPaths() []snet.Path { + paths := make([]snet.Path, 0, len(picker.currentPathPick)) + for _, pick := range picker.currentPathPick { + paths = append(paths, picker.availablePaths[pick.pathIndex]) + } + return paths +} diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go new file mode 100644 index 0000000..21ba4fe --- /dev/null +++ b/monitor/pathstodestination.go @@ -0,0 +1,236 @@ +// Copyright 2019 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + log "github.com/inconshreveable/log15" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/topology" + "go.uber.org/atomic" + "net" + "time" +) + +type PathsToDestination struct { + pm *PathManager + dst *Destination + modifyTime time.Time + ExtnUpdated atomic.Bool + allPaths []snet.Path + paths []PathMeta // nil indicates that the destination is in the same AS as the sender and we can use an empty path + canSendLocally bool // (only if destination in same AS) indicates if we can send packets +} + +type PathMeta struct { + path snet.Path + fingerprint snet.PathFingerprint + iface *net.Interface + enabled bool // Indicates whether this path can be used at the moment + updated bool // Indicates whether this path needs to be synced to the C path +} + +type HerculesPathHeader struct { + Header []byte //!< C.HERCULES_MAX_HEADERLEN bytes + PartialChecksum uint16 //SCION L4 checksum over header with 0 payload +} + +func initNewPathsToDestinationWithEmptyPath(pm *PathManager, dst *Destination) *PathsToDestination { + return &PathsToDestination{ + pm: pm, + dst: dst, + paths: nil, + modifyTime: time.Now(), + } +} + +func initNewPathsToDestination(pm *PathManager, src *snet.UDPAddr, dst *Destination) (*PathsToDestination, error) { + paths, err := newPathQuerier().Query(context.Background(), dst.hostAddr.IA) + if err != nil { + return nil, err + } + return &PathsToDestination{ + pm: pm, + dst: dst, + allPaths: paths, + paths: make([]PathMeta, dst.numPaths), + modifyTime: time.Unix(0, 0), + }, nil +} + +func (ptd *PathsToDestination) hasUsablePaths() bool { + if ptd.paths == nil { + return ptd.canSendLocally + } + for _, path := range ptd.paths { + if path.enabled { + return true + } + } + return false +} + +func (ptd *PathsToDestination) choosePaths() bool { + if ptd.allPaths == nil { + return false + } + + if ptd.modifyTime.After(time.Unix(0, 0)) { // TODO this chooses paths only once + if ptd.ExtnUpdated.Swap(false) { + ptd.modifyTime = time.Now() + return true + } + return false + } + + availablePaths := ptd.pm.filterPathsByActiveInterfaces(ptd.allPaths) + if len(availablePaths) == 0 { + log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) + } + + previousPathAvailable := make([]bool, ptd.dst.numPaths) + updated := ptd.choosePreviousPaths(&previousPathAvailable, &availablePaths) + + if ptd.disableVanishedPaths(&previousPathAvailable) { + updated = true + } + // Note: we keep vanished paths around until they can be replaced or re-enabled + + if ptd.chooseNewPaths(&previousPathAvailable, &availablePaths) { + updated = true + } + + if ptd.ExtnUpdated.Swap(false) || updated { + ptd.modifyTime = time.Now() + return true + } + return false +} + +func (ptd *PathsToDestination) choosePreviousPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { + updated := false + for newFingerprint := range *availablePaths { + for i := range ptd.paths { + pathMeta := &ptd.paths[i] + if newFingerprint == pathMeta.fingerprint { + if !pathMeta.enabled { + log.Info(fmt.Sprintf("[Destination %s] re-enabling path %d\n", ptd.dst.hostAddr.IA, i)) + pathMeta.enabled = true + updated = true + } + (*previousPathAvailable)[i] = true + break + } + } + } + return updated +} + +func (ptd *PathsToDestination) disableVanishedPaths(previousPathAvailable *[]bool) bool { + updated := false + for i, inUse := range *previousPathAvailable { + pathMeta := &ptd.paths[i] + if inUse == false && pathMeta.enabled { + log.Info(fmt.Sprintf("[Destination %s] disabling path %d\n", ptd.dst.hostAddr.IA, i)) + pathMeta.enabled = false + updated = true + } + } + return updated +} + +func (ptd *PathsToDestination) chooseNewPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { + updated := false + // XXX for now, we do not support replacing vanished paths + // check that no previous path available + for _, prev := range *previousPathAvailable { + if prev { + return false + } + } + + // pick paths + picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) + var pathSet []snet.Path + disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) + maxRuleIdx := 0 // the highest index of a PathSpec that is used (to be minimized) + for i := ptd.dst.numPaths; i > 0; i-- { + picker.reset(i) + for picker.nextRuleSet() { // iterate through different choices of PathSpecs to use + if pathSet != nil && maxRuleIdx < picker.maxRuleIdx() { // ignore rule set, if path set with lower maxRuleIndex is known + continue // due to the iteration order, we cannot break here + } + for picker.nextPick() { // iterate through different choices of paths obeying the rules of the current set of PathSpecs + curDisjointness := picker.disjointnessScore() + if pathSet == nil || disjointness < curDisjointness { // maximize disjointness + disjointness = curDisjointness + maxRuleIdx = picker.maxRuleIdx() + pathSet = picker.getPaths() + } + } + } + if pathSet != nil { // if no path set of size i found, try with i-1 + break + } + } + + log.Info(fmt.Sprintf("[Destination %s] using %d paths:", ptd.dst.hostAddr.IA, len(pathSet))) + for i, path := range pathSet { + log.Info(fmt.Sprintf("\t%s", path)) + fingerprint := snet.Fingerprint(path) + ptd.paths[i].path = path + ptd.paths[i].fingerprint = fingerprint + ptd.paths[i].enabled = true + ptd.paths[i].updated = true + ptd.paths[i].iface = (*availablePaths)[fingerprint].iface + updated = true + } + return updated +} + +func (ptd *PathsToDestination) preparePath(p *PathMeta) (*HerculesPathHeader, error) { + var err error + var iface *net.Interface + curDst := ptd.dst.hostAddr + fmt.Println("preparepath", curDst, iface) + if (*p).path == nil { + // in order to use a static empty path, we need to set the next hop on dst + fmt.Println("empty path") + curDst.NextHop = &net.UDPAddr{ + IP: ptd.dst.hostAddr.Host.IP, + Port: topology.EndhostPort, + } + fmt.Println("nexthop", curDst.NextHop) + iface, err = ptd.pm.interfaceForRoute(ptd.dst.hostAddr.Host.IP) + if err != nil { + return nil, err + } + curDst.Path = path.Empty{} + } else { + curDst.Path = (*p).path.Dataplane() + + curDst.NextHop = (*p).path.UnderlayNextHop() + iface = p.iface + } + + // path, err := prepareSCIONPacketHeader(ptd.pm.src, curDst, iface) + // if err != nil { + // return nil, err + // } + // return path, nil + return nil, fmt.Errorf("NOPE"); +} diff --git a/packet.h b/packet.h index 931af05..43b2f45 100644 --- a/packet.h +++ b/packet.h @@ -77,6 +77,8 @@ struct rbudp_initial_pkt { __u64 timestamp; __u8 path_index; __u8 flags; + __u32 name_len; + __u8 name[]; }; #define HANDSHAKE_FLAG_SET_RETURN_PATH 0x1u diff --git a/send_queue.c b/send_queue.c index 6248b55..59ec7f9 100644 --- a/send_queue.c +++ b/send_queue.c @@ -20,7 +20,6 @@ void init_send_queue(struct send_queue *queue, u32 num_entries) { - assert(sizeof(struct send_queue_unit) == 64); queue->units_base = (struct send_queue_unit *)calloc(num_entries + 1, sizeof(struct send_queue_unit)); // make sure units are aligned to cache lines u32 offset = (size_t)queue->units_base % CACHELINE_SIZE; diff --git a/send_queue.h b/send_queue.h index 29ec002..9b7c5d5 100644 --- a/send_queue.h +++ b/send_queue.h @@ -31,6 +31,7 @@ struct send_queue_unit { u32 ts; char a[CACHELINE_SIZE - 6 - SEND_QUEUE_ENTRIES_PER_UNIT * SEND_QUEUE_ENTRY_SIZE]; // force padding to 64 bytes }; +_Static_assert(sizeof(struct send_queue_unit) == 64, "struct send_queue_unit should be cache line sized"); // single producer, multi consumer queue // the queue is empty if head == tail From c4fc1006665b06db4466d3c964dab371328b57c9 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 23 May 2024 18:18:35 +0200 Subject: [PATCH 003/112] Arguments for daemon --- hercules.c | 445 +++++++++++++++++++++++++++++++++-------------------- hercules.h | 6 +- monitor.c | 20 +++ monitor.h | 2 + 4 files changed, 302 insertions(+), 171 deletions(-) diff --git a/hercules.c b/hercules.c index 354024e..c258cdb 100644 --- a/hercules.c +++ b/hercules.c @@ -4,6 +4,7 @@ // Enable extra warnings; cannot be enabled in CFLAGS because cgo generates a // ton of warnings that can apparantly not be suppressed. +#include #pragma GCC diagnostic warning "-Wextra" #include "hercules.h" @@ -36,6 +37,7 @@ #include #include #include +#include #include "linux/bpf_util.h" #include "bpf/src/libbpf.h" @@ -118,8 +120,9 @@ struct hercules_interface { struct hercules_config { u32 xdp_flags; + int xdp_mode; + bool configure_queues; struct hercules_app_addr local_addr; - int ether_size; }; enum session_state { @@ -148,11 +151,10 @@ struct hercules_session { struct hercules_server { struct hercules_config config; - int control_sockfd; - int *ifindices; - struct hercules_app_addr local_addr; + int control_sockfd; // AF_PACKET socket used for control traffic int queue; - int mtu; + int n_threads; + struct rx_p_args **worker_args; struct hercules_session *session_tx; //Current TX session u64 session_tx_counter; struct hercules_session *session_rx; //Current RX session @@ -163,10 +165,7 @@ struct hercules_server { int max_paths; int rate_limit; bool enable_pcc; - int n_pending; - char pending[5][20]; - struct xsk_umem_info *umem; - struct xsk_socket_info *xsk; + int *ifindices; int num_ifaces; struct hercules_interface ifaces[]; }; @@ -333,7 +332,7 @@ static void __exit_with_error(struct hercules_server *server, int error, const c exit(EXIT_FAILURE); } -#define exit_with_error(session, error) __exit_with_error(session, error, __FILE__, __func__, __LINE__) +#define exit_with_error(server, error) __exit_with_error(server, error, __FILE__, __func__, __LINE__) static void close_xsk(struct xsk_socket_info *xsk) { @@ -736,18 +735,17 @@ static struct xsk_umem_info *xsk_configure_umem(struct hercules_server *server, static struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, u32 ifidx, void *buffer, u64 size) { - debug_printf("xsk_configure_umem_server"); struct xsk_umem_info *umem; int ret; umem = calloc(1, sizeof(*umem)); if(!umem) - exit_with_error(NULL, errno); + exit_with_error(server, errno); ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, NULL); if(ret) - exit_with_error(NULL, -ret); + exit_with_error(server, -ret); umem->buffer = buffer; umem->iface = &server->ifaces[ifidx]; @@ -755,7 +753,7 @@ static struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *s // pushed in submit_initial_tx_frames() (assumption in pop_completion_ring() and handle_send_queue_unit()) ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS); if(ret) - exit_with_error(NULL, ret); + exit_with_error(server, ret); pthread_spin_init(&umem->lock, 0); return umem; } @@ -780,6 +778,11 @@ static void kick_all_tx(struct hercules_server *server, struct hercules_interfac kick_tx(server, iface->xsks[s]); } } +static void kick_tx_server(struct hercules_server *server){ + for (int i = 0; i < server->num_ifaces; i++){ + kick_all_tx(server, &server->ifaces[i]); + } +} static void submit_initial_rx_frames(struct hercules_server *server, struct xsk_umem_info *umem) { @@ -1378,11 +1381,10 @@ static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t /* } */ static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, - unsigned long timestamp, u32 path_index, bool set_return_path) +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path) { debug_printf("Sending initial"); - char buf[server->config.ether_size]; + char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); struct hercules_control_packet pld = { @@ -1848,23 +1850,24 @@ static inline void allocate_tx_frames(struct hercules_server *server, } struct tx_send_p_args { - struct sender_state *tx_state; + struct hercules_server *server; struct xsk_socket_info *xsks[]; }; static void tx_send_p(void *arg) { - struct hercules_server *server = arg; + struct tx_send_p_args *args = arg; + struct hercules_server *server = args->server; while (1) { struct hercules_session *session_tx = atomic_load(&server->session_tx); if (session_tx == NULL || atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { /* debug_printf("Invalid session, dropping sendq unit"); */ - kick_tx(server, &server->xsk[0]); + kick_tx_server(server); continue; } struct send_queue_unit unit; int ret = send_queue_pop(session_tx->send_queue, &unit); if (!ret) { - kick_tx(server, &server->xsk[0]); + kick_tx_server(server); continue; } u32 num_chunks_in_unit = 0; @@ -1877,7 +1880,7 @@ static void tx_send_p(void *arg) { debug_printf("unit has %d chunks", num_chunks_in_unit); u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; memset(frame_addrs, 0xFF, sizeof(frame_addrs)); - for (int i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ + for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ if (i >= num_chunks_in_unit){ debug_printf("not using unit slot %d", i); frame_addrs[0][i] = 0; @@ -1887,10 +1890,10 @@ static void tx_send_p(void *arg) { allocate_tx_frames(server, frame_addrs); debug_printf("done allocating frames"); // At this point we claimed 7 frames (as many as can fit in a sendq unit) - tx_handle_send_queue_unit(server, session_tx->tx_state, &server->xsk, + tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, frame_addrs, &unit); /* assert(num_chunks_in_unit == 7); */ - kick_tx(server, &server->xsk[0]); + kick_tx_server(server); } } @@ -2328,7 +2331,7 @@ static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_stat return; } - char buf[server->config.ether_size]; + char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); struct hercules_control_packet control_pkt = { @@ -2492,7 +2495,7 @@ static void rx_send_cts_ack(struct hercules_server *server, struct receiver_stat return; } - char buf[server->config.ether_size]; + char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); struct hercules_control_packet control_pkt = { @@ -2510,7 +2513,7 @@ static void rx_send_cts_ack(struct hercules_server *server, struct receiver_stat static void rx_send_ack_pkt(struct hercules_server *server, struct hercules_control_packet *control_pkt, struct hercules_path *path) { - char buf[server->config.ether_size]; + char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, @@ -2585,7 +2588,7 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s return; } - char buf[server->config.ether_size]; + char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); // XXX: could write ack payload directly to buf, but @@ -2665,18 +2668,20 @@ static void rx_trickle_nacks(void *arg) { } struct rx_p_args { - struct receiver_state *rx_state; + struct hercules_server *server; struct xsk_socket_info *xsks[]; }; static void *rx_p(void *arg) { - debug_printf("rx_p"); - struct hercules_server *server = arg; - /* int num_ifaces = server->num_ifaces; */ + struct rx_p_args *args = arg; + struct hercules_server *server = args->server; + int num_ifaces = server->num_ifaces; + u32 i = 0; while (1) { - struct hercules_session *session_rx = atomic_load(&server->session_rx); + struct hercules_session *session_rx = atomic_load(&server->session_rx); if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { - rx_receive_batch(session_rx->rx_state, server->xsk); + rx_receive_batch(session_rx->rx_state, args->xsks[i % num_ifaces]); + i++; } } return NULL; @@ -3061,7 +3066,7 @@ static void xsk_map__add_xsk(struct hercules_server *server, xskmap map, int ind /* * Load a BPF program redirecting IP traffic to the XSK. */ -static void load_xsk_redirect_userspace(struct hercules_server *server, int num_threads) +static void load_xsk_redirect_userspace(struct hercules_server *server, struct rx_p_args *args[], int num_threads) { debug_printf("Loading XDP program for redirection"); for(int i = 0; i < server->num_ifaces; i++) { @@ -3077,7 +3082,7 @@ static void load_xsk_redirect_userspace(struct hercules_server *server, int num_ exit_with_error(server, -xsks_map_fd); } for(int s = 0; s < num_threads; s++) { - xsk_map__add_xsk(server, xsks_map_fd, s, server->xsk); + xsk_map__add_xsk(server, xsks_map_fd, s, args[s]->xsks[i]); } // push XSKs meta @@ -3185,47 +3190,49 @@ static void *tx_p(void *arg) { /* return session; */ /* } */ -struct hercules_server *hercules_init_server(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, int queue, int mtu){ - debug_printf("init_server, %d interfaces, queue %d, mtu %d", num_ifaces, queue, mtu); - struct hercules_server *server; - server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); - if (server == NULL){ - debug_quit("init calloc"); - } - printf("local %llx %x %x\n", local_addr.ia, local_addr.ip, local_addr.port); - server->ifindices = ifindices; - server->num_ifaces = num_ifaces; - server->local_addr = local_addr; - server->queue = queue; - server->mtu = mtu; - server->session_rx = NULL; - server->session_tx = NULL; - server->session_tx_counter = 0; - - for(int i = 0; i < num_ifaces; i++) { - debug_printf("iter %d", i); - server->ifaces[i] = (struct hercules_interface) { - .queue = queue, - .ifid = ifindices[i], - .ethtool_rule = -1, - }; - if_indextoname(ifindices[i], server->ifaces[i].ifname); - debug_printf("using queue %d on interface %s", server->ifaces[i].queue, server->ifaces[i].ifname); - } - - pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); - - server->config.ether_size = mtu; - server->config.local_addr = local_addr; - server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); - assert(server->control_sockfd != -1); - - debug_printf("init complete"); - return server; -} +struct hercules_server * +hercules_init_server(int *ifindices, int num_ifaces, + const struct hercules_app_addr local_addr, int queue, + int xdp_mode, int n_threads, bool configure_queues) { + struct hercules_server *server; + server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); + if (server == NULL) { + exit_with_error(NULL, ENOMEM); + } + debug_printf("local address %llx %x %x", local_addr.ia, local_addr.ip, + local_addr.port); + server->ifindices = ifindices; + server->num_ifaces = num_ifaces; + server->queue = queue; + server->n_threads = n_threads; + server->session_rx = NULL; + server->session_tx = NULL; + server->session_tx_counter = 0; + server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); + server->config.local_addr = local_addr; + server->config.configure_queues = configure_queues; + + for (int i = 0; i < num_ifaces; i++) { + server->ifaces[i] = (struct hercules_interface){ + .queue = queue, + .ifid = ifindices[i], + .ethtool_rule = -1, + }; + if_indextoname(ifindices[i], server->ifaces[i].ifname); + debug_printf("using queue %d on interface %s", server->ifaces[i].queue, + server->ifaces[i].ifname); + } + pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); + server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (server->control_sockfd == -1) { + exit_with_error(server, 0); + } + debug_printf("init complete"); + return server; +} struct path_stats *make_path_stats_buffer(int num_paths) { struct path_stats *path_stats = calloc(1, sizeof(*path_stats) + num_paths * sizeof(path_stats->paths[0])); @@ -3634,61 +3641,89 @@ static void socket_handler(void *arg) { } } -static void xdp_setup(struct hercules_server *server, int xdp_mode){ - // Prepare UMEM for XSK socket - void *umem_buf; - int ret = posix_memalign(&umem_buf, getpagesize(), - NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); - if (ret) { - debug_quit("Allocating umem buffer"); +static void xdp_setup(struct hercules_server *server) { + for (int i = 0; i < server->num_ifaces; i++) { + debug_printf("Preparing interface %d", i); + // Prepare UMEM for XSK sockets + void *umem_buf; + int ret = posix_memalign(&umem_buf, getpagesize(), + NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + if (ret) { + exit_with_error(server, ENOMEM); + } + debug_printf("Allocated umem buffer"); + + struct xsk_umem_info *umem = xsk_configure_umem_server( + server, i, umem_buf, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + debug_printf("Configured umem"); + + server->ifaces[i].xsks = + calloc(server->n_threads, sizeof(*server->ifaces[i].xsks)); + server->ifaces[i].umem = umem; + submit_initial_tx_frames(server, umem); + submit_initial_rx_frames(server, umem); + debug_printf("umem interface %d %s, queue %d", umem->iface->ifid, + umem->iface->ifname, umem->iface->queue); + if (server->ifaces[i].ifid != umem->iface->ifid) { + debug_printf( + "cannot configure XSK on interface %d with queue on interface %d", + server->ifaces[i].ifid, umem->iface->ifid); + exit_with_error(server, EINVAL); + } + + // Create XSK sockets + for (int t = 0; t < server->n_threads; t++) { + struct xsk_socket_info *xsk; + xsk = calloc(1, sizeof(*xsk)); + if (!xsk) { + exit_with_error(server, ENOMEM); + } + xsk->umem = umem; + + struct xsk_socket_config cfg; + cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; + cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; + cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; + cfg.xdp_flags = server->config.xdp_flags; + cfg.bind_flags = server->config.xdp_mode; + ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, + server->queue, umem->umem, &xsk->rx, + &xsk->tx, &umem->fq, &umem->cq, &cfg); + if (ret) { + exit_with_error(server, -ret); + } + ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, + &server->ifaces[i].prog_id, + server->config.xdp_flags); + if (ret) { + exit_with_error(server, -ret); + } + server->ifaces[i].xsks[t] = xsk; + } } - debug_printf("Allocated umem buffer"); - struct xsk_umem_info *umem = xsk_configure_umem_server( - server, 0, umem_buf, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); - debug_printf("Configured umem"); - server->ifaces[0].xsks = calloc(1, sizeof(*server->ifaces[0].xsks)); - server->ifaces[0].umem = umem; - submit_initial_tx_frames(NULL, umem); - submit_initial_rx_frames(NULL, umem); - debug_printf("umem created"); - debug_printf("umem interface %d %s, queue %d", umem->iface->ifid, - umem->iface->ifname, umem->iface->queue); - - // Create XSK socket - struct xsk_socket_info *xsk; - xsk = calloc(1, sizeof(*xsk)); - if (!xsk) { - debug_quit("xsk calloc"); + for (int t = 0; t < server->n_threads; t++){ + server->worker_args[t] = malloc(sizeof(*server->worker_args) + server->num_ifaces*sizeof(*server->worker_args[t]->xsks)); + if (server->worker_args[t] == NULL){ + exit_with_error(server, ENOMEM); + } + server->worker_args[t]->server = server; + for (int i = 0; i < server->num_ifaces; i++){ + server->worker_args[t]->xsks[i] = server->ifaces[i].xsks[t]; + } } - xsk->umem = umem; - - assert(server->ifaces[0].ifid == umem->iface->ifid); - struct xsk_socket_config cfg; - cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; - cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; - cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; - cfg.xdp_flags = server->config.xdp_flags; - cfg.bind_flags = xdp_mode; - ret = xsk_socket__create_shared(&xsk->xsk, "ens5f0", 0, umem->umem, &xsk->rx, - &xsk->tx, &umem->fq, &umem->cq, &cfg); - if (ret) { - debug_quit("xsk socket create"); + + load_xsk_redirect_userspace(server, server->worker_args, server->n_threads); + // TODO this is not set anywhere? + if (server->config.configure_queues){ + configure_rx_queues(server); } - server->xsk = xsk; - debug_printf("xsk socket created"); - load_xsk_redirect_userspace(server, 1); debug_printf("XSK stuff complete"); - // SETUP COMPLETE - server->ifaces[0].xsks = &xsk; - server->umem = umem; } -void hercules_main(struct hercules_server *server, int xdp_mode) { +void hercules_main(struct hercules_server *server) { debug_printf("Hercules main"); - debug_printf("#ifaces %d, mtu %d, queue %d", server->num_ifaces, server->mtu, - server->queue); - xdp_setup(server, xdp_mode); + xdp_setup(server); // Start socket handler thread @@ -3697,10 +3732,9 @@ void hercules_main(struct hercules_server *server, int xdp_mode) { // Start event receiver thread -debug_printf("Starting event receiver thread"); + debug_printf("Starting event receiver thread"); pthread_t events = start_thread(NULL, events_p, server); - // XXX only needed for PCC // Start the NACK sender thread debug_printf("starting NACK trickle thread"); pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); @@ -3710,13 +3744,19 @@ debug_printf("Starting event receiver thread"); pthread_t trickle_acks = start_thread(NULL, rx_trickle_acks, server); - // Start the RX worker thread - debug_printf("starting thread rx_p"); - pthread_t rx_p_thread = start_thread(NULL, rx_p, server); + // Start the RX worker threads + pthread_t rx_workers[server->n_threads]; + for (int i = 0; i < server->n_threads; i++) { + debug_printf("starting thread rx_p %d", i); + rx_workers[i] = start_thread(NULL, rx_p, server->worker_args[i]); + } - // Start the TX worker thread - debug_printf("starting thread tx_send_p"); - pthread_t tx_send_p_thread = start_thread(NULL, tx_send_p, server); + // Start the TX worker threads + pthread_t tx_workers[server->n_threads]; + for (int i = 0; i < server->n_threads; i++) { + debug_printf("starting thread tx_send_p %d", i); + tx_workers[i] = start_thread(NULL, tx_send_p, server->worker_args[i]); + } // Start the TX scheduler thread debug_printf("starting thread tx_p"); @@ -3727,49 +3767,116 @@ debug_printf("Starting event receiver thread"); } } +void usage(){ + fprintf(stderr, "usage: ?? TODO\n"); + exit(1); +} + +// TODO what's up with multiple interfaces? +#define HERCULES_MAX_INTERFACES 1 int main(int argc, char *argv[]) { - usock = socket(AF_UNIX, SOCK_DGRAM, 0); - assert(usock > 0); - struct sockaddr_un name; - name.sun_family = AF_UNIX; - strcpy(name.sun_path, "/var/hercules.sock"); - unlink("/var/hercules.sock"); - int ret = bind(usock, &name, sizeof(name)); - assert(!ret); - - int ifindices[1] = {3}; - int num_ifaces = 1; - struct hercules_app_addr local_addr = { - .ia = 0xe20f0100aaff1100ULL, .ip = 0x232a8c0UL, .port = 0x7b00}; - struct hercules_app_addr receiver = local_addr; + // Options: + // -i interface + // -l listen address + // -z XDP zerocopy mode + // -q queue + // -t TX worker threads + // -r RX worker threads + unsigned int if_idxs[HERCULES_MAX_INTERFACES]; // XXX 10 should be enough + int n_interfaces = 0; + struct hercules_app_addr listen_addr = {.ia = 0, .ip = 0, .port = 0}; + int xdp_mode = XDP_COPY; int queue = 0; - int mtu = 1200; - if (argc == 2 && strcmp(argv[1], "TX") == 0) { - // Sender - // scionlab1, 192.168.50.1:123 - local_addr.ip = 0x132a8c0UL; + int tx_threads = 1; + int rx_threads = 1; + int opt; + while ((opt = getopt(argc, argv, "i:l:q:t:r:z")) != -1) { + switch (opt) { + case 'i': + debug_printf("Using interface %s", optarg); + if (n_interfaces >= HERCULES_MAX_INTERFACES) { + fprintf(stderr, "Too many interfaces specified\n"); + exit(1); + } + if_idxs[n_interfaces] = if_nametoindex(optarg); + if (if_idxs[n_interfaces] == 0) { + fprintf(stderr, "No such interface: %s\n", optarg); + exit(1); + } + n_interfaces++; + break; + case 'l':; + // Expect something of the form: 17-ffaa:1:fe2,192.168.50.2:123 + u64 ia; + u16 *ia_ptr = (u16 *)&ia; + char ip_str[100]; + u32 ip; + u16 port; + int ret = sscanf(optarg, "%hu-%hx:%hx:%hx,%99[^:]:%hu", ia_ptr + 3, + ia_ptr + 2, ia_ptr + 1, ia_ptr + 0, ip_str, &port); + if (ret != 6) { + fprintf(stderr, "Error parsing listen address\n"); + exit(1); + } + listen_addr.ia = htobe64(ia); + listen_addr.port = htons(port); + ret = inet_pton(AF_INET, ip_str, &listen_addr.ip); + if (ret != 1) { + fprintf(stderr, "Error parsing listen address\n"); + exit(1); + } + break; + case 'q': + queue = strtol(optarg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + fprintf(stderr, "Error parsing queue\n"); + exit(1); + } + break; + case 't': + tx_threads = strtol(optarg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + fprintf(stderr, "Error parsing number of tx threads\n"); + exit(1); + } + break; + case 'r': + rx_threads = strtol(optarg, NULL, 10); + if (errno == EINVAL || errno == ERANGE) { + fprintf(stderr, "Error parsing number of rx threads\n"); + exit(1); + } + break; + case 'z': + xdp_mode = XDP_ZEROCOPY; + break; + default: + usage(); + } } - struct hercules_server *server = - hercules_init_server(ifindices, num_ifaces, local_addr, queue, mtu); - if (argc == 2 && strcmp(argv[1], "TX") == 0) { - // Sender - server->n_pending = 1; - strncpy(server->pending[0], "hercules", 20); - strncpy(server->pending[1], "smallfile", 20); - server->destinations = &receiver; - server->num_dests = 1; - int np = 1; - server->num_paths = &np; // num paths per dest - server->max_paths = 1; // max paths per dest - server->rate_limit = 10e6; - server->enable_pcc = true; - - struct hercules_path *path; - int n_paths; - monitor_get_paths(usock, 1, &n_paths, &path); - - server->paths_per_dest = path; + if (n_interfaces == 0 || listen_addr.ip == 0) { + fprintf(stderr, "Missing required argument\n"); + exit(1); + } + if (rx_threads != tx_threads) { + // XXX This is not required, but if they are different we need to take care + // to allocate the right number of sockets, or have XDP sockets that are + // each used only for one of TX/RX + fprintf(stderr, "TX/RX threads must match\n"); + exit(1); } + debug_printf("Starting Hercules using queue %d, %d rx threads, %d tx " + "threads, xdp mode 0x%x", + queue, rx_threads, tx_threads, xdp_mode); + + usock = monitor_bind_daemon_socket(); + if (usock == 0) { + fprintf(stderr, "Error binding daemon socket\n"); + exit(1); + } + + struct hercules_server *server = + hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, rx_threads, false); - hercules_main(server, XDP_COPY); + hercules_main(server); } diff --git a/hercules.h b/hercules.h index 03c05c5..6069042 100644 --- a/hercules.h +++ b/hercules.h @@ -23,6 +23,8 @@ #define MAX_NUM_SOCKETS 256 #define HERCULES_MAX_HEADERLEN 256 +#define HERCULES_MAX_PKTSIZE 9000 + struct hercules_path_header { const char header[HERCULES_MAX_HEADERLEN]; //!< headerlen bytes @@ -123,8 +125,8 @@ struct rx_print_args{ struct hercules_server * hercules_init_server(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, int queue, - int mtu); -void hercules_main(struct hercules_server *server, int xdp_mode); + int xdp_mode, int n_threads, bool configure_queues); +void hercules_main(struct hercules_server *server); void hercules_main_sender(struct hercules_server *server, int xdp_mode, const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc); void print_rbudp_pkt(const char *pkt, bool recv); struct hercules_session *make_session(struct hercules_server *server); diff --git a/monitor.c b/monitor.c index 40e4728..bf6817a 100644 --- a/monitor.c +++ b/monitor.c @@ -6,6 +6,7 @@ #include #include #include +#include bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, struct hercules_path *path) { @@ -97,3 +98,22 @@ bool monitor_get_new_job(int sockfd, char *name) { strncpy(name, reply->filename, reply->filename_len); return true; } + +#define HERCULES_DAEMON_SOCKET_PATH "/var/hercules.sock" +// Bind the socket for the daemon. The file is deleted if already present. +// Returns the file descriptor if successful, 0 otherwise. +int monitor_bind_daemon_socket(){ + int usock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (usock <= 0){ + return 0; + } + struct sockaddr_un name; + name.sun_family = AF_UNIX; + strcpy(name.sun_path, HERCULES_DAEMON_SOCKET_PATH); + unlink(HERCULES_DAEMON_SOCKET_PATH); + int ret = bind(usock, &name, sizeof(name)); + if (ret){ + return 0; + } + return usock; +} diff --git a/monitor.h b/monitor.h index 7ae6021..507c6b2 100644 --- a/monitor.h +++ b/monitor.h @@ -22,6 +22,8 @@ bool monitor_get_new_job(int sockfd, char *name); // TODO bool monitor_update_job(int sockfd, int job_id); +int monitor_bind_daemon_socket(); + // Messages used for communication between the Hercules daemon and monitor // via unix socket. Queries are sent by the daemon, Replies by the monitor. #pragma pack(push) From d9ee3b8d8aba1252f3cf9ebbe81c3fdc5be83966 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 24 May 2024 14:03:19 +0200 Subject: [PATCH 004/112] rework some stuff, fix kick --- bpf_prgm/redirect_userspace.c | 22 ++++---- hercules.c | 94 +++++++++++++++++++---------------- monitor.c | 6 ++- monitor.h | 6 ++- monitor/monitor.go | 58 +++++++++++++++------ monitor/pathmanager.go | 1 + monitor/pathstodestination.go | 1 + packet.h | 1 + 8 files changed, 118 insertions(+), 71 deletions(-) diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index ab60569..fcf06f0 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -68,9 +68,10 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) } // check if IP address matches - if(iph->daddr != addr->ip) { - return XDP_PASS; // not addressed to us (IP address) - } + // FIXME uncomment + /* if(iph->daddr != addr->ip) { */ + /* return XDP_PASS; // not addressed to us (IP address) */ + /* } */ // check if UDP port matches const struct udphdr *udph = (const struct udphdr *)(iph + 1); @@ -113,12 +114,13 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) } const struct scionaddrhdr_ipv4 *scionaddrh = (const struct scionaddrhdr_ipv4 *)(scionh + 1); - if(scionaddrh->dst_ia != addr->ia) { - return XDP_PASS; // not addressed to us (IA) - } - if(scionaddrh->dst_ip != addr->ip) { - return XDP_PASS; // not addressed to us (IP in SCION hdr) - } + // FIXME uncomment this + /* if(scionaddrh->dst_ia != addr->ia) { */ + /* return XDP_PASS; // not addressed to us (IA) */ + /* } */ + /* if(scionaddrh->dst_ip != addr->ip) { */ + /* return XDP_PASS; // not addressed to us (IP in SCION hdr) */ + /* } */ size_t offset = next_offset; @@ -156,4 +158,4 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) 0); // XXX distribute across multiple sockets, once available } -char _license[] SEC("license") = "GPL"; \ No newline at end of file +char _license[] SEC("license") = "GPL"; diff --git a/hercules.c b/hercules.c index c258cdb..540cdc3 100644 --- a/hercules.c +++ b/hercules.c @@ -121,6 +121,7 @@ struct hercules_interface { struct hercules_config { u32 xdp_flags; int xdp_mode; + int queue; bool configure_queues; struct hercules_app_addr local_addr; }; @@ -140,10 +141,11 @@ struct hercules_session { struct receiver_state *rx_state; struct sender_state *tx_state; enum session_state state; - bool is_running; - bool is_closed; struct send_queue *send_queue; + struct hercules_app_addr destination; + int num_paths; + struct hercules_path *paths_to_dest; // State for stat dump /* size_t rx_npkts; */ /* size_t tx_npkts; */ @@ -152,16 +154,11 @@ struct hercules_session { struct hercules_server { struct hercules_config config; int control_sockfd; // AF_PACKET socket used for control traffic - int queue; int n_threads; struct rx_p_args **worker_args; struct hercules_session *session_tx; //Current TX session u64 session_tx_counter; struct hercules_session *session_rx; //Current RX session - struct hercules_app_addr *destinations; - struct hercules_path *paths_per_dest; - int num_dests; - int *num_paths; int max_paths; int rate_limit; bool enable_pcc; @@ -181,6 +178,8 @@ struct receiver_state { u32 total_chunks; /** Memory mapped file for receive */ char *mem; + /** Packet size */ + u32 etherlen; struct bitset received_chunks; @@ -547,7 +546,7 @@ static const char *parse_pkt(const struct hercules_server *server, const char *p const struct scionaddrhdr_ipv4 *scionaddrh = (const struct scionaddrhdr_ipv4 *)(pkt + offset + sizeof(struct scionhdr)); if(scionaddrh->dst_ia != server->config.local_addr.ia) { - debug_printf("not addressed to us (IA)"); + debug_printf("not addressed to us (IA): expect %llx, have %llx", server->config.local_addr.ia, scionaddrh->dst_ia); return NULL; } if(scionaddrh->dst_ip != server->config.local_addr.ip) { @@ -671,7 +670,7 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p if(seqnr >= rx_state->path_state[path_idx].seq_rcvd.num) { // XXX: currently we cannot track these sequence numbers, as a consequence congestion control breaks at this // point, abort. - if(!rx_state->session->is_running) { + if(rx_state->session->state != SESSION_STATE_RUNNING) { return true; } else { fprintf(stderr, "sequence number overflow %d / %d\n", seqnr, @@ -761,7 +760,6 @@ static struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *s static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) { int ret; - /* debug_printf("kicking tx"); */ do { ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); } while(ret < 0 && errno == EAGAIN); @@ -769,7 +767,6 @@ static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) if(ret < 0 && errno != ENOBUFS && errno != EBUSY) { exit_with_error(server, errno); } - /* debug_printf("kicked"); */ } static void kick_all_tx(struct hercules_server *server, struct hercules_interface *iface) @@ -779,6 +776,7 @@ static void kick_all_tx(struct hercules_server *server, struct hercules_interfac } } static void kick_tx_server(struct hercules_server *server){ + for (int i = 0; i < server->num_ifaces; i++){ kick_all_tx(server, &server->ifaces[i]); } @@ -1470,7 +1468,7 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); while(reserved != num_frames) { reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - if(!session->is_running) { + if(session->state != SESSION_STATE_RUNNING) { pthread_spin_unlock(&umem->lock); return; } @@ -2000,9 +1998,9 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 struct sender_state_per_receiver *rcvr = &tx_state->receiver[rcvr_idx]; u32 num_chunks_prepared = 0; u32 chunk_idx = rcvr->prev_chunk_idx; - debug_printf("n chunks %d", num_chunks); + /* debug_printf("n chunks %d", num_chunks); */ for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { - debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); + /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ chunk_idx = bitset__scan_neg(&rcvr->acked_chunks, chunk_idx); if(chunk_idx == tx_state->total_chunks) { if(rcvr->prev_chunk_idx == 0) { // this receiver has finished @@ -2027,13 +2025,13 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 const u64 ack_due = prev_transmit + rcvr->ack_wait_duration; // 0 for first round if(now >= ack_due) { // add the chunk to the current batch /* debug_printf("now >= ack due, adding chunk"); */ - debug_printf("adding chunk idx %d", chunk_idx); + /* debug_printf("adding chunk idx %d", chunk_idx); */ *chunks = chunk_idx++; *chunk_rcvr = rcvr_idx; chunks++; chunk_rcvr++; } else { // no chunk to send - skip this receiver in the current batch - debug_printf("now < ack due, no chunk to send"); + /* debug_printf("now < ack due, no chunk to send"); */ (*wait_until) = ack_due; break; } @@ -2087,7 +2085,7 @@ static void tx_only(struct hercules_server *server, struct sender_state *tx_stat u8 chunk_rcvr[BATCH_SIZE]; u32 max_chunks_per_rcvr[tx_state->num_receivers]; - while(tx_state->session->is_running && finished_count < tx_state->num_receivers) { + while(tx_state->session->state == SESSION_STATE_RUNNING && finished_count < tx_state->num_receivers) { pop_completion_rings(server); send_path_handshakes(tx_state); u64 next_ack_due = 0; @@ -2209,9 +2207,9 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i bitset__create(&receiver->acked_chunks, tx_state->total_chunks); receiver->path_index = 0; receiver->handshake_rtt = 0; - receiver->num_paths = num_paths[d]; + receiver->num_paths = num_paths; receiver->paths = paths; - receiver->addr = dests[d]; + receiver->addr = *dests; receiver->cts_received = false; } update_hercules_tx_paths(tx_state); @@ -2262,7 +2260,7 @@ static char *rx_mmap(struct hercules_server *server, const char *pathname, size_ return mem; } -static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t filesize, int chunklen, +static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t filesize, int chunklen, int etherlen, bool is_pcc_benchmark) { struct receiver_state *rx_state; @@ -2277,6 +2275,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, ch rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; rx_state->mem = rx_mmap(NULL, filename, filesize); + rx_state->etherlen = etherlen; return rx_state; } @@ -2314,7 +2313,7 @@ static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_p memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); pthread_spin_lock(&usock_lock); - int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, path); + int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, rx_state->etherlen, path); pthread_spin_unlock(&usock_lock); if(!ret) { return false; @@ -2649,7 +2648,7 @@ static void rx_trickle_nacks(void *arg) { if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { u32 ack_nr = 0; struct receiver_state *rx_state = session_rx->rx_state; - while (rx_state->session->is_running && !rx_received_all(rx_state)) { + while (rx_state->session->state == SESSION_STATE_RUNNING && !rx_received_all(rx_state)) { u64 ack_round_start = get_nsecs(); rx_send_nacks(server, rx_state, ack_round_start, ack_nr); u64 ack_round_end = get_nsecs(); @@ -2712,8 +2711,10 @@ static void events_p(void *arg) { char fname[1000]; memset(fname, 0, 1000); int count; + u16 jobid; + struct hercules_app_addr dest; pthread_spin_lock(&usock_lock); - int ret = monitor_get_new_job(usock, fname); + int ret = monitor_get_new_job(usock, fname, &jobid, &dest); pthread_spin_unlock(&usock_lock); if (ret) { debug_printf("new job: %s", fname); @@ -2736,13 +2737,25 @@ static void events_p(void *arg) { } close(f); - u32 chunklen = server->paths_per_dest[0].payloadlen - rbudp_headerlen; - atomic_store(&server->session_tx, make_session(server)); + struct hercules_session *session = make_session(server); + session->state = SESSION_STATE_PENDING; + session->destination = server->config.local_addr; + session->destination.ip = 0x132a8c0UL; + + int n_paths; + struct hercules_path *paths; + monitor_get_paths(usock, jobid, &n_paths, &paths); + debug_printf("received %d paths", n_paths); + session->num_paths = n_paths; + session->paths_to_dest = paths; + + u32 chunklen = session->paths_to_dest[0].payloadlen - rbudp_headerlen; + atomic_store(&server->session_tx, session); atomic_fetch_add(&server->session_tx_counter, 1); struct sender_state *tx_state = init_tx_state( server->session_tx, filesize, chunklen, server->rate_limit, mem, - server->destinations, server->paths_per_dest, server->num_dests, - server->num_paths, server->max_paths); + &session->destination, session->paths_to_dest, 1, + session->num_paths, server->max_paths); server->session_tx->tx_state = tx_state; unsigned long timestamp = get_nsecs(); tx_send_initial(server, &tx_state->receiver[0].paths[0], fname, @@ -2817,8 +2830,9 @@ static void events_p(void *arg) { struct hercules_session *session = make_session(server); server->session_rx = session; session->state = SESSION_STATE_CREATED; + // TODO fill in etherlen struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt.name, parsed_pkt.filesize, parsed_pkt.chunklen, false); + session, parsed_pkt.name, parsed_pkt.filesize, parsed_pkt.chunklen, 1200, false); session->rx_state = rx_state; /* session->state = SESSION_STATE_READY; */ rx_handle_initial(server, rx_state, &parsed_pkt, buf, 3, @@ -2868,7 +2882,7 @@ static void events_p(void *arg) { } } -/* #define DEBUG_PRINT_PKTS */ +#define DEBUG_PRINT_PKTS #ifdef DEBUG_PRINT_PKTS void print_rbudp_pkt(const char *pkt, bool recv) { struct hercules_header *h = (struct hercules_header *)pkt; @@ -2921,7 +2935,6 @@ struct hercules_session *make_session(struct hercules_server *server) { struct hercules_session *s; s = calloc(1, sizeof(*s)); assert(s); - s->is_running = true; s->state = SESSION_STATE_NONE; int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, sizeof(*s->send_queue)); @@ -2955,13 +2968,13 @@ static struct receiver_state *rx_receive_hs(struct hercules_server *server, if (rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len - rbudp_headerlen, &parsed_pkt)) { debug_printf("parsed initial"); struct hercules_session *session = make_session(server); + // TODO fill in etherlen struct receiver_state *rx_state = make_rx_state( - session, NULL, parsed_pkt.filesize, parsed_pkt.chunklen, false); + session, NULL, parsed_pkt.filesize, parsed_pkt.chunklen, 1200, false); session->rx_state = rx_state; /* rx_handle_initial(rx_state, &parsed_pkt, pkt, 3, rbudp_pkt + rbudp_headerlen, len); */ xsk_ring_cons__release(&xsk->rx, rcvd); struct hercules_session s; - s.is_running = true; submit_rx_frames(&s, xsk->umem, frame_addrs, rcvd); return rx_state; } else { @@ -2974,7 +2987,6 @@ static struct receiver_state *rx_receive_hs(struct hercules_server *server, } xsk_ring_cons__release(&xsk->rx, rcvd); struct hercules_session s; - s.is_running = true; submit_rx_frames(&s, xsk->umem, frame_addrs, rcvd); return NULL; } @@ -3126,7 +3138,7 @@ static void *tx_p(void *arg) { u32 cur_num_chunks = prepare_rcvr_chunks( tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, &ack_due, max_chunks_per_rcvr[0]); - debug_printf("prepared %d chunks", cur_num_chunks); + /* debug_printf("prepared %d chunks", cur_num_chunks); */ num_chunks += cur_num_chunks; if (num_chunks > 0){ u8 rcvr_path[1] = {0}; @@ -3203,7 +3215,7 @@ hercules_init_server(int *ifindices, int num_ifaces, local_addr.port); server->ifindices = ifindices; server->num_ifaces = num_ifaces; - server->queue = queue; + server->config.queue = queue; server->n_threads = n_threads; server->session_rx = NULL; server->session_tx = NULL; @@ -3687,7 +3699,7 @@ static void xdp_setup(struct hercules_server *server) { cfg.xdp_flags = server->config.xdp_flags; cfg.bind_flags = server->config.xdp_mode; ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, - server->queue, umem->umem, &xsk->rx, + server->config.queue, umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); if (ret) { exit_with_error(server, -ret); @@ -3700,6 +3712,7 @@ static void xdp_setup(struct hercules_server *server) { } server->ifaces[i].xsks[t] = xsk; } + server->ifaces[i].num_sockets = server->n_threads; } for (int t = 0; t < server->n_threads; t++){ server->worker_args[t] = malloc(sizeof(*server->worker_args) + server->num_ifaces*sizeof(*server->worker_args[t]->xsks)); @@ -3713,7 +3726,7 @@ static void xdp_setup(struct hercules_server *server) { } load_xsk_redirect_userspace(server, server->worker_args, server->n_threads); - // TODO this is not set anywhere? + // TODO this is not set anywhere, so it will never run if (server->config.configure_queues){ configure_rx_queues(server); } @@ -3725,12 +3738,6 @@ void hercules_main(struct hercules_server *server) { xdp_setup(server); - - // Start socket handler thread -/* debug_printf("Starting socket thread"); */ -/* pthread_t socket_handler_thread = start_thread(NULL, socket_handler, server); */ - - // Start event receiver thread debug_printf("Starting event receiver thread"); pthread_t events = start_thread(NULL, events_p, server); @@ -3764,6 +3771,7 @@ void hercules_main(struct hercules_server *server) { while (1) { // XXX STOP HERE + // FIXME make this thread do something } } diff --git a/monitor.c b/monitor.c index bf6817a..fdf5208 100644 --- a/monitor.c +++ b/monitor.c @@ -8,7 +8,7 @@ #include #include -bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, +bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, int etherlen, struct hercules_path *path) { struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; @@ -20,6 +20,7 @@ bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, assert(msg); msg->msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; + msg->payload.reply_path.etherlen = etherlen; msg->payload.reply_path.sample_len = rx_sample_len; memcpy(msg->payload.reply_path.sample, rx_sample_buf, rx_sample_len); debug_printf("sending %ld bytes", msg_len); @@ -79,7 +80,7 @@ bool monitor_get_paths(int sockfd, int job_id, int *n_paths, return true; } -bool monitor_get_new_job(int sockfd, char *name) { +bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest) { struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; strcpy(monitor.sun_path, "/var/herculesmon.sock"); @@ -96,6 +97,7 @@ bool monitor_get_new_job(int sockfd, char *name) { return false; } strncpy(name, reply->filename, reply->filename_len); + *job_id = reply->job_id; return true; } diff --git a/monitor.h b/monitor.h index 507c6b2..0e160df 100644 --- a/monitor.h +++ b/monitor.h @@ -3,11 +3,12 @@ #include "hercules.h" #include #include +#include "utils.h" // Get a reply path from the monitor. The reversed path will be written to // *path. Returns false in case of error. bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, - struct hercules_path *path); + int etherlen, struct hercules_path *path); // Get SCION paths from the monitor. The caller is responsible for freeing // **paths. @@ -16,7 +17,7 @@ bool monitor_get_paths(int sockfd, int job_id, int *n_paths, // Check if the monitor has a new job available // TODO -bool monitor_get_new_job(int sockfd, char *name); +bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest); // Inform the monitor about a transfer's (new) status // TODO @@ -35,6 +36,7 @@ int monitor_bind_daemon_socket(); #define SOCKMSG_TYPE_GET_REPLY_PATH (1) struct sockmsg_reply_path_Q { uint16_t sample_len; + uint16_t etherlen; uint8_t sample[]; }; struct sockmsg_reply_path_A { diff --git a/monitor/monitor.go b/monitor/monitor.go index c29363a..f49458b 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -26,15 +26,12 @@ import ( // #include "../monitor.h" import "C" -// TODO should not be here -const etherLen = 1200 - type layerWithOpts struct { Layer gopacket.SerializableLayer Opts gopacket.SerializeOptions } -func prepareUnderlayPacketHeader(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, dstPort uint16) ([]byte, error) { +func prepareUnderlayPacketHeader(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, dstPort uint16, etherLen int) ([]byte, error) { ethHeader := 14 ipHeader := 20 udpHeader := 8 @@ -106,7 +103,7 @@ func serializeLayersWOpts(w gopacket.SerializeBuffer, layersWOpts ...layerWithOp return nil } -func getReplyPathHeader(buf []byte) (*HerculesPathHeader, error) { +func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, error) { packet := gopacket.NewPacket(buf, layers.LayerTypeEthernet, gopacket.Default) if err := packet.ErrorLayer(); err != nil { return nil, fmt.Errorf("error decoding some part of the packet: %v", err) @@ -160,7 +157,7 @@ func getReplyPathHeader(buf []byte) (*HerculesPathHeader, error) { return nil, errors.New("error decoding SCION/UDP") } - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort)) + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort), etherLen) if err != nil { return nil, err } @@ -320,21 +317,22 @@ func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAd } func getNewHeader() HerculesPathHeader { - srcAddr := addr.MustParseAddr("17-ffaa:1:fe2,192.168.50.1") + srcAddr := addr.MustParseAddr("17-ffaa:1:113c,192.168.50.1") srcIP := net.ParseIP(srcAddr.Host.String()) - dstAddr := addr.MustParseAddr("17-ffaa:1:fe2,192.168.50.2") + dstAddr := addr.MustParseAddr("17-ffaa:1:113c,192.168.50.2") dstIP := net.ParseIP(dstAddr.Host.String()) // querier := newPathQuerier() // path, err := querier.Query(context.Background(), dest) // fmt.Println(path, err) emptyPath := path.Empty{} + etherLen := 1200 iface, err := net.InterfaceByName("ens5f0") fmt.Println(iface, err) dstMAC, srcMAC, err := getAddrs(iface, dstIP) fmt.Println(dstMAC, srcMAC, err) fmt.Println(dstIP, srcIP) - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(30041)) + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(30041), etherLen) fmt.Println(underlayHeader, err) payload := snet.UDPPayload{ @@ -374,9 +372,25 @@ func getNewHeader() HerculesPathHeader { return herculesPath } +func preparePaths(etherLen int, localAddress snet.UDPAddr, interfaces []*net.Interface, destination snet.UDPAddr) { + fmt.Println("preparepaths", interfaces) + // TODO replace bps limit with the correct value + dest := []*Destination{{hostAddr: &destination, numPaths: 1}} + pm, err := initNewPathManager(interfaces, dest, &localAddress, uint64(etherLen)) + fmt.Println("pm init", pm, err) + pm.choosePaths() + fmt.Println("finally", pm.dsts[0].allPaths, pm.dsts[0].paths) +} + +type TransferState int +const ( + Queued TransferState = iota + Submitted + Done +) type HerculesTransfer struct { id int - status int + status TransferState file string dest snet.UDPAddr } @@ -406,7 +420,7 @@ func httpreq(w http.ResponseWriter, r *http.Request) { transfersLock.Lock() transfers[nextID] = &HerculesTransfer{ id: nextID, - status: 0, + status: Queued, file: file, dest: *destParsed, } @@ -425,6 +439,19 @@ func main() { usock, err := net.ListenUnixgram("unixgram", local) fmt.Println(usock, err) + ifs, _ := net.Interfaces() + iffs := []*net.Interface{} + for i, _ := range ifs { + iffs = append(iffs, &ifs[i]) + } + // src, _ := snet.ParseUDPAddr("17-ffaa:1:fe2,192.168.50.1:123") + // dst, _ := snet.ParseUDPAddr("17-ffaa:1:fe2,192.168.50.2:123") + // querier := newPathQuerier() + // path, err := querier.Query(context.Background(), dest) + // fmt.Println(path, err) + // preparePaths(1200, *src, iffs, *dst) + + http.HandleFunc("/", httpreq) go http.ListenAndServe(":8000", nil) @@ -441,7 +468,10 @@ func main() { fmt.Println("reply path") sample_len := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] - replyPath, err := getReplyPathHeader(buf[:sample_len]) + etherlen := binary.LittleEndian.Uint16(buf[:2]) + buf = buf[2:] + fmt.Println(etherlen) + replyPath, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) fmt.Println(replyPath, err) b := SerializePath(replyPath) usock.WriteToUnix(b, a) @@ -452,14 +482,14 @@ func main() { for _, job := range transfers { if job.status == 0 { selectedJob = job - job.status = 1 + job.status = Submitted break } } transfersLock.Unlock() var b []byte if selectedJob != nil { - fmt.Println("sending file to daemon", selectedJob.file) + fmt.Println("sending file to daemon:", selectedJob.file) // TODO Conversion between go and C strings? strlen := len(selectedJob.file) b = append(b, 1) diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index d52a1ed..dba17b4 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -127,6 +127,7 @@ func (pm *PathManager) interfaceForRoute(ip net.IP) (*net.Interface, error) { return nil, fmt.Errorf("could not find route for destination %s: %s", ip, err) } + fmt.Println(pm.interfaces) for _, route := range routes { if iface, ok := pm.interfaces[route.LinkIndex]; ok { fmt.Printf("sending via #%d (%s) to %s\n", route.LinkIndex, pm.interfaces[route.LinkIndex].Name, ip) diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 21ba4fe..9f20230 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -97,6 +97,7 @@ func (ptd *PathsToDestination) choosePaths() bool { return false } + fmt.Println("all paths", ptd.allPaths) availablePaths := ptd.pm.filterPathsByActiveInterfaces(ptd.allPaths) if len(availablePaths) == 0 { log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) diff --git a/packet.h b/packet.h index 43b2f45..a06e1b2 100644 --- a/packet.h +++ b/packet.h @@ -74,6 +74,7 @@ struct hercules_header { struct rbudp_initial_pkt { __u64 filesize; __u32 chunklen; + __u32 etherlen; __u64 timestamp; __u8 path_index; __u8 flags; From 4b2631777d35d408f71384d376d7b04fdc568d15 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sat, 25 May 2024 18:16:58 +0200 Subject: [PATCH 005/112] make monitor find actual paths --- bpf_prgm/redirect_userspace.c | 20 ++--- hercules.c | 30 +++++-- monitor.c | 103 ++++++++++----------- monitor.h | 27 ++++-- monitor/monitor.go | 162 +++++++++++++++++++++------------- packet.h | 1 + 6 files changed, 200 insertions(+), 143 deletions(-) diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index fcf06f0..01db42a 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -68,10 +68,9 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) } // check if IP address matches - // FIXME uncomment - /* if(iph->daddr != addr->ip) { */ - /* return XDP_PASS; // not addressed to us (IP address) */ - /* } */ + if(iph->daddr != addr->ip) { + return XDP_PASS; // not addressed to us (IP address) + } // check if UDP port matches const struct udphdr *udph = (const struct udphdr *)(iph + 1); @@ -114,13 +113,12 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) } const struct scionaddrhdr_ipv4 *scionaddrh = (const struct scionaddrhdr_ipv4 *)(scionh + 1); - // FIXME uncomment this - /* if(scionaddrh->dst_ia != addr->ia) { */ - /* return XDP_PASS; // not addressed to us (IA) */ - /* } */ - /* if(scionaddrh->dst_ip != addr->ip) { */ - /* return XDP_PASS; // not addressed to us (IP in SCION hdr) */ - /* } */ + if(scionaddrh->dst_ia != addr->ia) { + return XDP_PASS; // not addressed to us (IA) + } + if(scionaddrh->dst_ip != addr->ip) { + return XDP_PASS; // not addressed to us (IP in SCION hdr) + } size_t offset = next_offset; diff --git a/hercules.c b/hercules.c index 540cdc3..78c0372 100644 --- a/hercules.c +++ b/hercules.c @@ -1379,12 +1379,20 @@ static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t /* } */ static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path) +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) { debug_printf("Sending initial"); char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + u8 flags = 0; + if (set_return_path){ + flags |= HANDSHAKE_FLAG_SET_RETURN_PATH; + } + if (new_transfer){ + flags |= HANDSHAKE_FLAG_NEW_TRANSFER; + } + struct hercules_control_packet pld = { .type = CONTROL_PACKET_TYPE_INITIAL, .payload.initial = { @@ -1392,7 +1400,7 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path .chunklen = chunklen, .timestamp = timestamp, .path_index = path_index, - .flags = set_return_path ? HANDSHAKE_FLAG_SET_RETURN_PATH : 0, + .flags = flags, .name_len = strlen(filename), }, }; @@ -2183,8 +2191,8 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i exit(1); } - debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); - getchar(); + /* debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); */ + /* getchar(); */ struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); tx_state->session = session; @@ -2494,6 +2502,8 @@ static void rx_send_cts_ack(struct hercules_server *server, struct receiver_stat return; } + debug_printf("got reply path, idx %d", path.ifid); + char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); @@ -2712,9 +2722,10 @@ static void events_p(void *arg) { memset(fname, 0, 1000); int count; u16 jobid; + u16 mtu; struct hercules_app_addr dest; pthread_spin_lock(&usock_lock); - int ret = monitor_get_new_job(usock, fname, &jobid, &dest); + int ret = monitor_get_new_job(usock, fname, &jobid, &dest, &mtu); pthread_spin_unlock(&usock_lock); if (ret) { debug_printf("new job: %s", fname); @@ -2760,7 +2771,7 @@ static void events_p(void *arg) { unsigned long timestamp = get_nsecs(); tx_send_initial(server, &tx_state->receiver[0].paths[0], fname, tx_state->filesize, tx_state->chunklen, timestamp, 0, - true); + true, true); atomic_store(&server->session_tx->state, SESSION_STATE_WAIT_HS); } else { debug_printf("no new job."); @@ -2769,6 +2780,9 @@ static void events_p(void *arg) { } // XXX + /* // Set timeout on the socket */ + /* struct timeval to = {.tv_sec = 0, .tv_usec = 500}; */ + /* setsockopt(server->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addr_size); // XXX set timeout @@ -2835,7 +2849,7 @@ static void events_p(void *arg) { session, parsed_pkt.name, parsed_pkt.filesize, parsed_pkt.chunklen, 1200, false); session->rx_state = rx_state; /* session->state = SESSION_STATE_READY; */ - rx_handle_initial(server, rx_state, &parsed_pkt, buf, 3, + rx_handle_initial(server, rx_state, &parsed_pkt, buf, addr.sll_ifindex, rbudp_pkt + rbudp_headerlen, len); rx_send_cts_ack(server, rx_state); server->session_rx->state = SESSION_STATE_RUNNING; @@ -2882,7 +2896,7 @@ static void events_p(void *arg) { } } -#define DEBUG_PRINT_PKTS +/* #define DEBUG_PRINT_PKTS */ #ifdef DEBUG_PRINT_PKTS void print_rbudp_pkt(const char *pkt, bool recv) { struct hercules_header *h = (struct hercules_header *)pkt; diff --git a/monitor.c b/monitor.c index fdf5208..6448688 100644 --- a/monitor.c +++ b/monitor.c @@ -14,90 +14,83 @@ bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, monitor.sun_family = AF_UNIX; strcpy(monitor.sun_path, "/var/herculesmon.sock"); - struct hercules_sockmsg_Q *msg; - size_t msg_len = sizeof(*msg) + rx_sample_len; - msg = calloc(1, msg_len); - assert(msg); + struct hercules_sockmsg_Q msg; + msg.msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; + msg.payload.reply_path.etherlen = etherlen; + msg.payload.reply_path.sample_len = rx_sample_len; + memcpy(msg.payload.reply_path.sample, rx_sample_buf, rx_sample_len); + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); // TODO return val - msg->msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; - msg->payload.reply_path.etherlen = etherlen; - msg->payload.reply_path.sample_len = rx_sample_len; - memcpy(msg->payload.reply_path.sample, rx_sample_buf, rx_sample_len); - debug_printf("sending %ld bytes", msg_len); - sendto(sockfd, msg, msg_len, 0, &monitor, sizeof(monitor)); // TODO return val - free(msg); - - char buf[2000]; - memset(&buf, 0, 2000); - int n = recv(sockfd, &buf, sizeof(buf), 0); + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); debug_printf("Read %d bytes", n); if (n <= 0) { return false; } - struct hercules_sockmsg_A *reply = buf; - path->headerlen = reply->payload.reply_path.headerlen; - memcpy(&path->header.header, reply->payload.reply_path.header, - path->headerlen); - path->header.checksum = reply->payload.reply_path.chksum; - + memcpy(&path->header, reply.payload.reply_path.path.header, reply.payload.reply_path.path.headerlen); + path->headerlen = reply.payload.reply_path.path.headerlen; + path->header.checksum = reply.payload.reply_path.path.chksum; path->enabled = true; path->replaced = false; - path->payloadlen = 1200 - path->headerlen; + path->payloadlen = 1200 - path->headerlen; // TODO set correctly path->framelen = 1200; - path->ifid = 3; + path->ifid = reply.payload.reply_path.path.ifid; return true; } bool monitor_get_paths(int sockfd, int job_id, int *n_paths, struct hercules_path **paths) { - struct hercules_path *path = calloc(1, sizeof(*path)); - assert(path); - struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; strcpy(monitor.sun_path, "/var/herculesmon.sock"); - struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_PATHS}; - int len = sizeof(msg); - debug_printf("sending %d bytes", len); - sendto(sockfd, &msg, len, 0, &monitor, sizeof(monitor)); - char recvbuf[2000]; - memset(&recvbuf, 0, 2000); - int n = recv(sockfd, &recvbuf, sizeof(recvbuf), 0); + + struct hercules_sockmsg_Q msg; + msg.msgtype = SOCKMSG_TYPE_GET_PATHS; + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); debug_printf("receive %d bytes", n); - struct sockmsg_reply_path_A *reply = recvbuf + 2; - path->headerlen = reply->headerlen; - memcpy(path->header.header, reply->header, reply->headerlen); - path->header.checksum = reply->chksum; - path->enabled = true; - path->replaced = false; - path->payloadlen = 1200 - path->headerlen; - path->framelen = 1200; - path->ifid = 3; + int received_paths = reply.payload.paths.n_paths; + struct hercules_path *p = + calloc(received_paths, sizeof(struct hercules_path)); + + for (int i = 0; i < received_paths; i++) { + p[i].headerlen = reply.payload.paths.paths[i].headerlen; + memcpy(&p[i].header, reply.payload.paths.paths[i].header, + p[i].headerlen); + p[i].header.checksum = reply.payload.paths.paths[i].chksum; + p[i].enabled = true; + p[i].replaced = false; + p[i].payloadlen = 1200 - p[i].headerlen; // TODO set correctly + p[i].framelen = 1200; + p[i].ifid = reply.payload.paths.paths[i].ifid; + } - *n_paths = 1; - *paths = path; + *n_paths = received_paths; + *paths = p; return true; } -bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest) { +bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest, u16 *mtu) { struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; strcpy(monitor.sun_path, "/var/herculesmon.sock"); + struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; - int len = sizeof(msg); - debug_printf("sending %d bytes", len); - sendto(sockfd, &msg, len, 0, &monitor, sizeof(monitor)); - char recvbuf[2000]; - memset(&recvbuf, 0, 2000); - int n = recv(sockfd, &recvbuf, sizeof(recvbuf), 0); + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); debug_printf("receive %d bytes", n); - struct sockmsg_new_job_A *reply = recvbuf; - if (!reply->has_job){ + if (!reply.payload.newjob.has_job){ return false; } - strncpy(name, reply->filename, reply->filename_len); - *job_id = reply->job_id; + // XXX name needs to be allocated large enough by caller + strncpy(name, reply.payload.newjob.filename, reply.payload.newjob.filename_len); + *job_id = reply.payload.newjob.job_id; + *mtu = reply.payload.newjob.mtu; return true; } diff --git a/monitor.h b/monitor.h index 0e160df..d643f94 100644 --- a/monitor.h +++ b/monitor.h @@ -17,7 +17,7 @@ bool monitor_get_paths(int sockfd, int job_id, int *n_paths, // Check if the monitor has a new job available // TODO -bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest); +bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest, u16 *mtu); // Inform the monitor about a transfer's (new) status // TODO @@ -25,8 +25,14 @@ bool monitor_update_job(int sockfd, int job_id); int monitor_bind_daemon_socket(); +// Maximum size of variable-length fields in socket messages +#define SOCKMSG_MAX_PAYLOAD 2000 +// Maximum number of paths transferred +#define SOCKMSG_MAX_PATHS 10 + // Messages used for communication between the Hercules daemon and monitor // via unix socket. Queries are sent by the daemon, Replies by the monitor. +// Structs suffixed _Q are queries, ones suffixed _A are answers. #pragma pack(push) #pragma pack(1) @@ -37,12 +43,17 @@ int monitor_bind_daemon_socket(); struct sockmsg_reply_path_Q { uint16_t sample_len; uint16_t etherlen; - uint8_t sample[]; + uint8_t sample[SOCKMSG_MAX_PAYLOAD]; }; -struct sockmsg_reply_path_A { +struct sockmsg_serialized_path { uint16_t chksum; + uint16_t ifid; uint32_t headerlen; - uint8_t header[]; + uint8_t header[HERCULES_MAX_HEADERLEN]; +}; + +struct sockmsg_reply_path_A { + struct sockmsg_serialized_path path; }; // Ask the monitor for new transfer jobs. @@ -52,8 +63,9 @@ struct sockmsg_new_job_Q {}; struct sockmsg_new_job_A { uint8_t has_job; // The other fields are only valid if this is set to 1 uint16_t job_id; + uint16_t mtu; uint16_t filename_len; - uint8_t filename[]; + uint8_t filename[SOCKMSG_MAX_PAYLOAD]; }; // Get paths to use for a given job ID @@ -63,8 +75,7 @@ struct sockmsg_paths_Q { }; struct sockmsg_paths_A { uint16_t n_paths; - uint8_t paths[]; // This should be a concatenation of n_paths many paths, each - // laid out as struct sockmsg_reply_path_A above. + struct sockmsg_serialized_path paths[SOCKMSG_MAX_PATHS]; }; // Inform the monitor about a job's status @@ -85,6 +96,8 @@ struct hercules_sockmsg_Q { struct sockmsg_update_job_Q job_update; } payload; }; +// Used by go code +#define SOCKMSG_SIZE sizeof(struct hercules_sockmsg_Q) struct hercules_sockmsg_A { union { diff --git a/monitor/monitor.go b/monitor/monitor.go index f49458b..129b39c 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -1,8 +1,10 @@ package main import ( + "bytes" "encoding/binary" "errors" + "flag" "fmt" "io" "net" @@ -18,7 +20,6 @@ import ( // "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/snet/path" "github.com/scionproto/scion/private/topology" "github.com/vishvananda/netlink" ) @@ -103,63 +104,61 @@ func serializeLayersWOpts(w gopacket.SerializeBuffer, layersWOpts ...layerWithOp return nil } -func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, error) { +func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, net.IP, error) { packet := gopacket.NewPacket(buf, layers.LayerTypeEthernet, gopacket.Default) if err := packet.ErrorLayer(); err != nil { - return nil, fmt.Errorf("error decoding some part of the packet: %v", err) + return nil, nil, fmt.Errorf("error decoding some part of the packet: %v", err) } eth := packet.Layer(layers.LayerTypeEthernet) if eth == nil { - return nil, errors.New("error decoding ETH layer") + return nil, nil, errors.New("error decoding ETH layer") } dstMAC, srcMAC := eth.(*layers.Ethernet).SrcMAC, eth.(*layers.Ethernet).DstMAC ip4 := packet.Layer(layers.LayerTypeIPv4) if ip4 == nil { - return nil, errors.New("error decoding IPv4 layer") + return nil, nil, errors.New("error decoding IPv4 layer") } dstIP, srcIP := ip4.(*layers.IPv4).SrcIP, ip4.(*layers.IPv4).DstIP udp := packet.Layer(layers.LayerTypeUDP) if udp == nil { - return nil, errors.New("error decoding IPv4/UDP layer") + return nil, nil, errors.New("error decoding IPv4/UDP layer") } udpPayload := udp.(*layers.UDP).Payload udpDstPort := udp.(*layers.UDP).SrcPort if len(udpPayload) < 8 { // Guard against bug in ParseScnPkt - return nil, errors.New("error decoding SCION packet: payload too small") + return nil, nil, errors.New("error decoding SCION packet: payload too small") } sourcePkt := snet.Packet{ Bytes: udpPayload, } if err := sourcePkt.Decode(); err != nil { - return nil, fmt.Errorf("error decoding SCION packet: %v", err) + return nil, nil, fmt.Errorf("error decoding SCION packet: %v", err) } rpath, ok := sourcePkt.Path.(snet.RawPath) if !ok { - return nil, fmt.Errorf("error decoding SCION packet: unexpected dataplane path type") + return nil, nil, fmt.Errorf("error decoding SCION packet: unexpected dataplane path type") } if len(rpath.Raw) != 0 { replyPath, err := snet.DefaultReplyPather{}.ReplyPath(rpath) if err != nil { - return nil, fmt.Errorf("failed to reverse SCION path: %v", err) + return nil, nil, fmt.Errorf("failed to reverse SCION path: %v", err) } sourcePkt.Path = replyPath } - sourcePkt.Path = path.Empty{} - udpPkt, ok := sourcePkt.Payload.(snet.UDPPayload) if !ok { - return nil, errors.New("error decoding SCION/UDP") + return nil, nil, errors.New("error decoding SCION/UDP") } underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort), etherLen) if err != nil { - return nil, err + return nil, nil, err } payload := snet.UDPPayload{ @@ -178,7 +177,7 @@ func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, error) { } if err = destPkt.Serialize(); err != nil { - return nil, err + return nil, nil, err } scionHeaderLen := len(destPkt.Bytes) payloadLen := etherLen - len(underlayHeader) - scionHeaderLen @@ -186,7 +185,7 @@ func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, error) { destPkt.Payload = payload if err = destPkt.Serialize(); err != nil { - return nil, err + return nil, nil, err } scionHeader := destPkt.Bytes[:scionHeaderLen] scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) @@ -195,19 +194,22 @@ func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, error) { Header: headerBuf, PartialChecksum: scionChecksum, } - return &herculesPath, nil + return &herculesPath, dstIP, nil } -func SerializePath(from *HerculesPathHeader) []byte { +func SerializePath(from *HerculesPathHeader, ifid int) []byte { fmt.Println("serialize") out := make([]byte, 0, 1500) fmt.Println(out) out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) + out = binary.LittleEndian.AppendUint16(out, uint16(ifid)) fmt.Println(out) out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) fmt.Println(out) out = append(out, from.Header...) - fmt.Println(out) + out = append(out, bytes.Repeat([]byte{0x00}, C.HERCULES_MAX_HEADERLEN-len(from.Header))...) + fmt.Println("Serialized header", out, len(out)) + fmt.Printf("hex header % x\n", out) return out } @@ -316,28 +318,18 @@ func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAd return nil, errors.New("missing ARP entry") } -func getNewHeader() HerculesPathHeader { - srcAddr := addr.MustParseAddr("17-ffaa:1:113c,192.168.50.1") - srcIP := net.ParseIP(srcAddr.Host.String()) - dstAddr := addr.MustParseAddr("17-ffaa:1:113c,192.168.50.2") - dstIP := net.ParseIP(dstAddr.Host.String()) - // querier := newPathQuerier() - // path, err := querier.Query(context.Background(), dest) - // fmt.Println(path, err) - emptyPath := path.Empty{} - - etherLen := 1200 - iface, err := net.InterfaceByName("ens5f0") - fmt.Println(iface, err) - dstMAC, srcMAC, err := getAddrs(iface, dstIP) +// TODO no reason to pass in both net.udpaddr and addr.Addr, but the latter does not include the port +func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcAddr, dstAddr addr.Addr) HerculesPathHeader { + iface := path.iface + dstMAC, srcMAC, err := getAddrs(iface, path.path.UnderlayNextHop().IP) fmt.Println(dstMAC, srcMAC, err) - fmt.Println(dstIP, srcIP) - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(30041), etherLen) + + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcUDP.IP, path.path.UnderlayNextHop().IP, uint16(path.path.UnderlayNextHop().Port), etherLen) fmt.Println(underlayHeader, err) payload := snet.UDPPayload{ - SrcPort: 123, - DstPort: 123, + SrcPort: srcUDP.AddrPort().Port(), + DstPort: dstUDP.AddrPort().Port(), Payload: nil, } @@ -345,7 +337,7 @@ func getNewHeader() HerculesPathHeader { PacketInfo: snet.PacketInfo{ Destination: dstAddr, Source: srcAddr, - Path: emptyPath, + Path: path.path.Dataplane(), Payload: payload, }, } @@ -372,27 +364,51 @@ func getNewHeader() HerculesPathHeader { return herculesPath } -func preparePaths(etherLen int, localAddress snet.UDPAddr, interfaces []*net.Interface, destination snet.UDPAddr) { - fmt.Println("preparepaths", interfaces) +func pickPathsToDestination(etherLen int, numPaths int, localAddress snet.UDPAddr, interfaces []*net.Interface, destination snet.UDPAddr) []PathMeta { + dest := []*Destination{{hostAddr: &destination, numPaths: numPaths, pathSpec: &[]PathSpec{}}} // TODO replace bps limit with the correct value - dest := []*Destination{{hostAddr: &destination, numPaths: 1}} - pm, err := initNewPathManager(interfaces, dest, &localAddress, uint64(etherLen)) - fmt.Println("pm init", pm, err) + pm, _ := initNewPathManager(interfaces, dest, &localAddress, uint64(etherLen)) pm.choosePaths() - fmt.Println("finally", pm.dsts[0].allPaths, pm.dsts[0].paths) + numSelectedPaths := len(pm.dsts[0].paths) + fmt.Println("selected paths", numSelectedPaths) + return pm.dsts[0].paths +} + +func headersToDestination(src, dst snet.UDPAddr, ifs []*net.Interface, etherLen int) (int, []byte) { + fmt.Println("making headers", src, dst) + srcA := addr.Addr{ + IA: src.IA, + Host: addr.MustParseHost(src.Host.IP.String()), + } + dstA := addr.Addr{ + IA: dst.IA, + Host: addr.MustParseHost(dst.Host.IP.String()), + } + // TODO numpaths should come from somewhere + paths := pickPathsToDestination(etherLen, 1, src, ifs, dst) + numSelectedPaths := len(paths) + headers_ser := []byte{} + for _, p := range paths { + preparedHeader := prepareHeader(p, etherLen, *src.Host, *dst.Host, srcA, dstA) + headers_ser = append(headers_ser, SerializePath(&preparedHeader, p.iface.Index)...) + } + return numSelectedPaths, headers_ser } type TransferState int + const ( Queued TransferState = iota Submitted Done ) + type HerculesTransfer struct { id int status TransferState file string dest snet.UDPAddr + mtu int } var transfersLock sync.Mutex @@ -423,6 +439,7 @@ func httpreq(w http.ResponseWriter, r *http.Request) { status: Queued, file: file, dest: *destParsed, + mtu: 1200, } nextID += 1 transfersLock.Unlock() @@ -430,7 +447,23 @@ func httpreq(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "OK") } +func pathsForTransfer(id int) []C.struct_hercules_path { + paths := []C.struct_hercules_path{} + return paths +} + func main() { + var localAddr string + flag.StringVar(&localAddr, "l", "", "local address") + flag.Parse() + + src, err := snet.ParseUDPAddr(localAddr) + if err != nil || src.Host.Port == 0 { + flag.Usage() + return + } + + // TODO make socket paths congfigurable daemon, err := net.ResolveUnixAddr("unixgram", "/var/hercules.sock") fmt.Println(daemon, err) local, err := net.ResolveUnixAddr("unixgram", "/var/herculesmon.sock") @@ -444,36 +477,34 @@ func main() { for i, _ := range ifs { iffs = append(iffs, &ifs[i]) } - // src, _ := snet.ParseUDPAddr("17-ffaa:1:fe2,192.168.50.1:123") - // dst, _ := snet.ParseUDPAddr("17-ffaa:1:fe2,192.168.50.2:123") - // querier := newPathQuerier() - // path, err := querier.Query(context.Background(), dest) - // fmt.Println(path, err) - // preparePaths(1200, *src, iffs, *dst) + pm, err := initNewPathManager(iffs, nil, src, 0); + fmt.Println(err) http.HandleFunc("/", httpreq) go http.ListenAndServe(":8000", nil) for { - buf := make([]byte, 2000) - fmt.Println("read...") + buf := make([]byte, C.SOCKMSG_SIZE) + fmt.Println("read...", C.SOCKMSG_SIZE) n, a, err := usock.ReadFromUnix(buf) fmt.Println(n, a, err, buf) if n > 0 { - id := binary.LittleEndian.Uint16(buf[:2]) + msgtype := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] - switch id { + switch msgtype { case C.SOCKMSG_TYPE_GET_REPLY_PATH: fmt.Println("reply path") sample_len := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] etherlen := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] - fmt.Println(etherlen) - replyPath, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) + fmt.Println("smaple len", sample_len) + replyPath, nextHop, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) + iface, _ := pm.interfaceForRoute(nextHop) + fmt.Println("reply iface", iface) fmt.Println(replyPath, err) - b := SerializePath(replyPath) + b := SerializePath(replyPath, iface.Index) usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_NEW_JOB: @@ -489,11 +520,12 @@ func main() { transfersLock.Unlock() var b []byte if selectedJob != nil { - fmt.Println("sending file to daemon:", selectedJob.file) + fmt.Println("sending file to daemon:", selectedJob.file, selectedJob.id) // TODO Conversion between go and C strings? strlen := len(selectedJob.file) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) + b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.mtu)) b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) b = append(b, []byte(selectedJob.file)...) fmt.Println(b) @@ -504,10 +536,16 @@ func main() { usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_PATHS: - fmt.Println("fetch path") - header := getNewHeader() - b := binary.LittleEndian.AppendUint16(nil, uint16(1)) - b = append(b, SerializePath(&header)...) + // job_id := binary.LittleEndian.Uint16(buf[:2]) + // buf = buf[2:] + job_id := 1 + fmt.Println("fetch path, job", job_id) + transfersLock.Lock() + job, _ := transfers[int(job_id)] + n_headers, headers := headersToDestination(*src, job.dest, iffs, 1200) + transfersLock.Unlock() + b := binary.LittleEndian.AppendUint16(nil, uint16(n_headers)) + b = append(b, headers...) usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_UPDATE_JOB: diff --git a/packet.h b/packet.h index a06e1b2..04d3eb5 100644 --- a/packet.h +++ b/packet.h @@ -84,6 +84,7 @@ struct rbudp_initial_pkt { #define HANDSHAKE_FLAG_SET_RETURN_PATH 0x1u #define HANDSHAKE_FLAG_HS_CONFIRM (0x1u << 1) +#define HANDSHAKE_FLAG_NEW_TRANSFER (0x1u << 2) // Structure of ACK RBUDP packets sent by the receiver. // Integers all transmitted in little endian (host endianness). From 88aeea5ab7302761e48ee011c7ac20a587dd2a44 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 26 May 2024 15:24:19 +0200 Subject: [PATCH 006/112] allow specifying mtu/nPaths when submitting job --- monitor/monitor.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/monitor/monitor.go b/monitor/monitor.go index 129b39c..d64c2b8 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "os" + "strconv" "sync" "syscall" "time" @@ -374,7 +375,7 @@ func pickPathsToDestination(etherLen int, numPaths int, localAddress snet.UDPAdd return pm.dsts[0].paths } -func headersToDestination(src, dst snet.UDPAddr, ifs []*net.Interface, etherLen int) (int, []byte) { +func headersToDestination(src, dst snet.UDPAddr, ifs []*net.Interface, etherLen int, nPaths int) (int, []byte) { fmt.Println("making headers", src, dst) srcA := addr.Addr{ IA: src.IA, @@ -385,7 +386,7 @@ func headersToDestination(src, dst snet.UDPAddr, ifs []*net.Interface, etherLen Host: addr.MustParseHost(dst.Host.IP.String()), } // TODO numpaths should come from somewhere - paths := pickPathsToDestination(etherLen, 1, src, ifs, dst) + paths := pickPathsToDestination(etherLen, nPaths, src, ifs, dst) numSelectedPaths := len(paths) headers_ser := []byte{} for _, p := range paths { @@ -409,6 +410,7 @@ type HerculesTransfer struct { file string dest snet.UDPAddr mtu int + nPaths int } var transfersLock sync.Mutex @@ -432,6 +434,22 @@ func httpreq(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "parse err") return } + mtu := 1200 + if r.URL.Query().Has("mtu") { + mtu, err = strconv.Atoi(r.URL.Query().Get("mtu")) + if err != nil { + io.WriteString(w, "parse err") + return + } + } + nPaths := 1 + if r.URL.Query().Has("np") { + mtu, err = strconv.Atoi(r.URL.Query().Get("np")) + if err != nil { + io.WriteString(w, "parse err") + return + } + } fmt.Println(destParsed) transfersLock.Lock() transfers[nextID] = &HerculesTransfer{ @@ -439,7 +457,8 @@ func httpreq(w http.ResponseWriter, r *http.Request) { status: Queued, file: file, dest: *destParsed, - mtu: 1200, + mtu: mtu, + nPaths: nPaths, } nextID += 1 transfersLock.Unlock() @@ -478,7 +497,7 @@ func main() { iffs = append(iffs, &ifs[i]) } - pm, err := initNewPathManager(iffs, nil, src, 0); + pm, err := initNewPathManager(iffs, nil, src, 0) fmt.Println(err) http.HandleFunc("/", httpreq) @@ -542,7 +561,7 @@ func main() { fmt.Println("fetch path, job", job_id) transfersLock.Lock() job, _ := transfers[int(job_id)] - n_headers, headers := headersToDestination(*src, job.dest, iffs, 1200) + n_headers, headers := headersToDestination(*src, job.dest, iffs, job.mtu, job.nPaths) transfersLock.Unlock() b := binary.LittleEndian.AppendUint16(nil, uint16(n_headers)) b = append(b, headers...) From f6d047d81e02d85f9852ad47c6fde5a629937e6a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 26 May 2024 15:24:47 +0200 Subject: [PATCH 007/112] look up return path only when needed --- frame_queue.h | 6 +++--- hercules.c | 48 ++++++++++++++++++++++++++++------------------ monitor.c | 2 ++ monitor/monitor.go | 9 ++++----- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/frame_queue.h b/frame_queue.h index 5f03e07..0d2f573 100644 --- a/frame_queue.h +++ b/frame_queue.h @@ -43,7 +43,7 @@ static inline int frame_queue__init(struct frame_queue *fq, u16 size) static inline u16 frame_queue__prod_reserve(struct frame_queue *fq, u16 num) { - debug_printf("Empty slots in fq %llu", fq->cons - fq->prod); + /* debug_printf("Empty slots in fq %llu", fq->cons - fq->prod); */ return umin16(atomic_load(&fq->cons) - fq->prod, num); } @@ -56,13 +56,13 @@ static inline void frame_queue__push(struct frame_queue *fq, u16 num) { atomic_fetch_add(&fq->prod, num); u64 now = atomic_load(&fq->prod) - atomic_load(&fq->cons) + fq->size; - debug_printf("Frames in fq after push %llu", now); + /* debug_printf("Frames in fq after push %llu", now); */ } static inline u16 frame_queue__cons_reserve(struct frame_queue *fq, u16 num) { /* debug_printf("Reserve prod %llu, cons %llu", fq->prod, fq->cons); */ - debug_printf("Frames in fq %llu", fq->prod - fq->cons + fq->size); + /* debug_printf("Frames in fq %llu", fq->prod - fq->cons + fq->size); */ return umin16(atomic_load(&fq->prod) - fq->cons + fq->size, num); } diff --git a/hercules.c b/hercules.c index 78c0372..e02dc14 100644 --- a/hercules.c +++ b/hercules.c @@ -187,6 +187,7 @@ struct receiver_state { char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; int rx_sample_len; int rx_sample_ifid; + struct hercules_path reply_path; // Start/end time of the current transfer u64 start_time; @@ -1499,7 +1500,6 @@ static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_ if(!rcvd){ return; } - debug_printf("received %ld", rcvd); // optimistically update receive timestamp u64 now = get_nsecs(); @@ -1737,12 +1737,11 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s struct sender_state_per_receiver *rcvr = &tx_state->receiver[unit->rcvr[i]]; struct hercules_path *path = &rcvr->paths[unit->paths[i]]; if(path->ifid == ifid) { - debug_printf("%d, %d", path->ifid, ifid); num_chunks_in_unit++; } } // We may have claimed more frames than we need, if the unit is not full - debug_printf("handling %d chunks in unit", num_chunks_in_unit); + /* debug_printf("handling %d chunks in unit", num_chunks_in_unit); */ u32 idx; if(xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != num_chunks_in_unit) { @@ -1784,13 +1783,13 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s stitch_checksum(path, path->header.checksum, pkt); } - debug_printf("submitting %d chunks in unit", num_chunks_in_unit); + /* debug_printf("submitting %d chunks in unit", num_chunks_in_unit); */ xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); if (num_chunks_in_unit != SEND_QUEUE_ENTRIES_PER_UNIT){ - debug_printf("Submitting only %d frames", num_chunks_in_unit); + /* debug_printf("Submitting only %d frames", num_chunks_in_unit); */ } __sync_synchronize(); - debug_printf("submit returns"); + /* debug_printf("submit returns"); */ } static inline void tx_handle_send_queue_unit(struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], @@ -1850,7 +1849,7 @@ static inline void allocate_tx_frames(struct hercules_server *server, break; } } - debug_printf("claiming %d frames", num_frames); + /* debug_printf("claiming %d frames", num_frames); */ claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); } } @@ -1883,18 +1882,18 @@ static void tx_send_p(void *arg) { } num_chunks_in_unit++; } - debug_printf("unit has %d chunks", num_chunks_in_unit); + /* debug_printf("unit has %d chunks", num_chunks_in_unit); */ u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; memset(frame_addrs, 0xFF, sizeof(frame_addrs)); for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ if (i >= num_chunks_in_unit){ - debug_printf("not using unit slot %d", i); + /* debug_printf("not using unit slot %d", i); */ frame_addrs[0][i] = 0; } } - debug_printf("allocating frames"); + /* debug_printf("allocating frames"); */ allocate_tx_frames(server, frame_addrs); - debug_printf("done allocating frames"); + /* debug_printf("done allocating frames"); */ // At this point we claimed 7 frames (as many as can fit in a sendq unit) tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, frame_addrs, &unit); @@ -2011,6 +2010,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ chunk_idx = bitset__scan_neg(&rcvr->acked_chunks, chunk_idx); if(chunk_idx == tx_state->total_chunks) { + debug_printf("prev %d", rcvr->prev_chunk_idx); if(rcvr->prev_chunk_idx == 0) { // this receiver has finished debug_printf("receiver has finished"); rcvr->finished = true; @@ -2191,8 +2191,8 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i exit(1); } - /* debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); */ - /* getchar(); */ + debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); + getchar(); struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); tx_state->session = session; @@ -2303,13 +2303,12 @@ static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initia return true; } - -static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) -{ +static bool rx_update_reply_path(struct receiver_state *rx_state){ // Get reply path for sending ACKs: // // XXX: race reading from shared mem. // Try to make a quick copy to at least limit the carnage. + debug_printf("Updating reply path"); if(!rx_state) { debug_printf("ERROR: invalid rx_state"); return false; @@ -2321,12 +2320,22 @@ static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_p memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); pthread_spin_lock(&usock_lock); - int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, rx_state->etherlen, path); + // TODO writing to reply path needs sync? + int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, rx_state->etherlen, &rx_state->reply_path); pthread_spin_unlock(&usock_lock); if(!ret) { return false; } - path->ifid = rx_state->rx_sample_ifid; + // XXX Do we always want to reply from the interface the packet was received on? + // TODO The monitor also returns an interface id (from route lookup) + rx_state->reply_path.ifid = rx_state->rx_sample_ifid; + return true; +} + + +static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) +{ + memcpy(path, &rx_state->reply_path, sizeof(*path)); return true; } @@ -2362,6 +2371,7 @@ static void rx_handle_initial(struct hercules_server *server, struct receiver_st if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { debug_printf("setting rx sample"); set_rx_sample(rx_state, ifid, buf, headerlen + payloadlen); + rx_update_reply_path(rx_state); } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize @@ -2896,7 +2906,7 @@ static void events_p(void *arg) { } } -/* #define DEBUG_PRINT_PKTS */ +#define DEBUG_PRINT_PKTS #ifdef DEBUG_PRINT_PKTS void print_rbudp_pkt(const char *pkt, bool recv) { struct hercules_header *h = (struct hercules_header *)pkt; diff --git a/monitor.c b/monitor.c index 6448688..9195625 100644 --- a/monitor.c +++ b/monitor.c @@ -46,6 +46,7 @@ bool monitor_get_paths(int sockfd, int job_id, int *n_paths, struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_GET_PATHS; + msg.payload.paths.job_id = job_id; sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); struct hercules_sockmsg_A reply; @@ -90,6 +91,7 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_ap // XXX name needs to be allocated large enough by caller strncpy(name, reply.payload.newjob.filename, reply.payload.newjob.filename_len); *job_id = reply.payload.newjob.job_id; + debug_printf("received job id %d", reply.payload.newjob.job_id); *mtu = reply.payload.newjob.mtu; return true; } diff --git a/monitor/monitor.go b/monitor/monitor.go index d64c2b8..c4f8c5c 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -376,7 +376,7 @@ func pickPathsToDestination(etherLen int, numPaths int, localAddress snet.UDPAdd } func headersToDestination(src, dst snet.UDPAddr, ifs []*net.Interface, etherLen int, nPaths int) (int, []byte) { - fmt.Println("making headers", src, dst) + fmt.Println("making headers", src, dst, nPaths) srcA := addr.Addr{ IA: src.IA, Host: addr.MustParseHost(src.Host.IP.String()), @@ -444,7 +444,7 @@ func httpreq(w http.ResponseWriter, r *http.Request) { } nPaths := 1 if r.URL.Query().Has("np") { - mtu, err = strconv.Atoi(r.URL.Query().Get("np")) + nPaths, err = strconv.Atoi(r.URL.Query().Get("np")) if err != nil { io.WriteString(w, "parse err") return @@ -555,9 +555,8 @@ func main() { usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_PATHS: - // job_id := binary.LittleEndian.Uint16(buf[:2]) - // buf = buf[2:] - job_id := 1 + job_id := binary.LittleEndian.Uint16(buf[:2]) + buf = buf[2:] fmt.Println("fetch path, job", job_id) transfersLock.Lock() job, _ := transfers[int(job_id)] From e579f414b40380eff72a298533ec7fbfc7cc700e Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 26 May 2024 18:22:36 +0200 Subject: [PATCH 008/112] re-enable sending path handshakes --- hercules.c | 165 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 42 deletions(-) diff --git a/hercules.c b/hercules.c index e02dc14..4abc5ad 100644 --- a/hercules.c +++ b/hercules.c @@ -1663,30 +1663,30 @@ static void update_hercules_tx_paths(struct sender_state *tx_state) free_path_lock(); } -void send_path_handshakes(struct sender_state *tx_state) -{ - return; - /* u64 now = get_nsecs(); */ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - struct sender_state_per_receiver *rcvr = &tx_state->receiver[r]; - for(u32 p = 0; p < rcvr->num_paths; p++) { - struct hercules_path *path = &rcvr->paths[p]; - if(path->enabled) { - /* u64 handshake_at = atomic_load(&path->next_handshake_at); */ - /* if(handshake_at < now) { */ - /* if(atomic_compare_exchange_strong(&path->next_handshake_at, &handshake_at, */ - /* now + PATH_HANDSHAKE_TIMEOUT_NS)) { */ - /* debug_printf("sending hs"); */ - /* tx_send_initial(tx_state->session, path, tx_state->filesize, tx_state->chunklen, get_nsecs(), p, */ - /* p == rcvr->return_path_idx); */ - /* } */ - /* } */ - } - } - } +void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { + u64 now = get_nsecs(); + struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; + + for (u32 p = 0; p < rcvr->num_paths; p++) { + struct hercules_path *path = &rcvr->paths[p]; + debug_printf("Checking path %d/%d", p, rcvr->num_paths); + if (path->enabled) { + u64 handshake_at = atomic_load(&path->next_handshake_at); + if (handshake_at < now) { + if (atomic_compare_exchange_strong(&path->next_handshake_at, + &handshake_at, + now + PATH_HANDSHAKE_TIMEOUT_NS)) { + debug_printf("sending hs on path %d", p); + // FIXME file name below? + tx_send_initial(server, path, "abcd", tx_state->filesize, + tx_state->chunklen, get_nsecs(), p, + false, false); + } + } + } + } } - static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { /* assert(num_frames == 7); */ @@ -1922,6 +1922,25 @@ u32 compute_max_chunks_per_rcvr(struct sender_state *tx_state, u32 *max_chunks_p } return total_chunks; } +// Collect path rate limits +u32 compute_max_chunks_current_path(struct sender_state *tx_state) { + u32 allowed_chunks = 0; + u64 now = get_nsecs(); + + if (!tx_state->receiver[0].paths[tx_state->receiver[0].path_index].enabled) { + return 0; // if a receiver does not have any enabled paths, we can actually + // end up here ... :( + } + + if (tx_state->receiver[0].cc_states != NULL) { // use PCC + struct ccontrol_state *cc_state = + &tx_state->receiver[0].cc_states[tx_state->receiver[0].path_index]; + allowed_chunks = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); + } else { // no path-based limit + allowed_chunks = BATCH_SIZE; + } + return allowed_chunks; +} // exclude receivers that have completed the current iteration u32 exclude_finished_receivers(struct sender_state *tx_state, u32 *max_chunks_per_rcvr, u32 total_chunks) @@ -2095,7 +2114,7 @@ static void tx_only(struct hercules_server *server, struct sender_state *tx_stat while(tx_state->session->state == SESSION_STATE_RUNNING && finished_count < tx_state->num_receivers) { pop_completion_rings(server); - send_path_handshakes(tx_state); + /* send_path_handshakes(tx_state); */ u64 next_ack_due = 0; u32 num_chunks_per_rcvr[tx_state->num_receivers]; memset(num_chunks_per_rcvr, 0, sizeof(num_chunks_per_rcvr)); @@ -2762,6 +2781,7 @@ static void events_p(void *arg) { session->state = SESSION_STATE_PENDING; session->destination = server->config.local_addr; session->destination.ip = 0x132a8c0UL; + // FIXME remove ip above int n_paths; struct hercules_path *paths; @@ -3144,32 +3164,93 @@ static void *tx_p(void *arg) { struct hercules_server *server = arg; while (1) { /* pthread_spin_lock(&server->biglock); */ - pop_completion_rings(server); + pop_completion_rings(server); + u32 chunks[BATCH_SIZE]; + u8 chunk_rcvr[BATCH_SIZE]; struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx != NULL && atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { + if (session_tx != NULL && + atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { struct sender_state *tx_state = session_tx->tx_state; - /* tx_only(server, tx_state); */ - u32 chunks[BATCH_SIZE]; - u8 chunk_rcvr[BATCH_SIZE]; - u32 max_chunks_per_rcvr[1] = {BATCH_SIZE}; + debug_printf("Start transmit round"); + tx_state->prev_rate_check = get_nsecs(); + pop_completion_rings(server); + send_path_handshakes(server, tx_state); u64 next_ack_due = 0; - u32 total_chunks = BATCH_SIZE; + + // in each iteration, we send packets on a single path to each receiver + // collect the rate limits for each active path + u32 allowed_chunks = compute_max_chunks_current_path(tx_state); + + if (allowed_chunks == + 0) { // we hit the rate limits on every path; switch paths + iterate_paths(tx_state); + continue; + } + + // TODO re-enable? + // sending rates might add up to more than BATCH_SIZE, shrink + // proportionally, if needed + /* shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); */ + const u64 now = get_nsecs(); u32 num_chunks = 0; struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; - u64 ack_due = 0; - u32 cur_num_chunks = prepare_rcvr_chunks( - tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, - &ack_due, max_chunks_per_rcvr[0]); - /* debug_printf("prepared %d chunks", cur_num_chunks); */ - num_chunks += cur_num_chunks; - if (num_chunks > 0){ - u8 rcvr_path[1] = {0}; - produce_batch(server, session_tx, tx_state, rcvr_path, chunks, chunk_rcvr, num_chunks); - tx_state->tx_npkts_queued += num_chunks; - } - /* sleep_nsecs(1e9); // XXX FIXME */ + if (!rcvr->finished) { + u64 ack_due = 0; + // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks to + // send + u32 cur_num_chunks = prepare_rcvr_chunks( + tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, + &ack_due, allowed_chunks); + num_chunks += cur_num_chunks; + if (rcvr->finished) { + if (rcvr->cc_states) { + terminate_cc(rcvr); + kick_cc(tx_state); + } + } else { + // only wait for the nearest ack + if (next_ack_due) { + if (next_ack_due > ack_due) { + next_ack_due = ack_due; + } + } else { + next_ack_due = ack_due; + } + } + } + + if (num_chunks > 0) { + u8 rcvr_path[tx_state->num_receivers]; + prepare_rcvr_paths(tx_state, rcvr_path); + produce_batch(server, session_tx, tx_state, rcvr_path, chunks, chunk_rcvr, + num_chunks); + tx_state->tx_npkts_queued += num_chunks; + rate_limit_tx(tx_state); + + // update book-keeping + struct sender_state_per_receiver *receiver = &tx_state->receiver[0]; + u32 path_idx = tx_state->receiver[0].path_index; + if (receiver->cc_states != NULL) { + struct ccontrol_state *cc_state = &receiver->cc_states[path_idx]; + // FIXME allowed_chunks below is not correct (3x) + atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); + atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); + if (pcc_has_active_mi(cc_state, now)) { + atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, + allowed_chunks); + } + } + } + + iterate_paths(tx_state); + + if (now < next_ack_due) { + // XXX if the session vanishes in the meantime, we might wait + // unnecessarily + sleep_until(next_ack_due); + } } } From 88d8187590f05cf04195d4eada974618c0009ab1 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 27 May 2024 15:12:35 +0200 Subject: [PATCH 009/112] clean up session states, add error enum --- hercules.c | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/hercules.c b/hercules.c index 4abc5ad..f9cbc40 100644 --- a/hercules.c +++ b/hercules.c @@ -126,21 +126,28 @@ struct hercules_config { struct hercules_app_addr local_addr; }; +// Some states are used only by the TX/RX side and are marked accordingly enum session_state { - SESSION_STATE_NONE, //< no session - SESSION_STATE_PENDING, //< Need to send HS and repeat until TO - SESSION_STATE_CREATED, //< - SESSION_STATE_READY, //< Receiver setup ready, need to send HS and CTS - SESSION_STATE_WAIT_HS, //< Waiting for HS - SESSION_STATE_WAIT_CTS, //< Waiting for CTS + SESSION_STATE_NONE, + SESSION_STATE_PENDING, //< (TX) Need to send HS and repeat until TO, waiting + // for a reflected HS packet + SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and + // CTS + SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS SESSION_STATE_RUNNING, //< Transfer in progress SESSION_STATE_DONE, //< Transfer done (or cancelled with error) }; +enum session_error { + SESSION_ERROR_OK, //< No error, transfer completed successfully + SESSION_ERROR_TIMEOUT, //< Session timed out +}; + struct hercules_session { struct receiver_state *rx_state; struct sender_state *tx_state; enum session_state state; + enum session_error error; struct send_queue *send_queue; struct hercules_app_addr destination; @@ -351,6 +358,11 @@ static inline struct hercules_interface *get_interface_by_id(struct hercules_ser return NULL; } +static inline void quit_session(struct hercules_session *s, enum session_error err){ + s->error = err; + s->state = SESSION_STATE_DONE; +} + // XXX: from lib/scion/udp.c /* * Calculate UDP checksum @@ -2802,7 +2814,6 @@ static void events_p(void *arg) { tx_send_initial(server, &tx_state->receiver[0].paths[0], fname, tx_state->filesize, tx_state->chunklen, timestamp, 0, true, true); - atomic_store(&server->session_tx->state, SESSION_STATE_WAIT_HS); } else { debug_printf("no new job."); } @@ -2861,7 +2872,7 @@ static void events_p(void *arg) { // we sent out earlier debug_printf("HS confirm packet"); if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_WAIT_HS) { + server->session_tx->state == SESSION_STATE_PENDING) { server->session_tx->state = SESSION_STATE_WAIT_CTS; } break; // Make sure we don't process this further @@ -2873,7 +2884,7 @@ static void events_p(void *arg) { if (parsed_pkt.flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { struct hercules_session *session = make_session(server); server->session_rx = session; - session->state = SESSION_STATE_CREATED; + session->state = SESSION_STATE_NEW; // TODO fill in etherlen struct receiver_state *rx_state = make_rx_state( session, parsed_pkt.name, parsed_pkt.filesize, parsed_pkt.chunklen, 1200, false); From 77b96d1a5939ac93359807985bf6e35f2012ad70 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 27 May 2024 16:48:23 +0200 Subject: [PATCH 010/112] timeouts, HS resend --- hercules.c | 45 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/hercules.c b/hercules.c index f9cbc40..4d50db2 100644 --- a/hercules.c +++ b/hercules.c @@ -149,6 +149,8 @@ struct hercules_session { enum session_state state; enum session_error error; struct send_queue *send_queue; + u64 last_pkt_sent; + u64 last_pkt_rcvd; struct hercules_app_addr destination; int num_paths; @@ -238,6 +240,7 @@ struct sender_state { /** Filesize in bytes */ size_t filesize; + char filename[100]; /** Size of file data (in byte) per packet */ u32 chunklen; /** Number of packets that will make up the entire file. Equal to `ceil(filesize/chunklen)` */ @@ -2809,11 +2812,8 @@ static void events_p(void *arg) { server->session_tx, filesize, chunklen, server->rate_limit, mem, &session->destination, session->paths_to_dest, 1, session->num_paths, server->max_paths); + strncpy(tx_state, fname, 99); server->session_tx->tx_state = tx_state; - unsigned long timestamp = get_nsecs(); - tx_send_initial(server, &tx_state->receiver[0].paths[0], fname, - tx_state->filesize, tx_state->chunklen, timestamp, 0, - true, true); } else { debug_printf("no new job."); } @@ -2821,7 +2821,34 @@ static void events_p(void *arg) { } // XXX - /* // Set timeout on the socket */ + struct hercules_session *session_tx = atomic_load(&server->session_tx); + // TODO FIXME enable once receive timestamps are stored upon pkt reception + /* if (session_tx && session_tx->state == SESSION_STATE_DONE){ */ + /* if (get_nsecs() > session_tx->last_pkt_rcvd + 20e9){ */ + /* atomic_store(&server->session_tx, NULL); // FIXME leak */ + /* fprintf(stderr, "Cleaning up session..."); */ + /* } */ + /* } */ + /* // Time out if no packets received for 10 sec. */ + /* if (session_tx && session_tx->state != SESSION_STATE_DONE){ */ + /* if (get_nsecs() > session_tx->last_pkt_rcvd + 10e9){ */ + /* quit_session(session_tx, SESSION_ERROR_TIMEOUT); */ + /* debug_printf("Session timed out!"); */ + /* } */ + /* } */ + // (Re)send HS if needed + if (session_tx && session_tx->state == SESSION_STATE_PENDING) { + unsigned long timestamp = get_nsecs(); + if (timestamp > session_tx->last_pkt_sent + 1e9) { // Re-send HS every 1 sec. + struct sender_state *tx_state = session_tx->tx_state; + tx_send_initial(server, &tx_state->receiver[0].paths[0], tx_state->filename, + tx_state->filesize, tx_state->chunklen, timestamp, 0, + true, true); + session_tx->last_pkt_sent = timestamp; + } + } + + /* // Set timeout on the socket */ /* struct timeval to = {.tv_sec = 0, .tv_usec = 500}; */ /* setsockopt(server->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), 0, @@ -2911,8 +2938,7 @@ static void events_p(void *arg) { &server->session_tx->tx_state->receiver[0]); if (tx_acked_all(server->session_tx->tx_state)) { debug_printf("TX done, received all acks"); - /* server->session_tx = NULL; // FIXME leak */ - atomic_store(&server->session_tx, NULL); + quit_session(server->session_tx, SESSION_ERROR_OK); } } break; @@ -2997,6 +3023,11 @@ struct hercules_session *make_session(struct hercules_server *server) { exit_with_error(NULL, err); } init_send_queue(s->send_queue, BATCH_SIZE); + s->last_pkt_sent = 0; + s->last_pkt_rcvd = + get_nsecs(); // Set this to "now" to allow timing out HS at sender (when + // no packet was received yet), once packets are received it + // will be updated accordingly return s; } From cf53b7fa25d0ff4a271d7b17d7c59c07561d323a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 27 May 2024 17:37:53 +0200 Subject: [PATCH 011/112] remove unused code, stop session on error instead of stopping hercules --- hercules.c | 781 ++++++--------------------------------------------- hercules.h | 9 +- send_queue.h | 3 +- utils.h | 7 - 4 files changed, 85 insertions(+), 715 deletions(-) diff --git a/hercules.c b/hercules.c index 4d50db2..28b8de6 100644 --- a/hercules.c +++ b/hercules.c @@ -4,7 +4,6 @@ // Enable extra warnings; cannot be enabled in CFLAGS because cgo generates a // ton of warnings that can apparantly not be suppressed. -#include #pragma GCC diagnostic warning "-Wextra" #include "hercules.h" @@ -141,6 +140,7 @@ enum session_state { enum session_error { SESSION_ERROR_OK, //< No error, transfer completed successfully SESSION_ERROR_TIMEOUT, //< Session timed out + SESSION_ERROR_PCC, }; struct hercules_session { @@ -595,65 +595,6 @@ static const char *parse_pkt(const struct hercules_server *server, const char *p return parse_pkt_fast_path(pkt, length, check, offset); } -/* static bool recv_rbudp_control_pkt(struct hercules_session *session, char *buf, size_t buflen, */ -/* const char **payload, int *payloadlen, const struct scionaddrhdr_ipv4 **scionaddrh, */ -/* const struct udphdr **udphdr, u8 *path, int *ifid) */ -/* { */ -/* /\* debug_printf("receive rbudp control pkt..."); *\/ */ -/* struct sockaddr_ll addr; */ -/* socklen_t addr_size = sizeof(addr); */ -/* ssize_t len = recvfrom(session->control_sockfd, buf, buflen, 0, (struct sockaddr *) &addr, */ -/* &addr_size); // XXX set timeout */ -/* if(len == -1) { */ -/* if(errno == EAGAIN || errno == EINTR) { */ -/* /\* debug_printf("eagain/intr"); *\/ */ -/* return false; */ -/* } */ -/* exit_with_error(session, errno); // XXX: are there situations where we want to try again? */ -/* } */ - -/* if(get_interface_by_id(session, addr.sll_ifindex) == NULL) { */ -/* return false; // wrong interface, ignore packet */ -/* } */ - -/* const char *rbudp_pkt = parse_pkt(session, buf, len, true, scionaddrh, udphdr); */ -/* if(rbudp_pkt == NULL) { */ -/* debug_printf("Ignoring, failed parse"); */ -/* return false; */ -/* } */ -/* print_rbudp_pkt(rbudp_pkt, true); */ - -/* const size_t rbudp_len = len - (rbudp_pkt - buf); */ -/* if(rbudp_len < sizeof(u32)) { */ -/* debug_printf("Ignoring, length too short"); */ -/* return false; */ -/* } */ -/* u32 chunk_idx; */ -/* memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); */ -/* if(chunk_idx != UINT_MAX) { */ -/* debug_printf("Ignoring, chunk_idx != UINT_MAX"); */ -/* return false; */ -/* } */ - -/* *payload = rbudp_pkt + rbudp_headerlen; */ -/* *payloadlen = rbudp_len - rbudp_headerlen; */ -/* u32 path_idx; */ -/* memcpy(&path_idx, rbudp_pkt + sizeof(u32), sizeof(*path)); */ -/* if(path != NULL) { */ -/* *path = path_idx; */ -/* debug_printf("Path idx %u", path_idx); */ -/* } */ -/* if(ifid != NULL) { */ -/* *ifid = addr.sll_ifindex; */ -/* } */ - -/* atomic_fetch_add(&session->rx_npkts, 1); */ -/* if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { */ -/* atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); */ -/* } */ -/* return true; */ -/* } */ - static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { if(length < rbudp_headerlen + rx_state->chunklen) { @@ -1123,7 +1064,7 @@ static void pcc_monitor(struct sender_state *tx_state) u64 now = get_nsecs(); if(cc_state->mi_end == 0) { // TODO should not be necessary fprintf(stderr, "Assumption violated.\n"); - exit_with_error(NULL, EINVAL); + quit_session(tx_state->session, SESSION_ERROR_PCC); cc_state->mi_end = now; } u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI @@ -1205,89 +1146,6 @@ bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct s return updated; } -/* static void tx_recv_control_messages(struct sender_state *tx_state) */ -/* { */ -/* struct timeval to = {.tv_sec = 0, .tv_usec = 100}; */ -/* setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ -/* char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ - -/* // packet receive timeouts */ -/* u64 last_pkt_rcvd[tx_state->num_receivers]; */ -/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ -/* // tolerate some delay for first ACK */ -/* last_pkt_rcvd[r] = get_nsecs() */ -/* + 2 * tx_state->receiver[r].handshake_rtt // at startup, tolerate two additional RTTs */ -/* + 100 * ACK_RATE_TIME_MS * 1e6; // some drivers experience a short outage after activating XDP */ -/* } */ - -/* while(tx_state->session->is_running && !tx_acked_all(tx_state)) { */ -/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ -/* if(!tx_state->receiver[r].finished && last_pkt_rcvd[r] + 8 * ACK_RATE_TIME_MS * 1e6 < get_nsecs()) { */ -/* // Abort transmission after timeout. */ -/* debug_printf("receiver %d timed out: last %fs, now %fs", r, last_pkt_rcvd[r] / 1.e9, */ -/* get_nsecs() / 1.e9); */ -/* // XXX: this aborts all transmissions, as soon as one times out */ -/* exit_with_error(tx_state->session, ETIMEDOUT); */ -/* } */ -/* } */ - -/* const char *payload; */ -/* int payloadlen; */ -/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ -/* const struct udphdr *udphdr; */ -/* u8 path_idx; */ -/* if(recv_rbudp_control_pkt(tx_state->session, buf, sizeof buf, &payload, &payloadlen, */ -/* &scionaddrhdr, &udphdr, &path_idx, NULL)) { */ -/* const struct hercules_control_packet *control_pkt = (const struct hercules_control_packet *) payload; */ -/* if((u32) payloadlen < sizeof(control_pkt->type)) { */ -/* debug_printf("control packet too short"); */ -/* } else { */ -/* u32 control_pkt_payloadlen = payloadlen - sizeof(control_pkt->type); */ -/* u32 rcvr_idx = rcvr_by_src_address(tx_state, scionaddrhdr, udphdr); */ -/* if(rcvr_idx < tx_state->num_receivers) { */ -/* last_pkt_rcvd[rcvr_idx] = umax64(last_pkt_rcvd[rcvr_idx], get_nsecs()); */ -/* switch(control_pkt->type) { */ -/* case CONTROL_PACKET_TYPE_ACK: */ -/* if(control_pkt_payloadlen >= ack__len(&control_pkt->payload.ack)) { */ -/* struct rbudp_ack_pkt ack; */ -/* memcpy(&ack, &control_pkt->payload.ack, ack__len(&control_pkt->payload.ack)); */ -/* tx_register_acks(&ack, &tx_state->receiver[rcvr_idx]); */ -/* } */ -/* break; */ -/* case CONTROL_PACKET_TYPE_NACK: */ -/* if(tx_state->receiver[0].cc_states != NULL && */ -/* control_pkt_payloadlen >= ack__len(&control_pkt->payload.ack)) { */ -/* struct rbudp_ack_pkt nack; */ -/* memcpy(&nack, &control_pkt->payload.ack, ack__len(&control_pkt->payload.ack)); */ -/* nack_trace_push(nack.timestamp, nack.ack_nr); */ -/* tx_register_nacks(&nack, &tx_state->receiver[rcvr_idx].cc_states[path_idx]); */ -/* } */ -/* break; */ -/* case CONTROL_PACKET_TYPE_INITIAL: */ -/* if(control_pkt_payloadlen >= sizeof(control_pkt->payload.initial)) { */ -/* struct rbudp_initial_pkt initial; */ -/* memcpy(&initial, &control_pkt->payload.initial, sizeof(control_pkt->payload.initial)); */ -/* struct sender_state_per_receiver *receiver = &tx_state->receiver[rcvr_idx]; */ -/* if(tx_handle_handshake_reply(&initial, receiver)) { */ -/* debug_printf("[receiver %d] [path %d] handshake_rtt: %fs, MI: %fs", rcvr_idx, */ -/* initial.path_index, receiver->cc_states[initial.path_index].rtt, */ -/* receiver->cc_states[initial.path_index].pcc_mi_duration); */ -/* } */ -/* } */ -/* break; */ -/* default: */ -/* debug_printf("received a control packet of unknown type %d", control_pkt->type); */ -/* } */ -/* } */ -/* } */ -/* } */ - -/* if(tx_state->receiver[0].cc_states) { */ -/* pcc_monitor(tx_state); */ -/* } */ -/* } */ -/* } */ - static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t payloadlen, u32 rcvr) { const struct hercules_control_packet *control_pkt = (const struct hercules_control_packet *)cts; @@ -1301,98 +1159,6 @@ static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t return false; } -/* static bool tx_await_cts(struct sender_state *tx_state) */ -/* { */ -/* // count received CTS */ -/* u32 received = 0; */ -/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ -/* if(tx_state->receiver[r].cts_received) { */ -/* received++; */ -/* } */ -/* } */ - -/* // Set timeout on the socket */ -/* struct timeval to = {.tv_sec = 1, .tv_usec = 0}; */ -/* setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ - -/* char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ -/* const char *payload; */ -/* int payloadlen; */ -/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ -/* const struct udphdr *udphdr; */ -/* // Wait up to 20 seconds for the receiver to get ready */ -/* for(u64 start = get_nsecs(); start + 300e9l > get_nsecs();) { */ -/* if(recv_rbudp_control_pkt(tx_state->session, buf, sizeof buf, &payload, &payloadlen, &scionaddrhdr, &udphdr, NULL, NULL)) { */ -/* if(tx_handle_cts(tx_state, payload, payloadlen, rcvr_by_src_address(tx_state, scionaddrhdr, udphdr))) { */ -/* received++; */ -/* if(received >= tx_state->num_receivers) { */ -/* return true; */ -/* } */ -/* } */ -/* } */ -/* } */ -/* return false; */ -/* } */ - -/* static void tx_send_handshake_ack(struct sender_state *tx_state, u32 rcvr) */ -/* { */ -/* debug_printf("send HS ack"); */ -/* char buf[tx_state->session->config.ether_size]; */ -/* struct hercules_path *path = &tx_state->receiver[rcvr].paths[0]; */ -/* void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); */ - -/* struct rbudp_ack_pkt ack; */ -/* ack.num_acks = 0; */ - -/* fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&ack, ack__len(&ack), path->payloadlen); */ -/* stitch_checksum(path, path->header.checksum, buf); */ - -/* send_eth_frame(tx_state->session, path, buf); */ -/* atomic_fetch_add(&tx_state->session->tx_npkts, 1); */ -/* } */ - -/* static bool tx_await_rtt_ack(struct sender_state *tx_state, char *buf, size_t buflen, const struct scionaddrhdr_ipv4 **scionaddrhdr, const struct udphdr **udphdr) */ -/* { */ -/* const struct scionaddrhdr_ipv4 *scionaddrhdr_fallback; */ -/* if(scionaddrhdr == NULL) { */ -/* scionaddrhdr = &scionaddrhdr_fallback; */ -/* } */ - -/* const struct udphdr *udphdr_fallback; */ -/* if(udphdr == NULL) { */ -/* udphdr = &udphdr_fallback; */ -/* } */ - -/* // Set 0.1 second timeout on the socket */ -/* struct timeval to = {.tv_sec = 0, .tv_usec = 100e3}; */ -/* setsockopt(tx_state->session->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ - -/* const char *payload; */ -/* int payloadlen; */ -/* if(recv_rbudp_control_pkt(tx_state->session, buf, buflen, &payload, &payloadlen, scionaddrhdr, udphdr, NULL, NULL)) { */ -/* struct rbudp_initial_pkt parsed_pkt; */ -/* u32 rcvr = rcvr_by_src_address(tx_state, *scionaddrhdr, *udphdr); */ -/* if(rbudp_parse_initial(payload, payloadlen, &parsed_pkt)) { */ -/* if(rcvr < tx_state->num_receivers && tx_state->receiver[rcvr].handshake_rtt == 0) { */ -/* tx_state->receiver[rcvr].handshake_rtt = (u64)(get_nsecs() - parsed_pkt.timestamp); */ -/* if(parsed_pkt.filesize != tx_state->filesize || */ -/* parsed_pkt.chunklen != tx_state->chunklen) { */ -/* debug_printf("Receiver disagrees " */ -/* "on transfer parameters:\n" */ -/* "filesize: %llu\nchunklen: %u", */ -/* parsed_pkt.filesize, */ -/* parsed_pkt.chunklen); */ -/* return false; */ -/* } */ -/* tx_send_handshake_ack(tx_state, rcvr); */ -/* } */ -/* return true; */ -/* } else { */ -/* tx_handle_cts(tx_state, payload, payloadlen, rcvr); */ -/* } */ -/* } */ -/* return false; */ -/* } */ static void tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) @@ -1430,42 +1196,6 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path /* atomic_fetch_add(&server->tx_npkts, 1); */ } -/* static bool tx_handshake(struct sender_state *tx_state) */ -/* { */ -/* bool succeeded[tx_state->num_receivers]; */ -/* memset(succeeded, 0, sizeof(succeeded)); */ -/* for(u64 start = get_nsecs(); start >= get_nsecs() - tx_handshake_timeout;) { */ -/* int await = 0; */ -/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ -/* if(!succeeded[r]) { */ -/* unsigned long timestamp = get_nsecs(); */ -/* tx_send_initial(tx_state->session, &tx_state->receiver[r].paths[0], tx_state->filesize, */ -/* tx_state->chunklen, timestamp, 0, true); */ -/* await++; */ -/* } */ -/* } */ - -/* char buf[tx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ -/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ -/* const struct udphdr *udphdr; */ -/* for(u64 start_wait = get_nsecs(); get_nsecs() < start_wait + tx_handshake_retry_after;) { */ -/* if(tx_await_rtt_ack(tx_state, buf, sizeof buf, &scionaddrhdr, &udphdr)) { */ -/* u32 rcvr = rcvr_by_src_address(tx_state, scionaddrhdr, udphdr); */ -/* if(rcvr < tx_state->num_receivers && !succeeded[rcvr]) { */ -/* tx_state->receiver[rcvr].paths[0].next_handshake_at = UINT64_MAX; */ -/* succeeded[rcvr] = true; */ -/* await--; */ -/* if(await == 0) { */ -/* return true; */ -/* } */ -/* } */ -/* } */ -/* } */ -/* debug_printf("Timeout, retry."); */ -/* } */ -/* fprintf(stderr, "ERR: timeout during handshake. Gave up after %.0f seconds.\n", tx_handshake_timeout / 1e9); */ -/* return false; */ -/* } */ static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) { @@ -1531,7 +1261,7 @@ static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_ const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); if(rbudp_pkt) { - print_rbudp_pkt(rbudp_pkt, true); + debug_print_rbudp_pkt(rbudp_pkt, true); if(!handle_rbudp_data_pkt(rx_state, rbudp_pkt, len - (rbudp_pkt - pkt))) { struct rbudp_initial_pkt initial; if(rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len, &initial)) { @@ -1587,7 +1317,7 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence if(sizeof(chunk_idx) + sizeof(path_idx) + n < payloadlen) { memset(start_pad, 0, payloadlen - sizeof(chunk_idx) - sizeof(path_idx) - n); } - print_rbudp_pkt(rbudp_pkt, false); + debug_print_rbudp_pkt(rbudp_pkt, false); } @@ -1755,8 +1485,6 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s num_chunks_in_unit++; } } - // We may have claimed more frames than we need, if the unit is not full - /* debug_printf("handling %d chunks in unit", num_chunks_in_unit); */ u32 idx; if(xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != num_chunks_in_unit) { @@ -2215,7 +1943,7 @@ static void tx_only(struct hercules_server *server, struct sender_state *tx_stat static struct sender_state * init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, int max_rate_limit, char *mem, - const struct hercules_app_addr *dests, struct hercules_path *paths, u32 num_dests, const int *num_paths, + const struct hercules_app_addr *dests, struct hercules_path *paths, u32 num_dests, const int num_paths, u32 max_paths_per_dest) { u64 total_chunks = (filesize + chunklen - 1) / chunklen; @@ -2225,8 +1953,8 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i exit(1); } - debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); - getchar(); + /* debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); */ + /* getchar(); */ struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); tx_state->session = session; @@ -2240,9 +1968,6 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i tx_state->num_receivers = num_dests; tx_state->receiver = calloc(num_dests, sizeof(*tx_state->receiver)); tx_state->max_paths_per_rcvr = max_paths_per_dest; - tx_state->shd_paths = paths; - tx_state->shd_num_paths = num_paths; - tx_state->has_new_paths = false; for(u32 d = 0; d < num_dests; d++) { struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; @@ -2268,6 +1993,7 @@ static void destroy_tx_state(struct sender_state *tx_state) free(tx_state); } +// TODO should return error instead of quitting static char *rx_mmap(struct hercules_server *server, const char *pathname, size_t filesize) { debug_printf("mmap file: %s", pathname); @@ -2412,31 +2138,6 @@ static void rx_handle_initial(struct hercules_server *server, struct receiver_st rx_state->cts_sent_at = get_nsecs(); } -/* static struct receiver_state *rx_accept(struct hercules_server *server, int timeout, bool is_pcc_benchmark) */ -/* { */ -/* char buf[server->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ -/* __u64 start_wait = get_nsecs(); */ -/* struct timeval to = {.tv_sec = 1, .tv_usec = 0}; */ -/* setsockopt(server->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ - -/* // Wait for well formed startup packet */ -/* while(timeout == 0 || start_wait + timeout * 1e9 > get_nsecs()) { */ -/* const char *payload; */ -/* int payloadlen; */ -/* int ifid; */ -/* if(recv_rbudp_control_pkt(server, buf, sizeof buf, &payload, &payloadlen, NULL, NULL, NULL, &ifid)) { */ -/* struct rbudp_initial_pkt parsed_pkt; */ -/* if(rbudp_parse_initial(payload, payloadlen, &parsed_pkt)) { */ -/* struct receiver_state *rx_state = make_rx_state(server, parsed_pkt.filesize, parsed_pkt.chunklen, */ -/* is_pcc_benchmark); */ -/* rx_handle_initial(rx_state, &parsed_pkt, buf, ifid, payload, payloadlen); */ -/* return rx_state; */ -/* } */ -/* } */ -/* } */ -/* return NULL; */ -/* } */ - /* static void rx_get_rtt_estimate(void *arg) */ /* { */ /* struct receiver_state *rx_state = arg; */ @@ -2618,7 +2319,7 @@ static void rx_trickle_acks(void *arg) { 3 * rx_state->handshake_rtt) < get_nsecs()) { // Transmission timed out - exit_with_error(server, ETIMEDOUT); + quit_session(session_rx, SESSION_ERROR_TIMEOUT); } rx_send_acks(server, rx_state); if (rx_received_all(rx_state)) { @@ -2682,6 +2383,7 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s libbpf_smp_wmb(); /* assert(&path_state->seq_rcvd.lock != NULL); */ /* assert(path_state->seq_rcvd.lock != NULL); */ + // FIXME spurious segfault on unlock /* pthread_spin_unlock(&path_state->seq_rcvd.lock); */ path_state->nack_end = nack_end; } @@ -2773,6 +2475,15 @@ static void events_p(void *arg) { pthread_spin_unlock(&usock_lock); if (ret) { debug_printf("new job: %s", fname); + // TODO propagate open/map errors instead of quitting + // TODO check mtu large enough +/* if(HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)mtu) { */ +/* printf("MTU too small (min: %lu, given: %d)", */ +/* HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen, */ +/* mtu */ +/* ); */ +/* exit_with_error(session, EINVAL); */ +/* } */ debug_printf("Starting new TX"); int f = open(fname, O_RDONLY); if (f == -1) { @@ -2812,8 +2523,10 @@ static void events_p(void *arg) { server->session_tx, filesize, chunklen, server->rate_limit, mem, &session->destination, session->paths_to_dest, 1, session->num_paths, server->max_paths); - strncpy(tx_state, fname, 99); + strncpy(tx_state->filename, fname, 99); server->session_tx->tx_state = tx_state; + + } else { debug_printf("no new job."); } @@ -2822,20 +2535,19 @@ static void events_p(void *arg) { // XXX struct hercules_session *session_tx = atomic_load(&server->session_tx); - // TODO FIXME enable once receive timestamps are stored upon pkt reception - /* if (session_tx && session_tx->state == SESSION_STATE_DONE){ */ - /* if (get_nsecs() > session_tx->last_pkt_rcvd + 20e9){ */ - /* atomic_store(&server->session_tx, NULL); // FIXME leak */ - /* fprintf(stderr, "Cleaning up session..."); */ - /* } */ - /* } */ - /* // Time out if no packets received for 10 sec. */ - /* if (session_tx && session_tx->state != SESSION_STATE_DONE){ */ - /* if (get_nsecs() > session_tx->last_pkt_rcvd + 10e9){ */ - /* quit_session(session_tx, SESSION_ERROR_TIMEOUT); */ - /* debug_printf("Session timed out!"); */ - /* } */ - /* } */ + if (session_tx && session_tx->state == SESSION_STATE_DONE){ + if (get_nsecs() > session_tx->last_pkt_rcvd + 20e9){ + atomic_store(&server->session_tx, NULL); // FIXME leak + fprintf(stderr, "Cleaning up session...\n"); + } + } + // Time out if no packets received for 10 sec. + if (session_tx && session_tx->state != SESSION_STATE_DONE){ + if (get_nsecs() > session_tx->last_pkt_rcvd + 10e9){ + quit_session(session_tx, SESSION_ERROR_TIMEOUT); + debug_printf("Session timed out!"); + } + } // (Re)send HS if needed if (session_tx && session_tx->state == SESSION_STATE_PENDING) { unsigned long timestamp = get_nsecs(); @@ -2863,9 +2575,11 @@ static void events_p(void *arg) { errno); // XXX: are there situations where we want to try again? } - /* if (get_interface_by_id(session, addr.sll_ifindex) == NULL) { */ - /* continue; */ - /* } */ + // TODO rewrite this with select/poll + + if (get_interface_by_id(server, addr.sll_ifindex) == NULL) { + continue; + } const char *rbudp_pkt = parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr); if (rbudp_pkt == NULL) { @@ -2884,9 +2598,23 @@ static void events_p(void *arg) { debug_printf("Ignoring, chunk_idx != UINT_MAX"); continue; } - print_rbudp_pkt(rbudp_pkt, true); + debug_print_rbudp_pkt(rbudp_pkt, true); + // TODO Count received pkt +/* atomic_fetch_add(&session->rx_npkts, 1); */ +/* if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { */ +/* atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); */ +/* } */ + + // TODO this belongs somewhere? +/* if(tx_state->receiver[0].cc_states) { */ +/* pcc_monitor(tx_state); */ +/* } */ struct hercules_header *h = (struct hercules_header *)rbudp_pkt; if (h->chunk_idx == UINT_MAX) { + // FIXME does not belong here and counts wrong packets too + if (server->session_tx != NULL){ + server->session_tx->last_pkt_rcvd == get_nsecs(); + } const char *pl = rbudp_pkt + rbudp_headerlen; struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; switch (cp->type) { @@ -2900,6 +2628,25 @@ static void events_p(void *arg) { debug_printf("HS confirm packet"); if (server->session_tx != NULL && server->session_tx->state == SESSION_STATE_PENDING) { + if(server->enable_pcc) { + u64 now = get_nsecs(); + struct sender_state_per_receiver *receiver = &server->session_tx->tx_state->receiver[0]; + receiver->cc_states = init_ccontrol_state( + server->rate_limit, + server->session_tx->tx_state->total_chunks, + server->session_tx->num_paths, + server->max_paths, + server->max_paths + ); + ccontrol_update_rtt(&receiver->cc_states[0], receiver->handshake_rtt); + fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: %fs, MI: %fs\n", + 0, receiver->handshake_rtt / 1e9, receiver->cc_states[0].pcc_mi_duration); + + // make sure tx_only() performs RTT estimation on every enabled path + for(u32 p = 1; p < receiver->num_paths; p++) { + receiver->paths[p].next_handshake_at = now; + } + } server->session_tx->state = SESSION_STATE_WAIT_CTS; } break; // Make sure we don't process this further @@ -2951,21 +2698,13 @@ static void events_p(void *arg) { } else { debug_printf("Non-control packet received on control socket"); } - - /* *payload = rbudp_pkt + rbudp_headerlen; */ - /* *payloadlen = rbudp_len - rbudp_headerlen; */ - /* u32 path_idx; */ - /* memcpy(&path_idx, rbudp_pkt + sizeof(u32), sizeof(*path)); */ - /* if (path != NULL) { */ - /* *path = path_idx; */ - /* debug_printf("Path idx %u", path_idx); */ - /* } */ } } #define DEBUG_PRINT_PKTS #ifdef DEBUG_PRINT_PKTS -void print_rbudp_pkt(const char *pkt, bool recv) { +// recv indicates whether printed packets should be prefixed with TX or RX +void debug_print_rbudp_pkt(const char *pkt, bool recv) { struct hercules_header *h = (struct hercules_header *)pkt; const char *prefix = (recv) ? "RX->" : "<-TX"; printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, h->path, @@ -3005,7 +2744,7 @@ void print_rbudp_pkt(const char *pkt, bool recv) { } } #else -void print_rbudp_pkt(const char * pkt, bool recv){ +inline void debug_print_rbudp_pkt(const char * pkt, bool recv){ (void)pkt; (void)recv; return; @@ -3049,7 +2788,7 @@ static struct receiver_state *rx_receive_hs(struct hercules_server *server, const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); if (rbudp_pkt) { - print_rbudp_pkt(rbudp_pkt, true); + debug_print_rbudp_pkt(rbudp_pkt, true); struct rbudp_initial_pkt parsed_pkt; if (rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len - rbudp_headerlen, &parsed_pkt)) { debug_printf("parsed initial"); @@ -3299,55 +3038,6 @@ static void *tx_p(void *arg) { return NULL; } -/* struct hercules_session *hercules_init(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, */ -/* int queue, int mtu) */ -/* { */ -/* struct hercules_session *session; */ -/* int err = posix_memalign((void **) &session, CACHELINE_SIZE, */ -/* sizeof(*session) + num_ifaces * sizeof(*session->ifaces)); */ -/* if(err != 0) { */ -/* exit_with_error(NULL, err); */ -/* } */ -/* memset(session, 0, sizeof(*session)); */ -/* session->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; */ -/* if(HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)mtu) { */ -/* printf("MTU too small (min: %lu, given: %d)", */ -/* HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen, */ -/* mtu */ -/* ); */ -/* exit_with_error(session, EINVAL); */ -/* } */ -/* session->config.ether_size = mtu; */ -/* session->config.local_addr = local_addr; */ -/* session->num_ifaces = num_ifaces; */ - -/* for(int i = 0; i < num_ifaces; i++) { */ -/* session->ifaces[i] = (struct hercules_interface) { */ -/* .queue = queue, */ -/* .ifid = ifindices[i], */ -/* .ethtool_rule = -1, */ -/* }; */ -/* if_indextoname(ifindices[i], session->ifaces[i].ifname); */ -/* debug_printf("using queue %d on interface %s", session->ifaces[i].queue, session->ifaces[i].ifname); */ - -/* // Open RAW socket to receive and send control messages on */ -/* // Note: at the receiver, this socket will not receive any packets once the BPF has been */ -/* // activated, which will then redirect packets to one of the XSKs. */ -/* session->control_sockfd = open_control_socket(); */ -/* if(session->control_sockfd < 0) { */ -/* exit_with_error(session, -session->control_sockfd); */ -/* } */ -/* } */ - -/* struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; */ -/* setlocale(LC_ALL, ""); */ -/* if(setrlimit(RLIMIT_MEMLOCK, &r)) { */ -/* fprintf(stderr, "ERROR: setrlimit(RLIMIT_MEMLOCK) \"%s\"\n", */ -/* strerror(errno)); */ -/* exit(EXIT_FAILURE); */ -/* } */ -/* return session; */ -/* } */ struct hercules_server * hercules_init_server(int *ifindices, int num_ifaces, @@ -3388,7 +3078,6 @@ hercules_init_server(int *ifindices, int num_ifaces, if (server->control_sockfd == -1) { exit_with_error(server, 0); } - debug_printf("init complete"); return server; } @@ -3472,25 +3161,6 @@ struct path_stats *make_path_stats_buffer(int num_paths) { /* }; */ /* } */ -struct hercules_stats hercules_get_stats(struct hercules_session *session, struct path_stats* path_stats) -{ - libbpf_smp_rmb(); - (void)session; - (void)path_stats; - return (struct hercules_stats){.start_time = 0}; - /* if(!session->tx_state && !session->rx_state) { */ - /* return (struct hercules_stats){ */ - /* .start_time = 0 */ - /* }; */ - /* } */ - - /* if(session->tx_state) { */ - /* return tx_stats(session->tx_state, path_stats); */ - /* } else { */ - /* return rx_stats(session->rx_state, path_stats); */ - /* } */ -} - static pthread_t start_thread(struct hercules_server *server, void *(start_routine), void *arg) { @@ -3509,296 +3179,6 @@ static void join_thread(struct hercules_server *server, pthread_t pt) } } -struct hercules_stats -hercules_tx(struct hercules_session *session, const char *filename, int offset, int length, - const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, - const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc, int xdp_mode, int num_threads) -{ - (void)session; - (void)filename; - (void)offset; - (void)length; - (void)destinations; - (void)paths_per_dest; - (void)num_dests; - (void)num_paths; - (void)max_paths; - (void)max_rate_limit; - (void)enable_pcc; - (void)xdp_mode; - (void)num_threads; - return (struct hercules_stats){}; - /* // Open mmaped send file */ -/* int f = open(filename, O_RDONLY); */ -/* if(f == -1) { */ -/* exit_with_error(session, errno); */ -/* } */ - -/* struct stat stat; */ -/* int ret = fstat(f, &stat); */ -/* if(ret) { */ -/* exit_with_error(session, errno); */ -/* } */ -/* const size_t filesize = length == -1 ? stat.st_size : length; */ -/* offset = offset < 0 ? 0 : offset; */ - -/* if(offset + filesize > (size_t)stat.st_size) { */ -/* fprintf(stderr, "ERR: offset + length > filesize. Out of bounds\n"); */ -/* exit_with_error(session, EINVAL); */ -/* } */ - -/* char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE */ -/* #ifndef NO_PRELOAD */ -/* | MAP_POPULATE */ -/* #endif */ -/* , f, offset); */ -/* if(mem == MAP_FAILED) { */ -/* fprintf(stderr, "ERR: memory mapping failed\n"); */ -/* exit_with_error(session, errno); */ -/* } */ -/* close(f); */ - -/* u32 chunklen = paths_per_dest[0].payloadlen - rbudp_headerlen; */ -/* for(int d = 0; d < num_dests; d++) { */ -/* for(int p = 0; p < num_paths[d]; p++) { */ -/* chunklen = umin32(chunklen, paths_per_dest[d * max_paths + p].payloadlen - rbudp_headerlen); */ -/* } */ -/* } */ -/* struct sender_state *tx_state = init_tx_state(session, filesize, chunklen, max_rate_limit, mem, destinations, */ -/* paths_per_dest, num_dests, num_paths, max_paths); */ -/* libbpf_smp_rmb(); */ -/* session->tx_state = tx_state; */ -/* libbpf_smp_wmb(); */ - -/* if(!tx_handshake(tx_state)) { */ -/* exit_with_error(session, ETIMEDOUT); */ -/* } */ - -/* if(enable_pcc) { */ -/* u64 now = get_nsecs(); */ -/* for(int d = 0; d < num_dests; d++) { */ -/* struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; */ -/* receiver->cc_states = init_ccontrol_state( */ -/* max_rate_limit, */ -/* tx_state->total_chunks, */ -/* *num_paths, */ -/* max_paths, */ -/* max_paths * num_dests */ -/* ); */ -/* ccontrol_update_rtt(&receiver->cc_states[0], receiver->handshake_rtt); */ -/* fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: %fs, MI: %fs\n", */ -/* d, receiver->handshake_rtt / 1e9, receiver->cc_states[0].pcc_mi_duration); */ - -/* // make sure tx_only() performs RTT estimation on every enabled path */ -/* for(u32 p = 1; p < receiver->num_paths; p++) { */ -/* receiver->paths[p].next_handshake_at = now; */ -/* } */ -/* } */ -/* } */ - -/* tx_state->rate_limit = max_rate_limit; */ - -/* // Wait for CTS from receiver */ -/* printf("Waiting for receiver to get ready..."); fflush(stdout); */ -/* if(!tx_await_cts(tx_state)) { */ -/* exit_with_error(session, ETIMEDOUT); */ -/* } */ -/* printf(" OK\n"); */ - -/* init_send_queue(tx_state->send_queue, BATCH_SIZE); */ - -/* struct tx_send_p_args *args[num_threads]; */ -/* for(int i = 0; i < session->num_ifaces; i++) { */ -/* session->ifaces[i].xsks = calloc(num_threads, sizeof(*session->ifaces[i].xsks)); */ -/* session->ifaces[i].umem = create_umem(session, i); */ -/* submit_initial_tx_frames(session, session->ifaces[i].umem); */ -/* submit_initial_rx_frames(session, session->ifaces[i].umem); */ -/* } */ - -/* pthread_t senders[num_threads]; */ -/* session->is_running = true; */ -/* for(int t = 0; t < num_threads; t++) { */ -/* args[t] = malloc(sizeof(*args[t]) + session->num_ifaces * sizeof(*args[t]->xsks)); */ -/* args[t]->tx_state = tx_state; */ -/* for(int i = 0; i < session->num_ifaces; i++) { */ -/* args[t]->xsks[i] = xsk_configure_socket(session, i, session->ifaces[i].umem, session->ifaces[i].queue, */ -/* XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, xdp_mode); */ -/* session->ifaces[i].xsks[t] = args[t]->xsks[i]; */ -/* } */ -/* senders[t] = start_thread(session, tx_send_p, args[t]); */ -/* } */ - -/* tx_state->start_time = get_nsecs(); */ -/* pthread_t worker = start_thread(session, tx_p, tx_state); */ - -/* tx_recv_control_messages(tx_state); */ - -/* tx_state->end_time = get_nsecs(); */ -/* session->is_running = false; */ -/* join_thread(session, worker); */ - -/* if(!session->is_closed) { */ -/* session->is_closed = true; */ -/* remove_xdp_program(session); */ -/* } */ -/* for(int t = 0; t < num_threads; t++) { */ -/* join_thread(session, senders[t]); */ -/* for(int i = 0; i < session->num_ifaces; i++) { */ -/* close_xsk(args[t]->xsks[i]); */ -/* } */ -/* } */ -/* for(int i = 0; i < session->num_ifaces; i++) { */ -/* destroy_umem(session->ifaces[i].umem); */ -/* } */ -/* destroy_send_queue(tx_state->send_queue); */ - -/* struct hercules_stats stats = tx_stats(tx_state, NULL); */ - -/* if(enable_pcc) { */ -/* for(int d = 0; d < num_dests; d++) { */ -/* destroy_ccontrol_state(tx_state->receiver[d].cc_states, num_paths[d]); */ -/* } */ -/* } */ -/* close(session->control_sockfd); */ -/* destroy_tx_state(tx_state); */ -/* session->tx_state = NULL; */ -/* return stats; */ -} - -struct hercules_stats hercules_rx(struct hercules_session *session, const char *filename, int xdp_mode, - bool configure_queues, int accept_timeout, int num_threads, bool is_pcc_benchmark) -{ - (void)session; - (void)filename; - (void)xdp_mode; - (void)configure_queues; - (void)accept_timeout; - (void)num_threads; - (void)is_pcc_benchmark; - return (struct hercules_stats){}; - /* struct receiver_state *rx_state = rx_accept(session, accept_timeout, is_pcc_benchmark); */ - /* if(rx_state == NULL) { */ - /* exit_with_error(session, ETIMEDOUT); */ - /* } */ - /* libbpf_smp_rmb(); */ - /* session->rx_state = rx_state; */ - /* libbpf_smp_wmb(); */ - - /* pthread_t rtt_estimator; */ - /* if(configure_queues) { */ - /* rtt_estimator = start_thread(session, rx_rtt_and_configure, rx_state); */ - /* } else { */ - /* rtt_estimator = start_thread(session, rx_get_rtt_estimate, rx_state); */ - /* } */ - /* debug_printf("Filesize %lu Bytes, %u total chunks of size %u.", */ - /* rx_state->filesize, rx_state->total_chunks, rx_state->chunklen); */ - /* printf("Preparing file for receive..."); */ - /* fflush(stdout); */ - /* rx_state->mem = rx_mmap(session, filename, rx_state->filesize); */ - /* printf(" OK\n"); */ - /* join_thread(session, rtt_estimator); */ - /* debug_printf("cts_rtt: %fs", rx_state->handshake_rtt / 1e6); */ - - /* struct rx_p_args *worker_args[num_threads]; */ - /* for(int i = 0; i < session->num_ifaces; i++) { */ - /* session->ifaces[i].xsks = calloc(num_threads, sizeof(*session->ifaces[i].xsks)); */ - /* session->ifaces[i].umem = create_umem(session, i); */ - /* submit_initial_tx_frames(session, session->ifaces[i].umem); */ - /* submit_initial_rx_frames(session, session->ifaces[i].umem); */ - /* } */ - /* for(int t = 0; t < num_threads; t++) { */ - /* worker_args[t] = malloc(sizeof(*worker_args) + session->num_ifaces * sizeof(*worker_args[t]->xsks)); */ - /* worker_args[t]->rx_state = rx_state; */ - /* for(int i = 0; i < session->num_ifaces; i++) { */ - /* worker_args[t]->xsks[i] = xsk_configure_socket(session, i, session->ifaces[i].umem, */ - /* session->ifaces[i].queue, */ - /* XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD, xdp_mode); */ - /* session->ifaces[i].xsks[t] = worker_args[t]->xsks[i]; */ - /* } */ - /* } */ - - /* load_xsk_redirect_userspace(session, worker_args, num_threads); */ - /* if(configure_queues) { */ - /* configure_rx_queues(session); */ - /* } */ - - /* rx_state->start_time = get_nsecs(); */ - /* session->is_running = true; */ - - /* pthread_t worker[num_threads]; */ - /* for(int t = 0; t < num_threads; t++) { */ - /* worker[t] = start_thread(session, rx_p, worker_args[t]); */ - /* } */ - /* debug_printf("Started worker threads"); */ - - /* rx_send_cts_ack(rx_state); // send Clear To Send ACK */ - /* pthread_t trickle_nacks = start_thread(session, rx_trickle_nacks, rx_state); */ - /* debug_printf("Started trickle NACK thread"); */ - /* rx_trickle_acks(rx_state); */ - /* rx_send_acks(rx_state); */ - - /* rx_state->end_time = get_nsecs(); */ - /* session->is_running = false; */ - - /* debug_printf("Joining threads"); */ - /* join_thread(session, trickle_nacks); */ - /* for(int q = 0; q < num_threads; q++) { */ - /* join_thread(session, worker[q]); */ - /* } */ - - /* struct hercules_stats stats = rx_stats(rx_state, NULL); */ - - /* for(int i = 0; i < session->num_ifaces; i++) { */ - /* for(int t = 0; t < num_threads; t++) { */ - /* close_xsk(worker_args[t]->xsks[i]); */ - /* } */ - /* destroy_umem(session->ifaces[i].umem); */ - /* } */ - /* if(!session->is_closed) { */ - /* session->is_closed = true; */ - /* unconfigure_rx_queues(session); */ - /* remove_xdp_program(session); */ - /* } */ - /* bitset__destroy(&rx_state->received_chunks); */ - /* close(session->control_sockfd); */ - /* return stats; */ -} - -void hercules_close(struct hercules_server *server) -{ - (void)server; - return; - /* if(!session->is_closed) { */ - /* // Only essential cleanup. */ - /* session->is_closed = true; */ - /* session->is_running = false; // stop it, if not already stopped (benchmark mode) */ - /* remove_xdp_program(session); */ - /* unconfigure_rx_queues(session); */ - /* } */ - /* if(session->rx_state) { */ - /* free(session->rx_state); */ - /* session->rx_state = NULL; */ - /* } */ - /* if(session->tx_state) { */ - /* destroy_tx_state(session->tx_state); */ - /* session->tx_state = NULL; */ - /* } */ -} - -static void socket_handler(void *arg) { - /* struct hercules_server *server = arg; */ - char buf[1024]; - while (1) { - /* memset(&buf, 0, 1024); */ - /* struct sockaddr_un client; */ - /* socklen_t clen = sizeof(client); */ - /* int n = recvfrom(usock, &buf, 1000, 0, &client, &clen); */ - /* if (n > 0) { */ - /* printf("--> [%s] %s\n", client.sun_path, buf); */ - /* sendto(usock, "OK", 3, 0, &client, sizeof(client)); */ - /* } */ - } -} static void xdp_setup(struct hercules_server *server) { for (int i = 0; i < server->num_ifaces; i++) { @@ -3877,6 +3257,11 @@ static void xdp_setup(struct hercules_server *server) { if (server->config.configure_queues){ configure_rx_queues(server); } + // TODO when/where is this needed? + // same for rx_state +/* libbpf_smp_rmb(); */ +/* session->tx_state = tx_state; */ +/* libbpf_smp_wmb(); */ debug_printf("XSK stuff complete"); } diff --git a/hercules.h b/hercules.h index 6069042..df58b1f 100644 --- a/hercules.h +++ b/hercules.h @@ -115,20 +115,13 @@ struct receiver_state; struct hercules_stats hercules_rx(struct hercules_session *session, const char *filename, int xdp_mode, bool configure_queues, int accept_timeout, int num_threads, bool is_pcc_benchmark); - -struct rx_print_args{ - struct hercules_session *session; - struct xsk_socket_info *xsk; - struct send_queue *rxq; -}; - struct hercules_server * hercules_init_server(int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, int queue, int xdp_mode, int n_threads, bool configure_queues); void hercules_main(struct hercules_server *server); void hercules_main_sender(struct hercules_server *server, int xdp_mode, const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc); -void print_rbudp_pkt(const char *pkt, bool recv); +void debug_print_rbudp_pkt(const char *pkt, bool recv); struct hercules_session *make_session(struct hercules_server *server); #endif // __HERCULES_H__ diff --git a/send_queue.h b/send_queue.h index 9b7c5d5..647715f 100644 --- a/send_queue.h +++ b/send_queue.h @@ -28,8 +28,7 @@ struct send_queue_unit { u32 chunk_idx[SEND_QUEUE_ENTRIES_PER_UNIT]; u8 rcvr[SEND_QUEUE_ENTRIES_PER_UNIT]; u8 paths[SEND_QUEUE_ENTRIES_PER_UNIT]; - u32 ts; - char a[CACHELINE_SIZE - 6 - SEND_QUEUE_ENTRIES_PER_UNIT * SEND_QUEUE_ENTRY_SIZE]; // force padding to 64 bytes + char a[CACHELINE_SIZE - SEND_QUEUE_ENTRIES_PER_UNIT * SEND_QUEUE_ENTRY_SIZE]; // force padding to 64 bytes }; _Static_assert(sizeof(struct send_queue_unit) == 64, "struct send_queue_unit should be cache line sized"); diff --git a/utils.h b/utils.h index a5a508b..9e6aac3 100644 --- a/utils.h +++ b/utils.h @@ -27,15 +27,8 @@ typedef __u8 u8; #ifndef NDEBUG #define debug_printf(fmt, ...) printf("DEBUG: %s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, ##__VA_ARGS__) -#define debug_quit(fmt, ...) \ - do { \ - printf("DEBUG: %s:%d:%s(): " fmt "\n", __FILE__, __LINE__, __func__, \ - ##__VA_ARGS__); \ - exit_with_error(NULL, 1); \ - } while (0); #else #define debug_printf(...) ; -#define debug_quit(...) ; #endif #ifndef likely From a6c38f7ad7997fbe949760945bbbe50d873a9e40 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 28 May 2024 11:48:15 +0200 Subject: [PATCH 012/112] reply to HS packets for PCC --- congestion_control.c | 8 ++++- hercules.c | 72 +++++++++++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/congestion_control.c b/congestion_control.c index 5ad1749..5ff194a 100644 --- a/congestion_control.c +++ b/congestion_control.c @@ -16,6 +16,7 @@ struct ccontrol_state * init_ccontrol_state(u32 max_rate_limit, u32 total_chunks, size_t num_paths, size_t max_paths, size_t total_num_paths) { + debug_printf("init ccontrol state"); struct ccontrol_state *cc_states = calloc(max_paths, sizeof(struct ccontrol_state)); for(size_t i = 0; i < max_paths; i++) { struct ccontrol_state *cc_state = &cc_states[i]; @@ -45,6 +46,7 @@ void ccontrol_start_monitoring_interval(struct ccontrol_state *cc_state) void ccontrol_update_rtt(struct ccontrol_state *cc_state, u64 rtt) { + debug_printf("update rtt"); cc_state->rtt = rtt / 1e9; float m = (rand() % 6) / 10.f + 1.7; // m in [1.7, 2.2] @@ -90,6 +92,7 @@ void continue_ccontrol(struct ccontrol_state *cc_state) u32 ccontrol_can_send_npkts(struct ccontrol_state *cc_state, u64 now) { if(cc_state->state == pcc_uninitialized) { + debug_printf("uninit"); cc_state->state = pcc_startup; cc_state->mi_start = get_nsecs(); cc_state->mi_end = cc_state->mi_start + cc_state->pcc_mi_duration * 1e9; @@ -101,9 +104,12 @@ u32 ccontrol_can_send_npkts(struct ccontrol_state *cc_state, u64 now) u32 tx_pps = atomic_load(&cc_state->mi_tx_npkts) * 1000000000. / dt; if(tx_pps > cc_state->curr_rate) { + debug_printf("ret 0"); return 0; } - return (cc_state->curr_rate - tx_pps) * cc_state->pcc_mi_duration; + u32 ret = (cc_state->curr_rate - tx_pps) * cc_state->pcc_mi_duration; + debug_printf("ret %u", ret); + return ret; } void kick_ccontrol(struct ccontrol_state *cc_state) diff --git a/hercules.c b/hercules.c index 28b8de6..192d1e3 100644 --- a/hercules.c +++ b/hercules.c @@ -1414,7 +1414,7 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t for (u32 p = 0; p < rcvr->num_paths; p++) { struct hercules_path *path = &rcvr->paths[p]; - debug_printf("Checking path %d/%d", p, rcvr->num_paths); + /* debug_printf("Checking path %d/%d", p, rcvr->num_paths); */ if (path->enabled) { u64 handshake_at = atomic_load(&path->next_handshake_at); if (handshake_at < now) { @@ -1625,7 +1625,7 @@ static void tx_send_p(void *arg) { } num_chunks_in_unit++; } - /* debug_printf("unit has %d chunks", num_chunks_in_unit); */ + debug_printf("unit has %d chunks", num_chunks_in_unit); u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; memset(frame_addrs, 0xFF, sizeof(frame_addrs)); for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ @@ -1667,22 +1667,29 @@ u32 compute_max_chunks_per_rcvr(struct sender_state *tx_state, u32 *max_chunks_p } // Collect path rate limits u32 compute_max_chunks_current_path(struct sender_state *tx_state) { - u32 allowed_chunks = 0; - u64 now = get_nsecs(); + u32 allowed_chunks = 0; + u64 now = get_nsecs(); - if (!tx_state->receiver[0].paths[tx_state->receiver[0].path_index].enabled) { - return 0; // if a receiver does not have any enabled paths, we can actually - // end up here ... :( - } + if (!tx_state->receiver[0] + .paths[tx_state->receiver[0].path_index] + .enabled) { + debug_printf("path not enabled"); + return 0; // if a receiver does not have any enabled paths, we can + // actually end up here ... :( + } - if (tx_state->receiver[0].cc_states != NULL) { // use PCC - struct ccontrol_state *cc_state = - &tx_state->receiver[0].cc_states[tx_state->receiver[0].path_index]; - allowed_chunks = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); - } else { // no path-based limit - allowed_chunks = BATCH_SIZE; - } - return allowed_chunks; + if (tx_state->receiver[0].cc_states != NULL) { // use PCC + debug_printf("using pcc"); + struct ccontrol_state *cc_state = + &tx_state->receiver[0].cc_states[tx_state->receiver[0].path_index]; + allowed_chunks = + umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); + } else { // no path-based limit + debug_printf("no pcc"); + allowed_chunks = BATCH_SIZE; + } + debug_printf("allowed %d chunks", allowed_chunks); + return allowed_chunks; } // exclude receivers that have completed the current iteration @@ -1714,7 +1721,7 @@ u32 shrink_sending_rates(struct sender_state *tx_state, u32 *max_chunks_per_rcvr void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { for(u32 r = 0; r < tx_state->num_receivers; r++) { - rcvr_path[r] = tx_state->receiver[r].path_index; + rcvr_path[r] = tx_state->receiver->path_index; } } @@ -1767,7 +1774,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 struct sender_state_per_receiver *rcvr = &tx_state->receiver[rcvr_idx]; u32 num_chunks_prepared = 0; u32 chunk_idx = rcvr->prev_chunk_idx; - /* debug_printf("n chunks %d", num_chunks); */ + debug_printf("n chunks %d", num_chunks); for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ chunk_idx = bitset__scan_neg(&rcvr->acked_chunks, chunk_idx); @@ -2127,6 +2134,7 @@ static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_stat static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, int ifid, const char *payload, int payloadlen) { + debug_printf("handling initial"); const int headerlen = (int)(payload - buf); if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { debug_printf("setting rx sample"); @@ -2135,7 +2143,7 @@ static void rx_handle_initial(struct hercules_server *server, struct receiver_st } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize - rx_state->cts_sent_at = get_nsecs(); + rx_state->cts_sent_at = get_nsecs(); // FIXME why is this here? } /* static void rx_get_rtt_estimate(void *arg) */ @@ -2630,13 +2638,14 @@ static void events_p(void *arg) { server->session_tx->state == SESSION_STATE_PENDING) { if(server->enable_pcc) { u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = &server->session_tx->tx_state->receiver[0]; + struct sender_state_per_receiver *receiver = server->session_tx->tx_state->receiver; + receiver->handshake_rtt = now - parsed_pkt.timestamp; receiver->cc_states = init_ccontrol_state( - server->rate_limit, + 10000, server->session_tx->tx_state->total_chunks, server->session_tx->num_paths, - server->max_paths, - server->max_paths + server->session_tx->num_paths, + server->session_tx->num_paths ); ccontrol_update_rtt(&receiver->cc_states[0], receiver->handshake_rtt); fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: %fs, MI: %fs\n", @@ -2649,8 +2658,21 @@ static void events_p(void *arg) { } server->session_tx->state = SESSION_STATE_WAIT_CTS; } + if (server->session_tx != NULL && server->session_tx->state == SESSION_STATE_RUNNING){ + u64 now = get_nsecs(); + struct sender_state_per_receiver *receiver = server->session_tx->tx_state->receiver; + ccontrol_update_rtt(&receiver->cc_states[parsed_pkt.path_index], now - parsed_pkt.timestamp); + receiver->paths[parsed_pkt.path_index].next_handshake_at = UINT64_MAX; + } break; // Make sure we don't process this further } + if (server->session_rx != NULL && server->session_rx->state == SESSION_STATE_RUNNING){ + debug_printf("possible path hs"); + if (!( parsed_pkt.flags & HANDSHAKE_FLAG_NEW_TRANSFER )){ + debug_printf("send to handle initial"); + rx_handle_initial(server, server->session_rx->rx_state, &parsed_pkt, buf, addr.sll_ifindex, rbudp_pkt + rbudp_headerlen, len); + } + } // If a transfer is already running, ignore // XXX breaks multipath, for now if (server->session_rx == NULL) { @@ -2952,7 +2974,7 @@ static void *tx_p(void *arg) { if (session_tx != NULL && atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { struct sender_state *tx_state = session_tx->tx_state; - debug_printf("Start transmit round"); + /* debug_printf("Start transmit round"); */ tx_state->prev_rate_check = get_nsecs(); pop_completion_rings(server); @@ -3001,6 +3023,7 @@ static void *tx_p(void *arg) { } } } + debug_printf("continue iwth %d chunks", num_chunks); if (num_chunks > 0) { u8 rcvr_path[tx_state->num_receivers]; @@ -3060,6 +3083,7 @@ hercules_init_server(int *ifindices, int num_ifaces, server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); server->config.local_addr = local_addr; server->config.configure_queues = configure_queues; + server->enable_pcc = true; for (int i = 0; i < num_ifaces; i++) { server->ifaces[i] = (struct hercules_interface){ From 3bc15725458766750a52babd8042e17d43fc47ab Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 28 May 2024 13:50:54 +0200 Subject: [PATCH 013/112] remove unused code --- hercules.c | 48 +----------------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/hercules.c b/hercules.c index 192d1e3..581fcd9 100644 --- a/hercules.c +++ b/hercules.c @@ -1321,30 +1321,10 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence } -static pthread_mutex_t path_lock; - -void acquire_path_lock() -{ - pthread_mutex_lock(&path_lock); -} - -void free_path_lock() -{ - pthread_mutex_unlock(&path_lock); -} - -void push_hercules_tx_paths(struct hercules_session *session) -{ - if(session->tx_state != NULL) { - debug_printf("Got new paths!"); - session->tx_state->has_new_paths = true; - } -} - +// TODO static void update_hercules_tx_paths(struct sender_state *tx_state) { return; // FIXME HACK - acquire_path_lock(); tx_state->has_new_paths = false; u64 now = get_nsecs(); for(u32 r = 0; r < tx_state->num_receivers; r++) { @@ -1405,7 +1385,6 @@ static void update_hercules_tx_paths(struct sender_state *tx_state) } } } - free_path_lock(); } void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { @@ -2025,13 +2004,6 @@ static char *rx_mmap(struct hercules_server *server, const char *pathname, size_ exit_with_error(server, errno); } close(f); - // fault and dirty the pages - // This may be a terrible idea if filesize is larger than the available memory. - // Note: MAP_POPULATE does NOT help when preparing for _writing_. - /*int pagesize = getpagesize(); - for(ssize_t i = (ssize_t)filesize - 1; i > 0; i -= pagesize) { - mem[i] = 0; - }*/ return mem; } @@ -2237,14 +2209,6 @@ static int unconfigure_rx_queues(struct hercules_server *server) return error; } -static void rx_rtt_and_configure(void *arg) -{ - (void)arg; - /* struct receiver_state *rx_state = arg; */ - /* rx_get_rtt_estimate(arg); */ - // as soon as we got the RTT estimate, we are ready to set up the queues - /* configure_rx_queues(rx_state->session); */ -} static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) { @@ -2838,16 +2802,6 @@ static struct receiver_state *rx_receive_hs(struct hercules_server *server, return NULL; } -// Helper function: open a AF_PACKET socket. -// @returns -1 on error -static int open_control_socket() -{ - int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); - if(sockfd == -1) { - return -1; - } - return sockfd; -} static int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj) { From 11c3f65cbb41738aa974e5f1c2f870c5ac50f2b1 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 28 May 2024 17:02:37 +0200 Subject: [PATCH 014/112] move xdp setup to separate file --- bitset.h | 2 +- bpf_prgm/redirect_userspace.c | 1 - congestion_control.h | 1 + frame_queue.h | 2 + hercules.c | 2779 ++++++++++++--------------------- hercules.h | 287 +++- packet.h | 54 +- 7 files changed, 1246 insertions(+), 1880 deletions(-) diff --git a/bitset.h b/bitset.h index f44e870..cda6ac5 100644 --- a/bitset.h +++ b/bitset.h @@ -15,9 +15,9 @@ #ifndef __HERCULES_BITSET_H__ #define __HERCULES_BITSET_H__ -#include "hercules.h" #include "utils.h" #include +#include #include #include #include diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index 01db42a..ba18436 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -9,7 +9,6 @@ #include #include #include "packet.h" -#include "hercules.h" #include diff --git a/congestion_control.h b/congestion_control.h index af65842..408b536 100644 --- a/congestion_control.h +++ b/congestion_control.h @@ -3,6 +3,7 @@ #include "bitset.h" #include "hercules.h" +#include "utils.h" #include #define RCTS_INTERVALS 4 // Must be even diff --git a/frame_queue.h b/frame_queue.h index 0d2f573..7d70ca7 100644 --- a/frame_queue.h +++ b/frame_queue.h @@ -15,6 +15,8 @@ #ifndef HERCULES_FRAME_QUEUE_H #define HERCULES_FRAME_QUEUE_H +#include +#include #include "utils.h" struct frame_queue { diff --git a/hercules.c b/hercules.c index 581fcd9..7e6b7fc 100644 --- a/hercules.c +++ b/hercules.c @@ -5,6 +5,8 @@ // Enable extra warnings; cannot be enabled in CFLAGS because cgo generates a // ton of warnings that can apparantly not be suppressed. #pragma GCC diagnostic warning "-Wextra" +/* #pragma GCC diagnostic warning "-Wunused" */ +/* #pragma GCC diagnostic warning "-Wpedantic" */ #include "hercules.h" #include "packet.h" @@ -52,16 +54,14 @@ #include "send_queue.h" #include "bpf_prgms.h" #include "monitor.h" +#include "xdp.h" #define MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE 128 // E.g., SCION SPAO header added by LightningFilter #define L4_SCMP 1 -#define NUM_FRAMES (4 * 1024) -#define BATCH_SIZE 64 #define RANDOMIZE_FLOWID -//#define NO_PRELOAD #define RATE_LIMIT_CHECK 1000 // check rate limit every X packets // Maximum burst above target pps allowed @@ -78,194 +78,22 @@ static const u64 tx_handshake_timeout = 5e9; int usock; pthread_spinlock_t usock_lock; -// exported from hercules.go -extern int HerculesGetReplyPath(const char *packetPtr, int length, struct hercules_path *reply_path); +// Fill packet with n bytes from data and pad with zeros to payloadlen. +static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, + sequence_number seqnr, const char *data, size_t n, + size_t payloadlen); -struct xsk_umem_info { - struct xsk_ring_prod fq; - struct xsk_ring_cons cq; - struct frame_queue available_frames; - pthread_spinlock_t lock; - struct xsk_umem *umem; - void *buffer; - struct hercules_interface *iface; -}; - -struct xsk_socket_info { - struct xsk_ring_cons rx; - struct xsk_ring_prod tx; - struct xsk_umem_info *umem; - struct xsk_socket *xsk; -}; - -struct receiver_state_per_path { - struct bitset seq_rcvd; - sequence_number nack_end; - sequence_number prev_nack_end; - u64 rx_npkts; -}; - -struct hercules_interface { - char ifname[IFNAMSIZ]; - int ifid; - int queue; - u32 prog_id; - int ethtool_rule; - u32 num_sockets; - struct xsk_umem_info *umem; - struct xsk_socket_info **xsks; -}; - -struct hercules_config { - u32 xdp_flags; - int xdp_mode; - int queue; - bool configure_queues; - struct hercules_app_addr local_addr; -}; - -// Some states are used only by the TX/RX side and are marked accordingly -enum session_state { - SESSION_STATE_NONE, - SESSION_STATE_PENDING, //< (TX) Need to send HS and repeat until TO, waiting - // for a reflected HS packet - SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and - // CTS - SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS - SESSION_STATE_RUNNING, //< Transfer in progress - SESSION_STATE_DONE, //< Transfer done (or cancelled with error) -}; - -enum session_error { - SESSION_ERROR_OK, //< No error, transfer completed successfully - SESSION_ERROR_TIMEOUT, //< Session timed out - SESSION_ERROR_PCC, -}; - -struct hercules_session { - struct receiver_state *rx_state; - struct sender_state *tx_state; - enum session_state state; - enum session_error error; - struct send_queue *send_queue; - u64 last_pkt_sent; - u64 last_pkt_rcvd; - - struct hercules_app_addr destination; - int num_paths; - struct hercules_path *paths_to_dest; - // State for stat dump - /* size_t rx_npkts; */ - /* size_t tx_npkts; */ -}; - -struct hercules_server { - struct hercules_config config; - int control_sockfd; // AF_PACKET socket used for control traffic - int n_threads; - struct rx_p_args **worker_args; - struct hercules_session *session_tx; //Current TX session - u64 session_tx_counter; - struct hercules_session *session_rx; //Current RX session - int max_paths; - int rate_limit; - bool enable_pcc; - int *ifindices; - int num_ifaces; - struct hercules_interface ifaces[]; -}; - -struct receiver_state { - struct hercules_session *session; - atomic_uint_least64_t handshake_rtt; - /** Filesize in bytes */ - size_t filesize; - /** Size of file data (in byte) per packet */ - u32 chunklen; - /** Number of packets that will make up the entire file. Equal to `ceil(filesize/chunklen)` */ - u32 total_chunks; - /** Memory mapped file for receive */ - char *mem; - /** Packet size */ - u32 etherlen; - - struct bitset received_chunks; - - // XXX: reads/writes to this is are a huge data race. Need to sync. - char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; - int rx_sample_len; - int rx_sample_ifid; - struct hercules_path reply_path; - - // Start/end time of the current transfer - u64 start_time; - u64 end_time; - u64 cts_sent_at; - u64 last_pkt_rcvd; // Timeout detection - - u8 num_tracked_paths; - bool is_pcc_benchmark; - struct receiver_state_per_path path_state[256]; -}; - -struct sender_state_per_receiver { - u64 prev_round_start; - u64 prev_round_end; - u64 prev_slope; - u64 ack_wait_duration; - u32 prev_chunk_idx; - bool finished; - /** Next batch should be sent via this path */ - u8 path_index; - - struct bitset acked_chunks; - atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns - - u32 num_paths; - u32 return_path_idx; - struct hercules_app_addr addr; - struct hercules_path *paths; - struct ccontrol_state *cc_states; - bool cts_received; -}; - -struct sender_state { - struct hercules_session *session; - - // State for transmit rate control - size_t tx_npkts_queued; - u64 prev_rate_check; - size_t prev_tx_npkts_queued; - - /** Filesize in bytes */ - size_t filesize; - char filename[100]; - /** Size of file data (in byte) per packet */ - u32 chunklen; - /** Number of packets that will make up the entire file. Equal to `ceil(filesize/chunklen)` */ - u32 total_chunks; - /** Memory mapped file for receive */ - char *mem; - - _Atomic u32 rate_limit; - - // Start/end time of the current transfer - u64 start_time; - u64 end_time; +// Update header checksum according to packet contents +static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt); - u32 num_receivers; - struct sender_state_per_receiver *receiver; - u32 max_paths_per_rcvr; +void debug_print_rbudp_pkt(const char *pkt, bool recv); - // shared with Go - struct hercules_path *shd_paths; - const int *shd_num_paths; +static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt); - atomic_bool has_new_paths; -}; +static struct hercules_session *make_session(struct hercules_server *server); -typedef int xskmap; +/// COMMON /** * @param scionaddrhdr @@ -284,54 +112,6 @@ static u32 rcvr_by_src_address(struct sender_state *tx_state, const struct scion return r; } -static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence_number seqnr, const char *data, - size_t n, size_t payloadlen); - -static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt); - -static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt); - -static bool rx_received_all(const struct receiver_state *rx_state) -{ - return (rx_state->received_chunks.num_set == rx_state->total_chunks); -} - -static bool tx_acked_all(const struct sender_state *tx_state) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(tx_state->receiver[r].acked_chunks.num_set != tx_state->total_chunks) { - return false; - } - } - return true; -} - -static void set_rx_sample(struct receiver_state *rx_state, int ifid, const char *pkt, int len) -{ - rx_state->rx_sample_len = len; - rx_state->rx_sample_ifid = ifid; - memcpy(rx_state->rx_sample_buf, pkt, len); -} - -static void remove_xdp_program(struct hercules_server *server) -{ - for(int i = 0; i < server->num_ifaces; i++) { - u32 curr_prog_id = 0; - if(bpf_get_link_xdp_id(server->ifaces[i].ifid, &curr_prog_id, server->config.xdp_flags)) { - printf("bpf_get_link_xdp_id failed\n"); - exit(EXIT_FAILURE); - } - if(server->ifaces[i].prog_id == curr_prog_id) - bpf_set_link_xdp_fd(server->ifaces[i].ifid, -1, server->config.xdp_flags); - else if(!curr_prog_id) - printf("couldn't find a prog id on a given interface\n"); - else - printf("program on interface changed, not removing\n"); - } -} - -static int unconfigure_rx_queues(struct hercules_server *server); - static void __exit_with_error(struct hercules_server *server, int error, const char *file, const char *func, int line) { fprintf(stderr, "%s:%s:%i: errno: %d/\"%s\"\n", file, func, line, error, strerror(error)); @@ -344,13 +124,6 @@ static void __exit_with_error(struct hercules_server *server, int error, const c #define exit_with_error(server, error) __exit_with_error(server, error, __FILE__, __func__, __LINE__) -static void close_xsk(struct xsk_socket_info *xsk) -{ - // Removes socket and frees xsk - xsk_socket__delete(xsk->xsk); - free(xsk); -} - static inline struct hercules_interface *get_interface_by_id(struct hercules_server *server, int ifid) { for(int i = 0; i < server->num_ifaces; i++) { @@ -361,11 +134,157 @@ static inline struct hercules_interface *get_interface_by_id(struct hercules_ser return NULL; } +// Mark the session as done and store why it was stopped. static inline void quit_session(struct hercules_session *s, enum session_error err){ s->error = err; s->state = SESSION_STATE_DONE; } +static u32 ack__max_num_entries(u32 len) +{ + struct rbudp_ack_pkt ack; // dummy declval + return umin32(UINT8_MAX - 1, (len - sizeof(ack.num_acks) - sizeof(ack.ack_nr) - sizeof(ack.max_seq) - sizeof(ack.timestamp)) / sizeof(ack.acks[0])); +} + +static u32 ack__len(const struct rbudp_ack_pkt *ack) +{ + return sizeof(ack->num_acks) + sizeof(ack->ack_nr) + sizeof(ack->max_seq) + sizeof(ack->timestamp) + ack->num_acks * sizeof(ack->acks[0]); +} + +// Send the packet pointed to by *buf via the server's control socket. +// Used for transmitting control packets. +static void send_eth_frame(struct hercules_server *server, const struct hercules_path *path, void *buf) +{ + struct sockaddr_ll addr; + // Index of the network device + addr.sll_ifindex = path->ifid; + // Address length + addr.sll_halen = ETH_ALEN; + // Destination MAC; extracted from ethernet header + memcpy(addr.sll_addr, buf, ETH_ALEN); + + ssize_t ret = sendto(server->control_sockfd, buf, path->framelen, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_ll)); + if(ret == -1) { + exit_with_error(server, errno); + } +} + +#define DEBUG_PRINT_PKTS +#ifdef DEBUG_PRINT_PKTS +// recv indicates whether printed packets should be prefixed with TX or RX +void debug_print_rbudp_pkt(const char *pkt, bool recv) { + struct hercules_header *h = (struct hercules_header *)pkt; + const char *prefix = (recv) ? "RX->" : "<-TX"; + printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, h->path, + h->seqno); + if (h->chunk_idx == UINT_MAX) { + // Control packets + const char *pl = pkt + 9; + struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; + switch (cp->type) { + case CONTROL_PACKET_TYPE_INITIAL: + printf("%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx %u, Flags " + "0x%x, Name length %u [%s]\n", prefix, + cp->payload.initial.filesize, cp->payload.initial.chunklen, + cp->payload.initial.timestamp, cp->payload.initial.path_index, + cp->payload.initial.flags, cp->payload.initial.name_len, cp->payload.initial.name); + break; + case CONTROL_PACKET_TYPE_ACK: + printf("%s ACK ", prefix); + for (int r = 0; r < cp->payload.ack.num_acks; r++){ + printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); + } + printf("\n"); + break; + case CONTROL_PACKET_TYPE_NACK: + printf("%s NACK \n", prefix); + for (int r = 0; r < cp->payload.ack.num_acks; r++){ + printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); + } + printf("\n"); + break; + default: + printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); + break; + } + } else { + printf("%s ** PAYLOAD **\n", prefix); + } +} +#else +void debug_print_rbudp_pkt(const char * pkt, bool recv){ + (void)pkt; + (void)recv; + return; +} +#endif + +// Initialise a new session +static struct hercules_session *make_session(struct hercules_server *server) { + struct hercules_session *s; + s = calloc(1, sizeof(*s)); + assert(s); + s->state = SESSION_STATE_NONE; + int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, + sizeof(*s->send_queue)); + if (err != 0) { + exit_with_error(NULL, err); + } + init_send_queue(s->send_queue, BATCH_SIZE); + s->last_pkt_sent = 0; + s->last_pkt_rcvd = + get_nsecs(); // Set this to "now" to allow timing out HS at sender + // (when no packet was received yet), once packets are + // received it will be updated accordingly + + return s; +} + +// Initialise the Hercules server +struct hercules_server * +hercules_init_server(int *ifindices, int num_ifaces, + const struct hercules_app_addr local_addr, int queue, + int xdp_mode, int n_threads, bool configure_queues) { + struct hercules_server *server; + server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); + if (server == NULL) { + exit_with_error(NULL, ENOMEM); + } + debug_printf("local address %llx %x %x", local_addr.ia, local_addr.ip, + local_addr.port); + server->ifindices = ifindices; + server->num_ifaces = num_ifaces; + server->config.queue = queue; + server->n_threads = n_threads; + server->session_rx = NULL; + server->session_tx = NULL; + server->session_tx_counter = 0; + server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); + server->config.local_addr = local_addr; + server->config.configure_queues = configure_queues; + server->enable_pcc = false; + + for (int i = 0; i < num_ifaces; i++) { + server->ifaces[i] = (struct hercules_interface){ + .queue = queue, + .ifid = ifindices[i], + .ethtool_rule = -1, + }; + if_indextoname(ifindices[i], server->ifaces[i].ifname); + debug_printf("using queue %d on interface %s", server->ifaces[i].queue, + server->ifaces[i].ifname); + } + + pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); + + server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (server->control_sockfd == -1) { + exit_with_error(server, 0); + } + debug_printf("init complete"); + return server; +} +/// PACKET PARSING // XXX: from lib/scion/udp.c /* * Calculate UDP checksum @@ -595,6 +514,62 @@ static const char *parse_pkt(const struct hercules_server *server, const char *p return parse_pkt_fast_path(pkt, length, check, offset); } +static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) +{ + chk_input chk_input_s; + chk_input *chksum_struc = init_chk_input(&chk_input_s, 2); + assert(chksum_struc); + char *payload = pkt + path->headerlen; + precomputed_checksum = ~precomputed_checksum; // take one complement of precomputed checksum + chk_add_chunk(chksum_struc, (u8 *)&precomputed_checksum, 2); // add precomputed header checksum + chk_add_chunk(chksum_struc, (u8 *)payload, path->payloadlen); // add payload + u16 pkt_checksum = checksum(chksum_struc); + + mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); +} +// Fill packet with n bytes from data and pad with zeros to payloadlen. +static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, + sequence_number seqnr, const char *data, size_t n, + size_t payloadlen) { + void *rbudp_path_idx = mempcpy(rbudp_pkt, &chunk_idx, sizeof(chunk_idx)); + void *rbudp_seqnr = mempcpy(rbudp_path_idx, &path_idx, sizeof(path_idx)); + void *rbudp_payload = mempcpy(rbudp_seqnr, &seqnr, sizeof(seqnr)); + void *start_pad = mempcpy(rbudp_payload, data, n); + if (sizeof(chunk_idx) + sizeof(path_idx) + n < payloadlen) { + memset(start_pad, 0, + payloadlen - sizeof(chunk_idx) - sizeof(path_idx) - n); + } + debug_print_rbudp_pkt(rbudp_pkt, false); +} +static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt) +{ + struct hercules_control_packet control_pkt; + memcpy(&control_pkt, pkt, umin32(sizeof(control_pkt), len)); + if(control_pkt.type != CONTROL_PACKET_TYPE_INITIAL) { + debug_printf("Packet type not INITIAL"); + return false; + } + if(len < sizeof(control_pkt.type) + sizeof(*parsed_pkt)) { + debug_printf("Packet too short"); + return false; + } + memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt) + control_pkt.payload.initial.name_len); + return true; +} +/// RECEIVER + +static bool rx_received_all(const struct receiver_state *rx_state) +{ + return (rx_state->received_chunks.num_set == rx_state->total_chunks); +} + +static void set_rx_sample(struct receiver_state *rx_state, int ifid, const char *pkt, int len) +{ + rx_state->rx_sample_len = len; + rx_state->rx_sample_ifid = ifid; + memcpy(rx_state->rx_sample_buf, pkt, len); +} + static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { if(length < rbudp_headerlen + rx_state->chunklen) { @@ -662,207 +637,7 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p return true; } - -static struct xsk_umem_info *xsk_configure_umem(struct hercules_server *server, u32 ifidx, void *buffer, u64 size) -{ - debug_printf("xsk_configure_umem"); - struct xsk_umem_info *umem; - int ret; - - umem = calloc(1, sizeof(*umem)); - if(!umem) - exit_with_error(server, errno); - - ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, - NULL); - if(ret) - exit_with_error(server, -ret); - - umem->buffer = buffer; - umem->iface = &server->ifaces[ifidx]; - // The number of slots in the umem->available_frames queue needs to be larger than the number of frames in the loop, - // pushed in submit_initial_tx_frames() (assumption in pop_completion_ring() and handle_send_queue_unit()) - ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS*2); - if(ret) - exit_with_error(server, ret); - pthread_spin_init(&umem->lock, 0); - return umem; -} - -static struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, u32 ifidx, void *buffer, u64 size) -{ - struct xsk_umem_info *umem; - int ret; - - umem = calloc(1, sizeof(*umem)); - if(!umem) - exit_with_error(server, errno); - - ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, - NULL); - if(ret) - exit_with_error(server, -ret); - - umem->buffer = buffer; - umem->iface = &server->ifaces[ifidx]; - // The number of slots in the umem->available_frames queue needs to be larger than the number of frames in the loop, - // pushed in submit_initial_tx_frames() (assumption in pop_completion_ring() and handle_send_queue_unit()) - ret = frame_queue__init(&umem->available_frames, XSK_RING_PROD__DEFAULT_NUM_DESCS); - if(ret) - exit_with_error(server, ret); - pthread_spin_init(&umem->lock, 0); - return umem; -} - -static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) -{ - int ret; - do { - ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); - } while(ret < 0 && errno == EAGAIN); - - if(ret < 0 && errno != ENOBUFS && errno != EBUSY) { - exit_with_error(server, errno); - } -} - -static void kick_all_tx(struct hercules_server *server, struct hercules_interface *iface) -{ - for(u32 s = 0; s < iface->num_sockets; s++) { - kick_tx(server, iface->xsks[s]); - } -} -static void kick_tx_server(struct hercules_server *server){ - - for (int i = 0; i < server->num_ifaces; i++){ - kick_all_tx(server, &server->ifaces[i]); - } -} - -static void submit_initial_rx_frames(struct hercules_server *server, struct xsk_umem_info *umem) -{ - int initial_kernel_rx_frame_count = XSK_RING_PROD__DEFAULT_NUM_DESCS - BATCH_SIZE; - u32 idx; - int ret = xsk_ring_prod__reserve(&umem->fq, - initial_kernel_rx_frame_count, - &idx); - if(ret != initial_kernel_rx_frame_count) - exit_with_error(server, -ret); - for(int i = 0; i < initial_kernel_rx_frame_count; i++) - *xsk_ring_prod__fill_addr(&umem->fq, idx++) = - (XSK_RING_PROD__DEFAULT_NUM_DESCS + i) * XSK_UMEM__DEFAULT_FRAME_SIZE; - xsk_ring_prod__submit(&umem->fq, initial_kernel_rx_frame_count); -} - -static void submit_initial_tx_frames(struct hercules_server *server, struct xsk_umem_info *umem) -{ - // This number needs to be smaller than the number of slots in the umem->available_frames queue (initialized in - // xsk_configure_umem(); assumption in pop_completion_ring() and handle_send_queue_unit()) - int initial_tx_frames = XSK_RING_PROD__DEFAULT_NUM_DESCS - BATCH_SIZE; - int avail = frame_queue__prod_reserve(&umem->available_frames, initial_tx_frames); - if(initial_tx_frames > avail) { - debug_printf("trying to push %d initial frames, but only %d slots available", initial_tx_frames, avail); - exit_with_error(server, EINVAL); - } - for(int i = 0; i < avail; i++) { - frame_queue__prod_fill(&umem->available_frames, i, i * XSK_UMEM__DEFAULT_FRAME_SIZE); - } - frame_queue__push(&umem->available_frames, avail); -} - -static struct xsk_socket_info *xsk_configure_socket(struct hercules_server *server, int ifidx, - struct xsk_umem_info *umem, int queue, int libbpf_flags, - int bind_flags) -{ - struct xsk_socket_config cfg; - struct xsk_socket_info *xsk; - int ret; - - if(server->ifaces[ifidx].ifid != umem->iface->ifid) { - debug_printf("cannot configure XSK on interface %d with queue on interface %d", server->ifaces[ifidx].ifid, umem->iface->ifid); - exit_with_error(server, EINVAL); - } - - xsk = calloc(1, sizeof(*xsk)); - if(!xsk) - exit_with_error(server, errno); - - xsk->umem = umem; - cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; - cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; - cfg.libbpf_flags = libbpf_flags; - cfg.xdp_flags = server->config.xdp_flags; - cfg.bind_flags = bind_flags; - ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[ifidx].ifname, queue, umem->umem, &xsk->rx, &xsk->tx, - &umem->fq, &umem->cq, &cfg); - if(ret) - exit_with_error(server, -ret); - - ret = bpf_get_link_xdp_id(server->ifaces[ifidx].ifid, &server->ifaces[ifidx].prog_id, server->config.xdp_flags); - if(ret) - exit_with_error(server, -ret); - return xsk; -} - -static struct xsk_umem_info *create_umem(struct hercules_server *server, u32 ifidx) -{ - void *bufs; - int ret = posix_memalign(&bufs, getpagesize(), /* PAGE_SIZE aligned */ - NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); - if(ret) - exit_with_error(server, ret); - - struct xsk_umem_info *umem; - umem = xsk_configure_umem(server, ifidx, bufs, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); - return umem; -} - -static void destroy_umem(struct xsk_umem_info *umem) -{ - xsk_umem__delete(umem->umem); - free(umem->buffer); - free(umem); -} - -// Pop entries from completion ring and store them in umem->available_frames. -static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_info *umem) -{ - u32 idx; - size_t entries = xsk_ring_cons__peek(&umem->cq, SIZE_MAX, &idx); - if(entries > 0) { - u16 num = frame_queue__prod_reserve(&umem->available_frames, entries); - if(num < entries) { // there are less frames in the loop than the number of slots in frame_queue - debug_printf("trying to push %ld frames, only got %d slots in frame_queue", entries, num); - exit_with_error(server, EINVAL); - } - for(u16 i = 0; i < num; i++) { - frame_queue__prod_fill(&umem->available_frames, i, *xsk_ring_cons__comp_addr(&umem->cq, idx + i)); - } - frame_queue__push(&umem->available_frames, num); - xsk_ring_cons__release(&umem->cq, entries); - /* atomic_fetch_add(&server->tx_npkts, entries); */ - } -} - -static inline void pop_completion_rings(struct hercules_server *server) -{ - for(int i = 0; i < server->num_ifaces; i++) { - pop_completion_ring(server, server->ifaces[i].umem); - } -} - -static u32 ack__max_num_entries(u32 len) -{ - struct rbudp_ack_pkt ack; // dummy declval - return umin32(UINT8_MAX - 1, (len - sizeof(ack.num_acks) - sizeof(ack.ack_nr) - sizeof(ack.max_seq) - sizeof(ack.timestamp)) / sizeof(ack.acks[0])); -} - -static u32 ack__len(const struct rbudp_ack_pkt *ack) -{ - return sizeof(ack->num_acks) + sizeof(ack->ack_nr) + sizeof(ack->max_seq) + sizeof(ack->timestamp) + ack->num_acks * sizeof(ack->acks[0]); -} - -static u32 fill_ack_pkt(struct receiver_state *rx_state, u32 first, struct rbudp_ack_pkt *ack, size_t max_num_acks) +static u32 fill_ack_pkt(struct receiver_state *rx_state, u32 first, struct rbudp_ack_pkt *ack, size_t max_num_acks) { size_t e = 0; u32 curr = first; @@ -901,7 +676,6 @@ fill_nack_pkt(sequence_number first, struct rbudp_ack_pkt *ack, size_t max_num_a ack->num_acks = e; return curr; } - static bool has_more_nacks(sequence_number curr, struct bitset *seqs) { u32 begin = bitset__scan_neg(seqs, curr); @@ -909,348 +683,46 @@ static bool has_more_nacks(sequence_number curr, struct bitset *seqs) return end < seqs->num; } -static void send_eth_frame(struct hercules_server *server, const struct hercules_path *path, void *buf) -{ - struct sockaddr_ll addr; - // Index of the network device - addr.sll_ifindex = path->ifid; - // Address length - addr.sll_halen = ETH_ALEN; - // Destination MAC; extracted from ethernet header - memcpy(addr.sll_addr, buf, ETH_ALEN); - - ssize_t ret = sendto(server->control_sockfd, buf, path->framelen, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_ll)); - if(ret == -1) { - exit_with_error(server, errno); - } -} +static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, + int ifid, const char *payload, int payloadlen); -static void tx_register_acks(const struct rbudp_ack_pkt *ack, struct sender_state_per_receiver *rcvr) +static void +submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames) { - for(uint16_t e = 0; e < ack->num_acks; ++e) { - const u32 begin = ack->acks[e].begin; - const u32 end = ack->acks[e].end; - if(begin >= end || end > rcvr->acked_chunks.num) { - return; // Abort - } - for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized - bitset__set(&rcvr->acked_chunks, i); // don't need thread-safety here, all updates in same thread + u32 idx_fq = 0; + pthread_spin_lock(&umem->lock); + size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); + while(reserved != num_frames) { + reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); + if(session->state != SESSION_STATE_RUNNING) { + pthread_spin_unlock(&umem->lock); + return; } } -} - -#define NACK_TRACE_SIZE (1024*1024) -static u32 nack_trace_count = 0; -static struct { - long long sender_timestamp; - long long receiver_timestamp; - u32 nr; -} nack_trace[NACK_TRACE_SIZE]; - -static void nack_trace_push(u64 timestamp, u32 nr) { - return; - u32 idx = atomic_fetch_add(&nack_trace_count, 1); - if(idx >= NACK_TRACE_SIZE) { - fprintf(stderr, "oops: nack trace too small, trying to push #%d\n", idx); - exit(133); - } - nack_trace[idx].sender_timestamp = timestamp; - nack_trace[idx].receiver_timestamp = get_nsecs(); - nack_trace[idx].nr = nr; -} - -#define PCC_TRACE_SIZE (1024*1024) -static u32 pcc_trace_count = 0; -static struct { - u64 time; - sequence_number range_start, range_end, mi_min, mi_max; - u32 excess; - float loss; - u32 delta_left, delta_right, nnacks, nack_pkts; - enum pcc_state state; - u32 target_rate, actual_rate; - double target_duration, actual_duration; -} pcc_trace[PCC_TRACE_SIZE]; -static void pcc_trace_push(u64 time, sequence_number range_start, sequence_number range_end, sequence_number mi_min, - sequence_number mi_max, u32 excess, float loss, u32 delta_left, u32 delta_right, u32 nnacks, u32 nack_pkts, - enum pcc_state state, u32 target_rate, u32 actual_rate, double target_duration, double actual_duration) { - u32 idx = atomic_fetch_add(&pcc_trace_count, 1); - if(idx >= PCC_TRACE_SIZE) { - fprintf(stderr, "oops: pcc trace too small, trying to push #%d\n", idx); - exit(133); + for(size_t i = 0; i < num_frames; i++) { + *xsk_ring_prod__fill_addr(&umem->fq, idx_fq++) = addrs[i]; } - pcc_trace[idx].time = time; - pcc_trace[idx].range_start = range_start; - pcc_trace[idx].range_end = range_end; - pcc_trace[idx].mi_min = mi_min; - pcc_trace[idx].mi_max = mi_max; - pcc_trace[idx].excess = excess; - pcc_trace[idx].loss = loss; - pcc_trace[idx].delta_left = delta_left; - pcc_trace[idx].delta_right = delta_right; - pcc_trace[idx].nnacks = nnacks; - pcc_trace[idx].nack_pkts = nack_pkts; - pcc_trace[idx].state = state; - pcc_trace[idx].target_rate = target_rate; - pcc_trace[idx].actual_rate = actual_rate; - pcc_trace[idx].target_duration = target_duration; - pcc_trace[idx].actual_duration = actual_duration; + xsk_ring_prod__submit(&umem->fq, num_frames); + pthread_spin_unlock(&umem->lock); } -static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_state *cc_state) +static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_info *xsk) { - pthread_spin_lock(&cc_state->lock); - atomic_store(&cc_state->mi_seq_max, umax32(atomic_load(&cc_state->mi_seq_max), nack->max_seq)); - cc_state->num_nack_pkts++; - u32 counted = 0; - for(uint16_t e = 0; e < nack->num_acks; ++e) { - u32 begin = nack->acks[e].begin; - u32 end = nack->acks[e].end; - cc_state->mi_seq_min = umin32(cc_state->mi_seq_min, begin); - atomic_store(&cc_state->mi_seq_max_rcvd, umax32(atomic_load(&cc_state->mi_seq_max_rcvd), end)); - begin = umax32(begin, cc_state->mi_seq_start); - u32 seq_end = atomic_load(&cc_state->mi_seq_end); - if(seq_end != 0) { - end = umin32(end, seq_end); - } - if(begin >= end) { - continue; - } - counted += end - begin; - cc_state->num_nacks += end - begin; - begin -= cc_state->mi_seq_start; - end -= cc_state->mi_seq_start; - if(end >= cc_state->mi_nacked.num) { - fprintf(stderr, "Cannot track NACK! Out of range: nack end = %d >= bitset size %d\n", end, cc_state->mi_nacked.num); - } - end = umin32(end, cc_state->mi_nacked.num); - for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized - bitset__set(&cc_state->mi_nacked, i); // don't need thread-safety here, all updates in same thread - } + /* debug_printf("receive batch"); */ + u32 idx_rx = 0; + int ignored = 0; + + size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); + if(!rcvd){ + return; } - pthread_spin_unlock(&cc_state->lock); -} -static bool pcc_mi_elapsed(struct ccontrol_state *cc_state) -{ - if(cc_state->state == pcc_uninitialized) { - return false; - } - unsigned long now = get_nsecs(); - sequence_number cur_seq = atomic_load(&cc_state->last_seqnr) - 1; - sequence_number seq_rcvd = atomic_load(&cc_state->mi_seq_max); - - if (cc_state->mi_end <= now) { - if (cc_state->mi_seq_end == 0) { - cc_state->mi_end = now; - cc_state->mi_seq_end = cur_seq; - } - if(cc_state->mi_seq_end != 0 && - (cc_state->mi_seq_end < seq_rcvd || now > cc_state->mi_end + (unsigned long)(1.5e9 * cc_state->rtt))) { - return true; - } - } - return false; -} - -static void pcc_monitor(struct sender_state *tx_state) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - for(u32 cur_path = 0; cur_path < tx_state->receiver[r].num_paths; cur_path++) { - struct ccontrol_state *cc_state = &tx_state->receiver[r].cc_states[cur_path]; - pthread_spin_lock(&cc_state->lock); - if(pcc_mi_elapsed(cc_state)) { - u64 now = get_nsecs(); - if(cc_state->mi_end == 0) { // TODO should not be necessary - fprintf(stderr, "Assumption violated.\n"); - quit_session(tx_state->session, SESSION_ERROR_PCC); - cc_state->mi_end = now; - } - u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI - - u32 excess = 0; - if (cc_state->curr_rate * cc_state->pcc_mi_duration > throughput) { - excess = cc_state->curr_rate * cc_state->pcc_mi_duration - throughput; - } - u32 lost_npkts = atomic_load(&cc_state->mi_nacked.num_set); - // account for packets that are "stuck in queue" - if(cc_state->mi_seq_end > cc_state->mi_seq_max) { - lost_npkts += cc_state->mi_seq_end - cc_state->mi_seq_max; - } - lost_npkts = umin32(lost_npkts, throughput); - float loss = (float)(lost_npkts + excess) / (throughput + excess); - sequence_number start = cc_state->mi_seq_start; - sequence_number end = cc_state->mi_seq_end; - sequence_number mi_min = cc_state->mi_seq_min; - sequence_number mi_max = cc_state->mi_seq_max; - sequence_number delta_left = cc_state->mi_seq_start - cc_state->mi_seq_min; - sequence_number delta_right = cc_state->mi_seq_max - cc_state->mi_seq_end; - u32 nnacks = cc_state->num_nacks; - u32 nack_pkts = cc_state->num_nack_pkts; - enum pcc_state state = cc_state->state; - double actual_duration = (double)(cc_state->mi_end - cc_state->mi_start) / 1e9; - - pcc_trace_push(now, start, end, mi_min, mi_max, excess, loss, delta_left, delta_right, nnacks, nack_pkts, state, - cc_state->curr_rate * cc_state->pcc_mi_duration, throughput, cc_state->pcc_mi_duration, actual_duration); - - if(cc_state->num_nack_pkts != 0) { // skip PCC control if no NACKs received - if(cc_state->ignored_first_mi) { // first MI after booting will only contain partial feedback, skip it as well - pcc_control(cc_state, throughput, loss); - } - cc_state->ignored_first_mi = true; - } - - // TODO move the neccessary ones to cc_start_mi below - cc_state->mi_seq_min = UINT32_MAX; - cc_state->mi_seq_max = 0; - cc_state->mi_seq_max_rcvd = 0; - atomic_store(&cc_state->num_nacks, 0); - atomic_store(&cc_state->num_nack_pkts, 0); - cc_state->mi_end = 0; - - // Start new MI; only safe because no acks are processed during those updates - ccontrol_start_monitoring_interval(cc_state); - } - pthread_spin_unlock(&cc_state->lock); - } - } -} - -bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct sender_state_per_receiver *rcvr) -{ - bool updated = false; - if(initial->path_index < rcvr->num_paths) { - u64 rtt_estimate = get_nsecs() - initial->timestamp; - if(atomic_load(&rcvr->paths[initial->path_index].next_handshake_at) != UINT64_MAX) { - atomic_store(&rcvr->paths[initial->path_index].next_handshake_at, UINT64_MAX); - if(rcvr->cc_states != NULL && rcvr->cc_states[initial->path_index].rtt == DBL_MAX) { - ccontrol_update_rtt(&rcvr->cc_states[initial->path_index], rtt_estimate); - updated = true; - } - if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - rcvr->handshake_rtt = rtt_estimate; - if(rcvr->cc_states != NULL) { - u64 now = get_nsecs(); - for(u32 p = 0; p < rcvr->num_paths; p++) { - if(p != initial->path_index && rcvr->paths[p].enabled) { - rcvr->paths[p].next_handshake_at = now; - rcvr->cc_states[p].pcc_mi_duration = DBL_MAX; - rcvr->cc_states[p].rtt = DBL_MAX; - } - } - } - } - } - } - return updated; -} - -static bool tx_handle_cts(struct sender_state *tx_state, const char *cts, size_t payloadlen, u32 rcvr) -{ - const struct hercules_control_packet *control_pkt = (const struct hercules_control_packet *)cts; - if(payloadlen < sizeof(control_pkt->type) + sizeof(control_pkt->payload.ack.num_acks)) { - return false; - } - if(control_pkt->type == CONTROL_PACKET_TYPE_ACK && control_pkt->payload.ack.num_acks == 0) { - tx_state->receiver[rcvr].cts_received = true; - return true; - } - return false; -} - - -static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) -{ - debug_printf("Sending initial"); - char buf[HERCULES_MAX_PKTSIZE]; - void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); - - u8 flags = 0; - if (set_return_path){ - flags |= HANDSHAKE_FLAG_SET_RETURN_PATH; - } - if (new_transfer){ - flags |= HANDSHAKE_FLAG_NEW_TRANSFER; - } - - struct hercules_control_packet pld = { - .type = CONTROL_PACKET_TYPE_INITIAL, - .payload.initial = { - .filesize = filesize, - .chunklen = chunklen, - .timestamp = timestamp, - .path_index = path_index, - .flags = flags, - .name_len = strlen(filename), - }, - }; - assert(strlen(filename) < 100); // TODO - strncpy(pld.payload.initial.name, filename, 100); - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, sizeof(pld.type) + sizeof(pld.payload.initial) + pld.payload.initial.name_len, - path->payloadlen); - stitch_checksum(path, path->header.checksum, buf); - - send_eth_frame(server, path, buf); - /* atomic_fetch_add(&server->tx_npkts, 1); */ -} - - -static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) -{ - chk_input chk_input_s; - chk_input *chksum_struc = init_chk_input(&chk_input_s, 2); - assert(chksum_struc); - char *payload = pkt + path->headerlen; - precomputed_checksum = ~precomputed_checksum; // take one complement of precomputed checksum - chk_add_chunk(chksum_struc, (u8 *)&precomputed_checksum, 2); // add precomputed header checksum - chk_add_chunk(chksum_struc, (u8 *)payload, path->payloadlen); // add payload - u16 pkt_checksum = checksum(chksum_struc); - - mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); -} - -static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, - int ifid, const char *payload, int payloadlen); - -static void -submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames) -{ - u32 idx_fq = 0; - pthread_spin_lock(&umem->lock); - size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - while(reserved != num_frames) { - reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - if(session->state != SESSION_STATE_RUNNING) { - pthread_spin_unlock(&umem->lock); - return; - } - } - - for(size_t i = 0; i < num_frames; i++) { - *xsk_ring_prod__fill_addr(&umem->fq, idx_fq++) = addrs[i]; - } - xsk_ring_prod__submit(&umem->fq, num_frames); - pthread_spin_unlock(&umem->lock); -} - -static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_info *xsk) -{ - /* debug_printf("receive batch"); */ - u32 idx_rx = 0; - int ignored = 0; - - size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); - if(!rcvd){ - return; - } - - // optimistically update receive timestamp - u64 now = get_nsecs(); - u64 old_last_pkt_rcvd = atomic_load(&rx_state->last_pkt_rcvd); - if(old_last_pkt_rcvd < now) { - atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, &old_last_pkt_rcvd, now); + // optimistically update receive timestamp + u64 now = get_nsecs(); + u64 old_last_pkt_rcvd = atomic_load(&rx_state->last_pkt_rcvd); + if(old_last_pkt_rcvd < now) { + atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, &old_last_pkt_rcvd, now); } u64 frame_addrs[BATCH_SIZE]; @@ -1279,50 +751,510 @@ static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_ /* atomic_fetch_add(&rx_state->session->rx_npkts, (rcvd - ignored)); */ submit_rx_frames(rx_state->session, xsk->umem, frame_addrs, rcvd); } - -static void rate_limit_tx(struct sender_state *tx_state) +// TODO should return error instead of quitting +static char *rx_mmap(struct hercules_server *server, const char *pathname, size_t filesize) { - if(tx_state->prev_tx_npkts_queued + RATE_LIMIT_CHECK > tx_state->tx_npkts_queued) - return; - - u64 now = get_nsecs(); - u64 dt = now - tx_state->prev_rate_check; - - u64 d_npkts = tx_state->tx_npkts_queued - tx_state->prev_tx_npkts_queued; - - dt = umin64(dt, 1); - u32 tx_pps = d_npkts * 1.e9 / dt; - - if(tx_pps > tx_state->rate_limit) { - u64 min_dt = (d_npkts * 1.e9 / tx_state->rate_limit); - - // Busy wait implementation - while(now < tx_state->prev_rate_check + min_dt) { - now = get_nsecs(); - } + debug_printf("mmap file: %s", pathname); + int ret; + /*ret = unlink(pathname); + if(ret && errno != ENOENT) { + exit_with_error(server, errno); + }*/ + int f = open(pathname, O_RDWR | O_CREAT | O_EXCL, 0664); + if(f == -1 && errno == EEXIST) { + f = open(pathname, O_RDWR | O_EXCL); } - - tx_state->prev_rate_check = now; - tx_state->prev_tx_npkts_queued = tx_state->tx_npkts_queued; -} - -// Fill packet with n bytes from data and pad with zeros to payloadlen. -static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence_number seqnr, const char *data, - size_t n, size_t payloadlen) -{ - void *rbudp_path_idx = mempcpy(rbudp_pkt, &chunk_idx, sizeof(chunk_idx)); - void *rbudp_seqnr = mempcpy(rbudp_path_idx, &path_idx, sizeof(path_idx)); - void *rbudp_payload = mempcpy(rbudp_seqnr, &seqnr, sizeof(seqnr)); - void *start_pad = mempcpy(rbudp_payload, data, n); - if(sizeof(chunk_idx) + sizeof(path_idx) + n < payloadlen) { - memset(start_pad, 0, payloadlen - sizeof(chunk_idx) - sizeof(path_idx) - n); + if(f == -1) { + exit_with_error(server, errno); } - debug_print_rbudp_pkt(rbudp_pkt, false); + ret = fallocate(f, 0, 0, filesize); // Will fail on old filesystems (ext3) + if(ret) { + exit_with_error(server, errno); + } + char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); + if(mem == MAP_FAILED) { + exit_with_error(server, errno); + } + close(f); + return mem; } - -// TODO -static void update_hercules_tx_paths(struct sender_state *tx_state) +static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t filesize, int chunklen, int etherlen, + bool is_pcc_benchmark) +{ + struct receiver_state *rx_state; + rx_state = calloc(1, sizeof(*rx_state)); + rx_state->session = session; + rx_state->filesize = filesize; + rx_state->chunklen = chunklen; + rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; + bitset__create(&rx_state->received_chunks, rx_state->total_chunks); + rx_state->start_time = 0; + rx_state->end_time = 0; + rx_state->handshake_rtt = 0; + rx_state->is_pcc_benchmark = is_pcc_benchmark; + rx_state->mem = rx_mmap(NULL, filename, filesize); + rx_state->etherlen = etherlen; + return rx_state; +} +static bool rx_update_reply_path(struct receiver_state *rx_state){ + // Get reply path for sending ACKs: + // + // XXX: race reading from shared mem. + // Try to make a quick copy to at least limit the carnage. + debug_printf("Updating reply path"); + if(!rx_state) { + debug_printf("ERROR: invalid rx_state"); + return false; + } + int rx_sample_len = rx_state->rx_sample_len; + assert(rx_sample_len > 0); + assert(rx_sample_len <= XSK_UMEM__DEFAULT_FRAME_SIZE); + char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; + memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); + + pthread_spin_lock(&usock_lock); + // TODO writing to reply path needs sync? + int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, rx_state->etherlen, &rx_state->reply_path); + pthread_spin_unlock(&usock_lock); + if(!ret) { + return false; + } + // XXX Do we always want to reply from the interface the packet was received on? + // TODO The monitor also returns an interface id (from route lookup) + rx_state->reply_path.ifid = rx_state->rx_sample_ifid; + return true; +} + + +static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) +{ + memcpy(path, &rx_state->reply_path, sizeof(*path)); + return true; +} + +static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *pld) +{ + struct hercules_path path; + if(!rx_get_reply_path(rx_state, &path)) { + debug_printf("no return path"); + return; + } + + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); + + struct hercules_control_packet control_pkt = { + .type = CONTROL_PACKET_TYPE_INITIAL, + .payload.initial = *pld, + }; + control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; + + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, + sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); + stitch_checksum(&path, path.header.checksum, buf); + + send_eth_frame(server, &path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ +} + +static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, + int ifid, const char *payload, int payloadlen) +{ + debug_printf("handling initial"); + const int headerlen = (int)(payload - buf); + if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + debug_printf("setting rx sample"); + set_rx_sample(rx_state, ifid, buf, headerlen + payloadlen); + rx_update_reply_path(rx_state); + } + + rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize + rx_state->cts_sent_at = get_nsecs(); // FIXME why is this here? +} +static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) +{ + debug_printf("Send CTS ACK"); + struct hercules_path path; + if(!rx_get_reply_path(rx_state, &path)) { + debug_printf("no reply path"); + return; + } + + debug_printf("got reply path, idx %d", path.ifid); + + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); + + struct hercules_control_packet control_pkt = { + .type = CONTROL_PACKET_TYPE_ACK, + .payload.ack.num_acks = 0, + }; + + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, + sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); + stitch_checksum(&path, path.header.checksum, buf); + + send_eth_frame(server, &path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ +} + +static void rx_send_ack_pkt(struct hercules_server *server, struct hercules_control_packet *control_pkt, + struct hercules_path *path) { + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, + sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), path->payloadlen); + stitch_checksum(path, path->header.checksum, buf); + + send_eth_frame(server, path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ +} + +static void rx_send_acks(struct hercules_server *server, struct receiver_state *rx_state) +{ + /* debug_printf("rx_send_acks"); */ + struct hercules_path path; + if(!rx_get_reply_path(rx_state, &path)) { + debug_printf("no reply path"); + return; + } + // XXX: could write ack payload directly to buf, but + // doesnt work nicely with existing fill_rbudp_pkt helper. + struct hercules_control_packet control_pkt = { + .type = CONTROL_PACKET_TYPE_ACK, + }; + + const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); + + // send an empty ACK to keep connection alive until first packet arrives + u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries); + rx_send_ack_pkt(server, &control_pkt, &path); + for(; curr < rx_state->total_chunks;) { + curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries); + if(control_pkt.payload.ack.num_acks == 0) break; + rx_send_ack_pkt(server, &control_pkt, &path); + } +} + +static void rx_trickle_acks(void *arg) { + struct hercules_server *server = arg; + while (1) { + __sync_synchronize(); + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && + session_rx->state == SESSION_STATE_RUNNING) { + struct receiver_state *rx_state = session_rx->rx_state; + // XXX: data races in access to shared rx_state! + atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); + if (atomic_load(&rx_state->last_pkt_rcvd) + + umax64(100 * ACK_RATE_TIME_MS * 1e6, + 3 * rx_state->handshake_rtt) < + get_nsecs()) { + // Transmission timed out + quit_session(session_rx, SESSION_ERROR_TIMEOUT); + } + rx_send_acks(server, rx_state); + if (rx_received_all(rx_state)) { + debug_printf("Received all, done."); + session_rx->state = SESSION_STATE_DONE; + rx_send_acks(server, rx_state); + server->session_rx = NULL; // FIXME leak + atomic_store(&server->session_rx, NULL); + } + } + sleep_nsecs(ACK_RATE_TIME_MS * 1e6); + } +} + +static void rx_send_path_nacks(struct hercules_server *server, struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr) +{ + struct hercules_path path; + if(!rx_get_reply_path(rx_state, &path)) { + debug_printf("no reply path"); + return; + } + + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); + + // XXX: could write ack payload directly to buf, but + // doesnt work nicely with existing fill_rbudp_pkt helper. + struct hercules_control_packet control_pkt = { + .type = CONTROL_PACKET_TYPE_NACK, + }; + const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); + sequence_number nack_end = path_state->nack_end; + //sequence_number start = nack_end; + bool sent = false; + pthread_spin_lock(&path_state->seq_rcvd.lock); + libbpf_smp_rmb(); + for(u32 curr = path_state->nack_end; curr < path_state->seq_rcvd.num;) { + // Data to send + curr = fill_nack_pkt(curr, &control_pkt.payload.ack, max_entries, &path_state->seq_rcvd); + if(has_more_nacks(curr, &path_state->seq_rcvd)) { + control_pkt.payload.ack.max_seq = 0; + } else { + control_pkt.payload.ack.max_seq = path_state->seq_rcvd.max_set; + } + if(control_pkt.payload.ack.num_acks == 0 && sent) break; + sent = true; // send at least one packet each round + + control_pkt.payload.ack.ack_nr = nr; + control_pkt.payload.ack.timestamp = time; + + if(control_pkt.payload.ack.num_acks != 0) { + nack_end = control_pkt.payload.ack.acks[control_pkt.payload.ack.num_acks - 1].end; + } + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, path_idx, 0, (char *)&control_pkt, + sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); + stitch_checksum(&path, path.header.checksum, buf); + + send_eth_frame(server, &path, buf); + /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + } + libbpf_smp_wmb(); + /* assert(&path_state->seq_rcvd.lock != NULL); */ + /* assert(path_state->seq_rcvd.lock != NULL); */ + // FIXME spurious segfault on unlock + /* pthread_spin_unlock(&path_state->seq_rcvd.lock); */ + path_state->nack_end = nack_end; +} + +// sends the NACKs used for congestion control by the sender +static void rx_send_nacks(struct hercules_server *server, struct receiver_state *rx_state, u64 time, u32 nr) +{ + u8 num_paths = atomic_load(&rx_state->num_tracked_paths); + for(u8 p = 0; p < num_paths; p++) { + rx_send_path_nacks(server, rx_state, &rx_state->path_state[p], p, time, nr); + } +} + +/// SENDER +static bool tx_acked_all(const struct sender_state *tx_state) +{ + for(u32 r = 0; r < tx_state->num_receivers; r++) { + if(tx_state->receiver[r].acked_chunks.num_set != tx_state->total_chunks) { + return false; + } + } + return true; +} + +static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) +{ + int ret; + do { + ret = sendto(xsk_socket__fd(xsk->xsk), NULL, 0, MSG_DONTWAIT, NULL, 0); + } while(ret < 0 && errno == EAGAIN); + + if(ret < 0 && errno != ENOBUFS && errno != EBUSY) { + exit_with_error(server, errno); + } +} + +static void kick_all_tx(struct hercules_server *server, struct hercules_interface *iface) +{ + for(u32 s = 0; s < iface->num_sockets; s++) { + kick_tx(server, iface->xsks[s]); + } +} +static void kick_tx_server(struct hercules_server *server){ + + for (int i = 0; i < server->num_ifaces; i++){ + kick_all_tx(server, &server->ifaces[i]); + } +} +static void tx_register_acks(const struct rbudp_ack_pkt *ack, struct sender_state_per_receiver *rcvr) +{ + for(uint16_t e = 0; e < ack->num_acks; ++e) { + const u32 begin = ack->acks[e].begin; + const u32 end = ack->acks[e].end; + if(begin >= end || end > rcvr->acked_chunks.num) { + return; // Abort + } + for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized + bitset__set(&rcvr->acked_chunks, i); // don't need thread-safety here, all updates in same thread + } + } +} + +// Pop entries from completion ring and store them in umem->available_frames. +static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_info *umem) +{ + u32 idx; + size_t entries = xsk_ring_cons__peek(&umem->cq, SIZE_MAX, &idx); + if(entries > 0) { + u16 num = frame_queue__prod_reserve(&umem->available_frames, entries); + if(num < entries) { // there are less frames in the loop than the number of slots in frame_queue + debug_printf("trying to push %ld frames, only got %d slots in frame_queue", entries, num); + exit_with_error(server, EINVAL); + } + for(u16 i = 0; i < num; i++) { + frame_queue__prod_fill(&umem->available_frames, i, *xsk_ring_cons__comp_addr(&umem->cq, idx + i)); + } + frame_queue__push(&umem->available_frames, num); + xsk_ring_cons__release(&umem->cq, entries); + /* atomic_fetch_add(&server->tx_npkts, entries); */ + } +} + +static inline void pop_completion_rings(struct hercules_server *server) +{ + for(int i = 0; i < server->num_ifaces; i++) { + pop_completion_ring(server, server->ifaces[i].umem); + } +} + +static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_state *cc_state) +{ + pthread_spin_lock(&cc_state->lock); + atomic_store(&cc_state->mi_seq_max, umax32(atomic_load(&cc_state->mi_seq_max), nack->max_seq)); + cc_state->num_nack_pkts++; + u32 counted = 0; + for(uint16_t e = 0; e < nack->num_acks; ++e) { + u32 begin = nack->acks[e].begin; + u32 end = nack->acks[e].end; + cc_state->mi_seq_min = umin32(cc_state->mi_seq_min, begin); + atomic_store(&cc_state->mi_seq_max_rcvd, umax32(atomic_load(&cc_state->mi_seq_max_rcvd), end)); + begin = umax32(begin, cc_state->mi_seq_start); + u32 seq_end = atomic_load(&cc_state->mi_seq_end); + if(seq_end != 0) { + end = umin32(end, seq_end); + } + if(begin >= end) { + continue; + } + counted += end - begin; + cc_state->num_nacks += end - begin; + begin -= cc_state->mi_seq_start; + end -= cc_state->mi_seq_start; + if(end >= cc_state->mi_nacked.num) { + fprintf(stderr, "Cannot track NACK! Out of range: nack end = %d >= bitset size %d\n", end, cc_state->mi_nacked.num); + } + end = umin32(end, cc_state->mi_nacked.num); + for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized + bitset__set(&cc_state->mi_nacked, i); // don't need thread-safety here, all updates in same thread + } + } + pthread_spin_unlock(&cc_state->lock); +} + +bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct sender_state_per_receiver *rcvr) +{ + bool updated = false; + if(initial->path_index < rcvr->num_paths) { + u64 rtt_estimate = get_nsecs() - initial->timestamp; + if(atomic_load(&rcvr->paths[initial->path_index].next_handshake_at) != UINT64_MAX) { + atomic_store(&rcvr->paths[initial->path_index].next_handshake_at, UINT64_MAX); + if(rcvr->cc_states != NULL && rcvr->cc_states[initial->path_index].rtt == DBL_MAX) { + ccontrol_update_rtt(&rcvr->cc_states[initial->path_index], rtt_estimate); + updated = true; + } + if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + rcvr->handshake_rtt = rtt_estimate; + if(rcvr->cc_states != NULL) { + u64 now = get_nsecs(); + for(u32 p = 0; p < rcvr->num_paths; p++) { + if(p != initial->path_index && rcvr->paths[p].enabled) { + rcvr->paths[p].next_handshake_at = now; + rcvr->cc_states[p].pcc_mi_duration = DBL_MAX; + rcvr->cc_states[p].rtt = DBL_MAX; + } + } + } + } + } + } + return updated; +} + +static void +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) +{ + debug_printf("Sending initial"); + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + + u8 flags = 0; + if (set_return_path){ + flags |= HANDSHAKE_FLAG_SET_RETURN_PATH; + } + if (new_transfer){ + flags |= HANDSHAKE_FLAG_NEW_TRANSFER; + } + + struct hercules_control_packet pld = { + .type = CONTROL_PACKET_TYPE_INITIAL, + .payload.initial = { + .filesize = filesize, + .chunklen = chunklen, + .timestamp = timestamp, + .path_index = path_index, + .flags = flags, + .name_len = strlen(filename), + }, + }; + assert(strlen(filename) < 100); // TODO + strncpy(pld.payload.initial.name, filename, 100); + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, sizeof(pld.type) + sizeof(pld.payload.initial) + pld.payload.initial.name_len, + path->payloadlen); + stitch_checksum(path, path->header.checksum, buf); + + send_eth_frame(server, path, buf); + /* atomic_fetch_add(&server->tx_npkts, 1); */ +} +static void rate_limit_tx(struct sender_state *tx_state) +{ + if(tx_state->prev_tx_npkts_queued + RATE_LIMIT_CHECK > tx_state->tx_npkts_queued) + return; + + u64 now = get_nsecs(); + u64 dt = now - tx_state->prev_rate_check; + + u64 d_npkts = tx_state->tx_npkts_queued - tx_state->prev_tx_npkts_queued; + + dt = umin64(dt, 1); + u32 tx_pps = d_npkts * 1.e9 / dt; + + if(tx_pps > tx_state->rate_limit) { + u64 min_dt = (d_npkts * 1.e9 / tx_state->rate_limit); + + // Busy wait implementation + while(now < tx_state->prev_rate_check + min_dt) { + now = get_nsecs(); + } + } + + tx_state->prev_rate_check = now; + tx_state->prev_tx_npkts_queued = tx_state->tx_npkts_queued; +} +void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { + u64 now = get_nsecs(); + struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; + + for (u32 p = 0; p < rcvr->num_paths; p++) { + struct hercules_path *path = &rcvr->paths[p]; + /* debug_printf("Checking path %d/%d", p, rcvr->num_paths); */ + if (path->enabled) { + u64 handshake_at = atomic_load(&path->next_handshake_at); + if (handshake_at < now) { + if (atomic_compare_exchange_strong(&path->next_handshake_at, + &handshake_at, + now + PATH_HANDSHAKE_TIMEOUT_NS)) { + debug_printf("sending hs on path %d", p); + // FIXME file name below? + tx_send_initial(server, path, "abcd", tx_state->filesize, + tx_state->chunklen, get_nsecs(), p, + false, false); + } + } + } + } +} +// TODO +static void update_hercules_tx_paths(struct sender_state *tx_state) { return; // FIXME HACK tx_state->has_new_paths = false; @@ -1387,29 +1319,6 @@ static void update_hercules_tx_paths(struct sender_state *tx_state) } } -void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { - u64 now = get_nsecs(); - struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; - - for (u32 p = 0; p < rcvr->num_paths; p++) { - struct hercules_path *path = &rcvr->paths[p]; - /* debug_printf("Checking path %d/%d", p, rcvr->num_paths); */ - if (path->enabled) { - u64 handshake_at = atomic_load(&path->next_handshake_at); - if (handshake_at < now) { - if (atomic_compare_exchange_strong(&path->next_handshake_at, - &handshake_at, - now + PATH_HANDSHAKE_TIMEOUT_NS)) { - debug_printf("sending hs on path %d", p); - // FIXME file name below? - tx_send_initial(server, path, "abcd", tx_state->filesize, - tx_state->chunklen, get_nsecs(), p, - false, false); - } - } - } - } -} static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { @@ -1556,96 +1465,27 @@ produce_batch(struct hercules_server *server, struct hercules_session *session, unit->paths[num_chunks_in_unit] = UINT8_MAX; } send_queue_push(session->send_queue); - unit = NULL; - } - } -} - -static inline void allocate_tx_frames(struct hercules_server *server, - u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT]) -{ - for(int i = 0; i < server->num_ifaces; i++) { - int num_frames; - for(num_frames = 0; num_frames < SEND_QUEUE_ENTRIES_PER_UNIT; num_frames++) { - if(frame_addrs[i][num_frames] != (u64) -1) { - break; - } - } - /* debug_printf("claiming %d frames", num_frames); */ - claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); - } -} - -struct tx_send_p_args { - struct hercules_server *server; - struct xsk_socket_info *xsks[]; -}; - -static void tx_send_p(void *arg) { - struct tx_send_p_args *args = arg; - struct hercules_server *server = args->server; - while (1) { - struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx == NULL || atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { - /* debug_printf("Invalid session, dropping sendq unit"); */ - kick_tx_server(server); - continue; - } - struct send_queue_unit unit; - int ret = send_queue_pop(session_tx->send_queue, &unit); - if (!ret) { - kick_tx_server(server); - continue; - } - u32 num_chunks_in_unit = 0; - for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { - if(unit.paths[i] == UINT8_MAX) { - break; - } - num_chunks_in_unit++; - } - debug_printf("unit has %d chunks", num_chunks_in_unit); - u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; - memset(frame_addrs, 0xFF, sizeof(frame_addrs)); - for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ - if (i >= num_chunks_in_unit){ - /* debug_printf("not using unit slot %d", i); */ - frame_addrs[0][i] = 0; + unit = NULL; } } - /* debug_printf("allocating frames"); */ - allocate_tx_frames(server, frame_addrs); - /* debug_printf("done allocating frames"); */ - // At this point we claimed 7 frames (as many as can fit in a sendq unit) - tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, - frame_addrs, &unit); - /* assert(num_chunks_in_unit == 7); */ - kick_tx_server(server); - } } -// Collect path rate limits -u32 compute_max_chunks_per_rcvr(struct sender_state *tx_state, u32 *max_chunks_per_rcvr) +static inline void allocate_tx_frames(struct hercules_server *server, + u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT]) { - u32 total_chunks = 0; - u64 now = get_nsecs(); - - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(!tx_state->receiver[r].paths[tx_state->receiver[r].path_index].enabled) { - continue; // if a receiver does not have any enabled paths, we can actually end up here ... :( - } - if(tx_state->receiver[r].cc_states != NULL) { // use PCC - struct ccontrol_state *cc_state = &tx_state->receiver[r].cc_states[tx_state->receiver[r].path_index]; - max_chunks_per_rcvr[r] = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); - } else { // no path-based limit - max_chunks_per_rcvr[r] = BATCH_SIZE; + for(int i = 0; i < server->num_ifaces; i++) { + int num_frames; + for(num_frames = 0; num_frames < SEND_QUEUE_ENTRIES_PER_UNIT; num_frames++) { + if(frame_addrs[i][num_frames] != (u64) -1) { + break; + } } - total_chunks += max_chunks_per_rcvr[r]; + /* debug_printf("claiming %d frames", num_frames); */ + claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); } - return total_chunks; } // Collect path rate limits -u32 compute_max_chunks_current_path(struct sender_state *tx_state) { +static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { u32 allowed_chunks = 0; u64 now = get_nsecs(); @@ -1671,20 +1511,9 @@ u32 compute_max_chunks_current_path(struct sender_state *tx_state) { return allowed_chunks; } -// exclude receivers that have completed the current iteration -u32 exclude_finished_receivers(struct sender_state *tx_state, u32 *max_chunks_per_rcvr, u32 total_chunks) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(tx_state->receiver[r].finished) { - total_chunks -= max_chunks_per_rcvr[r]; - max_chunks_per_rcvr[r] = 0; - } - } - return total_chunks; -} // Send a total max of BATCH_SIZE -u32 shrink_sending_rates(struct sender_state *tx_state, u32 *max_chunks_per_rcvr, u32 total_chunks) +static u32 shrink_sending_rates(struct sender_state *tx_state, u32 *max_chunks_per_rcvr, u32 total_chunks) { if(total_chunks > BATCH_SIZE) { u32 new_total_chunks = 0; // due to rounding errors, we need to aggregate again @@ -1696,15 +1525,14 @@ u32 shrink_sending_rates(struct sender_state *tx_state, u32 *max_chunks_per_rcvr } return total_chunks; } - -void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) +static void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { for(u32 r = 0; r < tx_state->num_receivers; r++) { rcvr_path[r] = tx_state->receiver->path_index; } } -void iterate_paths(struct sender_state *tx_state) +static void iterate_paths(struct sender_state *tx_state) { for(u32 r = 0; r < tx_state->num_receivers; r++) { struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; @@ -1739,7 +1567,6 @@ static void kick_cc(struct sender_state *tx_state) } } } - // Select batch of un-ACKed chunks for (re)transmit: // Batch ends if an un-ACKed chunk is encountered for which we should keep // waiting a bit before retransmit. @@ -1796,136 +1623,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 return num_chunks_prepared; } -static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) -{ - return cc_state->state != pcc_terminated && - cc_state->state != pcc_uninitialized && - cc_state->mi_start + (u64)((cc_state->pcc_mi_duration) * 1e9) >= now; -} - -/** - * Transmit and retransmit chunks that have not been ACKed. - * For each retransmit chunk, wait (at least) one round trip time for the ACK to arrive. - * For large files transfers, this naturally allows to start retransmitting chunks at the beginning - * of the file, while chunks of the previous round at the end of the file are still in flight. - * - * Transmission to different receivers is interleaved in a round-robin fashion. - * Transmission through different paths is batched (i.e. use the same path within a batch) to prevent the receiver from - * ACKing individual chunks. - * - * The rounds of different receivers are isolated from each other. - * - * The estimates for the ACK-arrival time dont need to be accurate for correctness, i.e. regardless - * of how bad our estimate is, all chunks will be (re-)transmitted eventually. - * - if we *under-estimate* the RTT, we may retransmit chunks unnecessarily - * - waste bandwidth, waste sender disk reads & CPU time, waste receiver CPU time - * - potentially increase overall transmit time because necessary retransmit may be delayed by - * wasted resources - * - if we *over-estimate* the RTT, we wait unnecessarily - * This is only constant overhead per retransmit round, independent of number of packets or send - * rate. - * Thus it seems preferrable to *over-estimate* the ACK-arrival time. - * - * To avoid recording transmit time per chunk, only record start and end time of a transmit round - * and linearly interpolate for each receiver separately. - * This assumes a uniform send rate and that chunks that need to be retransmitted (i.e. losses) - * occur uniformly. - */ -static void tx_only(struct hercules_server *server, struct sender_state *tx_state) -{ - debug_printf("Start transmit round for all receivers"); - tx_state->prev_rate_check = get_nsecs(); - u32 finished_count = 0; - - u32 chunks[BATCH_SIZE]; - u8 chunk_rcvr[BATCH_SIZE]; - u32 max_chunks_per_rcvr[tx_state->num_receivers]; - - while(tx_state->session->state == SESSION_STATE_RUNNING && finished_count < tx_state->num_receivers) { - pop_completion_rings(server); - /* send_path_handshakes(tx_state); */ - u64 next_ack_due = 0; - u32 num_chunks_per_rcvr[tx_state->num_receivers]; - memset(num_chunks_per_rcvr, 0, sizeof(num_chunks_per_rcvr)); - - // in each iteration, we send packets on a single path to each receiver - // collect the rate limits for each active path - u32 total_chunks = compute_max_chunks_per_rcvr(tx_state, max_chunks_per_rcvr); - total_chunks = exclude_finished_receivers(tx_state, max_chunks_per_rcvr, total_chunks); - - if(total_chunks == 0) { // we hit the rate limits on every path; switch paths - if(tx_state->has_new_paths) { - update_hercules_tx_paths(tx_state); - } - iterate_paths(tx_state); - continue; - } - - // sending rates might add up to more than BATCH_SIZE, shrink proportionally, if needed - shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); - - const u64 now = get_nsecs(); - u32 num_chunks = 0; - for(u32 r = 0; r < tx_state->num_receivers; r++) { - struct sender_state_per_receiver *rcvr = &tx_state->receiver[r]; - if(!rcvr->finished) { - u64 ack_due = 0; - // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks to send - u32 cur_num_chunks = prepare_rcvr_chunks(tx_state, r, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, - &ack_due, max_chunks_per_rcvr[r]); - num_chunks += cur_num_chunks; - num_chunks_per_rcvr[r] += cur_num_chunks; - if(rcvr->finished) { - finished_count++; - if(rcvr->cc_states) { - terminate_cc(rcvr); - kick_cc(tx_state); - } - } else { - // only wait for the nearest ack - if(next_ack_due) { - if(next_ack_due > ack_due) { - next_ack_due = ack_due; - } - } else { - next_ack_due = ack_due; - } - } - } - } - - if(num_chunks > 0) { - u8 rcvr_path[tx_state->num_receivers]; - prepare_rcvr_paths(tx_state, rcvr_path); - produce_batch(server, NULL, tx_state, rcvr_path, chunks, chunk_rcvr, num_chunks); - tx_state->tx_npkts_queued += num_chunks; - rate_limit_tx(tx_state); - - // update book-keeping - for(u32 r = 0; r < tx_state->num_receivers; r++) { - struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; - u32 path_idx = tx_state->receiver[r].path_index; - if(receiver->cc_states != NULL) { - struct ccontrol_state *cc_state = &receiver->cc_states[path_idx]; - atomic_fetch_add(&cc_state->mi_tx_npkts, num_chunks_per_rcvr[r]); - atomic_fetch_add(&cc_state->total_tx_npkts, num_chunks_per_rcvr[r]); - if(pcc_has_active_mi(cc_state, now)) { - atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, num_chunks_per_rcvr[r]); - } - } - } - } - - if(tx_state->has_new_paths) { - update_hercules_tx_paths(tx_state); - } - iterate_paths(tx_state); - if(now < next_ack_due) { - sleep_until(next_ack_due); - } - } -} static struct sender_state * init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, int max_rate_limit, char *mem, @@ -1978,397 +1676,214 @@ static void destroy_tx_state(struct sender_state *tx_state) } free(tx_state); } +/// XDP -// TODO should return error instead of quitting -static char *rx_mmap(struct hercules_server *server, const char *pathname, size_t filesize) -{ - debug_printf("mmap file: %s", pathname); - int ret; - /*ret = unlink(pathname); - if(ret && errno != ENOENT) { - exit_with_error(server, errno); - }*/ - int f = open(pathname, O_RDWR | O_CREAT | O_EXCL, 0664); - if(f == -1 && errno == EEXIST) { - f = open(pathname, O_RDWR | O_EXCL); - } - if(f == -1) { - exit_with_error(server, errno); - } - ret = fallocate(f, 0, 0, filesize); // Will fail on old filesystems (ext3) - if(ret) { - exit_with_error(server, errno); - } - char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); - if(mem == MAP_FAILED) { - exit_with_error(server, errno); - } - close(f); - return mem; -} -static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t filesize, int chunklen, int etherlen, - bool is_pcc_benchmark) -{ - struct receiver_state *rx_state; - rx_state = calloc(1, sizeof(*rx_state)); - rx_state->session = session; - rx_state->filesize = filesize; - rx_state->chunklen = chunklen; - rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; - bitset__create(&rx_state->received_chunks, rx_state->total_chunks); - rx_state->start_time = 0; - rx_state->end_time = 0; - rx_state->handshake_rtt = 0; - rx_state->is_pcc_benchmark = is_pcc_benchmark; - rx_state->mem = rx_mmap(NULL, filename, filesize); - rx_state->etherlen = etherlen; - return rx_state; -} -static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt) -{ - struct hercules_control_packet control_pkt; - memcpy(&control_pkt, pkt, umin32(sizeof(control_pkt), len)); - if(control_pkt.type != CONTROL_PACKET_TYPE_INITIAL) { - debug_printf("Packet type not INITIAL"); - return false; - } - if(len < sizeof(control_pkt.type) + sizeof(*parsed_pkt)) { - debug_printf("Packet too short"); - return false; - } - memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt) + control_pkt.payload.initial.name_len); - return true; -} -static bool rx_update_reply_path(struct receiver_state *rx_state){ - // Get reply path for sending ACKs: - // - // XXX: race reading from shared mem. - // Try to make a quick copy to at least limit the carnage. - debug_printf("Updating reply path"); - if(!rx_state) { - debug_printf("ERROR: invalid rx_state"); - return false; - } - int rx_sample_len = rx_state->rx_sample_len; - assert(rx_sample_len > 0); - assert(rx_sample_len <= XSK_UMEM__DEFAULT_FRAME_SIZE); - char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; - memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); +/// PCC +#define NACK_TRACE_SIZE (1024*1024) +static u32 nack_trace_count = 0; +static struct { + long long sender_timestamp; + long long receiver_timestamp; + u32 nr; +} nack_trace[NACK_TRACE_SIZE]; - pthread_spin_lock(&usock_lock); - // TODO writing to reply path needs sync? - int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, rx_state->etherlen, &rx_state->reply_path); - pthread_spin_unlock(&usock_lock); - if(!ret) { - return false; +static void nack_trace_push(u64 timestamp, u32 nr) { + return; + u32 idx = atomic_fetch_add(&nack_trace_count, 1); + if(idx >= NACK_TRACE_SIZE) { + fprintf(stderr, "oops: nack trace too small, trying to push #%d\n", idx); + exit(133); } - // XXX Do we always want to reply from the interface the packet was received on? - // TODO The monitor also returns an interface id (from route lookup) - rx_state->reply_path.ifid = rx_state->rx_sample_ifid; - return true; -} - - -static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) -{ - memcpy(path, &rx_state->reply_path, sizeof(*path)); - return true; + nack_trace[idx].sender_timestamp = timestamp; + nack_trace[idx].receiver_timestamp = get_nsecs(); + nack_trace[idx].nr = nr; } -static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *pld) -{ - struct hercules_path path; - if(!rx_get_reply_path(rx_state, &path)) { - debug_printf("no return path"); - return; - } - - char buf[HERCULES_MAX_PKTSIZE]; - void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); - - struct hercules_control_packet control_pkt = { - .type = CONTROL_PACKET_TYPE_INITIAL, - .payload.initial = *pld, - }; - control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; - - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, - sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); - stitch_checksum(&path, path.header.checksum, buf); - - send_eth_frame(server, &path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ -} +#define PCC_TRACE_SIZE (1024*1024) +static u32 pcc_trace_count = 0; +static struct { + u64 time; + sequence_number range_start, range_end, mi_min, mi_max; + u32 excess; + float loss; + u32 delta_left, delta_right, nnacks, nack_pkts; + enum pcc_state state; + u32 target_rate, actual_rate; + double target_duration, actual_duration; +} pcc_trace[PCC_TRACE_SIZE]; -static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, - int ifid, const char *payload, int payloadlen) -{ - debug_printf("handling initial"); - const int headerlen = (int)(payload - buf); - if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - debug_printf("setting rx sample"); - set_rx_sample(rx_state, ifid, buf, headerlen + payloadlen); - rx_update_reply_path(rx_state); +static void pcc_trace_push(u64 time, sequence_number range_start, sequence_number range_end, sequence_number mi_min, + sequence_number mi_max, u32 excess, float loss, u32 delta_left, u32 delta_right, u32 nnacks, u32 nack_pkts, + enum pcc_state state, u32 target_rate, u32 actual_rate, double target_duration, double actual_duration) { + u32 idx = atomic_fetch_add(&pcc_trace_count, 1); + if(idx >= PCC_TRACE_SIZE) { + fprintf(stderr, "oops: pcc trace too small, trying to push #%d\n", idx); + exit(133); } - - rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize - rx_state->cts_sent_at = get_nsecs(); // FIXME why is this here? + pcc_trace[idx].time = time; + pcc_trace[idx].range_start = range_start; + pcc_trace[idx].range_end = range_end; + pcc_trace[idx].mi_min = mi_min; + pcc_trace[idx].mi_max = mi_max; + pcc_trace[idx].excess = excess; + pcc_trace[idx].loss = loss; + pcc_trace[idx].delta_left = delta_left; + pcc_trace[idx].delta_right = delta_right; + pcc_trace[idx].nnacks = nnacks; + pcc_trace[idx].nack_pkts = nack_pkts; + pcc_trace[idx].state = state; + pcc_trace[idx].target_rate = target_rate; + pcc_trace[idx].actual_rate = actual_rate; + pcc_trace[idx].target_duration = target_duration; + pcc_trace[idx].actual_duration = actual_duration; } -/* static void rx_get_rtt_estimate(void *arg) */ -/* { */ -/* struct receiver_state *rx_state = arg; */ -/* char buf[rx_state->session->config.ether_size + MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE]; */ -/* const char *payload; */ -/* int payloadlen; */ -/* const struct scionaddrhdr_ipv4 *scionaddrhdr; */ -/* const struct udphdr *udphdr; */ -/* for(u64 timeout = get_nsecs() + 5e9; timeout > get_nsecs();) { */ -/* if(recv_rbudp_control_pkt(rx_state->session, buf, sizeof buf, &payload, &payloadlen, */ -/* &scionaddrhdr, &udphdr, NULL, NULL)) { */ -/* u64 now = get_nsecs(); */ -/* rx_state->handshake_rtt = (now - rx_state->cts_sent_at) / 1000; */ -/* return; */ -/* } */ -/* } */ -/* exit_with_error(rx_state->session, ETIMEDOUT); */ -/* } */ -static void configure_rx_queues(struct hercules_server *server) +static bool pcc_mi_elapsed(struct ccontrol_state *cc_state) { - for(int i = 0; i < server->num_ifaces; i++) { - debug_printf("map UDP4 flow to %d.%d.%d.%d to queue %d on interface %s", - (u8) (server->config.local_addr.ip), - (u8) (server->config.local_addr.ip >> 8u), - (u8) (server->config.local_addr.ip >> 16u), - (u8) (server->config.local_addr.ip >> 24u), - server->ifaces[i].queue, - server->ifaces[i].ifname - ); - - char cmd[1024]; - int cmd_len = snprintf(cmd, 1024, "ethtool -N %s flow-type udp4 dst-ip %d.%d.%d.%d action %d", - server->ifaces[i].ifname, - (u8) (server->config.local_addr.ip), - (u8) (server->config.local_addr.ip >> 8u), - (u8) (server->config.local_addr.ip >> 16u), - (u8) (server->config.local_addr.ip >> 24u), - server->ifaces[i].queue - ); - if(cmd_len > 1023) { - fprintf(stderr, "could not configure queue %d on interface %s - command too long, abort\n", - server->ifaces[i].queue, server->ifaces[i].ifname); - unconfigure_rx_queues(server); - exit_with_error(server, EXIT_FAILURE); - } - - FILE *proc = popen(cmd, "r"); - int rule_id; - int num_parsed = fscanf(proc, "Added rule with ID %d", &rule_id); - int ret = pclose(proc); - if(ret != 0) { - fprintf(stderr, "could not configure queue %d on interface %s, abort\n", server->ifaces[i].queue, - server->ifaces[i].ifname); - unconfigure_rx_queues(server); - exit_with_error(server, ret); - } - if(num_parsed != 1) { - fprintf(stderr, "could not configure queue %d on interface %s, abort\n", server->ifaces[i].queue, - server->ifaces[i].ifname); - unconfigure_rx_queues(server); - exit_with_error(server, EXIT_FAILURE); - } - server->ifaces[i].ethtool_rule = rule_id; + if(cc_state->state == pcc_uninitialized) { + return false; } -} + unsigned long now = get_nsecs(); + sequence_number cur_seq = atomic_load(&cc_state->last_seqnr) - 1; + sequence_number seq_rcvd = atomic_load(&cc_state->mi_seq_max); -static int unconfigure_rx_queues(struct hercules_server *server) -{ - int error = 0; - for(int i = 0; i < server->num_ifaces; i++) { - if(server->ifaces[i].ethtool_rule >= 0) { - char cmd[1024]; - int cmd_len = snprintf(cmd, 1024, "ethtool -N %s delete %d", server->ifaces[i].ifname, - server->ifaces[i].ethtool_rule); - server->ifaces[i].ethtool_rule = -1; - if(cmd_len > 1023) { // This will never happen as the command to configure is strictly longer than this one - fprintf(stderr, "could not delete ethtool rule on interface %s - command too long\n", - server->ifaces[i].ifname); - error = EXIT_FAILURE; - continue; - } - int ret = system(cmd); - if(ret != 0) { - error = ret; - } + if (cc_state->mi_end <= now) { + if (cc_state->mi_seq_end == 0) { + cc_state->mi_end = now; + cc_state->mi_seq_end = cur_seq; + } + if(cc_state->mi_seq_end != 0 && + (cc_state->mi_seq_end < seq_rcvd || now > cc_state->mi_end + (unsigned long)(1.5e9 * cc_state->rtt))) { + return true; } } - return error; + return false; } - -static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) +static void pcc_monitor(struct sender_state *tx_state) { - debug_printf("Send CTS ACK"); - struct hercules_path path; - if(!rx_get_reply_path(rx_state, &path)) { - debug_printf("no reply path"); - return; - } - - debug_printf("got reply path, idx %d", path.ifid); - - char buf[HERCULES_MAX_PKTSIZE]; - void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); - - struct hercules_control_packet control_pkt = { - .type = CONTROL_PACKET_TYPE_ACK, - .payload.ack.num_acks = 0, - }; + for(u32 r = 0; r < tx_state->num_receivers; r++) { + for(u32 cur_path = 0; cur_path < tx_state->receiver[r].num_paths; cur_path++) { + struct ccontrol_state *cc_state = &tx_state->receiver[r].cc_states[cur_path]; + pthread_spin_lock(&cc_state->lock); + if(pcc_mi_elapsed(cc_state)) { + u64 now = get_nsecs(); + if(cc_state->mi_end == 0) { // TODO should not be necessary + fprintf(stderr, "Assumption violated.\n"); + quit_session(tx_state->session, SESSION_ERROR_PCC); + cc_state->mi_end = now; + } + u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, - sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); - stitch_checksum(&path, path.header.checksum, buf); + u32 excess = 0; + if (cc_state->curr_rate * cc_state->pcc_mi_duration > throughput) { + excess = cc_state->curr_rate * cc_state->pcc_mi_duration - throughput; + } + u32 lost_npkts = atomic_load(&cc_state->mi_nacked.num_set); + // account for packets that are "stuck in queue" + if(cc_state->mi_seq_end > cc_state->mi_seq_max) { + lost_npkts += cc_state->mi_seq_end - cc_state->mi_seq_max; + } + lost_npkts = umin32(lost_npkts, throughput); + float loss = (float)(lost_npkts + excess) / (throughput + excess); + sequence_number start = cc_state->mi_seq_start; + sequence_number end = cc_state->mi_seq_end; + sequence_number mi_min = cc_state->mi_seq_min; + sequence_number mi_max = cc_state->mi_seq_max; + sequence_number delta_left = cc_state->mi_seq_start - cc_state->mi_seq_min; + sequence_number delta_right = cc_state->mi_seq_max - cc_state->mi_seq_end; + u32 nnacks = cc_state->num_nacks; + u32 nack_pkts = cc_state->num_nack_pkts; + enum pcc_state state = cc_state->state; + double actual_duration = (double)(cc_state->mi_end - cc_state->mi_start) / 1e9; - send_eth_frame(server, &path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ -} + pcc_trace_push(now, start, end, mi_min, mi_max, excess, loss, delta_left, delta_right, nnacks, nack_pkts, state, + cc_state->curr_rate * cc_state->pcc_mi_duration, throughput, cc_state->pcc_mi_duration, actual_duration); -static void rx_send_ack_pkt(struct hercules_server *server, struct hercules_control_packet *control_pkt, - struct hercules_path *path) { - char buf[HERCULES_MAX_PKTSIZE]; - void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + if(cc_state->num_nack_pkts != 0) { // skip PCC control if no NACKs received + if(cc_state->ignored_first_mi) { // first MI after booting will only contain partial feedback, skip it as well + pcc_control(cc_state, throughput, loss); + } + cc_state->ignored_first_mi = true; + } - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, - sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), path->payloadlen); - stitch_checksum(path, path->header.checksum, buf); + // TODO move the neccessary ones to cc_start_mi below + cc_state->mi_seq_min = UINT32_MAX; + cc_state->mi_seq_max = 0; + cc_state->mi_seq_max_rcvd = 0; + atomic_store(&cc_state->num_nacks, 0); + atomic_store(&cc_state->num_nack_pkts, 0); + cc_state->mi_end = 0; - send_eth_frame(server, path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + // Start new MI; only safe because no acks are processed during those updates + ccontrol_start_monitoring_interval(cc_state); + } + pthread_spin_unlock(&cc_state->lock); + } + } } - -static void rx_send_acks(struct hercules_server *server, struct receiver_state *rx_state) +static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) { - /* debug_printf("rx_send_acks"); */ - struct hercules_path path; - if(!rx_get_reply_path(rx_state, &path)) { - debug_printf("no reply path"); - return; - } - // XXX: could write ack payload directly to buf, but - // doesnt work nicely with existing fill_rbudp_pkt helper. - struct hercules_control_packet control_pkt = { - .type = CONTROL_PACKET_TYPE_ACK, - }; - - const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); - - // send an empty ACK to keep connection alive until first packet arrives - u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries); - rx_send_ack_pkt(server, &control_pkt, &path); - for(; curr < rx_state->total_chunks;) { - curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries); - if(control_pkt.payload.ack.num_acks == 0) break; - rx_send_ack_pkt(server, &control_pkt, &path); - } + return cc_state->state != pcc_terminated && + cc_state->state != pcc_uninitialized && + cc_state->mi_start + (u64)((cc_state->pcc_mi_duration) * 1e9) >= now; } +/// workers +struct tx_send_p_args { + struct hercules_server *server; + struct xsk_socket_info *xsks[]; +}; -static void rx_trickle_acks(void *arg) { - struct hercules_server *server = arg; +// Read chunk ids from the send queue, fill in packets accorindgly and actually send them. +// This is the function run by the TX worker thread(s). +static void tx_send_p(void *arg) { + struct tx_send_p_args *args = arg; + struct hercules_server *server = args->server; while (1) { - __sync_synchronize(); - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && - session_rx->state == SESSION_STATE_RUNNING) { - struct receiver_state *rx_state = session_rx->rx_state; - // XXX: data races in access to shared rx_state! - atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); - if (atomic_load(&rx_state->last_pkt_rcvd) + - umax64(100 * ACK_RATE_TIME_MS * 1e6, - 3 * rx_state->handshake_rtt) < - get_nsecs()) { - // Transmission timed out - quit_session(session_rx, SESSION_ERROR_TIMEOUT); - } - rx_send_acks(server, rx_state); - if (rx_received_all(rx_state)) { - debug_printf("Received all, done."); - session_rx->state = SESSION_STATE_DONE; - rx_send_acks(server, rx_state); - server->session_rx = NULL; // FIXME leak - atomic_store(&server->session_rx, NULL); - } + struct hercules_session *session_tx = atomic_load(&server->session_tx); + if (session_tx == NULL || atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { + /* debug_printf("Invalid session, dropping sendq unit"); */ + kick_tx_server(server); + continue; } - sleep_nsecs(ACK_RATE_TIME_MS * 1e6); - } -} - -static void rx_send_path_nacks(struct hercules_server *server, struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr) -{ - struct hercules_path path; - if(!rx_get_reply_path(rx_state, &path)) { - debug_printf("no reply path"); - return; - } - - char buf[HERCULES_MAX_PKTSIZE]; - void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); - - // XXX: could write ack payload directly to buf, but - // doesnt work nicely with existing fill_rbudp_pkt helper. - struct hercules_control_packet control_pkt = { - .type = CONTROL_PACKET_TYPE_NACK, - }; - const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); - sequence_number nack_end = path_state->nack_end; - //sequence_number start = nack_end; - bool sent = false; - pthread_spin_lock(&path_state->seq_rcvd.lock); - libbpf_smp_rmb(); - for(u32 curr = path_state->nack_end; curr < path_state->seq_rcvd.num;) { - // Data to send - curr = fill_nack_pkt(curr, &control_pkt.payload.ack, max_entries, &path_state->seq_rcvd); - if(has_more_nacks(curr, &path_state->seq_rcvd)) { - control_pkt.payload.ack.max_seq = 0; - } else { - control_pkt.payload.ack.max_seq = path_state->seq_rcvd.max_set; - } - if(control_pkt.payload.ack.num_acks == 0 && sent) break; - sent = true; // send at least one packet each round - - control_pkt.payload.ack.ack_nr = nr; - control_pkt.payload.ack.timestamp = time; - - if(control_pkt.payload.ack.num_acks != 0) { - nack_end = control_pkt.payload.ack.acks[control_pkt.payload.ack.num_acks - 1].end; + struct send_queue_unit unit; + int ret = send_queue_pop(session_tx->send_queue, &unit); + if (!ret) { + kick_tx_server(server); + continue; + } + u32 num_chunks_in_unit = 0; + for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { + if(unit.paths[i] == UINT8_MAX) { + break; } - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, path_idx, 0, (char *)&control_pkt, - sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); - stitch_checksum(&path, path.header.checksum, buf); - - send_eth_frame(server, &path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + num_chunks_in_unit++; } - libbpf_smp_wmb(); - /* assert(&path_state->seq_rcvd.lock != NULL); */ - /* assert(path_state->seq_rcvd.lock != NULL); */ - // FIXME spurious segfault on unlock - /* pthread_spin_unlock(&path_state->seq_rcvd.lock); */ - path_state->nack_end = nack_end; -} - -// sends the NACKs used for congestion control by the sender -static void rx_send_nacks(struct hercules_server *server, struct receiver_state *rx_state, u64 time, u32 nr) -{ - u8 num_paths = atomic_load(&rx_state->num_tracked_paths); - for(u8 p = 0; p < num_paths; p++) { - rx_send_path_nacks(server, rx_state, &rx_state->path_state[p], p, time, nr); + debug_printf("unit has %d chunks", num_chunks_in_unit); + u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; + memset(frame_addrs, 0xFF, sizeof(frame_addrs)); + for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ + if (i >= num_chunks_in_unit){ + /* debug_printf("not using unit slot %d", i); */ + frame_addrs[0][i] = 0; + } } + /* debug_printf("allocating frames"); */ + allocate_tx_frames(server, frame_addrs); + /* debug_printf("done allocating frames"); */ + // At this point we claimed 7 frames (as many as can fit in a sendq unit) + tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, + frame_addrs, &unit); + /* assert(num_chunks_in_unit == 7); */ + kick_tx_server(server); + } } +// Send NACKs to the sender. Runs in its own thread. static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; while (1) { @@ -2394,11 +1909,7 @@ static void rx_trickle_nacks(void *arg) { } } -struct rx_p_args { - struct hercules_server *server; - struct xsk_socket_info *xsks[]; -}; - +// Receive data packets on the XDP sockets. Runs in the RX worker thread(s) static void *rx_p(void *arg) { struct rx_p_args *args = arg; struct hercules_server *server = args->server; @@ -2411,9 +1922,11 @@ static void *rx_p(void *arg) { i++; } } + + return NULL; } - +// TODO rework static void events_p(void *arg) { debug_printf("event listener thread started"); struct hercules_server *server = arg; @@ -2448,6 +1961,7 @@ static void events_p(void *arg) { if (ret) { debug_printf("new job: %s", fname); // TODO propagate open/map errors instead of quitting + // // TODO check mtu large enough /* if(HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)mtu) { */ /* printf("MTU too small (min: %lu, given: %d)", */ @@ -2676,6 +2190,7 @@ static void events_p(void *arg) { } break; case CONTROL_PACKET_TYPE_NACK: + //nack_trace_push(nack.timestamp, nack.ack_nr) break; default: break; @@ -2687,236 +2202,31 @@ static void events_p(void *arg) { } } -#define DEBUG_PRINT_PKTS -#ifdef DEBUG_PRINT_PKTS -// recv indicates whether printed packets should be prefixed with TX or RX -void debug_print_rbudp_pkt(const char *pkt, bool recv) { - struct hercules_header *h = (struct hercules_header *)pkt; - const char *prefix = (recv) ? "RX->" : "<-TX"; - printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, h->path, - h->seqno); - if (h->chunk_idx == UINT_MAX) { - // Control packets - const char *pl = pkt + 9; - struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; - switch (cp->type) { - case CONTROL_PACKET_TYPE_INITIAL: - printf("%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx %u, Flags " - "0x%x, Name length %u [%s]\n", prefix, - cp->payload.initial.filesize, cp->payload.initial.chunklen, - cp->payload.initial.timestamp, cp->payload.initial.path_index, - cp->payload.initial.flags, cp->payload.initial.name_len, cp->payload.initial.name); - break; - case CONTROL_PACKET_TYPE_ACK: - printf("%s ACK ", prefix); - for (int r = 0; r < cp->payload.ack.num_acks; r++){ - printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); - } - printf("\n"); - break; - case CONTROL_PACKET_TYPE_NACK: - printf("%s NACK \n", prefix); - for (int r = 0; r < cp->payload.ack.num_acks; r++){ - printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); - } - printf("\n"); - break; - default: - printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); - break; - } - } else { - printf("%s ** PAYLOAD **\n", prefix); - } -} -#else -inline void debug_print_rbudp_pkt(const char * pkt, bool recv){ - (void)pkt; - (void)recv; - return; -} -#endif - -struct hercules_session *make_session(struct hercules_server *server) { - struct hercules_session *s; - s = calloc(1, sizeof(*s)); - assert(s); - s->state = SESSION_STATE_NONE; - int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, - sizeof(*s->send_queue)); - if (err != 0) { - exit_with_error(NULL, err); - } - init_send_queue(s->send_queue, BATCH_SIZE); - s->last_pkt_sent = 0; - s->last_pkt_rcvd = - get_nsecs(); // Set this to "now" to allow timing out HS at sender (when - // no packet was received yet), once packets are received it - // will be updated accordingly - - return s; -} - -static struct receiver_state *rx_receive_hs(struct hercules_server *server, - struct xsk_socket_info *xsk) { - - u32 idx_rx = 0; - int ignored = 0; - size_t rcvd = xsk_ring_cons__peek(&xsk->rx, 1, &idx_rx); - if (!rcvd) - return NULL; - debug_printf("Received %lu pkts", rcvd); - u64 frame_addrs[BATCH_SIZE]; - for (size_t i = 0; i < rcvd; i++) { - u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; - frame_addrs[i] = addr; - u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; - const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); - const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); - if (rbudp_pkt) { - debug_print_rbudp_pkt(rbudp_pkt, true); - struct rbudp_initial_pkt parsed_pkt; - if (rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len - rbudp_headerlen, &parsed_pkt)) { - debug_printf("parsed initial"); - struct hercules_session *session = make_session(server); - // TODO fill in etherlen - struct receiver_state *rx_state = make_rx_state( - session, NULL, parsed_pkt.filesize, parsed_pkt.chunklen, 1200, false); - session->rx_state = rx_state; - /* rx_handle_initial(rx_state, &parsed_pkt, pkt, 3, rbudp_pkt + rbudp_headerlen, len); */ - xsk_ring_cons__release(&xsk->rx, rcvd); - struct hercules_session s; - submit_rx_frames(&s, xsk->umem, frame_addrs, rcvd); - return rx_state; - } else { - debug_printf("failed parse_initial"); - } - } else { - debug_printf("Dropping unparseable pkt"); - ignored++; - } - } - xsk_ring_cons__release(&xsk->rx, rcvd); - struct hercules_session s; - submit_rx_frames(&s, xsk->umem, frame_addrs, rcvd); - return NULL; -} - - -static int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj) -{ - static const int log_buf_size = 16 * 1024; - char log_buf[log_buf_size]; - int prog_fd; - - char tmp_file[] = "/tmp/hrcbpfXXXXXX"; - int fd = mkstemp(tmp_file); - if(fd < 0) { - return -errno; - } - if(prgm_size != write(fd, prgm, prgm_size)) { - debug_printf("Could not write bpf file"); - return -EXIT_FAILURE; - } - - struct bpf_object *_obj; - if(obj == NULL) { - obj = &_obj; - } - int ret = bpf_prog_load(tmp_file, BPF_PROG_TYPE_XDP, obj, &prog_fd); - debug_printf("error loading file(%s): %d %s", tmp_file, -ret, strerror(-ret)); - int unlink_ret = unlink(tmp_file); - if(0 != unlink_ret) { - fprintf(stderr, "Could not remove temporary file, error: %d", unlink_ret); - } - if(ret != 0) { - printf("BPF log buffer:\n%s", log_buf); - return ret; - } - return prog_fd; -} - -static void set_bpf_prgm_active(struct hercules_server *server, struct hercules_interface *iface, int prog_fd) -{ - int err = bpf_set_link_xdp_fd(iface->ifid, prog_fd, server->config.xdp_flags); - if(err) { - exit_with_error(server, -err); - } - - int ret = bpf_get_link_xdp_id(iface->ifid, &iface->prog_id, server->config.xdp_flags); - if(ret) { - exit_with_error(server, -ret); - } -} - - - -// XXX Workaround: the i40e driver (in zc mode) does not seem to allow sending if no program is loaded. -// Load an XDP program that just passes all packets (i.e. does the same thing as no program). -static int load_xsk_pass(struct hercules_server *server) -{ - int prog_fd; - for(int i = 0; i < server->num_ifaces; i++) { - prog_fd = load_bpf(bpf_prgm_pass, bpf_prgm_pass_size, NULL); - if(prog_fd < 0) { - exit_with_error(server, -prog_fd); - } - - set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); - } - return 0; -} - -static void xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, struct xsk_socket_info *xsk) -{ - int xsk_fd = xsk_socket__fd(xsk->xsk); - if(xsk_fd < 0) { - exit_with_error(server, -xsk_fd); - } - bpf_map_update_elem(map, &index, &xsk_fd, 0); -} - -/* - * Load a BPF program redirecting IP traffic to the XSK. +/** + * Transmit and retransmit chunks that have not been ACKed. + * For each retransmit chunk, wait (at least) one round trip time for the ACK to arrive. + * For large files transfers, this naturally allows to start retransmitting chunks at the beginning + * of the file, while chunks of the previous round at the end of the file are still in flight. + * + * Transmission through different paths is batched (i.e. use the same path within a batch) to prevent the receiver from + * ACKing individual chunks. + * + * The estimates for the ACK-arrival time dont need to be accurate for correctness, i.e. regardless + * of how bad our estimate is, all chunks will be (re-)transmitted eventually. + * - if we *under-estimate* the RTT, we may retransmit chunks unnecessarily + * - waste bandwidth, waste sender disk reads & CPU time, waste receiver CPU time + * - potentially increase overall transmit time because necessary retransmit may be delayed by + * wasted resources + * - if we *over-estimate* the RTT, we wait unnecessarily + * This is only constant overhead per retransmit round, independent of number of packets or send + * rate. + * Thus it seems preferrable to *over-estimate* the ACK-arrival time. + * + * To avoid recording transmit time per chunk, only record start and end time of a transmit round + * and linearly interpolate for each receiver separately. + * This assumes a uniform send rate and that chunks that need to be retransmitted (i.e. losses) + * occur uniformly. */ -static void load_xsk_redirect_userspace(struct hercules_server *server, struct rx_p_args *args[], int num_threads) -{ - debug_printf("Loading XDP program for redirection"); - for(int i = 0; i < server->num_ifaces; i++) { - struct bpf_object *obj; - int prog_fd = load_bpf(bpf_prgm_redirect_userspace, bpf_prgm_redirect_userspace_size, &obj); - if(prog_fd < 0) { - exit_with_error(server, prog_fd); - } - - // push XSKs - int xsks_map_fd = bpf_object__find_map_fd_by_name(obj, "xsks_map"); - if(xsks_map_fd < 0) { - exit_with_error(server, -xsks_map_fd); - } - for(int s = 0; s < num_threads; s++) { - xsk_map__add_xsk(server, xsks_map_fd, s, args[s]->xsks[i]); - } - - // push XSKs meta - int zero = 0; - int num_xsks_fd = bpf_object__find_map_fd_by_name(obj, "num_xsks"); - if(num_xsks_fd < 0) { - exit_with_error(server, -num_xsks_fd); - } - bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); - - // push local address - int local_addr_fd = bpf_object__find_map_fd_by_name(obj, "local_addr"); - if(local_addr_fd < 0) { - exit_with_error(server, -local_addr_fd); - } - bpf_map_update_elem(local_addr_fd, &zero, &server->config.local_addr, 0); - - set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); - } -} - static void *tx_p(void *arg) { struct hercules_server *server = arg; while (1) { @@ -3014,52 +2324,23 @@ static void *tx_p(void *arg) { return NULL; } - - -struct hercules_server * -hercules_init_server(int *ifindices, int num_ifaces, - const struct hercules_app_addr local_addr, int queue, - int xdp_mode, int n_threads, bool configure_queues) { - struct hercules_server *server; - server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); - if (server == NULL) { - exit_with_error(NULL, ENOMEM); - } - debug_printf("local address %llx %x %x", local_addr.ia, local_addr.ip, - local_addr.port); - server->ifindices = ifindices; - server->num_ifaces = num_ifaces; - server->config.queue = queue; - server->n_threads = n_threads; - server->session_rx = NULL; - server->session_tx = NULL; - server->session_tx_counter = 0; - server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); - server->config.local_addr = local_addr; - server->config.configure_queues = configure_queues; - server->enable_pcc = true; - - for (int i = 0; i < num_ifaces; i++) { - server->ifaces[i] = (struct hercules_interface){ - .queue = queue, - .ifid = ifindices[i], - .ethtool_rule = -1, - }; - if_indextoname(ifindices[i], server->ifaces[i].ifname); - debug_printf("using queue %d on interface %s", server->ifaces[i].queue, - server->ifaces[i].ifname); - } - - pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); - - server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); - if (server->control_sockfd == -1) { - exit_with_error(server, 0); - } - debug_printf("init complete"); - return server; +static pthread_t start_thread(struct hercules_server *server, void *(start_routine), void *arg) +{ + pthread_t pt; + int ret = pthread_create(&pt, NULL, start_routine, arg); + if(ret) + exit_with_error(server, ret); + return pt; } +static void join_thread(struct hercules_server *server, pthread_t pt) +{ + int ret = pthread_join(pt, NULL); + if(ret) { + exit_with_error(server, ret); + } +} +/// stats struct path_stats *make_path_stats_buffer(int num_paths) { struct path_stats *path_stats = calloc(1, sizeof(*path_stats) + num_paths * sizeof(path_stats->paths[0])); path_stats->num_paths = num_paths; @@ -3138,111 +2419,9 @@ struct path_stats *make_path_stats_buffer(int num_paths) { /* .rate_limit = 0 */ /* }; */ /* } */ +/// UNASSIGNED -static pthread_t start_thread(struct hercules_server *server, void *(start_routine), void *arg) -{ - pthread_t pt; - int ret = pthread_create(&pt, NULL, start_routine, arg); - if(ret) - exit_with_error(server, ret); - return pt; -} - -static void join_thread(struct hercules_server *server, pthread_t pt) -{ - int ret = pthread_join(pt, NULL); - if(ret) { - exit_with_error(server, ret); - } -} - - -static void xdp_setup(struct hercules_server *server) { - for (int i = 0; i < server->num_ifaces; i++) { - debug_printf("Preparing interface %d", i); - // Prepare UMEM for XSK sockets - void *umem_buf; - int ret = posix_memalign(&umem_buf, getpagesize(), - NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); - if (ret) { - exit_with_error(server, ENOMEM); - } - debug_printf("Allocated umem buffer"); - - struct xsk_umem_info *umem = xsk_configure_umem_server( - server, i, umem_buf, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); - debug_printf("Configured umem"); - - server->ifaces[i].xsks = - calloc(server->n_threads, sizeof(*server->ifaces[i].xsks)); - server->ifaces[i].umem = umem; - submit_initial_tx_frames(server, umem); - submit_initial_rx_frames(server, umem); - debug_printf("umem interface %d %s, queue %d", umem->iface->ifid, - umem->iface->ifname, umem->iface->queue); - if (server->ifaces[i].ifid != umem->iface->ifid) { - debug_printf( - "cannot configure XSK on interface %d with queue on interface %d", - server->ifaces[i].ifid, umem->iface->ifid); - exit_with_error(server, EINVAL); - } - - // Create XSK sockets - for (int t = 0; t < server->n_threads; t++) { - struct xsk_socket_info *xsk; - xsk = calloc(1, sizeof(*xsk)); - if (!xsk) { - exit_with_error(server, ENOMEM); - } - xsk->umem = umem; - - struct xsk_socket_config cfg; - cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; - cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; - cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; - cfg.xdp_flags = server->config.xdp_flags; - cfg.bind_flags = server->config.xdp_mode; - ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, - server->config.queue, umem->umem, &xsk->rx, - &xsk->tx, &umem->fq, &umem->cq, &cfg); - if (ret) { - exit_with_error(server, -ret); - } - ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, - &server->ifaces[i].prog_id, - server->config.xdp_flags); - if (ret) { - exit_with_error(server, -ret); - } - server->ifaces[i].xsks[t] = xsk; - } - server->ifaces[i].num_sockets = server->n_threads; - } - for (int t = 0; t < server->n_threads; t++){ - server->worker_args[t] = malloc(sizeof(*server->worker_args) + server->num_ifaces*sizeof(*server->worker_args[t]->xsks)); - if (server->worker_args[t] == NULL){ - exit_with_error(server, ENOMEM); - } - server->worker_args[t]->server = server; - for (int i = 0; i < server->num_ifaces; i++){ - server->worker_args[t]->xsks[i] = server->ifaces[i].xsks[t]; - } - } - - load_xsk_redirect_userspace(server, server->worker_args, server->n_threads); - // TODO this is not set anywhere, so it will never run - if (server->config.configure_queues){ - configure_rx_queues(server); - } - // TODO when/where is this needed? - // same for rx_state -/* libbpf_smp_rmb(); */ -/* session->tx_state = tx_state; */ -/* libbpf_smp_wmb(); */ - debug_printf("XSK stuff complete"); -} - void hercules_main(struct hercules_server *server) { debug_printf("Hercules main"); @@ -3283,6 +2462,7 @@ void hercules_main(struct hercules_server *server) { // XXX STOP HERE // FIXME make this thread do something } + join_thread(server, tx_p_thread); } void usage(){ @@ -3398,3 +2578,8 @@ int main(int argc, char *argv[]) { hercules_main(server); } + +/// Local Variables: +/// outline-regexp: "/// " +/// eval:(outline-minor-mode 1) +/// End: diff --git a/hercules.h b/hercules.h index df58b1f..df2b56b 100644 --- a/hercules.h +++ b/hercules.h @@ -15,62 +15,257 @@ #ifndef __HERCULES_H__ #define __HERCULES_H__ -#include -#include #include +#include +#include +#include #include -#define MAX_NUM_SOCKETS 256 -#define HERCULES_MAX_HEADERLEN 256 +#include "bpf/src/xsk.h" +#include "congestion_control.h" +#include "frame_queue.h" +#include "packet.h" +#define HERCULES_MAX_HEADERLEN 256 #define HERCULES_MAX_PKTSIZE 9000 - +#define BATCH_SIZE 64 +// Number of frames in UMEM area +#define NUM_FRAMES (4 * 1024) struct hercules_path_header { - const char header[HERCULES_MAX_HEADERLEN]; //!< headerlen bytes - __u16 checksum; //SCION L4 checksum over header with 0 payload + const char header[HERCULES_MAX_HEADERLEN]; //!< headerlen bytes + __u16 checksum; // SCION L4 checksum over header with 0 payload }; -struct hercules_session; - // Path are specified as ETH/IP/UDP/SCION/UDP headers. struct hercules_path { __u64 next_handshake_at; int headerlen; int payloadlen; - int framelen; //!< length of ethernet frame; headerlen + payloadlen + int framelen; //!< length of ethernet frame; headerlen + payloadlen int ifid; struct hercules_path_header header; - atomic_bool enabled; // e.g. when a path has been revoked and no replacement is available, this will be set to false + atomic_bool enabled; // e.g. when a path has been revoked and no + // replacement is available, this will be set to false atomic_bool replaced; }; +/// RECEIVER +// Per-path state at the receiver +struct receiver_state_per_path { + struct bitset seq_rcvd; + sequence_number nack_end; + sequence_number prev_nack_end; + u64 rx_npkts; +}; + +// Information specific to the receiving side of a session +struct receiver_state { + struct hercules_session *session; + atomic_uint_least64_t handshake_rtt; + /** Filesize in bytes */ + size_t filesize; + /** Size of file data (in byte) per packet */ + u32 chunklen; + /** Number of packets that will make up the entire file. Equal to + * `ceil(filesize/chunklen)` */ + u32 total_chunks; + /** Memory mapped file for receive */ + char *mem; + /** Packet size */ + u32 etherlen; + + struct bitset received_chunks; + + // TODO rx_sample can be removed in favour of reply_path + // XXX: reads/writes to this is are a huge data race. Need to sync. + char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; + int rx_sample_len; + int rx_sample_ifid; + // The reply path to use for contacting the sender. This is the reversed + // path of the last initial packet with the SET_RETURN_PATH flag set. + struct hercules_path reply_path; + + // Start/end time of the current transfer + u64 start_time; + u64 end_time; + u64 cts_sent_at; + u64 last_pkt_rcvd; // Timeout detection + + u8 num_tracked_paths; + bool is_pcc_benchmark; + struct receiver_state_per_path path_state[256]; +}; + +// SENDER +struct sender_state_per_receiver { + u64 prev_round_start; + u64 prev_round_end; + u64 prev_slope; + u64 ack_wait_duration; + u32 prev_chunk_idx; + bool finished; + /** Next batch should be sent via this path */ + u8 path_index; + + struct bitset acked_chunks; + atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns + + u32 num_paths; + u32 return_path_idx; + struct hercules_app_addr addr; + struct hercules_path *paths; + struct ccontrol_state *cc_states; + bool cts_received; +}; + +struct sender_state { + struct hercules_session *session; + + // State for transmit rate control + size_t tx_npkts_queued; + u64 prev_rate_check; + size_t prev_tx_npkts_queued; + + /** Filesize in bytes */ + size_t filesize; + char filename[100]; + /** Size of file data (in byte) per packet */ + u32 chunklen; + /** Number of packets that will make up the entire file. Equal to + * `ceil(filesize/chunklen)` */ + u32 total_chunks; + /** Memory mapped file for receive */ + char *mem; + + _Atomic u32 rate_limit; + + // Start/end time of the current transfer + u64 start_time; + u64 end_time; + + u32 num_receivers; + struct sender_state_per_receiver *receiver; + u32 max_paths_per_rcvr; + + // shared with Go + struct hercules_path *shd_paths; + const int *shd_num_paths; + + atomic_bool has_new_paths; +}; + +// SESSION +// Some states are used only by the TX/RX side and are marked accordingly +enum session_state { + SESSION_STATE_NONE, + SESSION_STATE_PENDING, //< (TX) Need to send HS and repeat until TO, + // waiting + // for a reflected HS packet + SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and + // CTS + SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS + SESSION_STATE_RUNNING, //< Transfer in progress + SESSION_STATE_DONE, //< Transfer done (or cancelled with error) +}; + +enum session_error { + SESSION_ERROR_OK, //< No error, transfer completed successfully + SESSION_ERROR_TIMEOUT, //< Session timed out + SESSION_ERROR_PCC, +}; + +// A session is a transfer between one sender and one receiver +struct hercules_session { + struct receiver_state *rx_state; + struct sender_state *tx_state; + enum session_state state; + enum session_error error; + struct send_queue *send_queue; + u64 last_pkt_sent; + u64 last_pkt_rcvd; -// Connection information -struct hercules_app_addr { - /** SCION IA. In network byte order. */ - __u64 ia; - /** SCION IP. In network byte order. */ - __u32 ip; - /** SCION/UDP port (L4, application). In network byte order. */ - __u16 port; + struct hercules_app_addr destination; + int num_paths; + struct hercules_path *paths_to_dest; + // State for stat dump + /* size_t rx_npkts; */ + /* size_t tx_npkts; */ }; -typedef __u64 ia; +// SERVER +struct hercules_interface { + char ifname[IFNAMSIZ]; + int ifid; + int queue; + u32 prog_id; + int ethtool_rule; + u32 num_sockets; + struct xsk_umem_info *umem; + struct xsk_socket_info **xsks; +}; + +// Config determined at program start +struct hercules_config { + u32 xdp_flags; + int xdp_mode; + int queue; + bool configure_queues; + struct hercules_app_addr local_addr; +}; +struct hercules_server { + struct hercules_config config; + int control_sockfd; // AF_PACKET socket used for control traffic + int n_threads; + struct rx_p_args **worker_args; + struct hercules_session *session_tx; // Current TX session + u64 session_tx_counter; + struct hercules_session *session_rx; // Current RX session + int max_paths; + int rate_limit; + bool enable_pcc; + int *ifindices; + int num_ifaces; + struct hercules_interface ifaces[]; +}; -struct hercules_server; -struct hercules_session *hercules_init(int *ifindices, int num_ifaces, struct hercules_app_addr local_addr, int queue, int mtu); -void hercules_close(struct hercules_server *server); +/// XDP +struct xsk_umem_info { + struct xsk_ring_prod fq; + struct xsk_ring_cons cq; + struct frame_queue available_frames; + pthread_spinlock_t lock; + struct xsk_umem *umem; + void *buffer; + struct hercules_interface *iface; +}; + +struct xsk_socket_info { + struct xsk_ring_cons rx; + struct xsk_ring_prod tx; + struct xsk_umem_info *umem; + struct xsk_socket *xsk; +}; + +typedef int xskmap; + +/// Thread args +struct rx_p_args { + struct hercules_server *server; + struct xsk_socket_info *xsks[]; +}; +/// STATS TODO struct path_stats_path { - __u64 total_packets; - __u64 pps_target; + __u64 total_packets; + __u64 pps_target; }; struct path_stats { - __u32 num_paths; - struct path_stats_path paths[1]; // XXX this is actually used as a dynamic struct member; the 1 is needed for CGO + __u32 num_paths; + struct path_stats_path paths[1]; // XXX this is actually used as a dynamic + // struct member; the 1 is needed for CGO }; struct path_stats *make_path_stats_buffer(int num_paths); @@ -86,42 +281,10 @@ struct hercules_stats { __u32 framelen; __u32 chunklen; __u32 total_chunks; - __u32 completed_chunks; //!< either number of acked (for sender) or received (for receiver) chunks + __u32 completed_chunks; //!< either number of acked (for sender) or + //!< received (for receiver) chunks __u32 rate_limit; }; -// Get the current stats of a running transfer. -// Returns stats with `start_time==0` if no transfer is active. -struct hercules_stats hercules_get_stats(struct hercules_session *session, struct path_stats* path_stats); - -void allocate_path_headers(struct hercules_session *session, struct hercules_path *path, int num_headers); -void push_hercules_tx_paths(struct hercules_session *session); - -// locks for working with the shared path memory -void acquire_path_lock(void); -void free_path_lock(void); - -// Initiate transfer of file over the given path. -// Synchronous; returns when the transfer has been completed or if it has failed. -// Does not take ownership of `paths`. -struct hercules_stats -hercules_tx(struct hercules_session *session, const char *filename, int offset, int length, - const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, - const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc, int xdp_mode, int num_threads); - -struct receiver_state; -// Initiate receiver, waiting for a transmitter to initiate the file transfer. -struct hercules_stats hercules_rx(struct hercules_session *session, const char *filename, int xdp_mode, - bool configure_queues, int accept_timeout, int num_threads, bool is_pcc_benchmark); - -struct hercules_server * -hercules_init_server(int *ifindices, int num_ifaces, - const struct hercules_app_addr local_addr, int queue, - int xdp_mode, int n_threads, bool configure_queues); -void hercules_main(struct hercules_server *server); -void hercules_main_sender(struct hercules_server *server, int xdp_mode, const struct hercules_app_addr *destinations, struct hercules_path *paths_per_dest, int num_dests, const int *num_paths, int max_paths, int max_rate_limit, bool enable_pcc); -void debug_print_rbudp_pkt(const char *pkt, bool recv); -struct hercules_session *make_session(struct hercules_server *server); - -#endif // __HERCULES_H__ +#endif // __HERCULES_H__ diff --git a/packet.h b/packet.h index 04d3eb5..8896000 100644 --- a/packet.h +++ b/packet.h @@ -27,15 +27,15 @@ // https://stackoverflow.com/questions/15442536/why-ip-header-variable-declarations-are-swapped-depending-on-byte-order struct scionhdr { #if __BYTE_ORDER == __LITTLE_ENDIAN - unsigned int version: 4; - unsigned int qos: 8; - unsigned int flow_id: 20; + unsigned int version : 4; + unsigned int qos : 8; + unsigned int flow_id : 20; #elif __BYTE_ORDER == __BIG_ENDIAN - unsigned int flow_id:20; - unsigned int qos:8; - unsigned int version:4; + unsigned int flow_id : 20; + unsigned int qos : 8; + unsigned int version : 4; #else -# error "Please fix " +#error "Please fix " #endif /** Type of the next header */ __u8 next_header; @@ -46,13 +46,13 @@ struct scionhdr { /** SCION path type */ __u8 path_type; /** Type of destination address */ - unsigned int dst_type: 2; + unsigned int dst_type : 2; /** Type of source address */ - unsigned int src_type: 2; + unsigned int src_type : 2; /** Length of destination address */ - unsigned int dst_len: 2; + unsigned int dst_len : 2; /** Length of source address */ - unsigned int src_len: 2; + unsigned int src_len : 2; __u16 reserved; }; @@ -64,9 +64,9 @@ struct scionaddrhdr_ipv4 { }; struct hercules_header { - __u32 chunk_idx; - __u8 path; - __u32 seqno; + __u32 chunk_idx; + __u8 path; + __u32 seqno; }; // Structure of first RBUDP packet sent by sender. @@ -89,14 +89,14 @@ struct rbudp_initial_pkt { // Structure of ACK RBUDP packets sent by the receiver. // Integers all transmitted in little endian (host endianness). struct rbudp_ack_pkt { - __u8 num_acks; //!< number of (valid) entries in `acks` + __u8 num_acks; //!< number of (valid) entries in `acks` __u32 max_seq; __u32 ack_nr; __u64 timestamp; struct { - __u32 begin; //!< index of first chunk that is ACKed with this range - __u32 end; //!< one-past-the-last chunk that is ACKed with this range - } acks[256]; //!< list of ranges that are ACKed + __u32 begin; //!< index of first chunk that is ACKed with this range + __u32 end; //!< one-past-the-last chunk that is ACKed with this range + } acks[256]; //!< list of ranges that are ACKed }; #define CONTROL_PACKET_TYPE_INITIAL 0 @@ -113,4 +113,20 @@ struct hercules_control_packet { #pragma pack(pop) -#endif //HERCULES_SCION_H +// XXX This is placed here (instead of in hercules.h) to avoid clang from +// complainig about atomics when building redirect_userspace.c with hercules.h +// included. + +// Connection information +struct hercules_app_addr { + /** SCION IA. In network byte order. */ + __u64 ia; + /** SCION IP. In network byte order. */ + __u32 ip; + /** SCION/UDP port (L4, application). In network byte order. */ + __u16 port; +}; +typedef __u64 ia; +#define MAX_NUM_SOCKETS 256 + +#endif // HERCULES_SCION_H From dd0ae792c7fa85930b0bff0876d734ce213ed2c8 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 28 May 2024 17:40:51 +0200 Subject: [PATCH 015/112] ensure return values are checked, group tx/rx code checked common section checked receiver section checked sender section --- hercules.c | 471 ++++++++++++++++++++++++++++------------------------- hercules.h | 14 +- monitor.c | 2 +- monitor.h | 2 +- packet.h | 10 +- xdp.c | 378 ++++++++++++++++++++++++++++++++++++++++++ xdp.h | 45 +++++ 7 files changed, 692 insertions(+), 230 deletions(-) create mode 100644 xdp.c create mode 100644 xdp.h diff --git a/hercules.c b/hercules.c index 7e6b7fc..903ac61 100644 --- a/hercules.c +++ b/hercules.c @@ -69,16 +69,14 @@ #define ACK_RATE_TIME_MS 100 // send ACKS after at most X milliseconds -static const int rbudp_headerlen = sizeof(u32) + sizeof(u8) + sizeof(sequence_number); -static const u64 tx_handshake_retry_after = 1e9; -static const u64 tx_handshake_timeout = 5e9; +static const int rbudp_headerlen = sizeof(struct hercules_header); +static const u64 session_timeout = 10e9; // 10 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path // TODO move and see if we can't use this from only 1 thread so no locks int usock; pthread_spinlock_t usock_lock; - // Fill packet with n bytes from data and pad with zeros to payloadlen. static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence_number seqnr, const char *data, size_t n, @@ -93,7 +91,7 @@ static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initia static struct hercules_session *make_session(struct hercules_server *server); -/// COMMON +/// OK COMMON /** * @param scionaddrhdr @@ -135,9 +133,10 @@ static inline struct hercules_interface *get_interface_by_id(struct hercules_ser } // Mark the session as done and store why it was stopped. -static inline void quit_session(struct hercules_session *s, enum session_error err){ - s->error = err; - s->state = SESSION_STATE_DONE; +static inline void quit_session(struct hercules_session *s, + enum session_error err) { + s->error = err; + s->state = SESSION_STATE_DONE; } static u32 ack__max_num_entries(u32 len) @@ -151,10 +150,10 @@ static u32 ack__len(const struct rbudp_ack_pkt *ack) return sizeof(ack->num_acks) + sizeof(ack->ack_nr) + sizeof(ack->max_seq) + sizeof(ack->timestamp) + ack->num_acks * sizeof(ack->acks[0]); } -// Send the packet pointed to by *buf via the server's control socket. +// Send the *raw* packet pointed to by buf via the server's control socket. // Used for transmitting control packets. -static void send_eth_frame(struct hercules_server *server, const struct hercules_path *path, void *buf) -{ +static void send_eth_frame(struct hercules_server *server, + const struct hercules_path *path, void *buf) { struct sockaddr_ll addr; // Index of the network device addr.sll_ifindex = path->ifid; @@ -163,8 +162,9 @@ static void send_eth_frame(struct hercules_server *server, const struct hercules // Destination MAC; extracted from ethernet header memcpy(addr.sll_addr, buf, ETH_ALEN); - ssize_t ret = sendto(server->control_sockfd, buf, path->framelen, 0, (struct sockaddr *) &addr, sizeof(struct sockaddr_ll)); - if(ret == -1) { + ssize_t ret = sendto(server->control_sockfd, buf, path->framelen, 0, + (struct sockaddr *)&addr, sizeof(struct sockaddr_ll)); + if (ret == -1) { exit_with_error(server, errno); } } @@ -219,16 +219,19 @@ void debug_print_rbudp_pkt(const char * pkt, bool recv){ } #endif -// Initialise a new session +// Initialise a new session. Returns null in case of error. static struct hercules_session *make_session(struct hercules_server *server) { struct hercules_session *s; s = calloc(1, sizeof(*s)); - assert(s); + if (s == NULL) { + return NULL; + } s->state = SESSION_STATE_NONE; int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, sizeof(*s->send_queue)); if (err != 0) { - exit_with_error(NULL, err); + free(s); + return NULL; } init_send_queue(s->send_queue, BATCH_SIZE); s->last_pkt_sent = 0; @@ -236,55 +239,57 @@ static struct hercules_session *make_session(struct hercules_server *server) { get_nsecs(); // Set this to "now" to allow timing out HS at sender // (when no packet was received yet), once packets are // received it will be updated accordingly - return s; } -// Initialise the Hercules server -struct hercules_server * -hercules_init_server(int *ifindices, int num_ifaces, - const struct hercules_app_addr local_addr, int queue, - int xdp_mode, int n_threads, bool configure_queues) { - struct hercules_server *server; - server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); - if (server == NULL) { - exit_with_error(NULL, ENOMEM); - } - debug_printf("local address %llx %x %x", local_addr.ia, local_addr.ip, - local_addr.port); - server->ifindices = ifindices; - server->num_ifaces = num_ifaces; - server->config.queue = queue; - server->n_threads = n_threads; - server->session_rx = NULL; - server->session_tx = NULL; - server->session_tx_counter = 0; - server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); - server->config.local_addr = local_addr; - server->config.configure_queues = configure_queues; - server->enable_pcc = false; - - for (int i = 0; i < num_ifaces; i++) { - server->ifaces[i] = (struct hercules_interface){ - .queue = queue, - .ifid = ifindices[i], - .ethtool_rule = -1, - }; - if_indextoname(ifindices[i], server->ifaces[i].ifname); - debug_printf("using queue %d on interface %s", server->ifaces[i].queue, - server->ifaces[i].ifname); - } - - pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); - - server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); - if (server->control_sockfd == -1) { - exit_with_error(server, 0); - } - debug_printf("init complete"); - return server; -} -/// PACKET PARSING +// Initialise the Hercules server. If this runs into trouble we just exit as +// there's no point in continuing. +struct hercules_server *hercules_init_server( + int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, + int queue, int xdp_mode, int n_threads, bool configure_queues) { + struct hercules_server *server; + server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); + if (server == NULL) { + exit_with_error(NULL, ENOMEM); + } + server->ifindices = ifindices; + server->num_ifaces = num_ifaces; + server->config.queue = queue; + server->n_threads = n_threads; + server->session_rx = NULL; + server->session_tx = NULL; + server->session_tx_counter = 0; + server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); + if (server->worker_args == NULL){ + exit_with_error(NULL, ENOMEM); + } + server->config.local_addr = local_addr; + server->config.configure_queues = configure_queues; + server->enable_pcc = + false; // TODO this should be per-path or at least per-transfer + + for (int i = 0; i < num_ifaces; i++) { + server->ifaces[i] = (struct hercules_interface){ + .queue = queue, + .ifid = ifindices[i], + .ethtool_rule = -1, + }; + if_indextoname(ifindices[i], server->ifaces[i].ifname); + debug_printf("using queue %d on interface %s", server->ifaces[i].queue, + server->ifaces[i].ifname); + } + + pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); + + server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); + if (server->control_sockfd == -1) { + exit_with_error(server, 0); + } + debug_printf("init complete"); + return server; +} + +/// OK PACKET PARSING // XXX: from lib/scion/udp.c /* * Calculate UDP checksum @@ -527,6 +532,7 @@ static void stitch_checksum(const struct hercules_path *path, u16 precomputed_ch mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); } + // Fill packet with n bytes from data and pad with zeros to payloadlen. static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence_number seqnr, const char *data, size_t n, @@ -541,6 +547,8 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, } debug_print_rbudp_pkt(rbudp_pkt, false); } + +// Parse an initial (HS) packet and copy its contents to *parsed_pkt static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt) { struct hercules_control_packet control_pkt; @@ -556,7 +564,8 @@ static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initia memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt) + control_pkt.payload.initial.name_len); return true; } -/// RECEIVER + +/// OK RECEIVER static bool rx_received_all(const struct receiver_state *rx_state) { @@ -607,7 +616,8 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p } else { fprintf(stderr, "sequence number overflow %d / %d\n", seqnr, rx_state->path_state[path_idx].seq_rcvd.num); - exit(EXIT_FAILURE); + quit_session(rx_state->session, SESSION_ERROR_SEQNO_OVERFLOW); + return false; } } bitset__set_mt_safe(&rx_state->path_state[path_idx].seq_rcvd, seqnr); @@ -676,6 +686,7 @@ fill_nack_pkt(sequence_number first, struct rbudp_ack_pkt *ack, size_t max_num_a ack->num_acks = e; return curr; } + static bool has_more_nacks(sequence_number curr, struct bitset *seqs) { u32 begin = bitset__scan_neg(seqs, curr); @@ -683,9 +694,6 @@ static bool has_more_nacks(sequence_number curr, struct bitset *seqs) return end < seqs->num; } -static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, - int ifid, const char *payload, int payloadlen); - static void submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames) { @@ -707,53 +715,51 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c pthread_spin_unlock(&umem->lock); } -static void rx_receive_batch(struct receiver_state *rx_state, struct xsk_socket_info *xsk) -{ - /* debug_printf("receive batch"); */ +// Read a batch of data packets from the XSK +static void rx_receive_batch(struct receiver_state *rx_state, + struct xsk_socket_info *xsk) { u32 idx_rx = 0; int ignored = 0; size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); - if(!rcvd){ + if (!rcvd) { return; } // optimistically update receive timestamp u64 now = get_nsecs(); u64 old_last_pkt_rcvd = atomic_load(&rx_state->last_pkt_rcvd); - if(old_last_pkt_rcvd < now) { - atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, &old_last_pkt_rcvd, now); + if (old_last_pkt_rcvd < now) { + atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, + &old_last_pkt_rcvd, now); } u64 frame_addrs[BATCH_SIZE]; - for(size_t i = 0; i < rcvd; i++) { + for (size_t i = 0; i < rcvd; i++) { u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; frame_addrs[i] = addr; u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); - if(rbudp_pkt) { + if (rbudp_pkt) { debug_print_rbudp_pkt(rbudp_pkt, true); - if(!handle_rbudp_data_pkt(rx_state, rbudp_pkt, len - (rbudp_pkt - pkt))) { - struct rbudp_initial_pkt initial; - if(rbudp_parse_initial(rbudp_pkt + rbudp_headerlen, len, &initial)) { - /* rx_handle_initial(rx_state, &initial, pkt, xsk->umem->iface->ifid, rbudp_pkt, (int) len - (int) (rbudp_pkt - pkt)); */ - } else { - ignored++; - } + if (!handle_rbudp_data_pkt(rx_state, rbudp_pkt, + len - (rbudp_pkt - pkt))) { + debug_printf("Non-data packet on XDP socket? Ignoring."); + ignored++; } } else { - debug_printf("ignoring packet failed parse"); + debug_printf("Unparseable packet on XDP socket, ignoring"); ignored++; } } xsk_ring_cons__release(&xsk->rx, rcvd); - /* atomic_fetch_add(&rx_state->session->rx_npkts, (rcvd - ignored)); */ + atomic_fetch_add(&rx_state->session->rx_npkts, (rcvd - ignored)); submit_rx_frames(rx_state->session, xsk->umem, frame_addrs, rcvd); } -// TODO should return error instead of quitting -static char *rx_mmap(struct hercules_server *server, const char *pathname, size_t filesize) -{ + +// Prepare a file and memory mapping to receive a file +static char *rx_mmap(const char *pathname, size_t filesize) { debug_printf("mmap file: %s", pathname); int ret; /*ret = unlink(pathname); @@ -761,29 +767,38 @@ static char *rx_mmap(struct hercules_server *server, const char *pathname, size_ exit_with_error(server, errno); }*/ int f = open(pathname, O_RDWR | O_CREAT | O_EXCL, 0664); - if(f == -1 && errno == EEXIST) { + if (f == -1 && errno == EEXIST) { f = open(pathname, O_RDWR | O_EXCL); } - if(f == -1) { - exit_with_error(server, errno); + if (f == -1) { + return NULL; } - ret = fallocate(f, 0, 0, filesize); // Will fail on old filesystems (ext3) - if(ret) { - exit_with_error(server, errno); + ret = fallocate(f, 0, 0, filesize); // Will fail on old filesystems (ext3) + if (ret) { + close(f); + return NULL; } char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); - if(mem == MAP_FAILED) { - exit_with_error(server, errno); + if (mem == MAP_FAILED) { + close(f); + return NULL; } + // TODO Shouldn't we keep the file open until the transfer is finished to + // prevent it being messed with? close(f); return mem; } -static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t filesize, int chunklen, int etherlen, - bool is_pcc_benchmark) -{ +// Create new receiver state. Returns null in case of error. +static struct receiver_state *make_rx_state(struct hercules_session *session, + char *filename, size_t filesize, + int chunklen, int etherlen, + bool is_pcc_benchmark) { struct receiver_state *rx_state; rx_state = calloc(1, sizeof(*rx_state)); + if (rx_state == NULL){ + return NULL; + } rx_state->session = session; rx_state->filesize = filesize; rx_state->chunklen = chunklen; @@ -793,48 +808,56 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, ch rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; - rx_state->mem = rx_mmap(NULL, filename, filesize); + rx_state->mem = rx_mmap(filename, filesize); + if (rx_state->mem == NULL) { + free(rx_state); + return NULL; + } rx_state->etherlen = etherlen; return rx_state; } -static bool rx_update_reply_path(struct receiver_state *rx_state){ - // Get reply path for sending ACKs: - // - // XXX: race reading from shared mem. - // Try to make a quick copy to at least limit the carnage. + +// Update the reply path using the header from a received packet. +// The packet is sent to the monior, which will return a new header with the +// path reversed. +static bool rx_update_reply_path( + struct receiver_state *rx_state, int ifid, int rx_sample_len, + const char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]) { debug_printf("Updating reply path"); - if(!rx_state) { + if (!rx_state) { debug_printf("ERROR: invalid rx_state"); return false; } - int rx_sample_len = rx_state->rx_sample_len; assert(rx_sample_len > 0); assert(rx_sample_len <= XSK_UMEM__DEFAULT_FRAME_SIZE); - char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; - memcpy(rx_sample_buf, rx_state->rx_sample_buf, rx_sample_len); pthread_spin_lock(&usock_lock); // TODO writing to reply path needs sync? - int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, rx_state->etherlen, &rx_state->reply_path); + int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, + rx_state->etherlen, &rx_state->reply_path); pthread_spin_unlock(&usock_lock); - if(!ret) { + if (!ret) { return false; } - // XXX Do we always want to reply from the interface the packet was received on? + // XXX Do we always want to reply from the interface the packet was received + // on? // TODO The monitor also returns an interface id (from route lookup) - rx_state->reply_path.ifid = rx_state->rx_sample_ifid; + rx_state->reply_path.ifid = ifid; return true; } - -static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) -{ +// Return a copy of the currently stored reply path. +static bool rx_get_reply_path(struct receiver_state *rx_state, + struct hercules_path *path) { memcpy(path, &rx_state->reply_path, sizeof(*path)); return true; } -static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *pld) -{ +// Reflect the received initial packet back to the sender. The sent packet is +// identical to the one received, but has the HS_CONFIRM flag set. +static void rx_send_rtt_ack(struct hercules_server *server, + struct receiver_state *rx_state, + struct rbudp_initial_pkt *pld) { struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { debug_printf("no return path"); @@ -848,6 +871,7 @@ static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_stat .type = CONTROL_PACKET_TYPE_INITIAL, .payload.initial = *pld, }; + /* strncpy(control_pkt.payload.initial.name, pld->name, pld->name_len); */ control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, @@ -855,25 +879,32 @@ static void rx_send_rtt_ack(struct hercules_server *server, struct receiver_stat stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + atomic_fetch_add(&rx_state->session->tx_npkts, 1); } -static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, - int ifid, const char *payload, int payloadlen) -{ +// Handle a received HS packet by reflecting it back to its sender and update +// the session's reply path if the corresponding flag was set +static void rx_handle_initial(struct hercules_server *server, + struct receiver_state *rx_state, + struct rbudp_initial_pkt *initial, + const char *buf, int ifid, const char *payload, + int payloadlen) { debug_printf("handling initial"); const int headerlen = (int)(payload - buf); - if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - debug_printf("setting rx sample"); - set_rx_sample(rx_state, ifid, buf, headerlen + payloadlen); - rx_update_reply_path(rx_state); + if (initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + rx_update_reply_path(rx_state, ifid, headerlen + payloadlen, buf); } - - rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize - rx_state->cts_sent_at = get_nsecs(); // FIXME why is this here? + rx_send_rtt_ack(server, rx_state, + initial); // echo back initial pkt to ACK filesize + rx_state->cts_sent_at = get_nsecs(); // FIXME why is this here? } -static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) -{ + +// Send an empty ACK, indicating to the sender that it may start sending data +// packets. +// XXX This is not strictly necessary, I think. Once the ACK sender thread sees +// the session it will start sending ACKs, which will also be empty. +static void rx_send_cts_ack(struct hercules_server *server, + struct receiver_state *rx_state) { debug_printf("Send CTS ACK"); struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { @@ -881,8 +912,6 @@ static void rx_send_cts_ack(struct hercules_server *server, struct receiver_stat return; } - debug_printf("got reply path, idx %d", path.ifid); - char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); @@ -894,27 +923,32 @@ static void rx_send_cts_ack(struct hercules_server *server, struct receiver_stat fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); - send_eth_frame(server, &path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + atomic_fetch_add(&rx_state->session->tx_npkts, 1); } -static void rx_send_ack_pkt(struct hercules_server *server, struct hercules_control_packet *control_pkt, - struct hercules_path *path) { +// TODO some other functions repeat this code +// Send the given control packet via the server's control socket. +static void send_control_pkt(struct hercules_server *server, + struct hercules_session *session, + struct hercules_control_packet *control_pkt, + struct hercules_path *path) { char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, - sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), path->payloadlen); + fill_rbudp_pkt( + rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, + sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), + path->payloadlen); stitch_checksum(path, path->header.checksum, buf); send_eth_frame(server, path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + atomic_fetch_add(&session->tx_npkts, 1); } +// Send as many ACK packets as necessary to convey all received packet ranges static void rx_send_acks(struct hercules_server *server, struct receiver_state *rx_state) { - /* debug_printf("rx_send_acks"); */ struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { debug_printf("no reply path"); @@ -930,43 +964,14 @@ static void rx_send_acks(struct hercules_server *server, struct receiver_state * // send an empty ACK to keep connection alive until first packet arrives u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries); - rx_send_ack_pkt(server, &control_pkt, &path); + send_control_pkt(server, rx_state->session, &control_pkt, &path); for(; curr < rx_state->total_chunks;) { curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries); if(control_pkt.payload.ack.num_acks == 0) break; - rx_send_ack_pkt(server, &control_pkt, &path); + send_control_pkt(server, rx_state->session, &control_pkt, &path); } } -static void rx_trickle_acks(void *arg) { - struct hercules_server *server = arg; - while (1) { - __sync_synchronize(); - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && - session_rx->state == SESSION_STATE_RUNNING) { - struct receiver_state *rx_state = session_rx->rx_state; - // XXX: data races in access to shared rx_state! - atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); - if (atomic_load(&rx_state->last_pkt_rcvd) + - umax64(100 * ACK_RATE_TIME_MS * 1e6, - 3 * rx_state->handshake_rtt) < - get_nsecs()) { - // Transmission timed out - quit_session(session_rx, SESSION_ERROR_TIMEOUT); - } - rx_send_acks(server, rx_state); - if (rx_received_all(rx_state)) { - debug_printf("Received all, done."); - session_rx->state = SESSION_STATE_DONE; - rx_send_acks(server, rx_state); - server->session_rx = NULL; // FIXME leak - atomic_store(&server->session_rx, NULL); - } - } - sleep_nsecs(ACK_RATE_TIME_MS * 1e6); - } -} static void rx_send_path_nacks(struct hercules_server *server, struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr) { @@ -1012,13 +1017,11 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); - /* atomic_fetch_add(&rx_state->session->tx_npkts, 1); */ + atomic_fetch_add(&rx_state->session->tx_npkts, 1); } libbpf_smp_wmb(); - /* assert(&path_state->seq_rcvd.lock != NULL); */ - /* assert(path_state->seq_rcvd.lock != NULL); */ // FIXME spurious segfault on unlock - /* pthread_spin_unlock(&path_state->seq_rcvd.lock); */ + pthread_spin_unlock(&path_state->seq_rcvd.lock); path_state->nack_end = nack_end; } @@ -1031,7 +1034,7 @@ static void rx_send_nacks(struct hercules_server *server, struct receiver_state } } -/// SENDER +/// OK SENDER static bool tx_acked_all(const struct sender_state *tx_state) { for(u32 r = 0; r < tx_state->num_receivers; r++) { @@ -1042,6 +1045,8 @@ static bool tx_acked_all(const struct sender_state *tx_state) return true; } +// Submitting the frames to the TX ring does not mean they will be sent immediately, +// this forces all submitted packets to be sent so we can get the frames back static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) { int ret; @@ -1060,12 +1065,14 @@ static void kick_all_tx(struct hercules_server *server, struct hercules_interfac kick_tx(server, iface->xsks[s]); } } + static void kick_tx_server(struct hercules_server *server){ for (int i = 0; i < server->num_ifaces; i++){ kick_all_tx(server, &server->ifaces[i]); } } + static void tx_register_acks(const struct rbudp_ack_pkt *ack, struct sender_state_per_receiver *rcvr) { for(uint16_t e = 0; e < ack->num_acks; ++e) { @@ -1096,7 +1103,7 @@ static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_ } frame_queue__push(&umem->available_frames, num); xsk_ring_cons__release(&umem->cq, entries); - /* atomic_fetch_add(&server->tx_npkts, entries); */ + atomic_fetch_add(&server->session_tx->tx_npkts, entries); } } @@ -1141,6 +1148,7 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ pthread_spin_unlock(&cc_state->lock); } +// TODO need to call this somewhere bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct sender_state_per_receiver *rcvr) { bool updated = false; @@ -1203,8 +1211,10 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path stitch_checksum(path, path->header.checksum, buf); send_eth_frame(server, path, buf); - /* atomic_fetch_add(&server->tx_npkts, 1); */ + atomic_fetch_add(&server->session_tx->tx_npkts, 1); } + +// TODO do something instead of spinning until time is up static void rate_limit_tx(struct sender_state *tx_state) { if(tx_state->prev_tx_npkts_queued + RATE_LIMIT_CHECK > tx_state->tx_npkts_queued) @@ -1230,13 +1240,13 @@ static void rate_limit_tx(struct sender_state *tx_state) tx_state->prev_rate_check = now; tx_state->prev_tx_npkts_queued = tx_state->tx_npkts_queued; } + void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { u64 now = get_nsecs(); struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; for (u32 p = 0; p < rcvr->num_paths; p++) { struct hercules_path *path = &rcvr->paths[p]; - /* debug_printf("Checking path %d/%d", p, rcvr->num_paths); */ if (path->enabled) { u64 handshake_at = atomic_load(&path->next_handshake_at); if (handshake_at < now) { @@ -1253,6 +1263,7 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t } } } + // TODO static void update_hercules_tx_paths(struct sender_state *tx_state) { @@ -1322,7 +1333,6 @@ static void update_hercules_tx_paths(struct sender_state *tx_state) static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { - /* assert(num_frames == 7); */ pthread_spin_lock(&iface->umem->lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); while(reserved != num_frames) { @@ -1376,7 +1386,7 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s u32 idx; if(xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != num_chunks_in_unit) { - // As there are less frames in the loop than slots in the TX ring, this should not happen + // As there are fewer frames in the loop than slots in the TX ring, this should not happen exit_with_error(NULL, EINVAL); } @@ -1413,14 +1423,7 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, seqnr, tx_state->mem + chunk_start, len, path->payloadlen); stitch_checksum(path, path->header.checksum, pkt); } - - /* debug_printf("submitting %d chunks in unit", num_chunks_in_unit); */ xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); - if (num_chunks_in_unit != SEND_QUEUE_ENTRIES_PER_UNIT){ - /* debug_printf("Submitting only %d frames", num_chunks_in_unit); */ - } - __sync_synchronize(); - /* debug_printf("submit returns"); */ } static inline void tx_handle_send_queue_unit(struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], @@ -1434,10 +1437,9 @@ static inline void tx_handle_send_queue_unit(struct hercules_server *server, str } static void -produce_batch(struct hercules_server *server, struct hercules_session *session, struct sender_state *tx_state, const u8 *path_by_rcvr, const u32 *chunks, +produce_batch(struct hercules_server *server, struct hercules_session *session, const u8 *path_by_rcvr, const u32 *chunks, const u8 *rcvr_by_chunk, u32 num_chunks) { - (void)tx_state; // FIXME u32 chk; u32 num_chunks_in_unit; struct send_queue_unit *unit = NULL; @@ -1480,11 +1482,11 @@ static inline void allocate_tx_frames(struct hercules_server *server, break; } } - /* debug_printf("claiming %d frames", num_frames); */ claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); } } -// Collect path rate limits + +// Compute rate limit for the path currently marked active static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { u32 allowed_chunks = 0; u64 now = get_nsecs(); @@ -1492,22 +1494,18 @@ static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { if (!tx_state->receiver[0] .paths[tx_state->receiver[0].path_index] .enabled) { - debug_printf("path not enabled"); return 0; // if a receiver does not have any enabled paths, we can // actually end up here ... :( } if (tx_state->receiver[0].cc_states != NULL) { // use PCC - debug_printf("using pcc"); struct ccontrol_state *cc_state = &tx_state->receiver[0].cc_states[tx_state->receiver[0].path_index]; allowed_chunks = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); } else { // no path-based limit - debug_printf("no pcc"); allowed_chunks = BATCH_SIZE; } - debug_printf("allowed %d chunks", allowed_chunks); return allowed_chunks; } @@ -1525,6 +1523,7 @@ static u32 shrink_sending_rates(struct sender_state *tx_state, u32 *max_chunks_p } return total_chunks; } + static void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { for(u32 r = 0; r < tx_state->num_receivers; r++) { @@ -1532,6 +1531,7 @@ static void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) } } +// Mark the next available path as active static void iterate_paths(struct sender_state *tx_state) { for(u32 r = 0; r < tx_state->num_receivers; r++) { @@ -1607,14 +1607,11 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 const u64 prev_transmit = umin64(rcvr->prev_round_start + rcvr->prev_slope * chunk_idx, rcvr->prev_round_end); const u64 ack_due = prev_transmit + rcvr->ack_wait_duration; // 0 for first round if(now >= ack_due) { // add the chunk to the current batch - /* debug_printf("now >= ack due, adding chunk"); */ - /* debug_printf("adding chunk idx %d", chunk_idx); */ *chunks = chunk_idx++; *chunk_rcvr = rcvr_idx; chunks++; chunk_rcvr++; } else { // no chunk to send - skip this receiver in the current batch - /* debug_printf("now < ack due, no chunk to send"); */ (*wait_until) = ack_due; break; } @@ -1625,22 +1622,27 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 -static struct sender_state * -init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, int max_rate_limit, char *mem, - const struct hercules_app_addr *dests, struct hercules_path *paths, u32 num_dests, const int num_paths, - u32 max_paths_per_dest) -{ +// Initialise new sender state. Returns null in case of error. +static struct sender_state *init_tx_state(struct hercules_session *session, + size_t filesize, int chunklen, + int max_rate_limit, char *mem, + const struct hercules_app_addr *dests, + struct hercules_path *paths, + u32 num_dests, const int num_paths, + u32 max_paths_per_dest) { u64 total_chunks = (filesize + chunklen - 1) / chunklen; - if(total_chunks >= UINT_MAX) { - fprintf(stderr, "File too big, not enough chunks available (chunks needed: %llu, chunks available: %u)\n", - total_chunks, UINT_MAX - 1); - exit(1); + if (total_chunks >= UINT_MAX) { + fprintf(stderr, + "File too big, not enough chunks available (chunks needed: " + "%llu, chunks available: %u)\n", + total_chunks, UINT_MAX - 1); + return NULL; } - /* debug_printf("Sending a file consisting of %lld chunks (ANY KEY TO CONTINUE)", total_chunks); */ - /* getchar(); */ - struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); + if (tx_state == NULL){ + return NULL; + } tx_state->session = session; tx_state->filesize = filesize; tx_state->chunklen = chunklen; @@ -1651,9 +1653,13 @@ init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, i tx_state->end_time = 0; tx_state->num_receivers = num_dests; tx_state->receiver = calloc(num_dests, sizeof(*tx_state->receiver)); + if (tx_state->receiver == NULL){ + free(tx_state); + return NULL; + } tx_state->max_paths_per_rcvr = max_paths_per_dest; - for(u32 d = 0; d < num_dests; d++) { + for (u32 d = 0; d < num_dests; d++) { struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; bitset__create(&receiver->acked_chunks, tx_state->total_chunks); receiver->path_index = 0; @@ -1676,10 +1682,6 @@ static void destroy_tx_state(struct sender_state *tx_state) } free(tx_state); } -/// XDP - - - /// PCC #define NACK_TRACE_SIZE (1024*1024) @@ -1883,6 +1885,37 @@ static void tx_send_p(void *arg) { } } +// Send ACKs to the sender. Runs in its own thread. +static void rx_trickle_acks(void *arg) { + struct hercules_server *server = arg; + while (1) { + __sync_synchronize(); + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && + session_rx->state == SESSION_STATE_RUNNING) { + struct receiver_state *rx_state = session_rx->rx_state; + // XXX: data races in access to shared rx_state! + atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); + if (atomic_load(&rx_state->last_pkt_rcvd) + + umax64(100 * ACK_RATE_TIME_MS * 1e6, + 3 * rx_state->handshake_rtt) < + get_nsecs()) { + // Transmission timed out + quit_session(session_rx, SESSION_ERROR_TIMEOUT); + } + rx_send_acks(server, rx_state); + if (rx_received_all(rx_state)) { + debug_printf("Received all, done."); + session_rx->state = SESSION_STATE_DONE; + rx_send_acks(server, rx_state); + server->session_rx = NULL; // FIXME leak + atomic_store(&server->session_rx, NULL); + } + } + sleep_nsecs(ACK_RATE_TIME_MS * 1e6); + } +} + // Send NACKs to the sender. Runs in its own thread. static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; @@ -2340,7 +2373,7 @@ static void join_thread(struct hercules_server *server, pthread_t pt) exit_with_error(server, ret); } } -/// stats +/// (TODO)stats struct path_stats *make_path_stats_buffer(int num_paths) { struct path_stats *path_stats = calloc(1, sizeof(*path_stats) + num_paths * sizeof(path_stats->paths[0])); path_stats->num_paths = num_paths; @@ -2419,9 +2452,7 @@ struct path_stats *make_path_stats_buffer(int num_paths) { /* .rate_limit = 0 */ /* }; */ /* } */ -/// UNASSIGNED - - +/// Hercules main void hercules_main(struct hercules_server *server) { debug_printf("Hercules main"); diff --git a/hercules.h b/hercules.h index df2b56b..a3d4b91 100644 --- a/hercules.h +++ b/hercules.h @@ -90,7 +90,10 @@ struct receiver_state { u64 start_time; u64 end_time; u64 cts_sent_at; - u64 last_pkt_rcvd; // Timeout detection + u64 last_pkt_rcvd; // Timeout detection + u64 last_new_pkt_rcvd; // If we don't receive any new chunks for a while + // something is presumably wrong and we abort the + // transfer u8 num_tracked_paths; bool is_pcc_benchmark; @@ -172,7 +175,8 @@ enum session_state { enum session_error { SESSION_ERROR_OK, //< No error, transfer completed successfully SESSION_ERROR_TIMEOUT, //< Session timed out - SESSION_ERROR_PCC, + SESSION_ERROR_PCC, //< Something wrong with PCC + SESSION_ERROR_SEQNO_OVERFLOW, }; // A session is a transfer between one sender and one receiver @@ -188,9 +192,9 @@ struct hercules_session { struct hercules_app_addr destination; int num_paths; struct hercules_path *paths_to_dest; - // State for stat dump - /* size_t rx_npkts; */ - /* size_t tx_npkts; */ + + size_t rx_npkts; + size_t tx_npkts; }; // SERVER diff --git a/monitor.c b/monitor.c index 9195625..8ea207b 100644 --- a/monitor.c +++ b/monitor.c @@ -8,7 +8,7 @@ #include #include -bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, int etherlen, +bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, int etherlen, struct hercules_path *path) { struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; diff --git a/monitor.h b/monitor.h index d643f94..3a08503 100644 --- a/monitor.h +++ b/monitor.h @@ -7,7 +7,7 @@ // Get a reply path from the monitor. The reversed path will be written to // *path. Returns false in case of error. -bool monitor_get_reply_path(int sockfd, char *rx_sample_buf, int rx_sample_len, +bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, int etherlen, struct hercules_path *path); // Get SCION paths from the monitor. The caller is responsible for freeing diff --git a/packet.h b/packet.h index 8896000..c22831b 100644 --- a/packet.h +++ b/packet.h @@ -63,6 +63,7 @@ struct scionaddrhdr_ipv4 { __u32 src_ip; }; +// The header used by both control and data packets struct hercules_header { __u32 chunk_idx; __u8 path; @@ -82,8 +83,11 @@ struct rbudp_initial_pkt { __u8 name[]; }; +// Indicates to the receiver this path should be used for (N)ACKs #define HANDSHAKE_FLAG_SET_RETURN_PATH 0x1u +// Indicates that the packet is a reflected HS packet #define HANDSHAKE_FLAG_HS_CONFIRM (0x1u << 1) +// Indicates that the packet is trying to start a new transfer #define HANDSHAKE_FLAG_NEW_TRANSFER (0x1u << 2) // Structure of ACK RBUDP packets sent by the receiver. @@ -113,9 +117,9 @@ struct hercules_control_packet { #pragma pack(pop) -// XXX This is placed here (instead of in hercules.h) to avoid clang from -// complainig about atomics when building redirect_userspace.c with hercules.h -// included. +// XXX The following are placed here (instead of in hercules.h) to stop clang +// complainig about atomics when building redirect_userspace.c with +// hercules.h included. // Connection information struct hercules_app_addr { diff --git a/xdp.c b/xdp.c new file mode 100644 index 0000000..1c7f8e9 --- /dev/null +++ b/xdp.c @@ -0,0 +1,378 @@ +#include "xdp.h" + +#include +#include + +#include "bpf/src/bpf.h" +#include "bpf_prgms.h" +#include "hercules.h" + +void remove_xdp_program(struct hercules_server *server) { + for (int i = 0; i < server->num_ifaces; i++) { + u32 curr_prog_id = 0; + if (bpf_get_link_xdp_id(server->ifaces[i].ifid, &curr_prog_id, + server->config.xdp_flags)) { + printf("bpf_get_link_xdp_id failed\n"); + exit(EXIT_FAILURE); + } + if (server->ifaces[i].prog_id == curr_prog_id) + bpf_set_link_xdp_fd(server->ifaces[i].ifid, -1, + server->config.xdp_flags); + else if (!curr_prog_id) + printf("couldn't find a prog id on a given interface\n"); + else + printf("program on interface changed, not removing\n"); + } +} + +void close_xsk(struct xsk_socket_info *xsk) { + xsk_socket__delete(xsk->xsk); + free(xsk); +} + +struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, + u32 ifidx, void *buffer, + u64 size) { + struct xsk_umem_info *umem; + int ret; + + umem = calloc(1, sizeof(*umem)); + if (!umem) { + return NULL; + } + + ret = + xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, NULL); + if (ret) { + return NULL; + } + + umem->buffer = buffer; + umem->iface = &server->ifaces[ifidx]; + // The number of slots in the umem->available_frames queue needs to be + // larger than the number of frames in the loop, pushed in + // submit_initial_tx_frames() (assumption in pop_completion_ring() and + // handle_send_queue_unit()) + ret = frame_queue__init(&umem->available_frames, + XSK_RING_PROD__DEFAULT_NUM_DESCS); + if (ret) { + return NULL; + } + pthread_spin_init(&umem->lock, 0); + return umem; +} + +void destroy_umem(struct xsk_umem_info *umem) { + xsk_umem__delete(umem->umem); + free(umem->buffer); + free(umem); +} + +int submit_initial_rx_frames(struct hercules_server *server, + struct xsk_umem_info *umem) { + int initial_kernel_rx_frame_count = + XSK_RING_PROD__DEFAULT_NUM_DESCS - BATCH_SIZE; + u32 idx; + int ret = + xsk_ring_prod__reserve(&umem->fq, initial_kernel_rx_frame_count, &idx); + if (ret != initial_kernel_rx_frame_count) { + return EINVAL; + } + for (int i = 0; i < initial_kernel_rx_frame_count; i++) + *xsk_ring_prod__fill_addr(&umem->fq, idx++) = + (XSK_RING_PROD__DEFAULT_NUM_DESCS + i) * + XSK_UMEM__DEFAULT_FRAME_SIZE; + xsk_ring_prod__submit(&umem->fq, initial_kernel_rx_frame_count); + return 0; +} + +int submit_initial_tx_frames(struct hercules_server *server, + struct xsk_umem_info *umem) { + // This number needs to be smaller than the number of slots in the + // umem->available_frames queue (initialized in xsk_configure_umem(); + // assumption in pop_completion_ring() and handle_send_queue_unit()) + int initial_tx_frames = XSK_RING_PROD__DEFAULT_NUM_DESCS - BATCH_SIZE; + int avail = + frame_queue__prod_reserve(&umem->available_frames, initial_tx_frames); + if (initial_tx_frames > avail) { + debug_printf( + "trying to push %d initial frames, but only %d slots available", + initial_tx_frames, avail); + return EINVAL; + } + for (int i = 0; i < avail; i++) { + frame_queue__prod_fill(&umem->available_frames, i, + i * XSK_UMEM__DEFAULT_FRAME_SIZE); + } + frame_queue__push(&umem->available_frames, avail); + return 0; +} + +int configure_rx_queues(struct hercules_server *server) { + for (int i = 0; i < server->num_ifaces; i++) { + debug_printf("map UDP4 flow to %d.%d.%d.%d to queue %d on interface %s", + (u8)(server->config.local_addr.ip), + (u8)(server->config.local_addr.ip >> 8u), + (u8)(server->config.local_addr.ip >> 16u), + (u8)(server->config.local_addr.ip >> 24u), + server->ifaces[i].queue, server->ifaces[i].ifname); + + char cmd[1024]; + int cmd_len = snprintf( + cmd, 1024, + "ethtool -N %s flow-type udp4 dst-ip %d.%d.%d.%d action %d", + server->ifaces[i].ifname, (u8)(server->config.local_addr.ip), + (u8)(server->config.local_addr.ip >> 8u), + (u8)(server->config.local_addr.ip >> 16u), + (u8)(server->config.local_addr.ip >> 24u), server->ifaces[i].queue); + if (cmd_len > 1023) { + fprintf(stderr, + "could not configure queue %d on interface %s - command " + "too long, abort\n", + server->ifaces[i].queue, server->ifaces[i].ifname); + unconfigure_rx_queues(server); + return 1; + } + + FILE *proc = popen(cmd, "r"); + int rule_id; + int num_parsed = fscanf(proc, "Added rule with ID %d", &rule_id); + int ret = pclose(proc); + if (ret != 0) { + fprintf(stderr, + "could not configure queue %d on interface %s, abort\n", + server->ifaces[i].queue, server->ifaces[i].ifname); + unconfigure_rx_queues(server); + return ENODEV; + } + if (num_parsed != 1) { + fprintf(stderr, + "could not configure queue %d on interface %s, abort\n", + server->ifaces[i].queue, server->ifaces[i].ifname); + unconfigure_rx_queues(server); + return ENODEV; + } + server->ifaces[i].ethtool_rule = rule_id; + } + return 0; +} + +int unconfigure_rx_queues(struct hercules_server *server) { + int error = 0; + for (int i = 0; i < server->num_ifaces; i++) { + if (server->ifaces[i].ethtool_rule >= 0) { + char cmd[1024]; + int cmd_len = snprintf(cmd, 1024, "ethtool -N %s delete %d", + server->ifaces[i].ifname, + server->ifaces[i].ethtool_rule); + server->ifaces[i].ethtool_rule = -1; + if (cmd_len > 1023) { // This will never happen as the command to + // configure is strictly longer than this one + fprintf(stderr, + "could not delete ethtool rule on interface %s - " + "command too long\n", + server->ifaces[i].ifname); + error = EXIT_FAILURE; + continue; + } + int ret = system(cmd); + if (ret != 0) { + error = ret; + } + } + } + return error; +} + +int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj) { + static const int log_buf_size = 16 * 1024; + char log_buf[log_buf_size]; + int prog_fd; + + char tmp_file[] = "/tmp/hrcbpfXXXXXX"; + int fd = mkstemp(tmp_file); + if (fd < 0) { + return -errno; + } + if (prgm_size != write(fd, prgm, prgm_size)) { + debug_printf("Could not write bpf file"); + return -EXIT_FAILURE; + } + + struct bpf_object *_obj; + if (obj == NULL) { + obj = &_obj; + } + int ret = bpf_prog_load(tmp_file, BPF_PROG_TYPE_XDP, obj, &prog_fd); + debug_printf("error loading file(%s): %d %s", tmp_file, -ret, + strerror(-ret)); + int unlink_ret = unlink(tmp_file); + if (0 != unlink_ret) { + fprintf(stderr, "Could not remove temporary file, error: %d", + unlink_ret); + } + if (ret != 0) { + printf("BPF log buffer:\n%s", log_buf); + return ret; + } + return prog_fd; +} + +int set_bpf_prgm_active(struct hercules_server *server, + struct hercules_interface *iface, int prog_fd) { + int err = + bpf_set_link_xdp_fd(iface->ifid, prog_fd, server->config.xdp_flags); + if (err) { + return 1; + } + + int ret = bpf_get_link_xdp_id(iface->ifid, &iface->prog_id, + server->config.xdp_flags); + if (ret) { + return 1; + } + return 0; +} + +int xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, + struct xsk_socket_info *xsk) { + int xsk_fd = xsk_socket__fd(xsk->xsk); + if (xsk_fd < 0) { + return 1; + } + bpf_map_update_elem(map, &index, &xsk_fd, 0); + return 0; +} + +/* + * Load a BPF program redirecting IP traffic to the XSK. + */ +int load_xsk_redirect_userspace(struct hercules_server *server, + struct rx_p_args *args[], int num_threads) { + debug_printf("Loading XDP program for redirection"); + for (int i = 0; i < server->num_ifaces; i++) { + struct bpf_object *obj; + int prog_fd = load_bpf(bpf_prgm_redirect_userspace, + bpf_prgm_redirect_userspace_size, &obj); + if (prog_fd < 0) { + return 1; + } + + // push XSKs + int xsks_map_fd = bpf_object__find_map_fd_by_name(obj, "xsks_map"); + if (xsks_map_fd < 0) { + return 1; + } + for (int s = 0; s < num_threads; s++) { + xsk_map__add_xsk(server, xsks_map_fd, s, args[s]->xsks[i]); + } + + // push XSKs meta + int zero = 0; + int num_xsks_fd = bpf_object__find_map_fd_by_name(obj, "num_xsks"); + if (num_xsks_fd < 0) { + return 1; + } + bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); + + // push local address + int local_addr_fd = bpf_object__find_map_fd_by_name(obj, "local_addr"); + if (local_addr_fd < 0) { + return 1; + } + bpf_map_update_elem(local_addr_fd, &zero, &server->config.local_addr, + 0); + + set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); + } + return 0; +} + +int xdp_setup(struct hercules_server *server) { + for (int i = 0; i < server->num_ifaces; i++) { + debug_printf("Preparing interface %d", i); + // Prepare UMEM for XSK sockets + void *umem_buf; + int ret = posix_memalign(&umem_buf, getpagesize(), + NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + if (ret) { + return ENOMEM; + } + debug_printf("Allocated umem buffer"); + + struct xsk_umem_info *umem = xsk_configure_umem_server( + server, i, umem_buf, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + debug_printf("Configured umem"); + + server->ifaces[i].xsks = + calloc(server->n_threads, sizeof(*server->ifaces[i].xsks)); + server->ifaces[i].umem = umem; + submit_initial_tx_frames(server, umem); + submit_initial_rx_frames(server, umem); + debug_printf("umem interface %d %s, queue %d", umem->iface->ifid, + umem->iface->ifname, umem->iface->queue); + if (server->ifaces[i].ifid != umem->iface->ifid) { + debug_printf( + "cannot configure XSK on interface %d with queue on interface " + "%d", + server->ifaces[i].ifid, umem->iface->ifid); + return EINVAL; + } + + // Create XSK sockets + for (int t = 0; t < server->n_threads; t++) { + struct xsk_socket_info *xsk; + xsk = calloc(1, sizeof(*xsk)); + if (!xsk) { + return ENOMEM; + } + xsk->umem = umem; + + struct xsk_socket_config cfg; + cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; + cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; + cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; + cfg.xdp_flags = server->config.xdp_flags; + cfg.bind_flags = server->config.xdp_mode; + ret = xsk_socket__create_shared( + &xsk->xsk, server->ifaces[i].ifname, server->config.queue, + umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); + if (ret) { + return -ret; + } + ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, + &server->ifaces[i].prog_id, + server->config.xdp_flags); + if (ret) { + return -ret; + } + server->ifaces[i].xsks[t] = xsk; + } + server->ifaces[i].num_sockets = server->n_threads; + } + for (int t = 0; t < server->n_threads; t++) { + server->worker_args[t] = + malloc(sizeof(*server->worker_args) + + server->num_ifaces * sizeof(*server->worker_args[t]->xsks)); + if (server->worker_args[t] == NULL) { + return ENOMEM; + } + server->worker_args[t]->server = server; + for (int i = 0; i < server->num_ifaces; i++) { + server->worker_args[t]->xsks[i] = server->ifaces[i].xsks[t]; + } + } + + load_xsk_redirect_userspace(server, server->worker_args, server->n_threads); + // TODO this is not set anywhere, so it will never run + if (server->config.configure_queues) { + configure_rx_queues(server); + } + // TODO when/where is this needed? + // same for rx_state + /* libbpf_smp_rmb(); */ + /* session->tx_state = tx_state; */ + /* libbpf_smp_wmb(); */ + debug_printf("XSK stuff complete"); + return 0; +} diff --git a/xdp.h b/xdp.h new file mode 100644 index 0000000..0fee3a8 --- /dev/null +++ b/xdp.h @@ -0,0 +1,45 @@ +#ifndef HERCULES_XDP_H_ +#define HERCULES_XDP_H_ + +#include "hercules.h" + +// Remove the XDP program loaded on all the server's interfaces +void remove_xdp_program(struct hercules_server *server); + +// Removes socket and frees xsk +void close_xsk(struct xsk_socket_info *xsk); +// +// Create and configure the UMEM for the given interface using the provided +// buffer and size. Also initializes the UMEM's frame queue. +struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, + u32 ifidx, void *buffer, + u64 size); + +void destroy_umem(struct xsk_umem_info *umem); + +int submit_initial_rx_frames(struct hercules_server *server, + struct xsk_umem_info *umem); + +int submit_initial_tx_frames(struct hercules_server *server, + struct xsk_umem_info *umem); + +// Configure the NIC(s) to send incoming packets to the queue Hercules is using. +int configure_rx_queues(struct hercules_server *server); + +// Remove ethtool rules previously set by configure_rx_queues +int unconfigure_rx_queues(struct hercules_server *server); + +int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj); + +int set_bpf_prgm_active(struct hercules_server *server, + struct hercules_interface *iface, int prog_fd); + +int xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, + struct xsk_socket_info *xsk); + +int load_xsk_redirect_userspace(struct hercules_server *server, + struct rx_p_args *args[], int num_threads); + +int xdp_setup(struct hercules_server *server); + +#endif // HERCULES_XDP_H_ From 87596c58276e4845ead694074c4b63b32904212c Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 29 May 2024 11:30:58 +0200 Subject: [PATCH 016/112] rework event handler --- hercules.c | 955 ++++++++++++++++++++++++++++++----------------------- hercules.h | 9 +- monitor.c | 1 - 3 files changed, 548 insertions(+), 417 deletions(-) diff --git a/hercules.c b/hercules.c index 903ac61..bb297da 100644 --- a/hercules.c +++ b/hercules.c @@ -71,6 +71,7 @@ static const int rbudp_headerlen = sizeof(struct hercules_header); static const u64 session_timeout = 10e9; // 10 sec +static const u64 session_hs_retransmit_interval = 2e9; // 2 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path // TODO move and see if we can't use this from only 1 thread so no locks @@ -87,7 +88,7 @@ static void stitch_checksum(const struct hercules_path *path, u16 precomputed_ch void debug_print_rbudp_pkt(const char *pkt, bool recv); -static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt); +static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, struct rbudp_initial_pkt **parsed_pkt); static struct hercules_session *make_session(struct hercules_server *server); @@ -173,43 +174,50 @@ static void send_eth_frame(struct hercules_server *server, #ifdef DEBUG_PRINT_PKTS // recv indicates whether printed packets should be prefixed with TX or RX void debug_print_rbudp_pkt(const char *pkt, bool recv) { - struct hercules_header *h = (struct hercules_header *)pkt; - const char *prefix = (recv) ? "RX->" : "<-TX"; - printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, h->path, - h->seqno); - if (h->chunk_idx == UINT_MAX) { - // Control packets - const char *pl = pkt + 9; - struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; - switch (cp->type) { - case CONTROL_PACKET_TYPE_INITIAL: - printf("%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx %u, Flags " - "0x%x, Name length %u [%s]\n", prefix, - cp->payload.initial.filesize, cp->payload.initial.chunklen, - cp->payload.initial.timestamp, cp->payload.initial.path_index, - cp->payload.initial.flags, cp->payload.initial.name_len, cp->payload.initial.name); - break; - case CONTROL_PACKET_TYPE_ACK: - printf("%s ACK ", prefix); - for (int r = 0; r < cp->payload.ack.num_acks; r++){ - printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); - } - printf("\n"); - break; - case CONTROL_PACKET_TYPE_NACK: - printf("%s NACK \n", prefix); - for (int r = 0; r < cp->payload.ack.num_acks; r++){ - printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); - } - printf("\n"); - break; - default: - printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); - break; - } - } else { - printf("%s ** PAYLOAD **\n", prefix); - } + struct hercules_header *h = (struct hercules_header *)pkt; + const char *prefix = (recv) ? "RX->" : "<-TX"; + printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, + h->path, h->seqno); + if (h->chunk_idx == UINT_MAX) { + // Control packets + const char *pl = pkt + 9; + struct hercules_control_packet *cp = + (struct hercules_control_packet *)pl; + switch (cp->type) { + case CONTROL_PACKET_TYPE_INITIAL: + printf( + "%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx " + "%u, Flags " + "0x%x, MTU %d, Name length %u [%s]\n", + prefix, cp->payload.initial.filesize, + cp->payload.initial.chunklen, cp->payload.initial.timestamp, + cp->payload.initial.path_index, cp->payload.initial.flags, + cp->payload.initial.etherlen, cp->payload.initial.name_len, + cp->payload.initial.name); + break; + case CONTROL_PACKET_TYPE_ACK: + printf("%s ACK ", prefix); + for (int r = 0; r < cp->payload.ack.num_acks; r++) { + printf("[%d - %d] ", cp->payload.ack.acks[r].begin, + cp->payload.ack.acks[r].end); + } + printf("\n"); + break; + case CONTROL_PACKET_TYPE_NACK: + printf("%s NACK \n", prefix); + for (int r = 0; r < cp->payload.ack.num_acks; r++) { + printf("[%d - %d] ", cp->payload.ack.acks[r].begin, + cp->payload.ack.acks[r].end); + } + printf("\n"); + break; + default: + printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); + break; + } + } else { + printf("%s ** PAYLOAD **\n", prefix); + } } #else void debug_print_rbudp_pkt(const char * pkt, bool recv){ @@ -242,6 +250,12 @@ static struct hercules_session *make_session(struct hercules_server *server) { return s; } +static void destroy_session(struct hercules_session *session) { + destroy_send_queue(session->send_queue); + free(session->send_queue); + free(session); +} + // Initialise the Hercules server. If this runs into trouble we just exit as // there's no point in continuing. struct hercules_server *hercules_init_server( @@ -548,20 +562,18 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, debug_print_rbudp_pkt(rbudp_pkt, false); } -// Parse an initial (HS) packet and copy its contents to *parsed_pkt -static bool rbudp_parse_initial(const char *pkt, size_t len, struct rbudp_initial_pkt *parsed_pkt) +// Check an initial (HS) packet and return a pointer to it in *parsed_pkt +static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, struct rbudp_initial_pkt **parsed_pkt) { - struct hercules_control_packet control_pkt; - memcpy(&control_pkt, pkt, umin32(sizeof(control_pkt), len)); - if(control_pkt.type != CONTROL_PACKET_TYPE_INITIAL) { + if(pkt->type != CONTROL_PACKET_TYPE_INITIAL) { debug_printf("Packet type not INITIAL"); return false; } - if(len < sizeof(control_pkt.type) + sizeof(*parsed_pkt)) { + if(len < sizeof(pkt->type) + sizeof(*parsed_pkt)) { debug_printf("Packet too short"); return false; } - memcpy(parsed_pkt, &control_pkt.payload.initial, sizeof(*parsed_pkt) + control_pkt.payload.initial.name_len); + *parsed_pkt = &pkt->payload.initial; return true; } @@ -778,6 +790,7 @@ static char *rx_mmap(const char *pathname, size_t filesize) { close(f); return NULL; } + // TODO why shared mapping? char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); if (mem == MAP_FAILED) { close(f); @@ -791,8 +804,9 @@ static char *rx_mmap(const char *pathname, size_t filesize) { // Create new receiver state. Returns null in case of error. static struct receiver_state *make_rx_state(struct hercules_session *session, - char *filename, size_t filesize, - int chunklen, int etherlen, + char *filename, size_t namelen, + size_t filesize, int chunklen, + int etherlen, bool is_pcc_benchmark) { struct receiver_state *rx_state; rx_state = calloc(1, sizeof(*rx_state)); @@ -808,6 +822,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; + filename[namelen+1] = 0; // HACK FIXME rx_state->mem = rx_mmap(filename, filesize); if (rx_state->mem == NULL) { free(rx_state); @@ -1179,7 +1194,7 @@ bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct s } static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, u32 etherlen, bool set_return_path, bool new_transfer) { debug_printf("Sending initial"); char buf[HERCULES_MAX_PKTSIZE]; @@ -1202,6 +1217,7 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path .path_index = path_index, .flags = flags, .name_len = strlen(filename), + .etherlen = etherlen, }, }; assert(strlen(filename) < 100); // TODO @@ -1256,7 +1272,7 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t debug_printf("sending hs on path %d", p); // FIXME file name below? tx_send_initial(server, path, "abcd", tx_state->filesize, - tx_state->chunklen, get_nsecs(), p, + tx_state->chunklen, get_nsecs(), p, 0, false, false); } } @@ -1683,7 +1699,7 @@ static void destroy_tx_state(struct sender_state *tx_state) free(tx_state); } -/// PCC +/// OK PCC #define NACK_TRACE_SIZE (1024*1024) static u32 nack_trace_count = 0; static struct { @@ -1828,6 +1844,7 @@ static void pcc_monitor(struct sender_state *tx_state) } } } + static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) { return cc_state->state != pcc_terminated && @@ -1840,399 +1857,508 @@ struct tx_send_p_args { struct xsk_socket_info *xsks[]; }; -// Read chunk ids from the send queue, fill in packets accorindgly and actually send them. -// This is the function run by the TX worker thread(s). +// Read chunk ids from the send queue, fill in packets accorindgly and actually +// send them. This is the function run by the TX worker thread(s). static void tx_send_p(void *arg) { struct tx_send_p_args *args = arg; - struct hercules_server *server = args->server; - while (1) { - struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx == NULL || atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { - /* debug_printf("Invalid session, dropping sendq unit"); */ - kick_tx_server(server); - continue; - } - struct send_queue_unit unit; - int ret = send_queue_pop(session_tx->send_queue, &unit); - if (!ret) { - kick_tx_server(server); - continue; - } - u32 num_chunks_in_unit = 0; - for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { - if(unit.paths[i] == UINT8_MAX) { - break; + struct hercules_server *server = args->server; + while (1) { + struct hercules_session *session_tx = atomic_load(&server->session_tx); + if (session_tx == NULL || + atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { + kick_tx_server(server); // flush any pending packets + continue; } - num_chunks_in_unit++; + struct send_queue_unit unit; + int ret = send_queue_pop(session_tx->send_queue, &unit); + if (!ret) { + kick_tx_server(server); + continue; + } + // The unit may contain fewer than the max number of chunks. We only + // want to allocate as many frames as there are packets to send, + // otherwise the unused frames would not be submitted to the TX rings + // and thus be lost. + u32 num_chunks_in_unit = 0; + for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { + if (unit.paths[i] == UINT8_MAX) { + break; + } + num_chunks_in_unit++; + } + u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; + memset(frame_addrs, 0xFF, sizeof(frame_addrs)); + for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { + if (i >= num_chunks_in_unit) { + frame_addrs[0][i] = 0; + } + } + allocate_tx_frames(server, frame_addrs); + tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, + frame_addrs, &unit); + kick_tx_server( + server); // FIXME should not be needed and probably inefficient } - debug_printf("unit has %d chunks", num_chunks_in_unit); - u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; - memset(frame_addrs, 0xFF, sizeof(frame_addrs)); - for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++){ - if (i >= num_chunks_in_unit){ - /* debug_printf("not using unit slot %d", i); */ - frame_addrs[0][i] = 0; - } - } - /* debug_printf("allocating frames"); */ - allocate_tx_frames(server, frame_addrs); - /* debug_printf("done allocating frames"); */ - // At this point we claimed 7 frames (as many as can fit in a sendq unit) - tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, - frame_addrs, &unit); - /* assert(num_chunks_in_unit == 7); */ - kick_tx_server(server); - } } // Send ACKs to the sender. Runs in its own thread. static void rx_trickle_acks(void *arg) { - struct hercules_server *server = arg; - while (1) { - __sync_synchronize(); - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && - session_rx->state == SESSION_STATE_RUNNING) { - struct receiver_state *rx_state = session_rx->rx_state; - // XXX: data races in access to shared rx_state! - atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); - if (atomic_load(&rx_state->last_pkt_rcvd) + - umax64(100 * ACK_RATE_TIME_MS * 1e6, - 3 * rx_state->handshake_rtt) < - get_nsecs()) { - // Transmission timed out - quit_session(session_rx, SESSION_ERROR_TIMEOUT); - } - rx_send_acks(server, rx_state); - if (rx_received_all(rx_state)) { - debug_printf("Received all, done."); - session_rx->state = SESSION_STATE_DONE; - rx_send_acks(server, rx_state); - server->session_rx = NULL; // FIXME leak - atomic_store(&server->session_rx, NULL); - } - } - sleep_nsecs(ACK_RATE_TIME_MS * 1e6); - } + struct hercules_server *server = arg; + while (1) { + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + struct receiver_state *rx_state = session_rx->rx_state; + // XXX: data races in access to shared rx_state! + atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); + if (atomic_load(&rx_state->last_pkt_rcvd) + + umax64(100 * ACK_RATE_TIME_MS * 1e6, + 3 * rx_state->handshake_rtt) < + get_nsecs()) { + // Transmission timed out + quit_session(session_rx, SESSION_ERROR_TIMEOUT); + } + rx_send_acks(server, rx_state); + if (rx_received_all( + rx_state)) { // TODO move this check to ack receive? + debug_printf("Received all, done."); + quit_session(session_rx, SESSION_ERROR_OK); + rx_send_acks(server, rx_state); + } + } + sleep_nsecs(ACK_RATE_TIME_MS * 1e6); + } } // Send NACKs to the sender. Runs in its own thread. static void rx_trickle_nacks(void *arg) { - struct hercules_server *server = arg; - while (1) { - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { - u32 ack_nr = 0; - struct receiver_state *rx_state = session_rx->rx_state; - while (rx_state->session->state == SESSION_STATE_RUNNING && !rx_received_all(rx_state)) { - u64 ack_round_start = get_nsecs(); - rx_send_nacks(server, rx_state, ack_round_start, ack_nr); - u64 ack_round_end = get_nsecs(); - if (ack_round_end > - ack_round_start + rx_state->handshake_rtt * 1000 / 4) { - // fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", - // ack_round_end - ack_round_start, rx_state->handshake_rtt * 1000 / - // 4); - } else { - sleep_until(ack_round_start + rx_state->handshake_rtt * 1000 / 4); - } - ack_nr++; - } - } - } + struct hercules_server *server = arg; + while (1) { + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + u32 ack_nr = 0; + struct receiver_state *rx_state = session_rx->rx_state; + // TODO remove this inner loop? + while (rx_state->session->state == SESSION_STATE_RUNNING && + !rx_received_all(rx_state)) { + u64 ack_round_start = get_nsecs(); + rx_send_nacks(server, rx_state, ack_round_start, ack_nr); + u64 ack_round_end = get_nsecs(); + if (ack_round_end > + ack_round_start + rx_state->handshake_rtt * 1000 / 4) { + // fprintf(stderr, "NACK send too slow (took %lld of + // %ld)\n", ack_round_end - ack_round_start, + // rx_state->handshake_rtt * 1000 / 4); + } else { + sleep_until(ack_round_start + + rx_state->handshake_rtt * 1000 / 4); + } + ack_nr++; + } + } + } } -// Receive data packets on the XDP sockets. Runs in the RX worker thread(s) -static void *rx_p(void *arg) { - struct rx_p_args *args = arg; - struct hercules_server *server = args->server; - int num_ifaces = server->num_ifaces; - u32 i = 0; - while (1) { - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { - rx_receive_batch(session_rx->rx_state, args->xsks[i % num_ifaces]); - i++; - } - } +// Receive data packets on the XDP sockets. Runs in the RX worker thread(s). +static void rx_p(void *arg) { + struct rx_p_args *args = arg; + struct hercules_server *server = args->server; + int num_ifaces = server->num_ifaces; + u32 i = 0; + while (1) { + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + rx_receive_batch(session_rx->rx_state, args->xsks[i % num_ifaces]); + i++; + } + } +} + +// Map the provided file into memory for reading. Returns pointer to the mapped +// area, or null on error. +static char *tx_mmap(char *fname, size_t *filesize) { + int f = open(fname, O_RDONLY); + if (f == -1) { + return NULL; + } + struct stat stat; + int ret = fstat(f, &stat); + if (ret) { + close(f); + return NULL; + } + const size_t fsize = stat.st_size; + char *mem = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, f, 0); + if (mem == MAP_FAILED) { + close(f); + return NULL; + } + close(f); + *filesize = fsize; + return mem; +} +// Check if the monitor has new transfer jobs available and, if so, start one +static void new_tx_if_available(struct hercules_server *server) { + char fname[1000]; + memset(fname, 0, 1000); + int count; + u16 jobid; + u16 mtu; + struct hercules_app_addr dest; - return NULL; + pthread_spin_lock(&usock_lock); + int ret = monitor_get_new_job(usock, fname, &jobid, &dest, &mtu); + pthread_spin_unlock(&usock_lock); + if (!ret) { + return; + } + debug_printf("new job: %s", fname); + if (HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + + rbudp_headerlen > + (size_t)mtu) { + debug_printf("supplied MTU too small"); + // TODO update_job with err + return; + } + if (mtu > HERCULES_MAX_PKTSIZE){ + debug_printf("supplied MTU too large"); + // TODO update_job with err + return; + } + size_t filesize; + char *mem = tx_mmap(fname, &filesize); + if (mem == NULL){ + debug_printf("mmap failed"); + // TODO update_job with err + return; + } + struct hercules_session *session = make_session(server); + if (session == NULL){ + // TODO update_job with err + debug_printf("error creating session"); + munmap(mem, filesize); + return; + } + session->state = SESSION_STATE_PENDING; + session->etherlen = mtu; + + int n_paths; + struct hercules_path *paths; + ret = monitor_get_paths(usock, jobid, &n_paths, &paths); + if (!ret){ + debug_printf("error getting paths"); + munmap(mem, filesize); + destroy_session(session); + return; + } + // TODO free paths + debug_printf("received %d paths", n_paths); + session->num_paths = n_paths; + session->paths_to_dest = paths; + + u32 chunklen = session->paths_to_dest[0].payloadlen - rbudp_headerlen; + atomic_store(&server->session_tx, session); + struct sender_state *tx_state = init_tx_state( + server->session_tx, filesize, chunklen, server->rate_limit, mem, + &session->destination, session->paths_to_dest, 1, session->num_paths, + server->max_paths); + strncpy(tx_state->filename, fname, 99); + server->session_tx->tx_state = tx_state; +} + +// Remove and free finished sessions +static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { + // Wait for twice the session timeout before removing the finished + // session (and thus before accepting new sessions). This ensures the + // other party has also quit or timed out its session and won't send + // packets that would then be mixed into future sessions. + // XXX This depends on both endpoints sharing the same timeout value, + // which is not negotiated but defined at the top of this file. + struct hercules_session *session_tx = atomic_load(&server->session_tx); + if (session_tx && session_tx->state == SESSION_STATE_DONE) { + if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { + atomic_store(&server->session_tx, NULL); // FIXME leak + fprintf(stderr, "Cleaning up TX session...\n"); + } + } + struct hercules_session *session_rx = atomic_load(&server->session_rx); + if (session_rx && session_rx->state == SESSION_STATE_DONE) { + if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { + atomic_store(&server->session_rx, NULL); // FIXME leak + fprintf(stderr, "Cleaning up RX session...\n"); + } + } } -// TODO rework -static void events_p(void *arg) { - debug_printf("event listener thread started"); - struct hercules_server *server = arg; - // Read a packet from the control socket and act accordingly. - struct sockaddr_ll addr; - socklen_t addr_size = sizeof(addr); - char buf[3000]; - const struct scionaddrhdr_ipv4 *scionaddrhdr; - const struct udphdr *udphdr; - /* u8 path_idx; */ - /* const char *payload; */ - /* int payloadlen; */ - u64 lastpoll = get_nsecs(); - - while (1) { // event handler thread loop - - // XXX monitor poll - if (atomic_load(&server->session_tx) == NULL) { - if (get_nsecs() > lastpoll + 1e9) { - lastpoll = get_nsecs(); - // every 1s - char fname[1000]; - memset(fname, 0, 1000); - int count; - u16 jobid; - u16 mtu; - struct hercules_app_addr dest; - pthread_spin_lock(&usock_lock); - int ret = monitor_get_new_job(usock, fname, &jobid, &dest, &mtu); - pthread_spin_unlock(&usock_lock); - if (ret) { - debug_printf("new job: %s", fname); - // TODO propagate open/map errors instead of quitting - // - // TODO check mtu large enough -/* if(HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)mtu) { */ -/* printf("MTU too small (min: %lu, given: %d)", */ -/* HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + rbudp_headerlen, */ -/* mtu */ -/* ); */ -/* exit_with_error(session, EINVAL); */ -/* } */ - debug_printf("Starting new TX"); - int f = open(fname, O_RDONLY); - if (f == -1) { - exit_with_error(NULL, errno); - } +// Time out if no packets received for a while +static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { + struct hercules_session *session_tx = server->session_tx; + if (session_tx && session_tx->state != SESSION_STATE_DONE) { + if (now > session_tx->last_pkt_rcvd + session_timeout) { + quit_session(session_tx, SESSION_ERROR_TIMEOUT); + debug_printf("Session (TX) timed out!"); + } + } + struct hercules_session *session_rx = server->session_rx; + if (session_rx && session_rx->state != SESSION_STATE_DONE) { + if (now > session_rx->last_pkt_rcvd + session_timeout) { + quit_session(session_rx, SESSION_ERROR_TIMEOUT); + debug_printf("Session (RX) timed out!"); + } + } +} - struct stat stat; - int ret = fstat(f, &stat); - if (ret) { - exit_with_error(NULL, errno); - } - const size_t filesize = stat.st_size; - char *mem = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, f, 0); - if (mem == MAP_FAILED) { - fprintf(stderr, "ERR: memory mapping failed\n"); - exit_with_error(NULL, errno); - } - close(f); - - struct hercules_session *session = make_session(server); - session->state = SESSION_STATE_PENDING; - session->destination = server->config.local_addr; - session->destination.ip = 0x132a8c0UL; - // FIXME remove ip above - - int n_paths; - struct hercules_path *paths; - monitor_get_paths(usock, jobid, &n_paths, &paths); - debug_printf("received %d paths", n_paths); - session->num_paths = n_paths; - session->paths_to_dest = paths; - - u32 chunklen = session->paths_to_dest[0].payloadlen - rbudp_headerlen; - atomic_store(&server->session_tx, session); - atomic_fetch_add(&server->session_tx_counter, 1); - struct sender_state *tx_state = init_tx_state( - server->session_tx, filesize, chunklen, server->rate_limit, mem, - &session->destination, session->paths_to_dest, 1, - session->num_paths, server->max_paths); - strncpy(tx_state->filename, fname, 99); - server->session_tx->tx_state = tx_state; +// (Re)send HS if needed +static void tx_retransmit_initial(struct hercules_server *server, u64 now) { + struct hercules_session *session_tx = server->session_tx; + if (session_tx && session_tx->state == SESSION_STATE_PENDING) { + if (now > + session_tx->last_pkt_sent + session_hs_retransmit_interval) { + struct sender_state *tx_state = session_tx->tx_state; + tx_send_initial(server, &tx_state->receiver[0].paths[0], + tx_state->filename, tx_state->filesize, + tx_state->chunklen, now, 0, session_tx->etherlen, true, true); + session_tx->last_pkt_sent = now; + } + } +} +static void tx_handle_hs_confirm(struct hercules_server *server, + struct rbudp_initial_pkt *parsed_pkt) { + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_PENDING) { + // This is a reply to the very first packet and confirms connection + // setup + if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { + debug_printf("Handshake did not have correct flag set"); + return; + } + if (server->enable_pcc) { + u64 now = get_nsecs(); + struct sender_state_per_receiver *receiver = + server->session_tx->tx_state->receiver; + receiver->handshake_rtt = now - parsed_pkt->timestamp; + // TODO where to get rate limit? + receiver->cc_states = init_ccontrol_state( + 10000, server->session_tx->tx_state->total_chunks, + server->session_tx->num_paths, server->session_tx->num_paths, + server->session_tx->num_paths); + ccontrol_update_rtt(&receiver->cc_states[0], + receiver->handshake_rtt); + fprintf(stderr, + "[receiver %d] [path 0] handshake_rtt: " + "%fs, MI: %fs\n", + 0, receiver->handshake_rtt / 1e9, + receiver->cc_states[0].pcc_mi_duration); + + // make sure we later perform RTT estimation + // on every enabled path + receiver->paths[0].next_handshake_at = + UINT64_MAX; // We just completed the HS for this path + for (u32 p = 1; p < receiver->num_paths; p++) { + receiver->paths[p].next_handshake_at = now; + } + } + server->session_tx->state = SESSION_STATE_WAIT_CTS; + return; + } - } else { - debug_printf("no new job."); - } - } - } - // XXX + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_RUNNING) { + // This is a reply to some handshake we sent during an already + // established session (e.g. to open a new path) + u64 now = get_nsecs(); + struct sender_state_per_receiver *receiver = + server->session_tx->tx_state->receiver; + // TODO re-enable + /* ccontrol_update_rtt(&receiver->cc_states[parsed_pkt->path_index], */ + /* now - parsed_pkt->timestamp); */ + receiver->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; + return; + } + // In other cases we just drop the packet + debug_printf("Dropping HS confirm packet, was not expecting one"); +} - struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx && session_tx->state == SESSION_STATE_DONE){ - if (get_nsecs() > session_tx->last_pkt_rcvd + 20e9){ - atomic_store(&server->session_tx, NULL); // FIXME leak - fprintf(stderr, "Cleaning up session...\n"); - } - } - // Time out if no packets received for 10 sec. - if (session_tx && session_tx->state != SESSION_STATE_DONE){ - if (get_nsecs() > session_tx->last_pkt_rcvd + 10e9){ - quit_session(session_tx, SESSION_ERROR_TIMEOUT); - debug_printf("Session timed out!"); - } - } - // (Re)send HS if needed - if (session_tx && session_tx->state == SESSION_STATE_PENDING) { - unsigned long timestamp = get_nsecs(); - if (timestamp > session_tx->last_pkt_sent + 1e9) { // Re-send HS every 1 sec. - struct sender_state *tx_state = session_tx->tx_state; - tx_send_initial(server, &tx_state->receiver[0].paths[0], tx_state->filename, - tx_state->filesize, tx_state->chunklen, timestamp, 0, - true, true); - session_tx->last_pkt_sent = timestamp; - } - } - /* // Set timeout on the socket */ - /* struct timeval to = {.tv_sec = 0, .tv_usec = 500}; */ - /* setsockopt(server->control_sockfd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ - ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), 0, - (struct sockaddr *)&addr, - &addr_size); // XXX set timeout - if (len == -1) { - if (errno == EAGAIN || errno == EINTR) { - continue; - } - exit_with_error( - NULL, - errno); // XXX: are there situations where we want to try again? - } +static void events_p(void *arg) { + debug_printf("event listener thread started"); + struct hercules_server *server = arg; - // TODO rewrite this with select/poll + struct sockaddr_ll addr; + socklen_t addr_size = sizeof(addr); + char buf[HERCULES_MAX_PKTSIZE]; + const struct scionaddrhdr_ipv4 *scionaddrhdr; + const struct udphdr *udphdr; + + u64 lastpoll = 0; + while (1) { // event handler thread loop + u64 now = get_nsecs(); + if (now > lastpoll + 1e9){ + if (server->session_tx == NULL) { + new_tx_if_available(server); + } + mark_timed_out_sessions(server, now); + cleanup_finished_sessions(server, now); + tx_retransmit_initial(server, now); + lastpoll = now; + } - if (get_interface_by_id(server, addr.sll_ifindex) == NULL) { - continue; - } - const char *rbudp_pkt = - parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr); - if (rbudp_pkt == NULL) { - /* debug_printf("Ignoring, failed parse"); */ - continue; - } + // XXX This is a bit of a hack: We want to handle received packets more + // frequently than we poll the monitor or check for expired sessions, so + // try to receive 100 times (non-blocking) before doing anything else. + // TODO re-enable dontwait flag + loop + for (int i = 0; i < 100; i++) { + ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), + MSG_DONTWAIT, (struct sockaddr *)&addr, + &addr_size); // XXX set timeout + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + continue; + } + exit_with_error(server, + errno); // XXX: are there situations where we + // want to try again? + } - const size_t rbudp_len = len - (rbudp_pkt - buf); - if (rbudp_len < sizeof(u32)) { - debug_printf("Ignoring, length too short"); - continue; - } - u32 chunk_idx; - memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); - if (chunk_idx != UINT_MAX) { - debug_printf("Ignoring, chunk_idx != UINT_MAX"); - continue; - } - debug_print_rbudp_pkt(rbudp_pkt, true); - // TODO Count received pkt -/* atomic_fetch_add(&session->rx_npkts, 1); */ -/* if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { */ -/* atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); */ -/* } */ + // Check the packet was received on an interface used by Hercules + if (get_interface_by_id(server, addr.sll_ifindex) == NULL) { + continue; + } - // TODO this belongs somewhere? -/* if(tx_state->receiver[0].cc_states) { */ -/* pcc_monitor(tx_state); */ -/* } */ - struct hercules_header *h = (struct hercules_header *)rbudp_pkt; - if (h->chunk_idx == UINT_MAX) { - // FIXME does not belong here and counts wrong packets too - if (server->session_tx != NULL){ - server->session_tx->last_pkt_rcvd == get_nsecs(); - } - const char *pl = rbudp_pkt + rbudp_headerlen; - struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; - switch (cp->type) { - case CONTROL_PACKET_TYPE_INITIAL:; - struct rbudp_initial_pkt parsed_pkt; - rbudp_parse_initial((const char *)cp, rbudp_len - rbudp_headerlen, - &parsed_pkt); - if (parsed_pkt.flags & HANDSHAKE_FLAG_HS_CONFIRM) { - // This is a confirmation for a handshake packet - // we sent out earlier - debug_printf("HS confirm packet"); - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_PENDING) { - if(server->enable_pcc) { - u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = server->session_tx->tx_state->receiver; - receiver->handshake_rtt = now - parsed_pkt.timestamp; - receiver->cc_states = init_ccontrol_state( - 10000, - server->session_tx->tx_state->total_chunks, - server->session_tx->num_paths, - server->session_tx->num_paths, - server->session_tx->num_paths - ); - ccontrol_update_rtt(&receiver->cc_states[0], receiver->handshake_rtt); - fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: %fs, MI: %fs\n", - 0, receiver->handshake_rtt / 1e9, receiver->cc_states[0].pcc_mi_duration); - - // make sure tx_only() performs RTT estimation on every enabled path - for(u32 p = 1; p < receiver->num_paths; p++) { - receiver->paths[p].next_handshake_at = now; - } + const char *rbudp_pkt = + parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr); + if (rbudp_pkt == NULL) { + continue; } - server->session_tx->state = SESSION_STATE_WAIT_CTS; - } - if (server->session_tx != NULL && server->session_tx->state == SESSION_STATE_RUNNING){ - u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = server->session_tx->tx_state->receiver; - ccontrol_update_rtt(&receiver->cc_states[parsed_pkt.path_index], now - parsed_pkt.timestamp); - receiver->paths[parsed_pkt.path_index].next_handshake_at = UINT64_MAX; - } - break; // Make sure we don't process this further - } - if (server->session_rx != NULL && server->session_rx->state == SESSION_STATE_RUNNING){ - debug_printf("possible path hs"); - if (!( parsed_pkt.flags & HANDSHAKE_FLAG_NEW_TRANSFER )){ - debug_printf("send to handle initial"); - rx_handle_initial(server, server->session_rx->rx_state, &parsed_pkt, buf, addr.sll_ifindex, rbudp_pkt + rbudp_headerlen, len); + + const size_t rbudp_len = len - (rbudp_pkt - buf); + if (rbudp_len < sizeof(u32)) { + debug_printf("Ignoring, length too short"); + continue; + } + + u32 chunk_idx; + memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); + if (chunk_idx != UINT_MAX) { + debug_printf("Ignoring, chunk_idx != UINT_MAX"); + continue; + } + + debug_print_rbudp_pkt(rbudp_pkt, true); + // TODO Count received pkt + /* atomic_fetch_add(&session->rx_npkts, 1); */ + /* if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { */ + /* atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, + * 1); */ + /* } */ + + // TODO this belongs somewhere? + /* if(tx_state->receiver[0].cc_states) { */ + /* pcc_monitor(tx_state); */ + /* } */ + // TODO check received packet has expected source address + struct hercules_header *h = (struct hercules_header *)rbudp_pkt; + if (h->chunk_idx == UINT_MAX) { // This is a control packet + const char *pl = rbudp_pkt + rbudp_headerlen; + struct hercules_control_packet *cp = + (struct hercules_control_packet *)pl; + + switch (cp->type) { + case CONTROL_PACKET_TYPE_INITIAL:; + struct rbudp_initial_pkt *parsed_pkt; + rbudp_check_initial(cp, + rbudp_len - rbudp_headerlen, + &parsed_pkt); + if (parsed_pkt->flags & HANDSHAKE_FLAG_HS_CONFIRM) { + // This is a confirmation for a handshake packet + // we sent out earlier + debug_printf("HS confirm packet"); + tx_handle_hs_confirm(server, parsed_pkt); + break; // Make sure we don't process this further + } + // Otherwise, we process and reflect the packet + if (server->session_rx != NULL && + server->session_rx->state == + SESSION_STATE_RUNNING) { + if (!(parsed_pkt->flags & + HANDSHAKE_FLAG_NEW_TRANSFER)) { + // This is a handshake that tries to open a new + // path for the running transfer + rx_handle_initial( + server, server->session_rx->rx_state, + parsed_pkt, buf, addr.sll_ifindex, + rbudp_pkt + rbudp_headerlen, len); + } + } + if (server->session_rx == NULL && + (parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { + // We don't have a running session and this is an + // attempt to start a new one, go ahead and start a + // new rx session + debug_printf("Accepting new rx session"); + if (parsed_pkt->flags & + HANDSHAKE_FLAG_SET_RETURN_PATH) { + // The very first packet needs to set the return + // path or we won't be able to reply + struct hercules_session *session = + make_session(server); + server->session_rx = session; + session->state = SESSION_STATE_NEW; + struct receiver_state *rx_state = make_rx_state( + session, parsed_pkt->name, parsed_pkt->name_len, + parsed_pkt->filesize, parsed_pkt->chunklen, + parsed_pkt->etherlen, false); + session->rx_state = rx_state; + rx_handle_initial(server, rx_state, parsed_pkt, + buf, addr.sll_ifindex, + rbudp_pkt + rbudp_headerlen, + len); + rx_send_cts_ack(server, rx_state); + server->session_rx->state = + SESSION_STATE_RUNNING; + } + } + break; + + case CONTROL_PACKET_TYPE_ACK: + if (server->session_tx != NULL && + server->session_tx->state == + SESSION_STATE_WAIT_CTS) { + if (cp->payload.ack.num_acks == 0) { + debug_printf("CTS received"); + atomic_store(&server->session_tx->state, + SESSION_STATE_RUNNING); + } + } + if (server->session_tx != NULL && + server->session_tx->state == + SESSION_STATE_RUNNING) { + tx_register_acks( + &cp->payload.ack, + &server->session_tx->tx_state->receiver[0]); + if (tx_acked_all(server->session_tx->tx_state)) { + debug_printf("TX done, received all acks"); + quit_session(server->session_tx, + SESSION_ERROR_OK); + } + } + break; + + case CONTROL_PACKET_TYPE_NACK: + // nack_trace_push(nack.timestamp, nack.ack_nr) + break; + default: + debug_printf("Received control packet of unknown type"); + break; + } + } else { + // This should never happen beacuse the xdp program redirects + // all data packets + debug_printf("Non-control packet received on control socket"); } } - // If a transfer is already running, ignore - // XXX breaks multipath, for now - if (server->session_rx == NULL) { - debug_printf("No current rx session"); - if (parsed_pkt.flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - struct hercules_session *session = make_session(server); - server->session_rx = session; - session->state = SESSION_STATE_NEW; - // TODO fill in etherlen - struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt.name, parsed_pkt.filesize, parsed_pkt.chunklen, 1200, false); - session->rx_state = rx_state; - /* session->state = SESSION_STATE_READY; */ - rx_handle_initial(server, rx_state, &parsed_pkt, buf, addr.sll_ifindex, - rbudp_pkt + rbudp_headerlen, len); - rx_send_cts_ack(server, rx_state); - server->session_rx->state = SESSION_STATE_RUNNING; - } - } - break; - case CONTROL_PACKET_TYPE_ACK: - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_WAIT_CTS) { - // TODO check this is actually a CTS ack - debug_printf("CTS received"); - atomic_store(&server->session_tx->state, SESSION_STATE_RUNNING); - } - __sync_synchronize(); - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_RUNNING) { - tx_register_acks(&cp->payload.ack, - &server->session_tx->tx_state->receiver[0]); - if (tx_acked_all(server->session_tx->tx_state)) { - debug_printf("TX done, received all acks"); - quit_session(server->session_tx, SESSION_ERROR_OK); - } - } - break; - case CONTROL_PACKET_TYPE_NACK: - //nack_trace_push(nack.timestamp, nack.ack_nr) - break; - default: - break; - } - /* debug_printf("done with packet"); */ - } else { - debug_printf("Non-control packet received on control socket"); - } - } + } } /** @@ -2320,12 +2446,11 @@ static void *tx_p(void *arg) { } } } - debug_printf("continue iwth %d chunks", num_chunks); if (num_chunks > 0) { u8 rcvr_path[tx_state->num_receivers]; prepare_rcvr_paths(tx_state, rcvr_path); - produce_batch(server, session_tx, tx_state, rcvr_path, chunks, chunk_rcvr, + produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, num_chunks); tx_state->tx_npkts_queued += num_chunks; rate_limit_tx(tx_state); diff --git a/hercules.h b/hercules.h index a3d4b91..dd64386 100644 --- a/hercules.h +++ b/hercules.h @@ -27,7 +27,13 @@ #include "packet.h" #define HERCULES_MAX_HEADERLEN 256 -#define HERCULES_MAX_PKTSIZE 9000 +// NOTE: The maximum packet size is limited by the size of a single XDP frame +// (page size - metadata overhead). This is around 3500, but the exact value +// depends on the driver. We're being conservative here. Support for larger +// packets is possible by using xdp in multibuffer mode, but this requires code +// to handle multi-buffer packets. +#define HERCULES_MAX_PKTSIZE 3000 +// Batch size for send/receive operations #define BATCH_SIZE 64 // Number of frames in UMEM area #define NUM_FRAMES (4 * 1024) @@ -188,6 +194,7 @@ struct hercules_session { struct send_queue *send_queue; u64 last_pkt_sent; u64 last_pkt_rcvd; + u32 etherlen; struct hercules_app_addr destination; int num_paths; diff --git a/monitor.c b/monitor.c index 8ea207b..11f55cc 100644 --- a/monitor.c +++ b/monitor.c @@ -84,7 +84,6 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_ap struct hercules_sockmsg_A reply; int n = recv(sockfd, &reply, sizeof(reply), 0); - debug_printf("receive %d bytes", n); if (!reply.payload.newjob.has_job){ return false; } From 31ae2c9696cd8de5efc6edc6e95e3fe9b9fdcc15 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 29 May 2024 17:32:56 +0200 Subject: [PATCH 017/112] drop received packets when no session running --- hercules.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/hercules.c b/hercules.c index bb297da..aa599a5 100644 --- a/hercules.c +++ b/hercules.c @@ -714,7 +714,7 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); while(reserved != num_frames) { reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - if(session->state != SESSION_STATE_RUNNING) { + if(session == NULL || session->state != SESSION_STATE_RUNNING) { pthread_spin_unlock(&umem->lock); return; } @@ -770,6 +770,20 @@ static void rx_receive_batch(struct receiver_state *rx_state, submit_rx_frames(rx_state->session, xsk->umem, frame_addrs, rcvd); } +static void rx_receive_and_drop(struct xsk_socket_info *xsk){ + u32 idx_rx = 0; + size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); + u64 frame_addrs[BATCH_SIZE]; + for (size_t i = 0; i < rcvd; i++) { + u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; + frame_addrs[i] = addr; + u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; + const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); + } + xsk_ring_cons__release(&xsk->rx, rcvd); + submit_rx_frames(NULL, xsk->umem, frame_addrs, rcvd); +} + // Prepare a file and memory mapping to receive a file static char *rx_mmap(const char *pathname, size_t filesize) { debug_printf("mmap file: %s", pathname); @@ -1970,6 +1984,15 @@ static void rx_p(void *arg) { rx_receive_batch(session_rx->rx_state, args->xsks[i % num_ifaces]); i++; } + else { + // Even though we don't currently have a running session, we might + // not have processed all received packets before stopping the + // previous session (or they might still be in flight). Drain any + // received packets to avoid erroneously assigning them to the next + // session. + rx_receive_and_drop(args->xsks[i % num_ifaces]); + i++; + } } } From fba658d9c5764ef95af10777127c8932ff44a0e3 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 29 May 2024 17:38:39 +0200 Subject: [PATCH 018/112] reorder code --- hercules.c | 213 +++++++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 106 deletions(-) diff --git a/hercules.c b/hercules.c index aa599a5..ab2c396 100644 --- a/hercules.c +++ b/hercules.c @@ -1713,6 +1713,101 @@ static void destroy_tx_state(struct sender_state *tx_state) free(tx_state); } +// (Re)send HS if needed +static void tx_retransmit_initial(struct hercules_server *server, u64 now) { + struct hercules_session *session_tx = server->session_tx; + if (session_tx && session_tx->state == SESSION_STATE_PENDING) { + if (now > + session_tx->last_pkt_sent + session_hs_retransmit_interval) { + struct sender_state *tx_state = session_tx->tx_state; + tx_send_initial(server, &tx_state->receiver[0].paths[0], + tx_state->filename, tx_state->filesize, + tx_state->chunklen, now, 0, session_tx->etherlen, true, true); + session_tx->last_pkt_sent = now; + } + } +} + +static void tx_handle_hs_confirm(struct hercules_server *server, + struct rbudp_initial_pkt *parsed_pkt) { + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_PENDING) { + // This is a reply to the very first packet and confirms connection + // setup + if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { + debug_printf("Handshake did not have correct flag set"); + return; + } + if (server->enable_pcc) { + u64 now = get_nsecs(); + struct sender_state_per_receiver *receiver = + server->session_tx->tx_state->receiver; + receiver->handshake_rtt = now - parsed_pkt->timestamp; + // TODO where to get rate limit? + receiver->cc_states = init_ccontrol_state( + 10000, server->session_tx->tx_state->total_chunks, + server->session_tx->num_paths, server->session_tx->num_paths, + server->session_tx->num_paths); + ccontrol_update_rtt(&receiver->cc_states[0], + receiver->handshake_rtt); + fprintf(stderr, + "[receiver %d] [path 0] handshake_rtt: " + "%fs, MI: %fs\n", + 0, receiver->handshake_rtt / 1e9, + receiver->cc_states[0].pcc_mi_duration); + + // make sure we later perform RTT estimation + // on every enabled path + receiver->paths[0].next_handshake_at = + UINT64_MAX; // We just completed the HS for this path + for (u32 p = 1; p < receiver->num_paths; p++) { + receiver->paths[p].next_handshake_at = now; + } + } + server->session_tx->state = SESSION_STATE_WAIT_CTS; + return; + } + + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_RUNNING) { + // This is a reply to some handshake we sent during an already + // established session (e.g. to open a new path) + u64 now = get_nsecs(); + struct sender_state_per_receiver *receiver = + server->session_tx->tx_state->receiver; + // TODO re-enable + /* ccontrol_update_rtt(&receiver->cc_states[parsed_pkt->path_index], */ + /* now - parsed_pkt->timestamp); */ + receiver->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; + return; + } + // In other cases we just drop the packet + debug_printf("Dropping HS confirm packet, was not expecting one"); +} + +// Map the provided file into memory for reading. Returns pointer to the mapped +// area, or null on error. +static char *tx_mmap(char *fname, size_t *filesize) { + int f = open(fname, O_RDONLY); + if (f == -1) { + return NULL; + } + struct stat stat; + int ret = fstat(f, &stat); + if (ret) { + close(f); + return NULL; + } + const size_t fsize = stat.st_size; + char *mem = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, f, 0); + if (mem == MAP_FAILED) { + close(f); + return NULL; + } + close(f); + *filesize = fsize; + return mem; +} /// OK PCC #define NACK_TRACE_SIZE (1024*1024) static u32 nack_trace_count = 0; @@ -1996,30 +2091,6 @@ static void rx_p(void *arg) { } } -// Map the provided file into memory for reading. Returns pointer to the mapped -// area, or null on error. -static char *tx_mmap(char *fname, size_t *filesize) { - int f = open(fname, O_RDONLY); - if (f == -1) { - return NULL; - } - struct stat stat; - int ret = fstat(f, &stat); - if (ret) { - close(f); - return NULL; - } - const size_t fsize = stat.st_size; - char *mem = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, f, 0); - if (mem == MAP_FAILED) { - close(f); - return NULL; - } - close(f); - *filesize = fsize; - return mem; -} - // Check if the monitor has new transfer jobs available and, if so, start one static void new_tx_if_available(struct hercules_server *server) { char fname[1000]; @@ -2131,79 +2202,8 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { } } -// (Re)send HS if needed -static void tx_retransmit_initial(struct hercules_server *server, u64 now) { - struct hercules_session *session_tx = server->session_tx; - if (session_tx && session_tx->state == SESSION_STATE_PENDING) { - if (now > - session_tx->last_pkt_sent + session_hs_retransmit_interval) { - struct sender_state *tx_state = session_tx->tx_state; - tx_send_initial(server, &tx_state->receiver[0].paths[0], - tx_state->filename, tx_state->filesize, - tx_state->chunklen, now, 0, session_tx->etherlen, true, true); - session_tx->last_pkt_sent = now; - } - } -} - -static void tx_handle_hs_confirm(struct hercules_server *server, - struct rbudp_initial_pkt *parsed_pkt) { - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_PENDING) { - // This is a reply to the very first packet and confirms connection - // setup - if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { - debug_printf("Handshake did not have correct flag set"); - return; - } - if (server->enable_pcc) { - u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = - server->session_tx->tx_state->receiver; - receiver->handshake_rtt = now - parsed_pkt->timestamp; - // TODO where to get rate limit? - receiver->cc_states = init_ccontrol_state( - 10000, server->session_tx->tx_state->total_chunks, - server->session_tx->num_paths, server->session_tx->num_paths, - server->session_tx->num_paths); - ccontrol_update_rtt(&receiver->cc_states[0], - receiver->handshake_rtt); - fprintf(stderr, - "[receiver %d] [path 0] handshake_rtt: " - "%fs, MI: %fs\n", - 0, receiver->handshake_rtt / 1e9, - receiver->cc_states[0].pcc_mi_duration); - - // make sure we later perform RTT estimation - // on every enabled path - receiver->paths[0].next_handshake_at = - UINT64_MAX; // We just completed the HS for this path - for (u32 p = 1; p < receiver->num_paths; p++) { - receiver->paths[p].next_handshake_at = now; - } - } - server->session_tx->state = SESSION_STATE_WAIT_CTS; - return; - } - - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_RUNNING) { - // This is a reply to some handshake we sent during an already - // established session (e.g. to open a new path) - u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = - server->session_tx->tx_state->receiver; - // TODO re-enable - /* ccontrol_update_rtt(&receiver->cc_states[parsed_pkt->path_index], */ - /* now - parsed_pkt->timestamp); */ - receiver->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; - return; - } - // In other cases we just drop the packet - debug_printf("Dropping HS confirm packet, was not expecting one"); -} - - +// Read control packets from the control socket and process them; also handles +// interaction with the monitor static void events_p(void *arg) { debug_printf("event listener thread started"); struct hercules_server *server = arg; @@ -2217,20 +2217,20 @@ static void events_p(void *arg) { u64 lastpoll = 0; while (1) { // event handler thread loop u64 now = get_nsecs(); - if (now > lastpoll + 1e9){ - if (server->session_tx == NULL) { - new_tx_if_available(server); - } - mark_timed_out_sessions(server, now); - cleanup_finished_sessions(server, now); - tx_retransmit_initial(server, now); - lastpoll = now; + /* if (now > lastpoll + 1e9){ */ + // XXX run the following every n seconds or every n socket reads? + if (server->session_tx == NULL) { + new_tx_if_available(server); } + mark_timed_out_sessions(server, now); + cleanup_finished_sessions(server, now); + tx_retransmit_initial(server, now); + /* lastpoll = now; */ + /* } */ // XXX This is a bit of a hack: We want to handle received packets more // frequently than we poll the monitor or check for expired sessions, so // try to receive 100 times (non-blocking) before doing anything else. - // TODO re-enable dontwait flag + loop for (int i = 0; i < 100; i++) { ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&addr, @@ -2505,6 +2505,7 @@ static void *tx_p(void *arg) { return NULL; } + static pthread_t start_thread(struct hercules_server *server, void *(start_routine), void *arg) { pthread_t pt; From 096043c8e594538832ccb414213c8f1bd9647bae Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 30 May 2024 13:58:07 +0200 Subject: [PATCH 019/112] re-enable pcc --- congestion_control.c | 2 - hercules.c | 179 +++++++++++++++++++++++++++++-------------- 2 files changed, 123 insertions(+), 58 deletions(-) diff --git a/congestion_control.c b/congestion_control.c index 5ff194a..52485f6 100644 --- a/congestion_control.c +++ b/congestion_control.c @@ -104,11 +104,9 @@ u32 ccontrol_can_send_npkts(struct ccontrol_state *cc_state, u64 now) u32 tx_pps = atomic_load(&cc_state->mi_tx_npkts) * 1000000000. / dt; if(tx_pps > cc_state->curr_rate) { - debug_printf("ret 0"); return 0; } u32 ret = (cc_state->curr_rate - tx_pps) * cc_state->pcc_mi_duration; - debug_printf("ret %u", ret); return ret; } diff --git a/hercules.c b/hercules.c index ab2c396..22d2b18 100644 --- a/hercules.c +++ b/hercules.c @@ -58,7 +58,7 @@ #define MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE 128 // E.g., SCION SPAO header added by LightningFilter -#define L4_SCMP 1 +#define L4_SCMP 202 #define RANDOMIZE_FLOWID @@ -170,7 +170,7 @@ static void send_eth_frame(struct hercules_server *server, } } -#define DEBUG_PRINT_PKTS +/* #define DEBUG_PRINT_PKTS */ #ifdef DEBUG_PRINT_PKTS // recv indicates whether printed packets should be prefixed with TX or RX void debug_print_rbudp_pkt(const char *pkt, bool recv) { @@ -196,7 +196,7 @@ void debug_print_rbudp_pkt(const char *pkt, bool recv) { cp->payload.initial.name); break; case CONTROL_PACKET_TYPE_ACK: - printf("%s ACK ", prefix); + printf("%s ACK (%d) ", prefix, cp->payload.ack.num_acks); for (int r = 0; r < cp->payload.ack.num_acks; r++) { printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); @@ -204,7 +204,7 @@ void debug_print_rbudp_pkt(const char *pkt, bool recv) { printf("\n"); break; case CONTROL_PACKET_TYPE_NACK: - printf("%s NACK \n", prefix); + printf("%s NACK (%d) ", prefix, cp->payload.ack.num_acks); for (int r = 0; r < cp->payload.ack.num_acks; r++) { printf("[%d - %d] ", cp->payload.ack.acks[r].begin, cp->payload.ack.acks[r].end); @@ -280,7 +280,7 @@ struct hercules_server *hercules_init_server( server->config.local_addr = local_addr; server->config.configure_queues = configure_queues; server->enable_pcc = - false; // TODO this should be per-path or at least per-transfer + true; // TODO this should be per-path or at least per-transfer for (int i = 0; i < num_ifaces; i++) { server->ifaces[i] = (struct hercules_interface){ @@ -594,7 +594,7 @@ static void set_rx_sample(struct receiver_state *rx_state, int ifid, const char static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { if(length < rbudp_headerlen + rx_state->chunklen) { - debug_printf("packet too short"); + debug_printf("packet too short: have %lu, expect %d", length, rbudp_headerlen + rx_state->chunklen ); return false; } @@ -745,6 +745,7 @@ static void rx_receive_batch(struct receiver_state *rx_state, atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, &old_last_pkt_rcvd, now); } + atomic_store(&rx_state->session->last_pkt_rcvd, now); u64 frame_addrs[BATCH_SIZE]; for (size_t i = 0; i < rcvd; i++) { @@ -1177,35 +1178,6 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ pthread_spin_unlock(&cc_state->lock); } -// TODO need to call this somewhere -bool tx_handle_handshake_reply(const struct rbudp_initial_pkt *initial, struct sender_state_per_receiver *rcvr) -{ - bool updated = false; - if(initial->path_index < rcvr->num_paths) { - u64 rtt_estimate = get_nsecs() - initial->timestamp; - if(atomic_load(&rcvr->paths[initial->path_index].next_handshake_at) != UINT64_MAX) { - atomic_store(&rcvr->paths[initial->path_index].next_handshake_at, UINT64_MAX); - if(rcvr->cc_states != NULL && rcvr->cc_states[initial->path_index].rtt == DBL_MAX) { - ccontrol_update_rtt(&rcvr->cc_states[initial->path_index], rtt_estimate); - updated = true; - } - if(initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - rcvr->handshake_rtt = rtt_estimate; - if(rcvr->cc_states != NULL) { - u64 now = get_nsecs(); - for(u32 p = 0; p < rcvr->num_paths; p++) { - if(p != initial->path_index && rcvr->paths[p].enabled) { - rcvr->paths[p].next_handshake_at = now; - rcvr->cc_states[p].pcc_mi_duration = DBL_MAX; - rcvr->cc_states[p].rtt = DBL_MAX; - } - } - } - } - } - } - return updated; -} static void tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, u32 etherlen, bool set_return_path, bool new_transfer) @@ -1531,6 +1503,7 @@ static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { if (tx_state->receiver[0].cc_states != NULL) { // use PCC struct ccontrol_state *cc_state = &tx_state->receiver[0].cc_states[tx_state->receiver[0].path_index]; + /* debug_printf("path idx %d", tx_state->receiver[0].path_index); */ allowed_chunks = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); } else { // no path-based limit @@ -1610,12 +1583,12 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 struct sender_state_per_receiver *rcvr = &tx_state->receiver[rcvr_idx]; u32 num_chunks_prepared = 0; u32 chunk_idx = rcvr->prev_chunk_idx; - debug_printf("n chunks %d", num_chunks); + /* debug_printf("n chunks %d", num_chunks); */ for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ chunk_idx = bitset__scan_neg(&rcvr->acked_chunks, chunk_idx); if(chunk_idx == tx_state->total_chunks) { - debug_printf("prev %d", rcvr->prev_chunk_idx); + /* debug_printf("prev %d", rcvr->prev_chunk_idx); */ if(rcvr->prev_chunk_idx == 0) { // this receiver has finished debug_printf("receiver has finished"); rcvr->finished = true; @@ -1744,8 +1717,11 @@ static void tx_handle_hs_confirm(struct hercules_server *server, server->session_tx->tx_state->receiver; receiver->handshake_rtt = now - parsed_pkt->timestamp; // TODO where to get rate limit? + // below is ~in Mb/s (but really pps) + u32 rate = 2000e3; // 20 Gbps + debug_printf("rate limit %u", rate); receiver->cc_states = init_ccontrol_state( - 10000, server->session_tx->tx_state->total_chunks, + rate, server->session_tx->tx_state->total_chunks, server->session_tx->num_paths, server->session_tx->num_paths, server->session_tx->num_paths); ccontrol_update_rtt(&receiver->cc_states[0], @@ -1775,10 +1751,23 @@ static void tx_handle_hs_confirm(struct hercules_server *server, u64 now = get_nsecs(); struct sender_state_per_receiver *receiver = server->session_tx->tx_state->receiver; - // TODO re-enable - /* ccontrol_update_rtt(&receiver->cc_states[parsed_pkt->path_index], */ - /* now - parsed_pkt->timestamp); */ + if (server->enable_pcc) { + ccontrol_update_rtt(&receiver->cc_states[parsed_pkt->path_index], + now - parsed_pkt->timestamp); + } receiver->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; + + // We have a new return path, redo handshakes on all other paths + if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH){ + receiver->handshake_rtt = now - parsed_pkt->timestamp; + for (u32 p = 0; p < receiver->num_paths; p++){ + if (p != parsed_pkt->path_index && receiver->paths[p].enabled){ + receiver->paths[p].next_handshake_at = now; + receiver->cc_states[p].pcc_mi_duration = DBL_MAX; + receiver->cc_states[p].rtt = DBL_MAX; + } + } + } return; } // In other cases we just drop the packet @@ -2054,9 +2043,9 @@ static void rx_trickle_nacks(void *arg) { u64 ack_round_end = get_nsecs(); if (ack_round_end > ack_round_start + rx_state->handshake_rtt * 1000 / 4) { - // fprintf(stderr, "NACK send too slow (took %lld of - // %ld)\n", ack_round_end - ack_round_start, - // rx_state->handshake_rtt * 1000 / 4); + /* fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", */ + /* ack_round_end - ack_round_start, */ + /* rx_state->handshake_rtt * 1000 / 4); */ } else { sleep_until(ack_round_start + rx_state->handshake_rtt * 1000 / 4); @@ -2150,6 +2139,9 @@ static void new_tx_if_available(struct hercules_server *server) { session->num_paths = n_paths; session->paths_to_dest = paths; + // TODO If the paths don't have the same header length this does not work: + // If the first path has a shorter header than the second, chunks won't fit + // on the second path u32 chunklen = session->paths_to_dest[0].payloadlen - rbudp_headerlen; atomic_store(&server->session_tx, session); struct sender_state *tx_state = init_tx_state( @@ -2202,6 +2194,61 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { } } +static inline void count_received_pkt(struct hercules_session *session, + u32 path_idx) { + atomic_fetch_add(&session->rx_npkts, 1); + if (path_idx < PCC_NO_PATH && session->rx_state != NULL) { + atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); + } +} + +struct prints{ + u32 rx_received; + u32 tx_sent; + u64 ts; +}; + +static void print_session_stats(struct hercules_server *server, + struct prints *p) { + u64 now = get_nsecs(); + if (now < p->ts + 500e6) { + return; + } + u64 tdiff = now - p->ts; + p->ts = now; + struct hercules_session *session_tx = server->session_tx; + if (session_tx && session_tx->state != SESSION_STATE_DONE) { + u32 sent_now = session_tx->tx_npkts; + double send_rate_pps = + (sent_now - p->tx_sent) / ( (double)tdiff / 1e9 ); + p->tx_sent = sent_now; + double send_rate = + 8 * send_rate_pps * server->session_tx->tx_state->chunklen / 1e6; + fprintf(stderr, "(TX) last: %llu, rx: %ld, tx:%ld, rate %.2f Mbps\n", + session_tx->last_pkt_rcvd, session_tx->rx_npkts, + session_tx->tx_npkts, send_rate); + } + struct hercules_session *session_rx = server->session_rx; + if (session_rx && session_rx->state != SESSION_STATE_DONE) { + u32 begin = bitset__scan_neg(&session_rx->rx_state->received_chunks, 0); + u32 rec_count = session_rx->rx_state->received_chunks.num_set; + u32 total = session_rx->rx_state->received_chunks.num; + u32 rcvd_now = session_rx->rx_npkts; + double recv_rate_pps = + (rcvd_now - p->rx_received) / ( (double)tdiff / 1e9 ); + p->rx_received = rcvd_now; + double recv_rate = + 8 * recv_rate_pps * server->session_rx->rx_state->chunklen / 1e6; + fprintf( + stderr, + "(RX) last: %llu, rx: %ld, tx:%ld, first missing %u, total %u/%u, rate %.2f\n", + session_rx->last_pkt_rcvd, session_rx->rx_npkts, + session_rx->tx_npkts, begin, rec_count, total, recv_rate); + } +} + +#define PRINT_STATS + // Read control packets from the control socket and process them; also handles // interaction with the monitor static void events_p(void *arg) { @@ -2215,6 +2262,7 @@ static void events_p(void *arg) { const struct udphdr *udphdr; u64 lastpoll = 0; + struct prints prints = {.rx_received=0, .ts=0, .tx_sent=0}; while (1) { // event handler thread loop u64 now = get_nsecs(); /* if (now > lastpoll + 1e9){ */ @@ -2225,6 +2273,9 @@ static void events_p(void *arg) { mark_timed_out_sessions(server, now); cleanup_finished_sessions(server, now); tx_retransmit_initial(server, now); +#ifdef PRINT_STATS + print_session_stats(server, &prints); +#endif /* lastpoll = now; */ /* } */ @@ -2269,23 +2320,15 @@ static void events_p(void *arg) { } debug_print_rbudp_pkt(rbudp_pkt, true); - // TODO Count received pkt - /* atomic_fetch_add(&session->rx_npkts, 1); */ - /* if(path_idx < PCC_NO_PATH && session->rx_state != NULL) { */ - /* atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, - * 1); */ - /* } */ - - // TODO this belongs somewhere? - /* if(tx_state->receiver[0].cc_states) { */ - /* pcc_monitor(tx_state); */ - /* } */ + // TODO Count received pkt, call count_received_pkt everywhere + // TODO check received packet has expected source address struct hercules_header *h = (struct hercules_header *)rbudp_pkt; if (h->chunk_idx == UINT_MAX) { // This is a control packet const char *pl = rbudp_pkt + rbudp_headerlen; struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; + u32 control_pkt_payloadlen = rbudp_len - rbudp_headerlen; switch (cp->type) { case CONTROL_PACKET_TYPE_INITIAL:; @@ -2345,6 +2388,10 @@ static void events_p(void *arg) { break; case CONTROL_PACKET_TYPE_ACK: + if (control_pkt_payloadlen < ack__len(&cp->payload.ack)){ + debug_printf("ACK packet too short"); + break; + } if (server->session_tx != NULL && server->session_tx->state == SESSION_STATE_WAIT_CTS) { @@ -2360,6 +2407,8 @@ static void events_p(void *arg) { tx_register_acks( &cp->payload.ack, &server->session_tx->tx_state->receiver[0]); + count_received_pkt(server->session_tx, h->path); + atomic_store(&server->session_tx->last_pkt_rcvd, get_nsecs()); if (tx_acked_all(server->session_tx->tx_state)) { debug_printf("TX done, received all acks"); quit_session(server->session_tx, @@ -2369,7 +2418,22 @@ static void events_p(void *arg) { break; case CONTROL_PACKET_TYPE_NACK: - // nack_trace_push(nack.timestamp, nack.ack_nr) + if (control_pkt_payloadlen < + ack__len(&cp->payload.ack)) { + debug_printf("NACK packet too short"); + break; + } + if (server->session_tx != NULL && + server->session_tx->state == + SESSION_STATE_RUNNING) { + count_received_pkt(server->session_tx, h->path); + nack_trace_push(cp->payload.ack.timestamp, + cp->payload.ack.ack_nr); + tx_register_nacks( + &cp->payload.ack, + &server->session_tx->tx_state->receiver + ->cc_states[h->path]); + } break; default: debug_printf("Received control packet of unknown type"); @@ -2380,6 +2444,9 @@ static void events_p(void *arg) { // all data packets debug_printf("Non-control packet received on control socket"); } + if (server->session_tx && server->session_tx->tx_state->receiver->cc_states){ + pcc_monitor(server->session_tx->tx_state); + } } } } From 929e4cc12e65068e00da525ba3f9aa503598c03f Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 30 May 2024 14:48:39 +0200 Subject: [PATCH 020/112] inform monitor about finished transfers --- hercules.c | 3 ++ hercules.h | 1 + monitor.c | 22 +++++++++- monitor.h | 103 +++++++++++++++++++++++++-------------------- monitor/monitor.go | 21 ++++++++- 5 files changed, 100 insertions(+), 50 deletions(-) diff --git a/hercules.c b/hercules.c index 22d2b18..a664d0b 100644 --- a/hercules.c +++ b/hercules.c @@ -2124,6 +2124,7 @@ static void new_tx_if_available(struct hercules_server *server) { } session->state = SESSION_STATE_PENDING; session->etherlen = mtu; + session->jobid = jobid; int n_paths; struct hercules_path *paths; @@ -2163,6 +2164,8 @@ static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { struct hercules_session *session_tx = atomic_load(&server->session_tx); if (session_tx && session_tx->state == SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { + monitor_update_job(usock, session_tx->jobid, session_tx->state, + session_tx->error); atomic_store(&server->session_tx, NULL); // FIXME leak fprintf(stderr, "Cleaning up TX session...\n"); } diff --git a/hercules.h b/hercules.h index dd64386..b7c58e4 100644 --- a/hercules.h +++ b/hercules.h @@ -195,6 +195,7 @@ struct hercules_session { u64 last_pkt_sent; u64 last_pkt_rcvd; u32 etherlen; + u32 jobid; struct hercules_app_addr destination; int num_paths; diff --git a/monitor.c b/monitor.c index 11f55cc..a38e318 100644 --- a/monitor.c +++ b/monitor.c @@ -95,9 +95,27 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_ap return true; } +bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum session_error err){ + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + + struct hercules_sockmsg_Q msg; + msg.msgtype = SOCKMSG_TYPE_UPDATE_JOB; + msg.payload.job_update.job_id = job_id; + msg.payload.job_update.status = state; + msg.payload.job_update.error = err; + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); + if (!reply.payload.job_update.ok){ + return false; + } + return true; +} + #define HERCULES_DAEMON_SOCKET_PATH "/var/hercules.sock" -// Bind the socket for the daemon. The file is deleted if already present. -// Returns the file descriptor if successful, 0 otherwise. int monitor_bind_daemon_socket(){ int usock = socket(AF_UNIX, SOCK_DGRAM, 0); if (usock <= 0){ diff --git a/monitor.h b/monitor.h index 3a08503..3060c7d 100644 --- a/monitor.h +++ b/monitor.h @@ -1,32 +1,41 @@ #ifndef HERCULES_MONITOR_H_ #define HERCULES_MONITOR_H_ -#include "hercules.h" #include #include + +#include "hercules.h" #include "utils.h" // Get a reply path from the monitor. The reversed path will be written to // *path. Returns false in case of error. -bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, - int etherlen, struct hercules_path *path); +bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, + int rx_sample_len, int etherlen, + struct hercules_path *path); // Get SCION paths from the monitor. The caller is responsible for freeing // **paths. bool monitor_get_paths(int sockfd, int job_id, int *n_paths, - struct hercules_path **paths); + struct hercules_path **paths); -// Check if the monitor has a new job available -// TODO -bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest, u16 *mtu); +// Check if the monitor has a new job available. +// If so the function returns true and the job's details are filled into the +// arguments. +bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, + struct hercules_app_addr *dest, u16 *mtu); -// Inform the monitor about a transfer's (new) status -// TODO -bool monitor_update_job(int sockfd, int job_id); +// Inform the monitor about a transfer's (new) status. +bool monitor_update_job(int sockfd, int job_id, enum session_state state, + enum session_error err); +// Bind the socket for the daemon. The file is deleted if already present. +// Returns the file descriptor if successful, 0 otherwise. int monitor_bind_daemon_socket(); -// Maximum size of variable-length fields in socket messages -#define SOCKMSG_MAX_PAYLOAD 2000 +// Maximum size of variable-length fields in socket messages. Since we pass +// entire packets to the monitor to get reply paths, this must be at least as +// large as HERCULES_MAX_PKT_SIZE. +#define SOCKMSG_MAX_PAYLOAD 5000 +_Static_assert(SOCKMSG_MAX_PAYLOAD >= HERCULES_MAX_PKTSIZE, "Socket messages too small"); // Maximum number of paths transferred #define SOCKMSG_MAX_PATHS 10 @@ -41,19 +50,19 @@ int monitor_bind_daemon_socket(); // checksum #define SOCKMSG_TYPE_GET_REPLY_PATH (1) struct sockmsg_reply_path_Q { - uint16_t sample_len; - uint16_t etherlen; - uint8_t sample[SOCKMSG_MAX_PAYLOAD]; + uint16_t sample_len; + uint16_t etherlen; + uint8_t sample[SOCKMSG_MAX_PAYLOAD]; }; struct sockmsg_serialized_path { - uint16_t chksum; - uint16_t ifid; - uint32_t headerlen; - uint8_t header[HERCULES_MAX_HEADERLEN]; + uint16_t chksum; + uint16_t ifid; + uint32_t headerlen; + uint8_t header[HERCULES_MAX_HEADERLEN]; }; struct sockmsg_reply_path_A { - struct sockmsg_serialized_path path; + struct sockmsg_serialized_path path; }; // Ask the monitor for new transfer jobs. @@ -61,53 +70,55 @@ struct sockmsg_reply_path_A { #define SOCKMSG_TYPE_GET_NEW_JOB (2) struct sockmsg_new_job_Q {}; struct sockmsg_new_job_A { - uint8_t has_job; // The other fields are only valid if this is set to 1 - uint16_t job_id; - uint16_t mtu; - uint16_t filename_len; - uint8_t filename[SOCKMSG_MAX_PAYLOAD]; + uint8_t has_job; // The other fields are only valid if this is set to 1 + uint16_t job_id; + uint16_t mtu; + uint16_t filename_len; + uint8_t filename[SOCKMSG_MAX_PAYLOAD]; }; // Get paths to use for a given job ID #define SOCKMSG_TYPE_GET_PATHS (3) struct sockmsg_paths_Q { - uint16_t job_id; + uint16_t job_id; }; struct sockmsg_paths_A { - uint16_t n_paths; - struct sockmsg_serialized_path paths[SOCKMSG_MAX_PATHS]; + uint16_t n_paths; + struct sockmsg_serialized_path paths[SOCKMSG_MAX_PATHS]; }; // Inform the monitor about a job's status #define SOCKMSG_TYPE_UPDATE_JOB (4) struct sockmsg_update_job_Q { - uint16_t job_id; - uint32_t status; // One of enum session_state - uint32_t error; // One of enum session_error + uint16_t job_id; + uint32_t status; // One of enum session_state + uint32_t error; // One of enum session_error +}; +struct sockmsg_update_job_A { + uint16_t ok; }; -struct sockmsg_update_job_A {}; struct hercules_sockmsg_Q { - uint16_t msgtype; - union { - struct sockmsg_reply_path_Q reply_path; - struct sockmsg_paths_Q paths; - struct sockmsg_new_job_Q newjob; - struct sockmsg_update_job_Q job_update; - } payload; + uint16_t msgtype; + union { + struct sockmsg_reply_path_Q reply_path; + struct sockmsg_paths_Q paths; + struct sockmsg_new_job_Q newjob; + struct sockmsg_update_job_Q job_update; + } payload; }; // Used by go code #define SOCKMSG_SIZE sizeof(struct hercules_sockmsg_Q) struct hercules_sockmsg_A { - union { - struct sockmsg_reply_path_A reply_path; - struct sockmsg_paths_A paths; - struct sockmsg_new_job_A newjob; - struct sockmsg_update_job_A job_update; - } payload; + union { + struct sockmsg_reply_path_A reply_path; + struct sockmsg_paths_A paths; + struct sockmsg_new_job_A newjob; + struct sockmsg_update_job_A job_update; + } payload; }; #pragma pack(pop) -#endif // HERCULES_MONITOR_H_ +#endif // HERCULES_MONITOR_H_ diff --git a/monitor/monitor.go b/monitor/monitor.go index c4f8c5c..5afe704 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -404,13 +404,16 @@ const ( Done ) +// TODO state/status names are confusing type HerculesTransfer struct { id int - status TransferState + status TransferState //< State as seen by the monitor file string dest snet.UDPAddr mtu int nPaths int + state C.enum_session_state //< The state returned by the server + err C.enum_session_error //< The error returned by the server } var transfersLock sync.Mutex @@ -567,7 +570,21 @@ func main() { usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_UPDATE_JOB: - fallthrough + job_id := binary.LittleEndian.Uint16(buf[:2]) + buf = buf[2:] + status := binary.LittleEndian.Uint32(buf[:4]) + buf = buf[4:] + errorcode := binary.LittleEndian.Uint32(buf[:4]) + buf = buf[4:] + fmt.Println("updating job", job_id, status, errorcode) + transfersLock.Lock() + job, _ := transfers[int(job_id)] + job.state = status + job.err = errorcode + fmt.Println(job, job.state) + transfersLock.Unlock() + b := binary.LittleEndian.AppendUint16(nil, uint16(1)) + usock.WriteToUnix(b, a) default: fmt.Println("unknown message?") From 293798099853252921434b3c575e907b5c62f1cf Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 31 May 2024 10:12:34 +0200 Subject: [PATCH 021/112] periodically ask for new paths --- hercules.c | 49 +++++++++++++++++------ monitor/monitor.go | 75 ++++++++++++++++++----------------- monitor/pathmanager.go | 54 ++++++------------------- monitor/pathstodestination.go | 51 +++++++----------------- 4 files changed, 103 insertions(+), 126 deletions(-) diff --git a/hercules.c b/hercules.c index a664d0b..01d88aa 100644 --- a/hercules.c +++ b/hercules.c @@ -1718,7 +1718,8 @@ static void tx_handle_hs_confirm(struct hercules_server *server, receiver->handshake_rtt = now - parsed_pkt->timestamp; // TODO where to get rate limit? // below is ~in Mb/s (but really pps) - u32 rate = 2000e3; // 20 Gbps + /* u32 rate = 2000e3; // 20 Gbps */ + u32 rate = 100; // 1 Mbps debug_printf("rate limit %u", rate); receiver->cc_states = init_ccontrol_state( rate, server->session_tx->tx_state->total_chunks, @@ -2197,6 +2198,25 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { } } +static void tx_update_paths(struct hercules_server *server) { + struct hercules_session *session_tx = server->session_tx; + if (session_tx && session_tx->state == SESSION_STATE_RUNNING) { + int n_paths; + struct hercules_path *paths; + bool ret = + monitor_get_paths(usock, session_tx->jobid, &n_paths, &paths); + if (!ret) { + debug_printf("error getting paths"); + return; + } + debug_printf("received %d paths", n_paths); + // XXX doesn't this break if we get more paths and the update is not + // atomic? + session_tx->num_paths = n_paths; + session_tx->paths_to_dest = paths; + } +} + static inline void count_received_pkt(struct hercules_session *session, u32 path_idx) { atomic_fetch_add(&session->rx_npkts, 1); @@ -2219,18 +2239,21 @@ static void print_session_stats(struct hercules_server *server, } u64 tdiff = now - p->ts; p->ts = now; + struct hercules_session *session_tx = server->session_tx; if (session_tx && session_tx->state != SESSION_STATE_DONE) { u32 sent_now = session_tx->tx_npkts; - double send_rate_pps = - (sent_now - p->tx_sent) / ( (double)tdiff / 1e9 ); + u32 acked_count = session_tx->tx_state->receiver->acked_chunks.num_set; + u32 total = session_tx->tx_state->receiver->acked_chunks.num; + double send_rate_pps = (sent_now - p->tx_sent) / ((double)tdiff / 1e9); p->tx_sent = sent_now; double send_rate = 8 * send_rate_pps * server->session_tx->tx_state->chunklen / 1e6; - fprintf(stderr, "(TX) last: %llu, rx: %ld, tx:%ld, rate %.2f Mbps\n", - session_tx->last_pkt_rcvd, session_tx->rx_npkts, - session_tx->tx_npkts, send_rate); + fprintf(stderr, "(TX) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", + acked_count, total, session_tx->rx_npkts, session_tx->tx_npkts, + send_rate); } + struct hercules_session *session_rx = server->session_rx; if (session_rx && session_rx->state != SESSION_STATE_DONE) { u32 begin = bitset__scan_neg(&session_rx->rx_state->received_chunks, 0); @@ -2238,15 +2261,13 @@ static void print_session_stats(struct hercules_server *server, u32 total = session_rx->rx_state->received_chunks.num; u32 rcvd_now = session_rx->rx_npkts; double recv_rate_pps = - (rcvd_now - p->rx_received) / ( (double)tdiff / 1e9 ); + (rcvd_now - p->rx_received) / ((double)tdiff / 1e9); p->rx_received = rcvd_now; double recv_rate = 8 * recv_rate_pps * server->session_rx->rx_state->chunklen / 1e6; - fprintf( - stderr, - "(RX) last: %llu, rx: %ld, tx:%ld, first missing %u, total %u/%u, rate %.2f\n", - session_rx->last_pkt_rcvd, session_rx->rx_npkts, - session_rx->tx_npkts, begin, rec_count, total, recv_rate); + fprintf(stderr, "(RX) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", + rec_count, total, session_rx->rx_npkts, session_rx->tx_npkts, + recv_rate); } } @@ -2279,6 +2300,10 @@ static void events_p(void *arg) { #ifdef PRINT_STATS print_session_stats(server, &prints); #endif + if (now > lastpoll + 10e9){ + tx_update_paths(server); + lastpoll = now; + } /* lastpoll = now; */ /* } */ diff --git a/monitor/monitor.go b/monitor/monitor.go index 5afe704..eadc70e 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -206,6 +206,11 @@ func SerializePath(from *HerculesPathHeader, ifid int) []byte { out = binary.LittleEndian.AppendUint16(out, uint16(ifid)) fmt.Println(out) out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) + if len(from.Header) > C.HERCULES_MAX_HEADERLEN { + // Header does not fit in the C struct + fmt.Println("!! Header too long !!") + // TODO This will panic in the below call to bytes.Repeat(), do something more appropriate + } fmt.Println(out) out = append(out, from.Header...) out = append(out, bytes.Repeat([]byte{0x00}, C.HERCULES_MAX_HEADERLEN-len(from.Header))...) @@ -365,55 +370,46 @@ func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcA return herculesPath } -func pickPathsToDestination(etherLen int, numPaths int, localAddress snet.UDPAddr, interfaces []*net.Interface, destination snet.UDPAddr) []PathMeta { - dest := []*Destination{{hostAddr: &destination, numPaths: numPaths, pathSpec: &[]PathSpec{}}} - // TODO replace bps limit with the correct value - pm, _ := initNewPathManager(interfaces, dest, &localAddress, uint64(etherLen)) - pm.choosePaths() - numSelectedPaths := len(pm.dsts[0].paths) - fmt.Println("selected paths", numSelectedPaths) - return pm.dsts[0].paths -} - -func headersToDestination(src, dst snet.UDPAddr, ifs []*net.Interface, etherLen int, nPaths int) (int, []byte) { - fmt.Println("making headers", src, dst, nPaths) +func headersToDestination(transfer HerculesTransfer) (int, []byte) { + fmt.Println("making headers", transfer.pm.src, transfer.dest, transfer.nPaths) srcA := addr.Addr{ - IA: src.IA, - Host: addr.MustParseHost(src.Host.IP.String()), + IA: localAddress.IA, + Host: addr.MustParseHost(localAddress.Host.IP.String()), } dstA := addr.Addr{ - IA: dst.IA, - Host: addr.MustParseHost(dst.Host.IP.String()), + IA: transfer.dest.IA, + Host: addr.MustParseHost(transfer.dest.Host.IP.String()), } - // TODO numpaths should come from somewhere - paths := pickPathsToDestination(etherLen, nPaths, src, ifs, dst) + transfer.pm.choosePaths() + paths := transfer.pm.dst.paths numSelectedPaths := len(paths) headers_ser := []byte{} for _, p := range paths { - preparedHeader := prepareHeader(p, etherLen, *src.Host, *dst.Host, srcA, dstA) + preparedHeader := prepareHeader(p, transfer.mtu, *transfer.pm.src.Host, *transfer.dest.Host, srcA, dstA) headers_ser = append(headers_ser, SerializePath(&preparedHeader, p.iface.Index)...) } return numSelectedPaths, headers_ser } -type TransferState int +type TransferStatus int const ( - Queued TransferState = iota + Queued TransferStatus = iota Submitted Done ) -// TODO state/status names are confusing type HerculesTransfer struct { - id int - status TransferState //< State as seen by the monitor - file string - dest snet.UDPAddr - mtu int - nPaths int - state C.enum_session_state //< The state returned by the server - err C.enum_session_error //< The error returned by the server + id int // ID identifying this transfer + status TransferStatus // Status as seen by the monitor + file string // Name of the file to transfer + dest snet.UDPAddr // Destination + mtu int // MTU to use + nPaths int // Maximum number of paths to use + pm *PathManager + // The following two fields are meaningless if the job's status is 'Queued' + state C.enum_session_state // The state returned by the server + err C.enum_session_error // The error returned by the server } var transfersLock sync.Mutex @@ -454,6 +450,8 @@ func httpreq(w http.ResponseWriter, r *http.Request) { } } fmt.Println(destParsed) + destination := &Destination{hostAddr: destParsed, numPaths: nPaths, pathSpec: &[]PathSpec{}} + pm, _ := initNewPathManager(interfaces, destination, localAddress, uint64(mtu)) transfersLock.Lock() transfers[nextID] = &HerculesTransfer{ id: nextID, @@ -462,6 +460,7 @@ func httpreq(w http.ResponseWriter, r *http.Request) { dest: *destParsed, mtu: mtu, nPaths: nPaths, + pm: pm, } nextID += 1 transfersLock.Unlock() @@ -469,10 +468,8 @@ func httpreq(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "OK") } -func pathsForTransfer(id int) []C.struct_hercules_path { - paths := []C.struct_hercules_path{} - return paths -} +var localAddress *snet.UDPAddr +var interfaces []*net.Interface func main() { var localAddr string @@ -484,6 +481,8 @@ func main() { flag.Usage() return } + localAddress = src + GlobalQuerier = newPathQuerier() // TODO make socket paths congfigurable daemon, err := net.ResolveUnixAddr("unixgram", "/var/hercules.sock") @@ -499,8 +498,12 @@ func main() { for i, _ := range ifs { iffs = append(iffs, &ifs[i]) } + interfaces = iffs - pm, err := initNewPathManager(iffs, nil, src, 0) + // used for looking up reply path interface + pm, err := initNewPathManager(iffs, &Destination{ + hostAddr: localAddress, + }, src, 0) fmt.Println(err) http.HandleFunc("/", httpreq) @@ -563,7 +566,7 @@ func main() { fmt.Println("fetch path, job", job_id) transfersLock.Lock() job, _ := transfers[int(job_id)] - n_headers, headers := headersToDestination(*src, job.dest, iffs, job.mtu, job.nPaths) + n_headers, headers := headersToDestination(*job) transfersLock.Unlock() b := binary.LittleEndian.AppendUint16(nil, uint16(n_headers)) b = append(b, headers...) diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index dba17b4..44c4cf2 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -31,11 +31,10 @@ type Destination struct { type PathManager struct { numPathSlotsPerDst int interfaces map[int]*net.Interface - dsts []*PathsToDestination + dst *PathsToDestination src *snet.UDPAddr syncTime time.Time maxBps uint64 - // cStruct CPathManagement } type PathWithInterface struct { @@ -47,66 +46,39 @@ type AppPathSet map[snet.PathFingerprint]PathWithInterface const numPathsResolved = 20 -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func initNewPathManager(interfaces []*net.Interface, dsts []*Destination, src *snet.UDPAddr, maxBps uint64) (*PathManager, error) { +func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr, maxBps uint64) (*PathManager, error) { ifMap := make(map[int]*net.Interface) for _, iface := range interfaces { ifMap[iface.Index] = iface } - numPathsPerDst := 0 pm := &PathManager{ interfaces: ifMap, src: src, - dsts: make([]*PathsToDestination, 0, len(dsts)), + dst: &PathsToDestination{}, syncTime: time.Unix(0, 0), maxBps: maxBps, } - for _, dst := range dsts { - var dstState *PathsToDestination - if src.IA == dst.hostAddr.IA { - dstState = initNewPathsToDestinationWithEmptyPath(pm, dst) - } else { - var err error - dstState, err = initNewPathsToDestination(pm, src, dst) - if err != nil { - return nil, err - } + if src.IA == dst.hostAddr.IA { + pm.dst = initNewPathsToDestinationWithEmptyPath(pm, dst) + } else { + var err error + pm.dst, err = initNewPathsToDestination(pm, src, dst) + if err != nil { + return nil, err } - pm.dsts = append(pm.dsts, dstState) - numPathsPerDst = max(numPathsPerDst, dst.numPaths) } - // allocate memory to pass paths to C - pm.numPathSlotsPerDst = numPathsPerDst - // pm.cStruct.initialize(len(dsts), numPathsPerDst) return pm, nil } -func (pm *PathManager) canSendToAllDests() bool { - for _, dst := range pm.dsts { - if !dst.hasUsablePaths() { - return false - } - } - return true +func (pm *PathManager) canSendToDest() bool { + return pm.dst.hasUsablePaths() } func (pm *PathManager) choosePaths() bool { - updated := false - for _, dst := range pm.dsts { - if dst.choosePaths() { - updated = true - } - } - return updated + return pm.dst.choosePaths() } func (pm *PathManager) filterPathsByActiveInterfaces(pathsAvail []snet.Path) AppPathSet { diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 9f20230..f980d57 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -26,6 +26,8 @@ import ( "time" ) +var GlobalQuerier snet.PathQuerier + type PathsToDestination struct { pm *PathManager dst *Destination @@ -33,7 +35,7 @@ type PathsToDestination struct { ExtnUpdated atomic.Bool allPaths []snet.Path paths []PathMeta // nil indicates that the destination is in the same AS as the sender and we can use an empty path - canSendLocally bool // (only if destination in same AS) indicates if we can send packets + canSendLocally bool // (only if destination in same AS) indicates if we can send packets } type PathMeta struct { @@ -59,14 +61,10 @@ func initNewPathsToDestinationWithEmptyPath(pm *PathManager, dst *Destination) * } func initNewPathsToDestination(pm *PathManager, src *snet.UDPAddr, dst *Destination) (*PathsToDestination, error) { - paths, err := newPathQuerier().Query(context.Background(), dst.hostAddr.IA) - if err != nil { - return nil, err - } return &PathsToDestination{ pm: pm, dst: dst, - allPaths: paths, + allPaths: nil, paths: make([]PathMeta, dst.numPaths), modifyTime: time.Unix(0, 0), }, nil @@ -85,15 +83,13 @@ func (ptd *PathsToDestination) hasUsablePaths() bool { } func (ptd *PathsToDestination) choosePaths() bool { - if ptd.allPaths == nil { + var err error + ptd.allPaths, err = GlobalQuerier.Query(context.Background(), ptd.dst.hostAddr.IA) + if err != nil { return false } - if ptd.modifyTime.After(time.Unix(0, 0)) { // TODO this chooses paths only once - if ptd.ExtnUpdated.Swap(false) { - ptd.modifyTime = time.Now() - return true - } + if ptd.allPaths == nil { return false } @@ -103,23 +99,11 @@ func (ptd *PathsToDestination) choosePaths() bool { log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) } - previousPathAvailable := make([]bool, ptd.dst.numPaths) - updated := ptd.choosePreviousPaths(&previousPathAvailable, &availablePaths) + // TODO Ensure this still does the right thing when the number of paths decreases (how to test?) + ptd.chooseNewPaths(&availablePaths) - if ptd.disableVanishedPaths(&previousPathAvailable) { - updated = true - } - // Note: we keep vanished paths around until they can be replaced or re-enabled - - if ptd.chooseNewPaths(&previousPathAvailable, &availablePaths) { - updated = true - } - - if ptd.ExtnUpdated.Swap(false) || updated { - ptd.modifyTime = time.Now() - return true - } - return false + fmt.Println("chosen paths", ptd.paths) + return true } func (ptd *PathsToDestination) choosePreviousPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { @@ -154,15 +138,8 @@ func (ptd *PathsToDestination) disableVanishedPaths(previousPathAvailable *[]boo return updated } -func (ptd *PathsToDestination) chooseNewPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { +func (ptd *PathsToDestination) chooseNewPaths(availablePaths *AppPathSet) bool { updated := false - // XXX for now, we do not support replacing vanished paths - // check that no previous path available - for _, prev := range *previousPathAvailable { - if prev { - return false - } - } // pick paths picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) @@ -233,5 +210,5 @@ func (ptd *PathsToDestination) preparePath(p *PathMeta) (*HerculesPathHeader, er // return nil, err // } // return path, nil - return nil, fmt.Errorf("NOPE"); + return nil, fmt.Errorf("NOPE") } From 7fe725e9ec510191ee6ce04f97e491a71c20c35d Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sat, 1 Jun 2024 19:23:43 +0200 Subject: [PATCH 022/112] session freeing, mark some fields atomic --- hercules.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++---- hercules.h | 11 ++++++----- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/hercules.c b/hercules.c index 01d88aa..f63fa19 100644 --- a/hercules.c +++ b/hercules.c @@ -250,7 +250,42 @@ static struct hercules_session *make_session(struct hercules_server *server) { return s; } -static void destroy_session(struct hercules_session *session) { +// Cleanup and free TX session +static void destroy_session_tx(struct hercules_session *session) { + if (session == NULL) { + return; + } + assert(session->state == SESSION_STATE_DONE); + + int ret = munmap(session->tx_state->mem, session->tx_state->filesize); + assert(ret == 0); // No reason this should ever fail + + free(session->paths_to_dest); + bitset__destroy(&session->tx_state->receiver->acked_chunks); + free(session->tx_state->receiver->paths); + bitset__destroy(&session->tx_state->receiver->cc_states->mi_nacked); + free(session->tx_state->receiver->cc_states); + free(session->tx_state->receiver); + free(session->tx_state); + + destroy_send_queue(session->send_queue); + free(session->send_queue); + free(session); +} + +// Cleanup and free RX session +static void destroy_session_rx(struct hercules_session *session) { + if (session == NULL) { + return; + } + assert(session->state == SESSION_STATE_DONE); + + int ret = munmap(session->rx_state->mem, session->rx_state->filesize); + assert(ret == 0); // No reason this should ever fail + + bitset__destroy(&session->rx_state->received_chunks); + free(session->rx_state); + destroy_send_queue(session->send_queue); free(session->send_queue); free(session); @@ -272,7 +307,6 @@ struct hercules_server *hercules_init_server( server->n_threads = n_threads; server->session_rx = NULL; server->session_tx = NULL; - server->session_tx_counter = 0; server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); if (server->worker_args == NULL){ exit_with_error(NULL, ENOMEM); @@ -2167,15 +2201,29 @@ static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { monitor_update_job(usock, session_tx->jobid, session_tx->state, session_tx->error); - atomic_store(&server->session_tx, NULL); // FIXME leak + struct hercules_session *current = server->session_tx; + atomic_store(&server->session_tx, NULL); fprintf(stderr, "Cleaning up TX session...\n"); + // At this point we don't know if some other thread still has a + // pointer to the session that it might dereference, so we cannot + // safely free it. So, we record the pointer and defer freeing it + // until after the next session has completed. At that point, no + // references to the deferred session should be around, so we then + // free it. + // XXX Is this really good enough? + destroy_session_tx(server->deferred_tx); + server->deferred_tx = current; } } struct hercules_session *session_rx = atomic_load(&server->session_rx); if (session_rx && session_rx->state == SESSION_STATE_DONE) { if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { - atomic_store(&server->session_rx, NULL); // FIXME leak + struct hercules_session *current = server->session_rx; + atomic_store(&server->session_rx, NULL); fprintf(stderr, "Cleaning up RX session...\n"); + // See the note above on deferred freeing + destroy_session_rx(server->deferred_rx); + server->deferred_rx = current; } } } diff --git a/hercules.h b/hercules.h index b7c58e4..53559f2 100644 --- a/hercules.h +++ b/hercules.h @@ -189,8 +189,8 @@ enum session_error { struct hercules_session { struct receiver_state *rx_state; struct sender_state *tx_state; - enum session_state state; - enum session_error error; + _Atomic enum session_state state; + _Atomic enum session_error error; struct send_queue *send_queue; u64 last_pkt_sent; u64 last_pkt_rcvd; @@ -231,9 +231,10 @@ struct hercules_server { int control_sockfd; // AF_PACKET socket used for control traffic int n_threads; struct rx_p_args **worker_args; - struct hercules_session *session_tx; // Current TX session - u64 session_tx_counter; - struct hercules_session *session_rx; // Current RX session + struct hercules_session * _Atomic session_tx; // Current TX session + struct hercules_session * deferred_tx; + struct hercules_session * _Atomic session_rx; // Current RX session + struct hercules_session * deferred_rx; int max_paths; int rate_limit; bool enable_pcc; From ac64813c8d7e6de78761e4ba6366c317ae092d86 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 2 Jun 2024 13:15:57 +0200 Subject: [PATCH 023/112] quit if no paths received --- hercules.c | 10 ++++++++-- hercules.h | 1 + monitor/pathstodestination.go | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/hercules.c b/hercules.c index f63fa19..23ecbb2 100644 --- a/hercules.c +++ b/hercules.c @@ -2164,10 +2164,11 @@ static void new_tx_if_available(struct hercules_server *server) { int n_paths; struct hercules_path *paths; ret = monitor_get_paths(usock, jobid, &n_paths, &paths); - if (!ret){ + if (!ret || n_paths == 0){ debug_printf("error getting paths"); munmap(mem, filesize); - destroy_session(session); + /* destroy_session(session); */ // FIXME + // TODO update job err return; } // TODO free paths @@ -2258,6 +2259,11 @@ static void tx_update_paths(struct hercules_server *server) { return; } debug_printf("received %d paths", n_paths); + if (n_paths == 0){ + free(paths); + quit_session(session_tx, SESSION_ERROR_NO_PATHS); + return; + } // XXX doesn't this break if we get more paths and the update is not // atomic? session_tx->num_paths = n_paths; diff --git a/hercules.h b/hercules.h index 53559f2..ff5b0f3 100644 --- a/hercules.h +++ b/hercules.h @@ -183,6 +183,7 @@ enum session_error { SESSION_ERROR_TIMEOUT, //< Session timed out SESSION_ERROR_PCC, //< Something wrong with PCC SESSION_ERROR_SEQNO_OVERFLOW, + SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination }; // A session is a transfer between one sender and one receiver diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index f980d57..451f32f 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -167,6 +167,10 @@ func (ptd *PathsToDestination) chooseNewPaths(availablePaths *AppPathSet) bool { } log.Info(fmt.Sprintf("[Destination %s] using %d paths:", ptd.dst.hostAddr.IA, len(pathSet))) + if (len(pathSet) == 0){ + ptd.paths = []PathMeta{} + return false + } for i, path := range pathSet { log.Info(fmt.Sprintf("\t%s", path)) fingerprint := snet.Fingerprint(path) From a889a8a465f38d84f9bc57648721029be8c98d96 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 3 Jun 2024 13:47:02 +0200 Subject: [PATCH 024/112] monitor: configfile --- monitor/go.mod | 3 +- monitor/go.sum | 7 ++- monitor/monitor.go | 123 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/monitor/go.mod b/monitor/go.mod index 857af6e..05ab4a7 100644 --- a/monitor/go.mod +++ b/monitor/go.mod @@ -1,11 +1,11 @@ module monitor - go 1.21 toolchain go1.21.6 require ( + github.com/BurntSushi/toml v0.3.1 github.com/google/gopacket v1.1.19 github.com/inconshreveable/log15 v2.16.0+incompatible github.com/scionproto/scion v0.10.0 @@ -36,6 +36,7 @@ require ( github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.0.0+incompatible // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect diff --git a/monitor/go.sum b/monitor/go.sum index 701b91f..f078e0e 100644 --- a/monitor/go.sum +++ b/monitor/go.sum @@ -31,6 +31,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -240,12 +241,16 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= diff --git a/monitor/monitor.go b/monitor/monitor.go index eadc70e..04f5acd 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -15,10 +15,10 @@ import ( "syscall" "time" + "github.com/BurntSushi/toml" "github.com/google/gopacket" "github.com/google/gopacket/layers" - // "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/private/topology" @@ -382,9 +382,15 @@ func headersToDestination(transfer HerculesTransfer) (int, []byte) { } transfer.pm.choosePaths() paths := transfer.pm.dst.paths - numSelectedPaths := len(paths) - headers_ser := []byte{} + enabledPaths := []PathMeta{} for _, p := range paths { + if p.enabled { + enabledPaths = append(enabledPaths, p) + } + } + numSelectedPaths := len(enabledPaths) + headers_ser := []byte{} + for _, p := range enabledPaths { preparedHeader := prepareHeader(p, transfer.mtu, *transfer.pm.src.Host, *transfer.dest.Host, srcA, dstA) headers_ser = append(headers_ser, SerializePath(&preparedHeader, p.iface.Index)...) } @@ -450,8 +456,8 @@ func httpreq(w http.ResponseWriter, r *http.Request) { } } fmt.Println(destParsed) - destination := &Destination{hostAddr: destParsed, numPaths: nPaths, pathSpec: &[]PathSpec{}} - pm, _ := initNewPathManager(interfaces, destination, localAddress, uint64(mtu)) + destination := findPathRule(&pathRules, destParsed) + pm, _ := initNewPathManager(interfaces, &destination, localAddress, uint64(mtu)) transfersLock.Lock() transfers[nextID] = &HerculesTransfer{ id: nextID, @@ -471,15 +477,120 @@ func httpreq(w http.ResponseWriter, r *http.Request) { var localAddress *snet.UDPAddr var interfaces []*net.Interface +type HostConfig struct { + HostAddr addr.Addr + NumPaths int + PathSpec []PathSpec +} +type ASConfig struct { + IA addr.IA + NumPaths int + PathSpec []PathSpec +} + +type MonitorConfig struct { + DestinationHosts []HostConfig + DestinationASes []ASConfig + DefaultNumPaths int +} + +type PathRules struct { + Hosts map[addr.Addr]HostConfig + ASes map[addr.IA]ASConfig + DefaultNumPaths int +} + +var config MonitorConfig +var pathRules PathRules = PathRules{} + +func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { + a := addr.Addr{ + IA: dest.IA, + Host: addr.MustParseHost(dest.Host.IP.String()), + } + confHost, ok := p.Hosts[a] + if ok { + return Destination{ + hostAddr: dest, + pathSpec: &confHost.PathSpec, + numPaths: confHost.NumPaths, + } + } + conf, ok := p.ASes[dest.IA] + if ok { + return Destination{ + hostAddr: dest, + pathSpec: &conf.PathSpec, + numPaths: conf.NumPaths, + } + } + return Destination{ + hostAddr: dest, + pathSpec: &[]PathSpec{}, + numPaths: p.DefaultNumPaths, + } +} + func main() { var localAddr string + var configFile string flag.StringVar(&localAddr, "l", "", "local address") + flag.StringVar(&configFile, "c", "herculesmon.conf", "Path to the monitor configuration file") flag.Parse() + meta, err := toml.DecodeFile(configFile, &config) + if err != nil { + fmt.Printf("Error reading configuration file (%v): %v\n", configFile, err) + os.Exit(1) + } + if len(meta.Undecoded()) > 0 { + fmt.Printf("Unknown element(s) in config file: %v\n", meta.Undecoded()) + os.Exit(1) + } + // TODO Would be nice not to have to do this dance and specify the maps directly in the config file, + // but the toml package crashes if the keys are addr.Addr + if config.DefaultNumPaths == 0 { + fmt.Println("Config: Default number of paths to use not set, using 1.") + config.DefaultNumPaths = 1 + } + pathRules.Hosts = map[addr.Addr]HostConfig{} + for _, host := range config.DestinationHosts { + numpaths := config.DefaultNumPaths + if host.NumPaths != 0 { + numpaths = host.NumPaths + } + pathspec := []PathSpec{} + if host.PathSpec != nil { + pathspec = host.PathSpec + } + pathRules.Hosts[host.HostAddr] = HostConfig{ + HostAddr: host.HostAddr, + NumPaths: numpaths, + PathSpec: pathspec, + } + } + pathRules.ASes = map[addr.IA]ASConfig{} + for _, as := range config.DestinationASes { + numpaths := config.DefaultNumPaths + if as.NumPaths != 0 { + numpaths = as.NumPaths + } + pathspec := []PathSpec{} + if as.PathSpec != nil { + pathspec = as.PathSpec + } + pathRules.ASes[as.IA] = ASConfig{ + IA: as.IA, + NumPaths: numpaths, + PathSpec: pathspec, + } + } + pathRules.DefaultNumPaths = config.DefaultNumPaths + src, err := snet.ParseUDPAddr(localAddr) if err != nil || src.Host.Port == 0 { flag.Usage() - return + os.Exit(1) } localAddress = src GlobalQuerier = newPathQuerier() From 26d2dce6388daea273eae085327e9c79d8990ccf Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 4 Jun 2024 08:53:50 +0200 Subject: [PATCH 025/112] implement fetching job status --- hercules.c | 2 +- monitor.c | 4 +++- monitor.h | 10 +++++++--- monitor/monitor.go | 45 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/hercules.c b/hercules.c index 23ecbb2..7f53c44 100644 --- a/hercules.c +++ b/hercules.c @@ -2201,7 +2201,7 @@ static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { if (session_tx && session_tx->state == SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { monitor_update_job(usock, session_tx->jobid, session_tx->state, - session_tx->error); + session_tx->error, 0, 0); // FIXME 0 0 struct hercules_session *current = server->session_tx; atomic_store(&server->session_tx, NULL); fprintf(stderr, "Cleaning up TX session...\n"); diff --git a/monitor.c b/monitor.c index a38e318..4087ea1 100644 --- a/monitor.c +++ b/monitor.c @@ -95,7 +95,7 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_ap return true; } -bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum session_error err){ +bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum session_error err, u64 seconds_elapsed, u64 bytes_acked){ struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; strcpy(monitor.sun_path, "/var/herculesmon.sock"); @@ -105,6 +105,8 @@ bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum s msg.payload.job_update.job_id = job_id; msg.payload.job_update.status = state; msg.payload.job_update.error = err; + msg.payload.job_update.seconds_elapsed = seconds_elapsed; + msg.payload.job_update.bytes_acked = bytes_acked; sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); struct hercules_sockmsg_A reply; diff --git a/monitor.h b/monitor.h index 3060c7d..d3e983e 100644 --- a/monitor.h +++ b/monitor.h @@ -25,7 +25,8 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, // Inform the monitor about a transfer's (new) status. bool monitor_update_job(int sockfd, int job_id, enum session_state state, - enum session_error err); + enum session_error err, u64 seconds_elapsed, + u64 bytes_acked); // Bind the socket for the daemon. The file is deleted if already present. // Returns the file descriptor if successful, 0 otherwise. @@ -35,7 +36,8 @@ int monitor_bind_daemon_socket(); // entire packets to the monitor to get reply paths, this must be at least as // large as HERCULES_MAX_PKT_SIZE. #define SOCKMSG_MAX_PAYLOAD 5000 -_Static_assert(SOCKMSG_MAX_PAYLOAD >= HERCULES_MAX_PKTSIZE, "Socket messages too small"); +_Static_assert(SOCKMSG_MAX_PAYLOAD >= HERCULES_MAX_PKTSIZE, + "Socket messages too small"); // Maximum number of paths transferred #define SOCKMSG_MAX_PATHS 10 @@ -93,9 +95,11 @@ struct sockmsg_update_job_Q { uint16_t job_id; uint32_t status; // One of enum session_state uint32_t error; // One of enum session_error + uint64_t seconds_elapsed; + uint64_t bytes_acked; }; struct sockmsg_update_job_A { - uint16_t ok; + uint16_t ok; }; struct hercules_sockmsg_Q { diff --git a/monitor/monitor.go b/monitor/monitor.go index 04f5acd..064311d 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -414,8 +414,10 @@ type HerculesTransfer struct { nPaths int // Maximum number of paths to use pm *PathManager // The following two fields are meaningless if the job's status is 'Queued' - state C.enum_session_state // The state returned by the server - err C.enum_session_error // The error returned by the server + state C.enum_session_state // The state returned by the server + err C.enum_session_error // The error returned by the server + time_elapsed int // Seconds the transfer has been running + chunks_acked int // Number of successfully transferred chunks } var transfersLock sync.Mutex @@ -425,7 +427,7 @@ var nextID int = 1 // GET params: // file (File to transfer) // dest (Destination IA+Host) -func httpreq(w http.ResponseWriter, r *http.Request) { +func http_submit(w http.ResponseWriter, r *http.Request) { fmt.Println(r) if !r.URL.Query().Has("file") || !r.URL.Query().Has("dest") { io.WriteString(w, "missing parameter") @@ -459,6 +461,7 @@ func httpreq(w http.ResponseWriter, r *http.Request) { destination := findPathRule(&pathRules, destParsed) pm, _ := initNewPathManager(interfaces, &destination, localAddress, uint64(mtu)) transfersLock.Lock() + jobid := nextID transfers[nextID] = &HerculesTransfer{ id: nextID, status: Queued, @@ -471,7 +474,28 @@ func httpreq(w http.ResponseWriter, r *http.Request) { nextID += 1 transfersLock.Unlock() - io.WriteString(w, "OK") + io.WriteString(w, fmt.Sprintf("OK %d\n", jobid)) +} + +// GET Params: +// id: An ID obtained by submitting a transfer +// Returns OK status state err seconds_elapsed chucks_acked +func http_status(w http.ResponseWriter, r *http.Request) { + if !r.URL.Query().Has("id") { + io.WriteString(w, "missing parameter") + return + } + id, err := strconv.Atoi(r.URL.Query().Get("id")) + if err != nil { + return + } + transfersLock.Lock() + info, ok := transfers[id] + transfersLock.Unlock() + if !ok { + return + } + io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.chunks_acked)) } var localAddress *snet.UDPAddr @@ -617,9 +641,11 @@ func main() { }, src, 0) fmt.Println(err) - http.HandleFunc("/", httpreq) + http.HandleFunc("/submit", http_submit) + http.HandleFunc("/status", http_status) go http.ListenAndServe(":8000", nil) + // TODO remove finished sessions after a while for { buf := make([]byte, C.SOCKMSG_SIZE) fmt.Println("read...", C.SOCKMSG_SIZE) @@ -690,11 +716,20 @@ func main() { buf = buf[4:] errorcode := binary.LittleEndian.Uint32(buf[:4]) buf = buf[4:] + seconds := binary.LittleEndian.Uint64(buf[:8]) + buf = buf[8:] + bytes_acked := binary.LittleEndian.Uint64(buf[:8]) + buf = buf[8:] fmt.Println("updating job", job_id, status, errorcode) transfersLock.Lock() job, _ := transfers[int(job_id)] job.state = status job.err = errorcode + if (job.state == C.SESSION_STATE_DONE){ + job.status = Done + } + job.chunks_acked = int(bytes_acked) // FIXME + job.time_elapsed = int(seconds) fmt.Println(job, job.state) transfersLock.Unlock() b := binary.LittleEndian.AppendUint16(nil, uint16(1)) From b696f1644f57628c4e087915ffc8e21474b9d616 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 4 Jun 2024 11:11:45 +0200 Subject: [PATCH 026/112] remove sender_state_per_receiver --- hercules.c | 537 ++++++++++++++++++++++++----------------------------- hercules.h | 102 +++++----- monitor.c | 2 - 3 files changed, 287 insertions(+), 354 deletions(-) diff --git a/hercules.c b/hercules.c index 7f53c44..ed29fef 100644 --- a/hercules.c +++ b/hercules.c @@ -72,6 +72,7 @@ static const int rbudp_headerlen = sizeof(struct hercules_header); static const u64 session_timeout = 10e9; // 10 sec static const u64 session_hs_retransmit_interval = 2e9; // 2 sec +static const u64 session_stale_timeout = 30e9; // 30 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path // TODO move and see if we can't use this from only 1 thread so no locks @@ -94,21 +95,13 @@ static struct hercules_session *make_session(struct hercules_server *server); /// OK COMMON -/** - * @param scionaddrhdr - * @return The receiver index given by the sender address in scionaddrhdr - */ -static u32 rcvr_by_src_address(struct sender_state *tx_state, const struct scionaddrhdr_ipv4 *scionaddrhdr, - const struct udphdr *udphdr) -{ - u32 r; - for(r = 0; r < tx_state->num_receivers; r++) { - struct hercules_app_addr *addr = &tx_state->receiver[r].addr; - if(scionaddrhdr->src_ia == addr->ia && scionaddrhdr->src_ip == addr->ip && udphdr->uh_sport == addr->port) { - break; - } - } - return r; +// Check the SCION UDP address matches the session's peer +static inline bool src_matches_address(struct hercules_session *session, + const struct scionaddrhdr_ipv4 *scionaddrhdr, + const struct udphdr *udphdr) { + struct hercules_app_addr *addr = &session->peer; + return scionaddrhdr->src_ia == addr->ia && + scionaddrhdr->src_ip == addr->ip && udphdr->uh_sport == addr->port; } static void __exit_with_error(struct hercules_server *server, int error, const char *file, const char *func, int line) @@ -260,12 +253,10 @@ static void destroy_session_tx(struct hercules_session *session) { int ret = munmap(session->tx_state->mem, session->tx_state->filesize); assert(ret == 0); // No reason this should ever fail - free(session->paths_to_dest); - bitset__destroy(&session->tx_state->receiver->acked_chunks); - free(session->tx_state->receiver->paths); - bitset__destroy(&session->tx_state->receiver->cc_states->mi_nacked); - free(session->tx_state->receiver->cc_states); - free(session->tx_state->receiver); + bitset__destroy(&session->tx_state->acked_chunks); + free(session->tx_state->paths); + bitset__destroy(&session->tx_state->cc_states->mi_nacked); + free(session->tx_state->cc_states); free(session->tx_state); destroy_send_queue(session->send_queue); @@ -618,13 +609,6 @@ static bool rx_received_all(const struct receiver_state *rx_state) return (rx_state->received_chunks.num_set == rx_state->total_chunks); } -static void set_rx_sample(struct receiver_state *rx_state, int ifid, const char *pkt, int len) -{ - rx_state->rx_sample_len = len; - rx_state->rx_sample_ifid = ifid; - memcpy(rx_state->rx_sample_buf, pkt, len); -} - static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { if(length < rbudp_headerlen + rx_state->chunklen) { @@ -689,6 +673,8 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p const size_t chunk_start = (size_t)chunk_idx * rx_state->chunklen; const size_t len = umin64(rx_state->chunklen, rx_state->filesize - chunk_start); memcpy(rx_state->mem + chunk_start, payload, len); + // Update last new pkt timestamp + atomic_store(&rx_state->session->last_new_pkt_rcvd, get_nsecs()); } return true; } @@ -779,6 +765,7 @@ static void rx_receive_batch(struct receiver_state *rx_state, atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, &old_last_pkt_rcvd, now); } + // TODO timestamps in multiple places... atomic_store(&rx_state->session->last_pkt_rcvd, now); u64 frame_addrs[BATCH_SIZE]; @@ -960,7 +947,6 @@ static void rx_handle_initial(struct hercules_server *server, } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize - rx_state->cts_sent_at = get_nsecs(); // FIXME why is this here? } // Send an empty ACK, indicating to the sender that it may start sending data @@ -1099,12 +1085,9 @@ static void rx_send_nacks(struct hercules_server *server, struct receiver_state } /// OK SENDER -static bool tx_acked_all(const struct sender_state *tx_state) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(tx_state->receiver[r].acked_chunks.num_set != tx_state->total_chunks) { - return false; - } +static bool tx_acked_all(const struct sender_state *tx_state) { + if (tx_state->acked_chunks.num_set != tx_state->total_chunks) { + return false; } return true; } @@ -1137,16 +1120,16 @@ static void kick_tx_server(struct hercules_server *server){ } } -static void tx_register_acks(const struct rbudp_ack_pkt *ack, struct sender_state_per_receiver *rcvr) +static void tx_register_acks(const struct rbudp_ack_pkt *ack, struct sender_state *tx_state) { for(uint16_t e = 0; e < ack->num_acks; ++e) { const u32 begin = ack->acks[e].begin; const u32 end = ack->acks[e].end; - if(begin >= end || end > rcvr->acked_chunks.num) { + if(begin >= end || end > tx_state->acked_chunks.num) { return; // Abort } for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized - bitset__set(&rcvr->acked_chunks, i); // don't need thread-safety here, all updates in same thread + bitset__set(&tx_state->acked_chunks, i); // don't need thread-safety here, all updates in same thread } } } @@ -1279,10 +1262,9 @@ static void rate_limit_tx(struct sender_state *tx_state) void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { u64 now = get_nsecs(); - struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; - for (u32 p = 0; p < rcvr->num_paths; p++) { - struct hercules_path *path = &rcvr->paths[p]; + for (u32 p = 0; p < tx_state->num_paths; p++) { + struct hercules_path *path = &tx_state->paths[p]; if (path->enabled) { u64 handshake_at = atomic_load(&path->next_handshake_at); if (handshake_at < now) { @@ -1304,66 +1286,66 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t static void update_hercules_tx_paths(struct sender_state *tx_state) { return; // FIXME HACK - tx_state->has_new_paths = false; - u64 now = get_nsecs(); - for(u32 r = 0; r < tx_state->num_receivers; r++) { - struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; - receiver->num_paths = tx_state->shd_num_paths[r]; - - bool replaced_return_path = false; - for(u32 p = 0; p < receiver->num_paths; p++) { - struct hercules_path *shd_path = &tx_state->shd_paths[r * tx_state->max_paths_per_rcvr + p]; - if(!shd_path->enabled && p == receiver->return_path_idx) { - receiver->return_path_idx++; - } - if(shd_path->replaced) { - shd_path->replaced = false; - // assert that chunk length fits into packet with new header - if(shd_path->payloadlen < (int)tx_state->chunklen + rbudp_headerlen) { - fprintf(stderr, - "cannot use path %d for receiver %d: header too big, chunk does not fit into payload\n", p, - r); - receiver->paths[p].enabled = false; - continue; - } - memcpy(&receiver->paths[p], shd_path, sizeof(struct hercules_path)); - - atomic_store(&receiver->paths[p].next_handshake_at, - UINT64_MAX); // by default do not send a new handshake - if(p == receiver->return_path_idx) { - atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure handshake_rtt is adapted - // don't trigger RTT estimate on other paths, as it will be triggered by the ACK on the new return path - replaced_return_path = true; - } - // reset PCC state - if(!replaced_return_path && receiver->cc_states != NULL) { - terminate_ccontrol(&receiver->cc_states[p]); - continue_ccontrol(&receiver->cc_states[p]); - atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure mi_duration is set - } - } else { - if(p == receiver->return_path_idx) { - atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure handshake_rtt is adapted - // don't trigger RTT estimate on other paths, as it will be triggered by the ACK on the new return path - replaced_return_path = true; - } - if(receiver->cc_states != NULL && receiver->paths[p].enabled != shd_path->enabled) { - if(shd_path->enabled) { // reactivate PCC - if(receiver->cc_states != NULL) { - double rtt = receiver->cc_states[p].rtt; - double mi_duration = receiver->cc_states[p].pcc_mi_duration; - continue_ccontrol(&receiver->cc_states[p]); - receiver->cc_states[p].rtt = rtt; - receiver->cc_states[p].pcc_mi_duration = mi_duration; - } - } else { // deactivate PCC - terminate_ccontrol(&receiver->cc_states[p]); - } - } - receiver->paths[p].enabled = shd_path->enabled; - } - } - } + /* tx_state->has_new_paths = false; */ + /* u64 now = get_nsecs(); */ + /* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ + /* struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; */ + /* receiver->num_paths = tx_state->shd_num_paths[r]; */ + + /* bool replaced_return_path = false; */ + /* for(u32 p = 0; p < receiver->num_paths; p++) { */ + /* struct hercules_path *shd_path = &tx_state->shd_paths[r * tx_state->max_paths_per_rcvr + p]; */ + /* if(!shd_path->enabled && p == receiver->return_path_idx) { */ + /* receiver->return_path_idx++; */ + /* } */ + /* if(shd_path->replaced) { */ + /* shd_path->replaced = false; */ + /* // assert that chunk length fits into packet with new header */ + /* if(shd_path->payloadlen < (int)tx_state->chunklen + rbudp_headerlen) { */ + /* fprintf(stderr, */ + /* "cannot use path %d for receiver %d: header too big, chunk does not fit into payload\n", p, */ + /* r); */ + /* receiver->paths[p].enabled = false; */ + /* continue; */ + /* } */ + /* memcpy(&receiver->paths[p], shd_path, sizeof(struct hercules_path)); */ + + /* atomic_store(&receiver->paths[p].next_handshake_at, */ + /* UINT64_MAX); // by default do not send a new handshake */ + /* if(p == receiver->return_path_idx) { */ + /* atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure handshake_rtt is adapted */ + /* // don't trigger RTT estimate on other paths, as it will be triggered by the ACK on the new return path */ + /* replaced_return_path = true; */ + /* } */ + /* // reset PCC state */ + /* if(!replaced_return_path && receiver->cc_states != NULL) { */ + /* terminate_ccontrol(&receiver->cc_states[p]); */ + /* continue_ccontrol(&receiver->cc_states[p]); */ + /* atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure mi_duration is set */ + /* } */ + /* } else { */ + /* if(p == receiver->return_path_idx) { */ + /* atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure handshake_rtt is adapted */ + /* // don't trigger RTT estimate on other paths, as it will be triggered by the ACK on the new return path */ + /* replaced_return_path = true; */ + /* } */ + /* if(receiver->cc_states != NULL && receiver->paths[p].enabled != shd_path->enabled) { */ + /* if(shd_path->enabled) { // reactivate PCC */ + /* if(receiver->cc_states != NULL) { */ + /* double rtt = receiver->cc_states[p].rtt; */ + /* double mi_duration = receiver->cc_states[p].pcc_mi_duration; */ + /* continue_ccontrol(&receiver->cc_states[p]); */ + /* receiver->cc_states[p].rtt = rtt; */ + /* receiver->cc_states[p].pcc_mi_duration = mi_duration; */ + /* } */ + /* } else { // deactivate PCC */ + /* terminate_ccontrol(&receiver->cc_states[p]); */ + /* } */ + /* } */ + /* receiver->paths[p].enabled = shd_path->enabled; */ + /* } */ + /* } */ + /* } */ } @@ -1413,8 +1395,7 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s if(unit->paths[i] == UINT8_MAX) { break; } - struct sender_state_per_receiver *rcvr = &tx_state->receiver[unit->rcvr[i]]; - struct hercules_path *path = &rcvr->paths[unit->paths[i]]; + struct hercules_path *path = &tx_state->paths[unit->paths[i]]; if(path->ifid == ifid) { num_chunks_in_unit++; } @@ -1431,8 +1412,7 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s if(unit->paths[i] == UINT8_MAX) { break; } - const struct sender_state_per_receiver *receiver = &tx_state->receiver[unit->rcvr[i]]; - const struct hercules_path *path = &receiver->paths[unit->paths[i]]; + const struct hercules_path *path = &tx_state->paths[unit->paths[i]]; if(path->ifid != ifid) { continue; } @@ -1452,9 +1432,9 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s #endif u8 track_path = PCC_NO_PATH; // put path_idx iff PCC is enabled sequence_number seqnr = 0; - if(receiver->cc_states != NULL) { + if(tx_state->cc_states != NULL) { track_path = unit->paths[i]; - seqnr = atomic_fetch_add(&receiver->cc_states[unit->paths[i]].last_seqnr, 1); + seqnr = atomic_fetch_add(&tx_state->cc_states[unit->paths[i]].last_seqnr, 1); } fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, seqnr, tx_state->mem + chunk_start, len, path->payloadlen); stitch_checksum(path, path->header.checksum, pkt); @@ -1527,16 +1507,14 @@ static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { u32 allowed_chunks = 0; u64 now = get_nsecs(); - if (!tx_state->receiver[0] - .paths[tx_state->receiver[0].path_index] - .enabled) { + if (!tx_state->paths[tx_state->path_index].enabled) { return 0; // if a receiver does not have any enabled paths, we can // actually end up here ... :( } - if (tx_state->receiver[0].cc_states != NULL) { // use PCC + if (tx_state->cc_states != NULL) { // use PCC struct ccontrol_state *cc_state = - &tx_state->receiver[0].cc_states[tx_state->receiver[0].path_index]; + &tx_state->cc_states[tx_state->path_index]; /* debug_printf("path idx %d", tx_state->receiver[0].path_index); */ allowed_chunks = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); @@ -1548,60 +1526,52 @@ static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { // Send a total max of BATCH_SIZE -static u32 shrink_sending_rates(struct sender_state *tx_state, u32 *max_chunks_per_rcvr, u32 total_chunks) -{ - if(total_chunks > BATCH_SIZE) { - u32 new_total_chunks = 0; // due to rounding errors, we need to aggregate again - for(u32 r = 0; r < tx_state->num_receivers; r++) { - max_chunks_per_rcvr[r] = max_chunks_per_rcvr[r] * BATCH_SIZE / total_chunks; - new_total_chunks += max_chunks_per_rcvr[r]; - } +static u32 shrink_sending_rates(struct sender_state *tx_state, + u32 *max_chunks_per_rcvr, u32 total_chunks) { + if (total_chunks > BATCH_SIZE) { + u32 new_total_chunks = + 0; // due to rounding errors, we need to aggregate again + max_chunks_per_rcvr[0] = + max_chunks_per_rcvr[0] * BATCH_SIZE / total_chunks; + new_total_chunks += max_chunks_per_rcvr[0]; return new_total_chunks; } return total_chunks; } -static void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - rcvr_path[r] = tx_state->receiver->path_index; - } +static void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { + rcvr_path[0] = tx_state->path_index; } // Mark the next available path as active -static void iterate_paths(struct sender_state *tx_state) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; - if(receiver->num_paths == 0) { - continue; - } - u32 prev_path_index = receiver->path_index; // we need this to break the loop if all paths are disabled - if(prev_path_index >= receiver->num_paths) { - prev_path_index = 0; - } - do { - receiver->path_index = (receiver->path_index + 1) % receiver->num_paths; - } while(!receiver->paths[receiver->path_index].enabled && receiver->path_index != prev_path_index); +static void iterate_paths(struct sender_state *tx_state) { + if (tx_state->num_paths == 0) { + return; } + u32 prev_path_index = + tx_state->path_index; // we need this to break the loop if all paths + // are disabled + if (prev_path_index >= tx_state->num_paths) { + prev_path_index = 0; + } + do { + tx_state->path_index = (tx_state->path_index + 1) % tx_state->num_paths; + } while (!tx_state->paths[tx_state->path_index].enabled && + tx_state->path_index != prev_path_index); } -static void terminate_cc(const struct sender_state_per_receiver *receiver) -{ - for(u32 i = 0; i < receiver->num_paths; i++) { - terminate_ccontrol(&receiver->cc_states[i]); +static void terminate_cc(const struct sender_state *tx_state) { + for (u32 i = 0; i < tx_state->num_paths; i++) { + terminate_ccontrol(&tx_state->cc_states[i]); } } -static void kick_cc(struct sender_state *tx_state) -{ - for(u32 r = 0; r < tx_state->num_receivers; r++) { - if(tx_state->receiver[r].finished) { - continue; - } - for(u32 p = 0; p < tx_state->receiver[r].num_paths; p++) { - kick_ccontrol(&tx_state->receiver[r].cc_states[p]); - } +static void kick_cc(struct sender_state *tx_state) { + if (tx_state->finished) { + return; + } + for (u32 p = 0; p < tx_state->num_paths; p++) { + kick_ccontrol(&tx_state->cc_states[p]); } } // Select batch of un-ACKed chunks for (re)transmit: @@ -1614,18 +1584,17 @@ static void kick_cc(struct sender_state *tx_state) static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 *chunks, u8 *chunk_rcvr, const u64 now, u64 *wait_until, u32 num_chunks) { - struct sender_state_per_receiver *rcvr = &tx_state->receiver[rcvr_idx]; u32 num_chunks_prepared = 0; - u32 chunk_idx = rcvr->prev_chunk_idx; + u32 chunk_idx = tx_state->prev_chunk_idx; /* debug_printf("n chunks %d", num_chunks); */ for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ - chunk_idx = bitset__scan_neg(&rcvr->acked_chunks, chunk_idx); + chunk_idx = bitset__scan_neg(&tx_state->acked_chunks, chunk_idx); if(chunk_idx == tx_state->total_chunks) { /* debug_printf("prev %d", rcvr->prev_chunk_idx); */ - if(rcvr->prev_chunk_idx == 0) { // this receiver has finished + if(tx_state->prev_chunk_idx == 0) { // this receiver has finished debug_printf("receiver has finished"); - rcvr->finished = true; + tx_state->finished = true; break; } @@ -1633,16 +1602,16 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 debug_printf("Receiver %d switches to next round", rcvr_idx); chunk_idx = 0; - rcvr->prev_round_start = rcvr->prev_round_end; - rcvr->prev_round_end = get_nsecs(); - u64 prev_round_dt = rcvr->prev_round_end - rcvr->prev_round_start; - rcvr->prev_slope = (prev_round_dt + tx_state->total_chunks - 1) / tx_state->total_chunks; // round up - rcvr->ack_wait_duration = 3 * (ACK_RATE_TIME_MS * 1000000UL + rcvr->handshake_rtt); + tx_state->prev_round_start = tx_state->prev_round_end; + tx_state->prev_round_end = get_nsecs(); + u64 prev_round_dt = tx_state->prev_round_end - tx_state->prev_round_start; + tx_state->prev_slope = (prev_round_dt + tx_state->total_chunks - 1) / tx_state->total_chunks; // round up + tx_state->ack_wait_duration = 3 * (ACK_RATE_TIME_MS * 1000000UL + tx_state->handshake_rtt); break; } - const u64 prev_transmit = umin64(rcvr->prev_round_start + rcvr->prev_slope * chunk_idx, rcvr->prev_round_end); - const u64 ack_due = prev_transmit + rcvr->ack_wait_duration; // 0 for first round + const u64 prev_transmit = umin64(tx_state->prev_round_start + tx_state->prev_slope * chunk_idx, tx_state->prev_round_end); + const u64 ack_due = prev_transmit + tx_state->ack_wait_duration; // 0 for first round if(now >= ack_due) { // add the chunk to the current batch *chunks = chunk_idx++; *chunk_rcvr = rcvr_idx; @@ -1653,7 +1622,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 break; } } - rcvr->prev_chunk_idx = chunk_idx; + tx_state->prev_chunk_idx = chunk_idx; return num_chunks_prepared; } @@ -1688,35 +1657,20 @@ static struct sender_state *init_tx_state(struct hercules_session *session, tx_state->rate_limit = max_rate_limit; tx_state->start_time = 0; tx_state->end_time = 0; - tx_state->num_receivers = num_dests; - tx_state->receiver = calloc(num_dests, sizeof(*tx_state->receiver)); - if (tx_state->receiver == NULL){ - free(tx_state); - return NULL; - } - tx_state->max_paths_per_rcvr = max_paths_per_dest; - for (u32 d = 0; d < num_dests; d++) { - struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; - bitset__create(&receiver->acked_chunks, tx_state->total_chunks); - receiver->path_index = 0; - receiver->handshake_rtt = 0; - receiver->num_paths = num_paths; - receiver->paths = paths; - receiver->addr = *dests; - receiver->cts_received = false; - } + bitset__create(&tx_state->acked_chunks, tx_state->total_chunks); + tx_state->path_index = 0; + tx_state->handshake_rtt = 0; + tx_state->num_paths = num_paths; + tx_state->paths = paths; + tx_state->session->peer = *dests; update_hercules_tx_paths(tx_state); return tx_state; } -static void destroy_tx_state(struct sender_state *tx_state) -{ - for(u32 d = 0; d < tx_state->num_receivers; d++) { - struct sender_state_per_receiver *receiver = &tx_state->receiver[d]; - bitset__destroy(&receiver->acked_chunks); - free(receiver->paths); - } +static void destroy_tx_state(struct sender_state *tx_state) { + bitset__destroy(&tx_state->acked_chunks); + free(tx_state->paths); free(tx_state); } @@ -1727,7 +1681,7 @@ static void tx_retransmit_initial(struct hercules_server *server, u64 now) { if (now > session_tx->last_pkt_sent + session_hs_retransmit_interval) { struct sender_state *tx_state = session_tx->tx_state; - tx_send_initial(server, &tx_state->receiver[0].paths[0], + tx_send_initial(server, &tx_state->paths[tx_state->return_path_idx], tx_state->filename, tx_state->filesize, tx_state->chunklen, now, 0, session_tx->etherlen, true, true); session_tx->last_pkt_sent = now; @@ -1737,8 +1691,10 @@ static void tx_retransmit_initial(struct hercules_server *server, u64 now) { static void tx_handle_hs_confirm(struct hercules_server *server, struct rbudp_initial_pkt *parsed_pkt) { - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_PENDING) { + struct hercules_session *session_tx = server->session_tx; + if (session_tx != NULL && + session_tx->state == SESSION_STATE_PENDING) { + struct sender_state *tx_state = session_tx->tx_state; // This is a reply to the very first packet and confirms connection // setup if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { @@ -1747,59 +1703,56 @@ static void tx_handle_hs_confirm(struct hercules_server *server, } if (server->enable_pcc) { u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = - server->session_tx->tx_state->receiver; - receiver->handshake_rtt = now - parsed_pkt->timestamp; + tx_state->handshake_rtt = now - parsed_pkt->timestamp; // TODO where to get rate limit? // below is ~in Mb/s (but really pps) /* u32 rate = 2000e3; // 20 Gbps */ u32 rate = 100; // 1 Mbps debug_printf("rate limit %u", rate); - receiver->cc_states = init_ccontrol_state( - rate, server->session_tx->tx_state->total_chunks, - server->session_tx->num_paths, server->session_tx->num_paths, - server->session_tx->num_paths); - ccontrol_update_rtt(&receiver->cc_states[0], - receiver->handshake_rtt); + tx_state->cc_states = init_ccontrol_state( + rate, tx_state->total_chunks, + tx_state->num_paths, tx_state->num_paths, + tx_state->num_paths); + ccontrol_update_rtt(&tx_state->cc_states[0], + tx_state->handshake_rtt); fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: " "%fs, MI: %fs\n", - 0, receiver->handshake_rtt / 1e9, - receiver->cc_states[0].pcc_mi_duration); + 0, tx_state->handshake_rtt / 1e9, + tx_state->cc_states[0].pcc_mi_duration); // make sure we later perform RTT estimation // on every enabled path - receiver->paths[0].next_handshake_at = + tx_state->paths[0].next_handshake_at = UINT64_MAX; // We just completed the HS for this path - for (u32 p = 1; p < receiver->num_paths; p++) { - receiver->paths[p].next_handshake_at = now; + for (u32 p = 1; p < tx_state->num_paths; p++) { + tx_state->paths[p].next_handshake_at = now; } } - server->session_tx->state = SESSION_STATE_WAIT_CTS; + session_tx->state = SESSION_STATE_WAIT_CTS; return; } - if (server->session_tx != NULL && - server->session_tx->state == SESSION_STATE_RUNNING) { + if (session_tx != NULL && + session_tx->state == SESSION_STATE_RUNNING) { + struct sender_state *tx_state = session_tx->tx_state; // This is a reply to some handshake we sent during an already // established session (e.g. to open a new path) u64 now = get_nsecs(); - struct sender_state_per_receiver *receiver = - server->session_tx->tx_state->receiver; if (server->enable_pcc) { - ccontrol_update_rtt(&receiver->cc_states[parsed_pkt->path_index], + ccontrol_update_rtt(&tx_state->cc_states[parsed_pkt->path_index], now - parsed_pkt->timestamp); } - receiver->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; + tx_state->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; // We have a new return path, redo handshakes on all other paths if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH){ - receiver->handshake_rtt = now - parsed_pkt->timestamp; - for (u32 p = 0; p < receiver->num_paths; p++){ - if (p != parsed_pkt->path_index && receiver->paths[p].enabled){ - receiver->paths[p].next_handshake_at = now; - receiver->cc_states[p].pcc_mi_duration = DBL_MAX; - receiver->cc_states[p].rtt = DBL_MAX; + tx_state->handshake_rtt = now - parsed_pkt->timestamp; + for (u32 p = 0; p < tx_state->num_paths; p++){ + if (p != parsed_pkt->path_index && tx_state->paths[p].enabled){ + tx_state->paths[p].next_handshake_at = now; + tx_state->cc_states[p].pcc_mi_duration = DBL_MAX; + tx_state->cc_states[p].rtt = DBL_MAX; } } } @@ -1917,64 +1870,62 @@ static bool pcc_mi_elapsed(struct ccontrol_state *cc_state) static void pcc_monitor(struct sender_state *tx_state) { - for(u32 r = 0; r < tx_state->num_receivers; r++) { - for(u32 cur_path = 0; cur_path < tx_state->receiver[r].num_paths; cur_path++) { - struct ccontrol_state *cc_state = &tx_state->receiver[r].cc_states[cur_path]; - pthread_spin_lock(&cc_state->lock); - if(pcc_mi_elapsed(cc_state)) { - u64 now = get_nsecs(); - if(cc_state->mi_end == 0) { // TODO should not be necessary - fprintf(stderr, "Assumption violated.\n"); - quit_session(tx_state->session, SESSION_ERROR_PCC); - cc_state->mi_end = now; - } - u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI + for(u32 cur_path = 0; cur_path < tx_state->num_paths; cur_path++) { + struct ccontrol_state *cc_state = &tx_state->cc_states[cur_path]; + pthread_spin_lock(&cc_state->lock); + if(pcc_mi_elapsed(cc_state)) { + u64 now = get_nsecs(); + if(cc_state->mi_end == 0) { // TODO should not be necessary + fprintf(stderr, "Assumption violated.\n"); + quit_session(tx_state->session, SESSION_ERROR_PCC); + cc_state->mi_end = now; + } + u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI - u32 excess = 0; - if (cc_state->curr_rate * cc_state->pcc_mi_duration > throughput) { - excess = cc_state->curr_rate * cc_state->pcc_mi_duration - throughput; - } - u32 lost_npkts = atomic_load(&cc_state->mi_nacked.num_set); - // account for packets that are "stuck in queue" - if(cc_state->mi_seq_end > cc_state->mi_seq_max) { - lost_npkts += cc_state->mi_seq_end - cc_state->mi_seq_max; - } - lost_npkts = umin32(lost_npkts, throughput); - float loss = (float)(lost_npkts + excess) / (throughput + excess); - sequence_number start = cc_state->mi_seq_start; - sequence_number end = cc_state->mi_seq_end; - sequence_number mi_min = cc_state->mi_seq_min; - sequence_number mi_max = cc_state->mi_seq_max; - sequence_number delta_left = cc_state->mi_seq_start - cc_state->mi_seq_min; - sequence_number delta_right = cc_state->mi_seq_max - cc_state->mi_seq_end; - u32 nnacks = cc_state->num_nacks; - u32 nack_pkts = cc_state->num_nack_pkts; - enum pcc_state state = cc_state->state; - double actual_duration = (double)(cc_state->mi_end - cc_state->mi_start) / 1e9; - - pcc_trace_push(now, start, end, mi_min, mi_max, excess, loss, delta_left, delta_right, nnacks, nack_pkts, state, - cc_state->curr_rate * cc_state->pcc_mi_duration, throughput, cc_state->pcc_mi_duration, actual_duration); - - if(cc_state->num_nack_pkts != 0) { // skip PCC control if no NACKs received - if(cc_state->ignored_first_mi) { // first MI after booting will only contain partial feedback, skip it as well - pcc_control(cc_state, throughput, loss); - } - cc_state->ignored_first_mi = true; + u32 excess = 0; + if (cc_state->curr_rate * cc_state->pcc_mi_duration > throughput) { + excess = cc_state->curr_rate * cc_state->pcc_mi_duration - throughput; + } + u32 lost_npkts = atomic_load(&cc_state->mi_nacked.num_set); + // account for packets that are "stuck in queue" + if(cc_state->mi_seq_end > cc_state->mi_seq_max) { + lost_npkts += cc_state->mi_seq_end - cc_state->mi_seq_max; + } + lost_npkts = umin32(lost_npkts, throughput); + float loss = (float)(lost_npkts + excess) / (throughput + excess); + sequence_number start = cc_state->mi_seq_start; + sequence_number end = cc_state->mi_seq_end; + sequence_number mi_min = cc_state->mi_seq_min; + sequence_number mi_max = cc_state->mi_seq_max; + sequence_number delta_left = cc_state->mi_seq_start - cc_state->mi_seq_min; + sequence_number delta_right = cc_state->mi_seq_max - cc_state->mi_seq_end; + u32 nnacks = cc_state->num_nacks; + u32 nack_pkts = cc_state->num_nack_pkts; + enum pcc_state state = cc_state->state; + double actual_duration = (double)(cc_state->mi_end - cc_state->mi_start) / 1e9; + + pcc_trace_push(now, start, end, mi_min, mi_max, excess, loss, delta_left, delta_right, nnacks, nack_pkts, state, + cc_state->curr_rate * cc_state->pcc_mi_duration, throughput, cc_state->pcc_mi_duration, actual_duration); + + if(cc_state->num_nack_pkts != 0) { // skip PCC control if no NACKs received + if(cc_state->ignored_first_mi) { // first MI after booting will only contain partial feedback, skip it as well + pcc_control(cc_state, throughput, loss); } + cc_state->ignored_first_mi = true; + } - // TODO move the neccessary ones to cc_start_mi below - cc_state->mi_seq_min = UINT32_MAX; - cc_state->mi_seq_max = 0; - cc_state->mi_seq_max_rcvd = 0; - atomic_store(&cc_state->num_nacks, 0); - atomic_store(&cc_state->num_nack_pkts, 0); - cc_state->mi_end = 0; + // TODO move the neccessary ones to cc_start_mi below + cc_state->mi_seq_min = UINT32_MAX; + cc_state->mi_seq_max = 0; + cc_state->mi_seq_max_rcvd = 0; + atomic_store(&cc_state->num_nacks, 0); + atomic_store(&cc_state->num_nack_pkts, 0); + cc_state->mi_end = 0; - // Start new MI; only safe because no acks are processed during those updates - ccontrol_start_monitoring_interval(cc_state); - } - pthread_spin_unlock(&cc_state->lock); + // Start new MI; only safe because no acks are processed during those updates + ccontrol_start_monitoring_interval(cc_state); } + pthread_spin_unlock(&cc_state->lock); } } @@ -2173,17 +2124,15 @@ static void new_tx_if_available(struct hercules_server *server) { } // TODO free paths debug_printf("received %d paths", n_paths); - session->num_paths = n_paths; - session->paths_to_dest = paths; // TODO If the paths don't have the same header length this does not work: // If the first path has a shorter header than the second, chunks won't fit // on the second path - u32 chunklen = session->paths_to_dest[0].payloadlen - rbudp_headerlen; + u32 chunklen = paths[0].payloadlen - rbudp_headerlen; atomic_store(&server->session_tx, session); struct sender_state *tx_state = init_tx_state( server->session_tx, filesize, chunklen, server->rate_limit, mem, - &session->destination, session->paths_to_dest, 1, session->num_paths, + &session->peer, paths, 1, n_paths, server->max_paths); strncpy(tx_state->filename, fname, 99); server->session_tx->tx_state = tx_state; @@ -2244,6 +2193,10 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { quit_session(session_rx, SESSION_ERROR_TIMEOUT); debug_printf("Session (RX) timed out!"); } + else if (new > session_rx->last_new_pkt_rcvd + session_stale_timeout){ + quit_session(session_tx, SESSION_ERROR_STALE); + debug_printf("Session (RX) stale!") + } } } @@ -2266,8 +2219,8 @@ static void tx_update_paths(struct hercules_server *server) { } // XXX doesn't this break if we get more paths and the update is not // atomic? - session_tx->num_paths = n_paths; - session_tx->paths_to_dest = paths; + session_tx->tx_state->num_paths = n_paths; + session_tx->tx_state->paths = paths; } } @@ -2297,8 +2250,8 @@ static void print_session_stats(struct hercules_server *server, struct hercules_session *session_tx = server->session_tx; if (session_tx && session_tx->state != SESSION_STATE_DONE) { u32 sent_now = session_tx->tx_npkts; - u32 acked_count = session_tx->tx_state->receiver->acked_chunks.num_set; - u32 total = session_tx->tx_state->receiver->acked_chunks.num; + u32 acked_count = session_tx->tx_state->acked_chunks.num_set; + u32 total = session_tx->tx_state->acked_chunks.num; double send_rate_pps = (sent_now - p->tx_sent) / ((double)tdiff / 1e9); p->tx_sent = sent_now; double send_rate = @@ -2488,7 +2441,7 @@ static void events_p(void *arg) { SESSION_STATE_RUNNING) { tx_register_acks( &cp->payload.ack, - &server->session_tx->tx_state->receiver[0]); + server->session_tx->tx_state); count_received_pkt(server->session_tx, h->path); atomic_store(&server->session_tx->last_pkt_rcvd, get_nsecs()); if (tx_acked_all(server->session_tx->tx_state)) { @@ -2513,7 +2466,7 @@ static void events_p(void *arg) { cp->payload.ack.ack_nr); tx_register_nacks( &cp->payload.ack, - &server->session_tx->tx_state->receiver + &server->session_tx->tx_state ->cc_states[h->path]); } break; @@ -2526,7 +2479,7 @@ static void events_p(void *arg) { // all data packets debug_printf("Non-control packet received on control socket"); } - if (server->session_tx && server->session_tx->tx_state->receiver->cc_states){ + if (server->session_tx && server->session_tx->tx_state->cc_states){ pcc_monitor(server->session_tx->tx_state); } } @@ -2593,8 +2546,7 @@ static void *tx_p(void *arg) { const u64 now = get_nsecs(); u32 num_chunks = 0; - struct sender_state_per_receiver *rcvr = &tx_state->receiver[0]; - if (!rcvr->finished) { + if (!tx_state->finished) { u64 ack_due = 0; // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks to // send @@ -2602,9 +2554,9 @@ static void *tx_p(void *arg) { tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, &ack_due, allowed_chunks); num_chunks += cur_num_chunks; - if (rcvr->finished) { - if (rcvr->cc_states) { - terminate_cc(rcvr); + if (tx_state->finished) { + if (tx_state->cc_states) { + terminate_cc(tx_state); kick_cc(tx_state); } } else { @@ -2620,7 +2572,7 @@ static void *tx_p(void *arg) { } if (num_chunks > 0) { - u8 rcvr_path[tx_state->num_receivers]; + u8 rcvr_path[1]; prepare_rcvr_paths(tx_state, rcvr_path); produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, num_chunks); @@ -2628,10 +2580,9 @@ static void *tx_p(void *arg) { rate_limit_tx(tx_state); // update book-keeping - struct sender_state_per_receiver *receiver = &tx_state->receiver[0]; - u32 path_idx = tx_state->receiver[0].path_index; - if (receiver->cc_states != NULL) { - struct ccontrol_state *cc_state = &receiver->cc_states[path_idx]; + u32 path_idx = tx_state->path_index; + if (tx_state->cc_states != NULL) { + struct ccontrol_state *cc_state = &tx_state->cc_states[path_idx]; // FIXME allowed_chunks below is not correct (3x) atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); diff --git a/hercules.h b/hercules.h index ff5b0f3..bf1e620 100644 --- a/hercules.h +++ b/hercules.h @@ -33,6 +33,7 @@ // packets is possible by using xdp in multibuffer mode, but this requires code // to handle multi-buffer packets. #define HERCULES_MAX_PKTSIZE 3000 +#define HERCULES_FILENAME_SIZE 1000 // Batch size for send/receive operations #define BATCH_SIZE 64 // Number of frames in UMEM area @@ -53,7 +54,6 @@ struct hercules_path { struct hercules_path_header header; atomic_bool enabled; // e.g. when a path has been revoked and no // replacement is available, this will be set to false - atomic_bool replaced; }; /// RECEIVER @@ -83,31 +83,29 @@ struct receiver_state { struct bitset received_chunks; - // TODO rx_sample can be removed in favour of reply_path - // XXX: reads/writes to this is are a huge data race. Need to sync. - char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]; - int rx_sample_len; - int rx_sample_ifid; // The reply path to use for contacting the sender. This is the reversed // path of the last initial packet with the SET_RETURN_PATH flag set. + // TODO needs atomic? struct hercules_path reply_path; // Start/end time of the current transfer u64 start_time; u64 end_time; - u64 cts_sent_at; u64 last_pkt_rcvd; // Timeout detection - u64 last_new_pkt_rcvd; // If we don't receive any new chunks for a while - // something is presumably wrong and we abort the - // transfer - u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; }; -// SENDER -struct sender_state_per_receiver { +/// SENDER +struct sender_state { + struct hercules_session *session; + + // State for transmit rate control + size_t tx_npkts_queued; + u64 prev_rate_check; + size_t prev_tx_npkts_queued; + _Atomic u32 rate_limit; u64 prev_round_start; u64 prev_round_end; u64 prev_slope; @@ -116,26 +114,13 @@ struct sender_state_per_receiver { bool finished; /** Next batch should be sent via this path */ u8 path_index; - - struct bitset acked_chunks; + struct bitset acked_chunks; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns - u32 num_paths; u32 return_path_idx; - struct hercules_app_addr addr; + u32 num_paths; struct hercules_path *paths; struct ccontrol_state *cc_states; - bool cts_received; -}; - -struct sender_state { - struct hercules_session *session; - - // State for transmit rate control - size_t tx_npkts_queued; - u64 prev_rate_check; - size_t prev_tx_npkts_queued; - /** Filesize in bytes */ size_t filesize; char filename[100]; @@ -146,25 +131,12 @@ struct sender_state { u32 total_chunks; /** Memory mapped file for receive */ char *mem; - - _Atomic u32 rate_limit; - // Start/end time of the current transfer u64 start_time; u64 end_time; - - u32 num_receivers; - struct sender_state_per_receiver *receiver; - u32 max_paths_per_rcvr; - - // shared with Go - struct hercules_path *shd_paths; - const int *shd_num_paths; - - atomic_bool has_new_paths; }; -// SESSION +/// SESSION // Some states are used only by the TX/RX side and are marked accordingly enum session_state { SESSION_STATE_NONE, @@ -181,32 +153,36 @@ enum session_state { enum session_error { SESSION_ERROR_OK, //< No error, transfer completed successfully SESSION_ERROR_TIMEOUT, //< Session timed out + SESSION_ERROR_STALE, //< Packets are being received, but none are new SESSION_ERROR_PCC, //< Something wrong with PCC SESSION_ERROR_SEQNO_OVERFLOW, - SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination + SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination }; // A session is a transfer between one sender and one receiver struct hercules_session { - struct receiver_state *rx_state; - struct sender_state *tx_state; + struct receiver_state *rx_state; //< Valid if this is the receiving side + struct sender_state *tx_state; //< Valid if this is the sending side _Atomic enum session_state state; - _Atomic enum session_error error; + _Atomic enum session_error error; //< Valid if the session's state is DONE struct send_queue *send_queue; + u64 last_pkt_sent; - u64 last_pkt_rcvd; + u64 last_pkt_rcvd; //< Used for timeout detection + u64 last_new_pkt_rcvd; //< If we only receive packets containing + // already-seen chunks for a while something is + // probably wrong + u32 jobid; //< The monitor's ID for this job u32 etherlen; - u32 jobid; - struct hercules_app_addr destination; - int num_paths; - struct hercules_path *paths_to_dest; + struct hercules_app_addr peer; + // Number of sent/received packets (for stats) size_t rx_npkts; size_t tx_npkts; }; -// SERVER +/// SERVER struct hercules_interface { char ifname[IFNAMSIZ]; int ifid; @@ -230,15 +206,18 @@ struct hercules_config { struct hercules_server { struct hercules_config config; int control_sockfd; // AF_PACKET socket used for control traffic - int n_threads; - struct rx_p_args **worker_args; - struct hercules_session * _Atomic session_tx; // Current TX session - struct hercules_session * deferred_tx; - struct hercules_session * _Atomic session_rx; // Current RX session - struct hercules_session * deferred_rx; int max_paths; int rate_limit; - bool enable_pcc; + int n_threads; // Number of RX/TX worker threads + struct rx_p_args **worker_args; // Args passed to RX workers + + struct hercules_session *_Atomic session_tx; // Current TX session + struct hercules_session *deferred_tx; // Previous TX session, no longer + // active, waiting to be freed + struct hercules_session *_Atomic session_rx; // Current RX session + struct hercules_session *deferred_rx; + + bool enable_pcc; // TODO make per path or session or something int *ifindices; int num_ifaces; struct hercules_interface ifaces[]; @@ -302,3 +281,8 @@ struct hercules_stats { }; #endif // __HERCULES_H__ + +/// Local Variables: +/// outline-regexp: "/// " +/// eval:(outline-minor-mode 1) +/// End: diff --git a/monitor.c b/monitor.c index 4087ea1..f25d1be 100644 --- a/monitor.c +++ b/monitor.c @@ -31,7 +31,6 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample path->headerlen = reply.payload.reply_path.path.headerlen; path->header.checksum = reply.payload.reply_path.path.chksum; path->enabled = true; - path->replaced = false; path->payloadlen = 1200 - path->headerlen; // TODO set correctly path->framelen = 1200; path->ifid = reply.payload.reply_path.path.ifid; @@ -63,7 +62,6 @@ bool monitor_get_paths(int sockfd, int job_id, int *n_paths, p[i].headerlen); p[i].header.checksum = reply.payload.paths.paths[i].chksum; p[i].enabled = true; - p[i].replaced = false; p[i].payloadlen = 1200 - p[i].headerlen; // TODO set correctly p[i].framelen = 1200; p[i].ifid = reply.payload.paths.paths[i].ifid; From 961ced9dc2819fe2a0e429465f16f14ae9f2e7f6 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 4 Jun 2024 16:14:31 +0200 Subject: [PATCH 027/112] path swapping, cleanup --- congestion_control.c | 35 +++-- congestion_control.h | 2 +- hercules.c | 310 ++++++++++++++++++++++--------------------- hercules.h | 28 ++-- 4 files changed, 194 insertions(+), 181 deletions(-) diff --git a/congestion_control.c b/congestion_control.c index 52485f6..4edf2aa 100644 --- a/congestion_control.c +++ b/congestion_control.c @@ -13,21 +13,15 @@ #define MSS 1460 -struct ccontrol_state * -init_ccontrol_state(u32 max_rate_limit, u32 total_chunks, size_t num_paths, size_t max_paths, size_t total_num_paths) -{ - debug_printf("init ccontrol state"); - struct ccontrol_state *cc_states = calloc(max_paths, sizeof(struct ccontrol_state)); - for(size_t i = 0; i < max_paths; i++) { - struct ccontrol_state *cc_state = &cc_states[i]; - cc_state->max_rate_limit = max_rate_limit; - cc_state->num_paths = num_paths; - cc_state->total_num_paths = total_num_paths; - pthread_spin_init(&cc_state->lock, PTHREAD_PROCESS_PRIVATE); - - continue_ccontrol(cc_state); - } - return cc_states; +struct ccontrol_state *init_ccontrol_state(u32 max_rate_limit, + u32 total_chunks, u32 num_paths) { + struct ccontrol_state *cc_state = calloc(1, sizeof(struct ccontrol_state)); + cc_state->max_rate_limit = max_rate_limit; + cc_state->num_paths = num_paths; + pthread_spin_init(&cc_state->lock, PTHREAD_PROCESS_PRIVATE); + + continue_ccontrol(cc_state); + return cc_state; } void ccontrol_start_monitoring_interval(struct ccontrol_state *cc_state) @@ -60,7 +54,7 @@ void ccontrol_update_rtt(struct ccontrol_state *cc_state, u64 rtt) // initial rate should be per-receiver fair u32 initial_rate = umin32( (u32)(MSS / cc_state->rtt), - cc_state->max_rate_limit / (cc_state->num_paths * cc_state->total_num_paths) + cc_state->max_rate_limit / (cc_state->num_paths) ); cc_state->curr_rate = initial_rate; cc_state->prev_rate = initial_rate; @@ -70,10 +64,11 @@ void ccontrol_update_rtt(struct ccontrol_state *cc_state, u64 rtt) ccontrol_start_monitoring_interval(cc_state); } -void terminate_ccontrol(struct ccontrol_state *cc_state) -{ - cc_state->state = pcc_terminated; - cc_state->curr_rate = 0; +void terminate_ccontrol(struct ccontrol_state *cc_state) { + if (cc_state != NULL) { + cc_state->state = pcc_terminated; + cc_state->curr_rate = 0; + } } void continue_ccontrol(struct ccontrol_state *cc_state) diff --git a/congestion_control.h b/congestion_control.h index 408b536..b6857f4 100644 --- a/congestion_control.h +++ b/congestion_control.h @@ -76,7 +76,7 @@ struct ccontrol_state { */ // Initialize congestion control state struct ccontrol_state * -init_ccontrol_state(u32 max_rate_limit, u32 total_chunks, size_t num_paths, size_t max_paths, size_t total_num_paths); +init_ccontrol_state(u32 max_rate_limit, u32 total_chunks, u32 num_paths); void terminate_ccontrol(struct ccontrol_state *cc_state); void continue_ccontrol(struct ccontrol_state *cc_state); void ccontrol_update_rtt(struct ccontrol_state *cc_state, u64 rtt); diff --git a/hercules.c b/hercules.c index ed29fef..b9efd95 100644 --- a/hercules.c +++ b/hercules.c @@ -236,10 +236,12 @@ static struct hercules_session *make_session(struct hercules_server *server) { } init_send_queue(s->send_queue, BATCH_SIZE); s->last_pkt_sent = 0; + u64 now = get_nsecs(); s->last_pkt_rcvd = - get_nsecs(); // Set this to "now" to allow timing out HS at sender - // (when no packet was received yet), once packets are - // received it will be updated accordingly + now; // Set this to "now" to allow timing out HS at sender + // (when no packet was received yet), once packets are + // received it will be updated accordingly + s->last_new_pkt_rcvd = now; return s; } @@ -254,9 +256,10 @@ static void destroy_session_tx(struct hercules_session *session) { assert(ret == 0); // No reason this should ever fail bitset__destroy(&session->tx_state->acked_chunks); - free(session->tx_state->paths); - bitset__destroy(&session->tx_state->cc_states->mi_nacked); - free(session->tx_state->cc_states); + // TODO when can we free pathset? + /* free(session->tx_state->paths); */ + /* bitset__destroy(&session->tx_state->cc_states->mi_nacked); */ + /* free(session->tx_state->cc_states); */ free(session->tx_state); destroy_send_queue(session->send_queue); @@ -1263,8 +1266,9 @@ static void rate_limit_tx(struct sender_state *tx_state) void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { u64 now = get_nsecs(); - for (u32 p = 0; p < tx_state->num_paths; p++) { - struct hercules_path *path = &tx_state->paths[p]; + struct path_set *pathset = tx_state->pathset; + for (u32 p = 0; p < pathset->n_paths; p++) { + struct hercules_path *path = &pathset->paths[p]; if (path->enabled) { u64 handshake_at = atomic_load(&path->next_handshake_at); if (handshake_at < now) { @@ -1282,73 +1286,6 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t } } -// TODO -static void update_hercules_tx_paths(struct sender_state *tx_state) -{ - return; // FIXME HACK - /* tx_state->has_new_paths = false; */ - /* u64 now = get_nsecs(); */ - /* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ - /* struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; */ - /* receiver->num_paths = tx_state->shd_num_paths[r]; */ - - /* bool replaced_return_path = false; */ - /* for(u32 p = 0; p < receiver->num_paths; p++) { */ - /* struct hercules_path *shd_path = &tx_state->shd_paths[r * tx_state->max_paths_per_rcvr + p]; */ - /* if(!shd_path->enabled && p == receiver->return_path_idx) { */ - /* receiver->return_path_idx++; */ - /* } */ - /* if(shd_path->replaced) { */ - /* shd_path->replaced = false; */ - /* // assert that chunk length fits into packet with new header */ - /* if(shd_path->payloadlen < (int)tx_state->chunklen + rbudp_headerlen) { */ - /* fprintf(stderr, */ - /* "cannot use path %d for receiver %d: header too big, chunk does not fit into payload\n", p, */ - /* r); */ - /* receiver->paths[p].enabled = false; */ - /* continue; */ - /* } */ - /* memcpy(&receiver->paths[p], shd_path, sizeof(struct hercules_path)); */ - - /* atomic_store(&receiver->paths[p].next_handshake_at, */ - /* UINT64_MAX); // by default do not send a new handshake */ - /* if(p == receiver->return_path_idx) { */ - /* atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure handshake_rtt is adapted */ - /* // don't trigger RTT estimate on other paths, as it will be triggered by the ACK on the new return path */ - /* replaced_return_path = true; */ - /* } */ - /* // reset PCC state */ - /* if(!replaced_return_path && receiver->cc_states != NULL) { */ - /* terminate_ccontrol(&receiver->cc_states[p]); */ - /* continue_ccontrol(&receiver->cc_states[p]); */ - /* atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure mi_duration is set */ - /* } */ - /* } else { */ - /* if(p == receiver->return_path_idx) { */ - /* atomic_store(&receiver->paths[p].next_handshake_at, now); // make sure handshake_rtt is adapted */ - /* // don't trigger RTT estimate on other paths, as it will be triggered by the ACK on the new return path */ - /* replaced_return_path = true; */ - /* } */ - /* if(receiver->cc_states != NULL && receiver->paths[p].enabled != shd_path->enabled) { */ - /* if(shd_path->enabled) { // reactivate PCC */ - /* if(receiver->cc_states != NULL) { */ - /* double rtt = receiver->cc_states[p].rtt; */ - /* double mi_duration = receiver->cc_states[p].pcc_mi_duration; */ - /* continue_ccontrol(&receiver->cc_states[p]); */ - /* receiver->cc_states[p].rtt = rtt; */ - /* receiver->cc_states[p].pcc_mi_duration = mi_duration; */ - /* } */ - /* } else { // deactivate PCC */ - /* terminate_ccontrol(&receiver->cc_states[p]); */ - /* } */ - /* } */ - /* receiver->paths[p].enabled = shd_path->enabled; */ - /* } */ - /* } */ - /* } */ -} - - static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { pthread_spin_lock(&iface->umem->lock); @@ -1391,11 +1328,13 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s struct send_queue_unit *unit) { u32 num_chunks_in_unit = 0; + struct path_set *pathset = tx_state->pathset; for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { if(unit->paths[i] == UINT8_MAX) { break; } - struct hercules_path *path = &tx_state->paths[unit->paths[i]]; + // TODO path idx may be larger if paths changed in meantime + struct hercules_path *path = &pathset->paths[unit->paths[i]]; if(path->ifid == ifid) { num_chunks_in_unit++; } @@ -1412,7 +1351,8 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s if(unit->paths[i] == UINT8_MAX) { break; } - const struct hercules_path *path = &tx_state->paths[unit->paths[i]]; + // TODO check idx not too large (see above) + const struct hercules_path *path = &pathset->paths[unit->paths[i]]; if(path->ifid != ifid) { continue; } @@ -1432,9 +1372,9 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s #endif u8 track_path = PCC_NO_PATH; // put path_idx iff PCC is enabled sequence_number seqnr = 0; - if(tx_state->cc_states != NULL) { + if(path->cc_state != NULL) { track_path = unit->paths[i]; - seqnr = atomic_fetch_add(&tx_state->cc_states[unit->paths[i]].last_seqnr, 1); + seqnr = atomic_fetch_add(&path->cc_state->last_seqnr, 1); } fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, seqnr, tx_state->mem + chunk_start, len, path->payloadlen); stitch_checksum(path, path->header.checksum, pkt); @@ -1507,15 +1447,17 @@ static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { u32 allowed_chunks = 0; u64 now = get_nsecs(); - if (!tx_state->paths[tx_state->path_index].enabled) { + // TODO pass in pathset instead? (atomics/free) + struct path_set *pathset = tx_state->pathset; + // TODO make sure path_index is reset correctly on pathset change + struct hercules_path *path = &pathset->paths[pathset->path_index]; + if (!path->enabled) { return 0; // if a receiver does not have any enabled paths, we can // actually end up here ... :( } - if (tx_state->cc_states != NULL) { // use PCC - struct ccontrol_state *cc_state = - &tx_state->cc_states[tx_state->path_index]; - /* debug_printf("path idx %d", tx_state->receiver[0].path_index); */ + if (path->cc_state) { // use PCC + struct ccontrol_state *cc_state = path->cc_state; allowed_chunks = umin32(BATCH_SIZE, ccontrol_can_send_npkts(cc_state, now)); } else { // no path-based limit @@ -1539,30 +1481,33 @@ static u32 shrink_sending_rates(struct sender_state *tx_state, return total_chunks; } -static void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { - rcvr_path[0] = tx_state->path_index; +// TODO remove +static inline void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { + rcvr_path[0] = tx_state->pathset->path_index; } // Mark the next available path as active static void iterate_paths(struct sender_state *tx_state) { - if (tx_state->num_paths == 0) { + struct path_set *pathset = tx_state->pathset; + if (pathset->n_paths == 0) { return; } u32 prev_path_index = - tx_state->path_index; // we need this to break the loop if all paths - // are disabled - if (prev_path_index >= tx_state->num_paths) { + pathset->path_index; // we need this to break the loop if all paths + // are disabled + if (prev_path_index >= pathset->n_paths) { prev_path_index = 0; } do { - tx_state->path_index = (tx_state->path_index + 1) % tx_state->num_paths; - } while (!tx_state->paths[tx_state->path_index].enabled && - tx_state->path_index != prev_path_index); + pathset->path_index = (pathset->path_index + 1) % pathset->n_paths; + } while (!pathset->paths[pathset->path_index].enabled && + pathset->path_index != prev_path_index); } static void terminate_cc(const struct sender_state *tx_state) { - for (u32 i = 0; i < tx_state->num_paths; i++) { - terminate_ccontrol(&tx_state->cc_states[i]); + struct path_set *pathset = tx_state->pathset; + for (u32 i = 0; i < pathset->n_paths; i++) { + terminate_ccontrol(pathset->paths[i].cc_state); } } @@ -1570,8 +1515,9 @@ static void kick_cc(struct sender_state *tx_state) { if (tx_state->finished) { return; } - for (u32 p = 0; p < tx_state->num_paths; p++) { - kick_ccontrol(&tx_state->cc_states[p]); + struct path_set *pathset = tx_state->pathset; + for (u32 p = 0; p < pathset->n_paths; p++) { + kick_ccontrol(pathset->paths[p].cc_state); } } // Select batch of un-ACKed chunks for (re)transmit: @@ -1659,18 +1605,23 @@ static struct sender_state *init_tx_state(struct hercules_session *session, tx_state->end_time = 0; bitset__create(&tx_state->acked_chunks, tx_state->total_chunks); - tx_state->path_index = 0; tx_state->handshake_rtt = 0; - tx_state->num_paths = num_paths; - tx_state->paths = paths; + struct path_set *pathset = calloc(1, sizeof(*tx_state->pathset)); + if (pathset == NULL){ + free(tx_state); + return NULL; + } + pathset->n_paths = num_paths; + memcpy(pathset->paths, paths, sizeof(*paths)*num_paths); + tx_state->pathset = pathset; tx_state->session->peer = *dests; - update_hercules_tx_paths(tx_state); return tx_state; } static void destroy_tx_state(struct sender_state *tx_state) { bitset__destroy(&tx_state->acked_chunks); - free(tx_state->paths); + // TODO freeing pathset + /* free(tx_state->paths); */ free(tx_state); } @@ -1681,7 +1632,8 @@ static void tx_retransmit_initial(struct hercules_server *server, u64 now) { if (now > session_tx->last_pkt_sent + session_hs_retransmit_interval) { struct sender_state *tx_state = session_tx->tx_state; - tx_send_initial(server, &tx_state->paths[tx_state->return_path_idx], + struct path_set *pathset = tx_state->pathset; + tx_send_initial(server, &pathset->paths[tx_state->return_path_idx], tx_state->filename, tx_state->filesize, tx_state->chunklen, now, 0, session_tx->etherlen, true, true); session_tx->last_pkt_sent = now; @@ -1702,6 +1654,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, return; } if (server->enable_pcc) { + struct path_set *pathset = tx_state->pathset; u64 now = get_nsecs(); tx_state->handshake_rtt = now - parsed_pkt->timestamp; // TODO where to get rate limit? @@ -1709,24 +1662,26 @@ static void tx_handle_hs_confirm(struct hercules_server *server, /* u32 rate = 2000e3; // 20 Gbps */ u32 rate = 100; // 1 Mbps debug_printf("rate limit %u", rate); - tx_state->cc_states = init_ccontrol_state( - rate, tx_state->total_chunks, - tx_state->num_paths, tx_state->num_paths, - tx_state->num_paths); - ccontrol_update_rtt(&tx_state->cc_states[0], + for (u32 i = 0; i < pathset->n_paths; i++){ + pathset->paths[i].cc_state = init_ccontrol_state( + rate, tx_state->total_chunks, + pathset->n_paths); + } + ccontrol_update_rtt(pathset->paths[0].cc_state, tx_state->handshake_rtt); + // TODO assumption: return path is always idx 0 fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: " "%fs, MI: %fs\n", 0, tx_state->handshake_rtt / 1e9, - tx_state->cc_states[0].pcc_mi_duration); + pathset->paths[0].cc_state->pcc_mi_duration); // make sure we later perform RTT estimation // on every enabled path - tx_state->paths[0].next_handshake_at = + pathset->paths[0].next_handshake_at = UINT64_MAX; // We just completed the HS for this path - for (u32 p = 1; p < tx_state->num_paths; p++) { - tx_state->paths[p].next_handshake_at = now; + for (u32 p = 1; p < pathset->n_paths; p++) { + pathset->paths[p].next_handshake_at = now; } } session_tx->state = SESSION_STATE_WAIT_CTS; @@ -1736,23 +1691,24 @@ static void tx_handle_hs_confirm(struct hercules_server *server, if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { struct sender_state *tx_state = session_tx->tx_state; + struct path_set *pathset = tx_state->pathset; // This is a reply to some handshake we sent during an already // established session (e.g. to open a new path) u64 now = get_nsecs(); if (server->enable_pcc) { - ccontrol_update_rtt(&tx_state->cc_states[parsed_pkt->path_index], + ccontrol_update_rtt(pathset->paths[parsed_pkt->path_index].cc_state, now - parsed_pkt->timestamp); } - tx_state->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; + pathset->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; // We have a new return path, redo handshakes on all other paths if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH){ tx_state->handshake_rtt = now - parsed_pkt->timestamp; - for (u32 p = 0; p < tx_state->num_paths; p++){ - if (p != parsed_pkt->path_index && tx_state->paths[p].enabled){ - tx_state->paths[p].next_handshake_at = now; - tx_state->cc_states[p].pcc_mi_duration = DBL_MAX; - tx_state->cc_states[p].rtt = DBL_MAX; + for (u32 p = 0; p < pathset->n_paths; p++){ + if (p != parsed_pkt->path_index && pathset->paths[p].enabled){ + pathset->paths[p].next_handshake_at = now; + pathset->paths[p].cc_state->pcc_mi_duration = DBL_MAX; + pathset->paths[p].cc_state->rtt = DBL_MAX; } } } @@ -1870,8 +1826,12 @@ static bool pcc_mi_elapsed(struct ccontrol_state *cc_state) static void pcc_monitor(struct sender_state *tx_state) { - for(u32 cur_path = 0; cur_path < tx_state->num_paths; cur_path++) { - struct ccontrol_state *cc_state = &tx_state->cc_states[cur_path]; + struct path_set *pathset = tx_state->pathset; + for(u32 cur_path = 0; cur_path < pathset->n_paths; cur_path++) { + struct ccontrol_state *cc_state = pathset->paths[cur_path].cc_state; + if (cc_state == NULL){ // Not using PCC + continue; + } pthread_spin_lock(&cc_state->lock); if(pcc_mi_elapsed(cc_state)) { u64 now = get_nsecs(); @@ -2193,9 +2153,9 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { quit_session(session_rx, SESSION_ERROR_TIMEOUT); debug_printf("Session (RX) timed out!"); } - else if (new > session_rx->last_new_pkt_rcvd + session_stale_timeout){ - quit_session(session_tx, SESSION_ERROR_STALE); - debug_printf("Session (RX) stale!") + else if (now > session_rx->last_new_pkt_rcvd + session_stale_timeout){ + quit_session(session_rx, SESSION_ERROR_STALE); + debug_printf("Session (RX) stale!"); } } } @@ -2203,6 +2163,8 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { static void tx_update_paths(struct hercules_server *server) { struct hercules_session *session_tx = server->session_tx; if (session_tx && session_tx->state == SESSION_STATE_RUNNING) { + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *old_pathset = tx_state->pathset; int n_paths; struct hercules_path *paths; bool ret = @@ -2212,15 +2174,68 @@ static void tx_update_paths(struct hercules_server *server) { return; } debug_printf("received %d paths", n_paths); - if (n_paths == 0){ + if (n_paths == 0) { free(paths); quit_session(session_tx, SESSION_ERROR_NO_PATHS); return; } - // XXX doesn't this break if we get more paths and the update is not - // atomic? - session_tx->tx_state->num_paths = n_paths; - session_tx->tx_state->paths = paths; + struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); + if (new_pathset == NULL) { + return; + } + new_pathset->n_paths = n_paths; + memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); + u32 path_lim = + (old_pathset->n_paths > (u32)n_paths) ? (u32)n_paths : old_pathset->n_paths; + bool replaced_return_path = false; + for (u32 i = 0; i < path_lim; i++) { + // Set these two values before the comparison or it would fail even + // if paths are the same. + new_pathset->paths[i].next_handshake_at = + old_pathset->paths[i].next_handshake_at; + new_pathset->paths[i].cc_state = old_pathset->paths[i].cc_state; + + if (memcmp(&old_pathset->paths[i], &new_pathset->paths[i], + sizeof(struct hercules_path)) == 0) { + // Old and new path are the same, CC state carries over. + // Since we copied the CC state before just leave as-is. + debug_printf("Path %d not changed", i); + } else { + debug_printf("Path %d changed, resetting CC", i); + if (i == 0) { + // TODO assumption (also in other places): return path is + // always idx 0 + replaced_return_path = true; + } + // TODO whether to use pcc should be decided on a per-path basis + // by the monitor + if (server->enable_pcc) { + // TODO assert chunk length fits onto path + // The new path is different, restart CC + // TODO where to get rate + u32 rate = 100; + new_pathset->paths[i].cc_state = init_ccontrol_state( + rate, tx_state->total_chunks, new_pathset->n_paths); + // Re-send a handshake to update path rtt + new_pathset->paths[i].next_handshake_at = 0; + } + } + if (replaced_return_path) { + // If we changed the return path we re-send the handshake on all + // paths to update RTT + debug_printf( + "Re-sending HS on path %d because return path changed", i); + new_pathset->paths[i].next_handshake_at = 0; + } + } + // Finally, swap in the new pathset + tx_state->pathset = new_pathset; + free(paths); // These were *copied* into the new pathset + // TODO when it's safe (when?), free: + // - the old pathset + // - its cc states that were not carried over + // - ! If we have fewer paths in the new set than in the old one don't + // forget to free ALL old states } } @@ -2466,8 +2481,8 @@ static void events_p(void *arg) { cp->payload.ack.ack_nr); tx_register_nacks( &cp->payload.ack, - &server->session_tx->tx_state - ->cc_states[h->path]); + // TODO h->path may be too large if pathset changed + server->session_tx->tx_state->pathset->paths[h->path].cc_state); } break; default: @@ -2479,7 +2494,7 @@ static void events_p(void *arg) { // all data packets debug_printf("Non-control packet received on control socket"); } - if (server->session_tx && server->session_tx->tx_state->cc_states){ + if (server->session_tx){ pcc_monitor(server->session_tx->tx_state); } } @@ -2555,10 +2570,8 @@ static void *tx_p(void *arg) { &ack_due, allowed_chunks); num_chunks += cur_num_chunks; if (tx_state->finished) { - if (tx_state->cc_states) { terminate_cc(tx_state); kick_cc(tx_state); - } } else { // only wait for the nearest ack if (next_ack_due) { @@ -2580,20 +2593,21 @@ static void *tx_p(void *arg) { rate_limit_tx(tx_state); // update book-keeping - u32 path_idx = tx_state->path_index; - if (tx_state->cc_states != NULL) { - struct ccontrol_state *cc_state = &tx_state->cc_states[path_idx]; - // FIXME allowed_chunks below is not correct (3x) - atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); - atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); - if (pcc_has_active_mi(cc_state, now)) { - atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, - allowed_chunks); - } - } - } - - iterate_paths(tx_state); + struct path_set *pathset = tx_state->pathset; + u32 path_idx = pathset->path_index; + struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; + if (cc_state != NULL) { + // FIXME allowed_chunks below is not correct (3x) + atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); + atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); + if (pcc_has_active_mi(cc_state, now)) { + atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, + allowed_chunks); + } + } + } + + iterate_paths(tx_state); if (now < next_ack_due) { // XXX if the session vanishes in the meantime, we might wait diff --git a/hercules.h b/hercules.h index bf1e620..85f0991 100644 --- a/hercules.h +++ b/hercules.h @@ -50,10 +50,11 @@ struct hercules_path { int headerlen; int payloadlen; int framelen; //!< length of ethernet frame; headerlen + payloadlen - int ifid; + int ifid; // Interface to use for sending struct hercules_path_header header; atomic_bool enabled; // e.g. when a path has been revoked and no // replacement is available, this will be set to false + struct ccontrol_state *cc_state; // This path's PCC state }; /// RECEIVER @@ -91,13 +92,21 @@ struct receiver_state { // Start/end time of the current transfer u64 start_time; u64 end_time; - u64 last_pkt_rcvd; // Timeout detection + u64 last_pkt_rcvd; // Timeout detection u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; }; /// SENDER + +// Used to atomically swap in new paths +struct path_set { + u32 n_paths; + u8 path_index; // Path to use for sending next batch (used by tx_p) + struct hercules_path paths[256]; +}; + struct sender_state { struct hercules_session *session; @@ -112,15 +121,11 @@ struct sender_state { u64 ack_wait_duration; u32 prev_chunk_idx; bool finished; - /** Next batch should be sent via this path */ - u8 path_index; struct bitset acked_chunks; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns - u32 return_path_idx; - u32 num_paths; - struct hercules_path *paths; - struct ccontrol_state *cc_states; + u32 return_path_idx; // TODO set where? + struct path_set *_Atomic pathset; // Paths currently in use /** Filesize in bytes */ size_t filesize; char filename[100]; @@ -141,8 +146,7 @@ struct sender_state { enum session_state { SESSION_STATE_NONE, SESSION_STATE_PENDING, //< (TX) Need to send HS and repeat until TO, - // waiting - // for a reflected HS packet + // waiting for a reflected HS packet SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and // CTS SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS @@ -172,7 +176,7 @@ struct hercules_session { u64 last_new_pkt_rcvd; //< If we only receive packets containing // already-seen chunks for a while something is // probably wrong - u32 jobid; //< The monitor's ID for this job + u32 jobid; //< The monitor's ID for this job u32 etherlen; struct hercules_app_addr peer; @@ -208,7 +212,7 @@ struct hercules_server { int control_sockfd; // AF_PACKET socket used for control traffic int max_paths; int rate_limit; - int n_threads; // Number of RX/TX worker threads + int n_threads; // Number of RX/TX worker threads struct rx_p_args **worker_args; // Args passed to RX workers struct hercules_session *_Atomic session_tx; // Current TX session From 4c89f39c695567fb3e1b244432ced5e89813823a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 4 Jun 2024 18:35:31 +0200 Subject: [PATCH 028/112] Handle SCMP messages --- hercules.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++--- packet.h | 36 ++++++++++++++ 2 files changed, 166 insertions(+), 7 deletions(-) diff --git a/hercules.c b/hercules.c index b9efd95..ae3ce47 100644 --- a/hercules.c +++ b/hercules.c @@ -433,14 +433,109 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec return pkt + offset; } +// The SCMP packet contains a copy of the offending message we sent, parse it to +// figure out which path the SCMP message is referring to. +// Returns the offending path's id, or PCC_NO_PATH on failure. +// XXX Not checking dst or source ia/addr/port in reflected packet +static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length) { + size_t offset = 0; + const char *pkt = NULL; + debug_printf("SCMP type %d", scmp->type); + switch (scmp->type) { + case SCMP_DEST_UNREACHABLE: + case SCMP_PKT_TOO_BIG: + case SCMP_PARAMETER_PROBLEM:; + pkt = (const char *)scmp->msg.err.offending_packet; + offset += offsetof(struct scmp_message, msg.err.offending_packet); + break; + case SCMP_EXT_IF_DOWN: + pkt = (const char *)scmp->msg.ext_down.offending_packet; + offset += + offsetof(struct scmp_message, msg.ext_down.offending_packet); + break; + case SCMP_INT_CONN_DOWN: + pkt = (const char *)scmp->msg.int_down.offending_packet; + offset += + offsetof(struct scmp_message, msg.int_down.offending_packet); + break; + default: + debug_printf("Unknown or unhandled SCMP type: %d", scmp->type); + return PCC_NO_PATH; + } + // Parse SCION Common header + if (offset + sizeof(struct scionhdr) > length) { + debug_printf("too short for SCION header: %zu %zu", offset, length); + return PCC_NO_PATH; + } + + const struct scionhdr *scionh = (const struct scionhdr *)(pkt); + if (scionh->version != 0u) { + debug_printf("unsupported SCION version: %u != 0", scionh->version); + return PCC_NO_PATH; + } + if (scionh->dst_type != 0u) { + debug_printf("unsupported destination address type: %u != 0 (IPv4)", + scionh->dst_type); + } + if (scionh->src_type != 0u) { + debug_printf("unsupported source address type: %u != 0 (IPv4)", + scionh->src_type); + } + + __u8 next_header = scionh->next_header; + size_t next_offset = offset + scionh->header_len * SCION_HEADER_LINELEN; + if (next_header == SCION_HEADER_HBH) { + if (next_offset + 2 > length) { + debug_printf("too short for SCION HBH options header: %zu %zu", + next_offset, length); + return PCC_NO_PATH; + } + next_header = *((__u8 *)pkt + next_offset); + next_offset += + (*((__u8 *)pkt + next_offset + 1) + 1) * SCION_HEADER_LINELEN; + } + if (next_header == SCION_HEADER_E2E) { + if (next_offset + 2 > length) { + debug_printf("too short for SCION E2E options header: %zu %zu", + next_offset, length); + return PCC_NO_PATH; + } + next_header = *((__u8 *)pkt + next_offset); + next_offset += + (*((__u8 *)pkt + next_offset + 1) + 1) * SCION_HEADER_LINELEN; + } + if (next_header != IPPROTO_UDP) { + return PCC_NO_PATH; + } + const struct scionaddrhdr_ipv4 *scionaddrh = + (const struct scionaddrhdr_ipv4 *)(pkt + offset + + sizeof(struct scionhdr)); + offset = next_offset; + + // Finally parse the L4-UDP header + if (offset + sizeof(struct udphdr) > length) { + debug_printf("too short for SCION/UDP header: %zu %zu", offset, length); + return PCC_NO_PATH; + } + + const struct udphdr *l4udph = (const struct udphdr *)(pkt + offset); + + offset += sizeof(struct udphdr); + const struct hercules_header *rbudp_hdr = + (const struct hercules_header *)(pkt + offset); + return rbudp_hdr->path; +} + // Parse ethernet/IP/UDP/SCION/UDP packet, // check that it is addressed to us, // check SCION-UDP checksum if set. // sets scionaddrh_o to SCION address header, if provided // return rbudp-packet (i.e. SCION/UDP packet payload) -static const char *parse_pkt(const struct hercules_server *server, const char *pkt, size_t length, bool check, - const struct scionaddrhdr_ipv4 **scionaddrh_o, const struct udphdr **udphdr_o) -{ +static const char *parse_pkt(const struct hercules_server *server, + const char *pkt, size_t length, bool check, + const struct scionaddrhdr_ipv4 **scionaddrh_o, + const struct udphdr **udphdr_o, + u8 *scmp_offending_path_o) { // Parse Ethernet frame if(sizeof(struct ether_header) > length) { debug_printf("too short for eth header: %zu", length); @@ -518,8 +613,15 @@ static const char *parse_pkt(const struct hercules_server *server, const char *p next_offset += (*((__u8 *)pkt + next_offset + 1) + 1) * SCION_HEADER_LINELEN; } if(next_header != IPPROTO_UDP) { - if(next_header == L4_SCMP) { - debug_printf("SCION/SCMP L4: not implemented, ignoring..."); + if (next_header == L4_SCMP) { + if (next_offset + sizeof(struct scmp_message) > length) { + debug_printf("SCMP, too short?"); + return NULL; + } + const struct scmp_message *scmp_msg = + (const struct scmp_message *)(pkt + next_offset); + *scmp_offending_path_o = + parse_scmp_packet(scmp_msg, length - next_offset); } else { debug_printf("unknown SCION L4: %u", next_header); } @@ -2350,9 +2452,30 @@ static void events_p(void *arg) { continue; } - const char *rbudp_pkt = - parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr); + u8 scmp_bad_path = PCC_NO_PATH; + const char *rbudp_pkt = parse_pkt( + server, buf, len, true, &scionaddrhdr, &udphdr, &scmp_bad_path); if (rbudp_pkt == NULL) { + if (scmp_bad_path != PCC_NO_PATH) { + debug_printf("Received SCMP error on path %d, disabling", + scmp_bad_path); + // XXX We disable the path that received an SCMP error. The + // next time we fetch new paths from the monitor it will be + // re-enabled, if it's still present. It may be desirable to + // retry a disabled path earlier, depending on how often we + // update paths and on the exact SCMP error. Also, should + // "destination unreachable" be treated as a permanent + // failure and the session abandoned immediately? + struct hercules_session *session_tx = server->session_tx; + if (session_tx != NULL && + session_tx->state == SESSION_STATE_RUNNING) { + struct path_set *pathset = + session_tx->tx_state->pathset; + if (scmp_bad_path < pathset->n_paths) { + pathset->paths[scmp_bad_path].enabled = false; + } + } + } continue; } diff --git a/packet.h b/packet.h index c22831b..2279820 100644 --- a/packet.h +++ b/packet.h @@ -63,6 +63,42 @@ struct scionaddrhdr_ipv4 { __u32 src_ip; }; +// Used for destination unreachable, packet too big, and parameter problem, +// since all 3 have the same offset to the offending packet. +#define SCMP_DEST_UNREACHABLE 1 +#define SCMP_PKT_TOO_BIG 2 +#define SCMP_PARAMETER_PROBLEM 4 +struct scmp_err { + __u32 unused; + __u8 offending_packet[]; +}; + +#define SCMP_EXT_IF_DOWN 5 +struct scmp_extif_down { + __u64 ia; + __u64 iface; + __u8 offending_packet[]; +}; + +#define SCMP_INT_CONN_DOWN 6 +struct scmp_internal_down { + __u64 ia; + __u64 ingress_if; + __u64 egress_if; + __u8 offending_packet[]; +}; + +struct scmp_message { + __u8 type; + __u8 code; + __u16 chksum; + union { + struct scmp_err err; + struct scmp_extif_down ext_down; + struct scmp_internal_down int_down; + } msg; +}; + // The header used by both control and data packets struct hercules_header { __u32 chunk_idx; From 459f8d7cb7af968b736a1716eeb7d2dfb4676b11 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 5 Jun 2024 08:36:03 +0200 Subject: [PATCH 029/112] update monitor stats --- hercules.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/hercules.c b/hercules.c index ae3ce47..fe353a3 100644 --- a/hercules.c +++ b/hercules.c @@ -1786,6 +1786,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, pathset->paths[p].next_handshake_at = now; } } + tx_state->start_time = get_nsecs(); session_tx->state = SESSION_STATE_WAIT_CTS; return; } @@ -2315,7 +2316,7 @@ static void tx_update_paths(struct hercules_server *server) { // TODO assert chunk length fits onto path // The new path is different, restart CC // TODO where to get rate - u32 rate = 100; + u32 rate = 100 * 20000; new_pathset->paths[i].cc_state = init_ccontrol_state( rate, tx_state->total_chunks, new_pathset->n_paths); // Re-send a handshake to update path rtt @@ -2395,6 +2396,17 @@ static void print_session_stats(struct hercules_server *server, } } +static void tx_update_monitor(struct hercules_server *server, u64 now) { + struct hercules_session *session_tx = server->session_tx; + if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { + monitor_update_job(usock, session_tx->jobid, session_tx->state, 0, + ( now - session_tx->tx_state->start_time ) / (int)1e9, + session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set); + debug_printf("elapsed %llu", (now - session_tx->tx_state->start_time)/(u64)1e9); + } +} + #define PRINT_STATS // Read control packets from the control socket and process them; also handles @@ -2428,6 +2440,10 @@ static void events_p(void *arg) { tx_update_paths(server); lastpoll = now; } + if (now > lastpoll + 2e9){ + tx_update_monitor(server, now); + lastpoll = now; + } /* lastpoll = now; */ /* } */ From fdb0484ad191917b753e8793bdcaee1b257f745a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 5 Jun 2024 09:31:35 +0200 Subject: [PATCH 030/112] fix intra-as (empty paths) --- hercules.c | 20 ++++++++++++-------- monitor/monitor.go | 5 +++++ monitor/pathstodestination.go | 20 +++++++++++++++++--- send_queue.h | 3 +-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/hercules.c b/hercules.c index fe353a3..0316ddb 100644 --- a/hercules.c +++ b/hercules.c @@ -307,6 +307,9 @@ struct hercules_server *hercules_init_server( } server->config.local_addr = local_addr; server->config.configure_queues = configure_queues; + server->config.xdp_mode = xdp_mode; + /* server->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; */ + // FIXME with flags set, setup may fail and we don't catch it? server->enable_pcc = true; // TODO this should be per-path or at least per-transfer @@ -1390,6 +1393,7 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { + // TODO FIXME Lock contention, significantly affects performance pthread_spin_lock(&iface->umem->lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); while(reserved != num_frames) { @@ -1515,7 +1519,7 @@ produce_batch(struct hercules_server *server, struct hercules_session *session, } } - unit->rcvr[num_chunks_in_unit] = rcvr_by_chunk[chk]; + /* unit->rcvr[num_chunks_in_unit] = rcvr_by_chunk[chk]; */ unit->paths[num_chunks_in_unit] = path_by_rcvr[rcvr_by_chunk[chk]]; unit->chunk_idx[num_chunks_in_unit] = chunks[chk]; @@ -1761,7 +1765,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, tx_state->handshake_rtt = now - parsed_pkt->timestamp; // TODO where to get rate limit? // below is ~in Mb/s (but really pps) - /* u32 rate = 2000e3; // 20 Gbps */ + /* u32 rate = 20000e3; // 200 Gbps */ u32 rate = 100; // 1 Mbps debug_printf("rate limit %u", rate); for (u32 i = 0; i < pathset->n_paths; i++){ @@ -2316,7 +2320,7 @@ static void tx_update_paths(struct hercules_server *server) { // TODO assert chunk length fits onto path // The new path is different, restart CC // TODO where to get rate - u32 rate = 100 * 20000; + u32 rate = 100; new_pathset->paths[i].cc_state = init_ccontrol_state( rate, tx_state->total_chunks, new_pathset->n_paths); // Re-send a handshake to update path rtt @@ -2359,7 +2363,7 @@ struct prints{ static void print_session_stats(struct hercules_server *server, struct prints *p) { u64 now = get_nsecs(); - if (now < p->ts + 500e6) { + if (now < p->ts + 1e9) { return; } u64 tdiff = now - p->ts; @@ -2436,10 +2440,10 @@ static void events_p(void *arg) { #ifdef PRINT_STATS print_session_stats(server, &prints); #endif - if (now > lastpoll + 10e9){ - tx_update_paths(server); - lastpoll = now; - } + /* if (now > lastpoll + 10e9){ */ + /* tx_update_paths(server); */ + /* lastpoll = now; */ + /* } */ if (now > lastpoll + 2e9){ tx_update_monitor(server, now); lastpoll = now; diff --git a/monitor/monitor.go b/monitor/monitor.go index 064311d..78655f4 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -21,6 +21,7 @@ import ( "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" "github.com/scionproto/scion/private/topology" "github.com/vishvananda/netlink" ) @@ -157,6 +158,10 @@ func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, net.IP, return nil, nil, errors.New("error decoding SCION/UDP") } + if (sourcePkt.Source.IA == sourcePkt.Destination.IA){ + sourcePkt.Path = path.Empty{} + } + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort), etherLen) if err != nil { return nil, nil, err diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 451f32f..e407523 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -17,13 +17,14 @@ package main import ( "context" "fmt" + "net" + "time" + log "github.com/inconshreveable/log15" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/path" "github.com/scionproto/scion/private/topology" "go.uber.org/atomic" - "net" - "time" ) var GlobalQuerier snet.PathQuerier @@ -52,10 +53,14 @@ type HerculesPathHeader struct { } func initNewPathsToDestinationWithEmptyPath(pm *PathManager, dst *Destination) *PathsToDestination { + dst.hostAddr.NextHop = &net.UDPAddr{ + IP: dst.hostAddr.Host.IP, + Port: topology.EndhostPort, + } return &PathsToDestination{ pm: pm, dst: dst, - paths: nil, + paths: make([]PathMeta, 1), modifyTime: time.Now(), } } @@ -93,6 +98,15 @@ func (ptd *PathsToDestination) choosePaths() bool { return false } + if ptd.allPaths[0].UnderlayNextHop() == nil { + ptd.allPaths[0] = path.Path{ + Src: ptd.pm.src.IA, + Dst: ptd.dst.hostAddr.IA, + DataplanePath: path.Empty{}, + NextHop: ptd.dst.hostAddr.NextHop, + } + } + fmt.Println("all paths", ptd.allPaths) availablePaths := ptd.pm.filterPathsByActiveInterfaces(ptd.allPaths) if len(availablePaths) == 0 { diff --git a/send_queue.h b/send_queue.h index 647715f..021bbf9 100644 --- a/send_queue.h +++ b/send_queue.h @@ -19,14 +19,13 @@ #include "utils.h" #define CACHELINE_SIZE 64 -#define SEND_QUEUE_ENTRY_SIZE 6 +#define SEND_QUEUE_ENTRY_SIZE 5 #define SEND_QUEUE_ENTRIES_PER_UNIT 7 // With this layout, 10 chunks fit into each cache line. Assumes a cache line size of 64 bytes. // sizeof(struct send_queue_unit) = 64 struct send_queue_unit { u32 chunk_idx[SEND_QUEUE_ENTRIES_PER_UNIT]; - u8 rcvr[SEND_QUEUE_ENTRIES_PER_UNIT]; u8 paths[SEND_QUEUE_ENTRIES_PER_UNIT]; char a[CACHELINE_SIZE - SEND_QUEUE_ENTRIES_PER_UNIT * SEND_QUEUE_ENTRY_SIZE]; // force padding to 64 bytes }; From c4ad90f6472a266d1f5eccbb6e51d7a4bd653026 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 7 Jun 2024 08:56:08 +0200 Subject: [PATCH 031/112] Change makefile to distinguish server/monitor --- Makefile | 64 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 4dd46e7..7b48acb 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,52 @@ -.PHONY: builder builder_image install clean +# TODO change binary names +TARGET_SERVER := runHercules +TARGET_MONITOR := runMonitor -all: hercules mockules +CC := gcc +CFLAGS = -O3 -std=gnu11 -D_GNU_SOURCE +# CFLAGS += -Wall -Wextra +# for debugging: +CFLAGS += -g3 -DDEBUG +# CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets +CFLAGS += -DPRINT_STATS +LDFLAGS = -lbpf -Lbpf/src -lm -lelf -pthread -lz -z noexecstack +DEPFLAGS:=-MP -MD -install: all -ifndef DESTDIR - $(error DESTDIR is not set) -endif - cp hercules mockules/mockules $(DESTDIR) +SRCS := $(wildcard *.c) +OBJS := $(SRCS:.c=.o) +DEPS := $(OBJS:.o=.d) +MONITORFILES := $(wildcard monitor/*) -hercules: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf_prgm/pass.o bpf/src/libbpf.a - @# update modification dates in assembly, so that the new version gets loaded - @sed -i -e "s/\(load bpf_prgm_pass\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/pass.c | head -c 32)/g" bpf_prgms.s - @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s - @taggedRef=$$(git describe --tags --long --dirty 2>/dev/null) && startupVersion=$$(git rev-parse --abbrev-ref HEAD)"-$${taggedRef}" || \ - startupVersion=$$(git rev-parse --abbrev-ref HEAD)"-untagged-"$$(git describe --tags --dirty --always); \ - docker exec hercules-builder go build -ldflags "-X main.startupVersion=$${startupVersion}" +VERSION := $(shell (ref=$$(git describe --tags --long --dirty 2>/dev/null) && echo $$(git rev-parse --abbrev-ref HEAD)-$$ref) ||\ + echo $$(git rev-parse --abbrev-ref HEAD)-untagged-$$(git describe --tags --dirty --always)) + +# TODO: Install target +# TODO: Native vs docker builds +all: $(TARGET_MONITOR) $(TARGET_SERVER) + +# List all headers as dependency because we include a header file via cgo (which in turn may include other headers) +$(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) + cd monitor && go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" -herculesC: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf_prgm/pass.o bpf/src/libbpf.a +$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a builder @# update modification dates in assembly, so that the new version gets loaded @sed -i -e "s/\(load bpf_prgm_pass\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/pass.c | head -c 32)/g" bpf_prgms.s @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s - docker exec hercules-builder cc -g -O0 -std=gnu99 -o runHercules -DDEBUG -D_GNU_SOURCE *.c bpf_prgms.s -Lbpf/src -lbpf -lm -lelf -pthread -lz - docker exec hercules-builder sh -c "cd monitor; go build -o ../runMonitor ." + docker exec hercules-builder $(CC) -o $@ $(OBJS) bpf_prgms.s $(LDFLAGS) -bpf_prgm/%.ll: bpf_prgm/%.c builder +%.o: %.c builder + docker exec hercules-builder $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ + +hercules: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf_prgm/pass.o bpf/src/libbpf.a + docker exec hercules-builder go build -ldflags "-X main.startupVersion=$${startupVersion}" + +bpf_prgm/%.ll: bpf_prgm/%.c docker exec hercules-builder clang -S -target bpf -D __BPF_TRACING__ -I. -Wall -O2 -emit-llvm -c -g -o $@ $< -bpf_prgm/%.o: bpf_prgm/%.ll builder +bpf_prgm/%.o: bpf_prgm/%.ll docker exec hercules-builder llc -march=bpf -filetype=obj -o $@ $< # explicitly list intermediates for dependency resolution -bpf_prgm/pass.ll: bpf_prgm/redirect_userspace.ll: bpf/src/libbpf.a: builder @@ -44,9 +60,14 @@ bpf/src/libbpf.a: builder docker exec -w /`basename $(PWD)`/bpf/src hercules-builder $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ fi + +.PHONY: builder builder_image install clean all + +# what to do with this?? mockules: builder mockules/main.go mockules/network.go docker exec -w /`basename $(PWD)`/mockules hercules-builder go build +# docker stuff builder: builder_image @docker container ls -a --format={{.Names}} | grep hercules-builder -q || \ docker run -t --entrypoint cat --name hercules-builder -v $(PWD):/`basename $(PWD)` -w /`basename $(PWD)` -d hercules-builder @@ -58,6 +79,9 @@ builder_image: docker build -t hercules-builder --build-arg UID=$(shell id -u) --build-arg GID=$(shell id -g) . clean: + rm -rf $(TARGET_MONITOR) $(TARGET_SERVER) $(OBJS) $(DEPS) rm -f hercules mockules/mockules docker container rm -f hercules-builder || true docker rmi hercules-builder || true + +-include $(DEPS) From abb8317bc441f9edaa03149524e67e52332916e9 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 7 Jun 2024 09:43:30 +0200 Subject: [PATCH 032/112] Remove monitor socket locking --- Makefile | 2 +- hercules.c | 46 ++++++++++++++++++---------------------------- hercules.h | 1 + 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 7b48acb..15598f1 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ CFLAGS = -O3 -std=gnu11 -D_GNU_SOURCE # for debugging: CFLAGS += -g3 -DDEBUG # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets -CFLAGS += -DPRINT_STATS +# CFLAGS += -DPRINT_STATS LDFLAGS = -lbpf -Lbpf/src -lm -lelf -pthread -lz -z noexecstack DEPFLAGS:=-MP -MD diff --git a/hercules.c b/hercules.c index 0316ddb..c483447 100644 --- a/hercules.c +++ b/hercules.c @@ -60,7 +60,6 @@ #define L4_SCMP 202 - #define RANDOMIZE_FLOWID #define RATE_LIMIT_CHECK 1000 // check rate limit every X packets @@ -75,10 +74,6 @@ static const u64 session_hs_retransmit_interval = 2e9; // 2 sec static const u64 session_stale_timeout = 30e9; // 30 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path -// TODO move and see if we can't use this from only 1 thread so no locks -int usock; -pthread_spinlock_t usock_lock; - // Fill packet with n bytes from data and pad with zeros to payloadlen. static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, sequence_number seqnr, const char *data, size_t n, @@ -295,6 +290,12 @@ struct hercules_server *hercules_init_server( if (server == NULL) { exit_with_error(NULL, ENOMEM); } + + server->usock = monitor_bind_daemon_socket(); + if (server->usock == 0) { + fprintf(stderr, "Error binding daemon socket\n"); + exit_with_error(NULL, EINVAL); + } server->ifindices = ifindices; server->num_ifaces = num_ifaces; server->config.queue = queue; @@ -324,8 +325,6 @@ struct hercules_server *hercules_init_server( server->ifaces[i].ifname); } - pthread_spin_init(&usock_lock, PTHREAD_PROCESS_PRIVATE); - server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); if (server->control_sockfd == -1) { exit_with_error(server, 0); @@ -980,8 +979,8 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, // The packet is sent to the monior, which will return a new header with the // path reversed. static bool rx_update_reply_path( - struct receiver_state *rx_state, int ifid, int rx_sample_len, - const char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]) { + struct hercules_server *server, struct receiver_state *rx_state, int ifid, + int rx_sample_len, const char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]) { debug_printf("Updating reply path"); if (!rx_state) { debug_printf("ERROR: invalid rx_state"); @@ -990,11 +989,10 @@ static bool rx_update_reply_path( assert(rx_sample_len > 0); assert(rx_sample_len <= XSK_UMEM__DEFAULT_FRAME_SIZE); - pthread_spin_lock(&usock_lock); // TODO writing to reply path needs sync? - int ret = monitor_get_reply_path(usock, rx_sample_buf, rx_sample_len, - rx_state->etherlen, &rx_state->reply_path); - pthread_spin_unlock(&usock_lock); + int ret = + monitor_get_reply_path(server->usock, rx_sample_buf, rx_sample_len, + rx_state->etherlen, &rx_state->reply_path); if (!ret) { return false; } @@ -1051,7 +1049,7 @@ static void rx_handle_initial(struct hercules_server *server, debug_printf("handling initial"); const int headerlen = (int)(payload - buf); if (initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - rx_update_reply_path(rx_state, ifid, headerlen + payloadlen, buf); + rx_update_reply_path(server, rx_state, ifid, headerlen + payloadlen, buf); } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize @@ -2142,9 +2140,7 @@ static void new_tx_if_available(struct hercules_server *server) { u16 mtu; struct hercules_app_addr dest; - pthread_spin_lock(&usock_lock); - int ret = monitor_get_new_job(usock, fname, &jobid, &dest, &mtu); - pthread_spin_unlock(&usock_lock); + int ret = monitor_get_new_job(server->usock, fname, &jobid, &dest, &mtu); if (!ret) { return; } @@ -2181,7 +2177,7 @@ static void new_tx_if_available(struct hercules_server *server) { int n_paths; struct hercules_path *paths; - ret = monitor_get_paths(usock, jobid, &n_paths, &paths); + ret = monitor_get_paths(server->usock, jobid, &n_paths, &paths); if (!ret || n_paths == 0){ debug_printf("error getting paths"); munmap(mem, filesize); @@ -2216,7 +2212,7 @@ static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { struct hercules_session *session_tx = atomic_load(&server->session_tx); if (session_tx && session_tx->state == SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { - monitor_update_job(usock, session_tx->jobid, session_tx->state, + monitor_update_job(server->usock, session_tx->jobid, session_tx->state, session_tx->error, 0, 0); // FIXME 0 0 struct hercules_session *current = server->session_tx; atomic_store(&server->session_tx, NULL); @@ -2275,7 +2271,7 @@ static void tx_update_paths(struct hercules_server *server) { int n_paths; struct hercules_path *paths; bool ret = - monitor_get_paths(usock, session_tx->jobid, &n_paths, &paths); + monitor_get_paths(server->usock, session_tx->jobid, &n_paths, &paths); if (!ret) { debug_printf("error getting paths"); return; @@ -2403,7 +2399,7 @@ static void print_session_stats(struct hercules_server *server, static void tx_update_monitor(struct hercules_server *server, u64 now) { struct hercules_session *session_tx = server->session_tx; if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { - monitor_update_job(usock, session_tx->jobid, session_tx->state, 0, + monitor_update_job(server->usock, session_tx->jobid, session_tx->state, 0, ( now - session_tx->tx_state->start_time ) / (int)1e9, session_tx->tx_state->chunklen * session_tx->tx_state->acked_chunks.num_set); @@ -2525,7 +2521,7 @@ static void events_p(void *arg) { switch (cp->type) { case CONTROL_PACKET_TYPE_INITIAL:; - struct rbudp_initial_pkt *parsed_pkt; + struct rbudp_initial_pkt *parsed_pkt = NULL; rbudp_check_initial(cp, rbudp_len - rbudp_headerlen, &parsed_pkt); @@ -3004,12 +3000,6 @@ int main(int argc, char *argv[]) { "threads, xdp mode 0x%x", queue, rx_threads, tx_threads, xdp_mode); - usock = monitor_bind_daemon_socket(); - if (usock == 0) { - fprintf(stderr, "Error binding daemon socket\n"); - exit(1); - } - struct hercules_server *server = hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, rx_threads, false); diff --git a/hercules.h b/hercules.h index 85f0991..267e1db 100644 --- a/hercules.h +++ b/hercules.h @@ -210,6 +210,7 @@ struct hercules_config { struct hercules_server { struct hercules_config config; int control_sockfd; // AF_PACKET socket used for control traffic + int usock; // Unix socket used for communication with the monitor int max_paths; int rate_limit; int n_threads; // Number of RX/TX worker threads From 29343b447f5ec510d459446ebcf3976be87a80ac Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 7 Jun 2024 10:17:22 +0200 Subject: [PATCH 033/112] go code cleanup --- config.go | 491 ------------------------ cutils.go | 683 ---------------------------------- go.mod | 65 ---- go.sum | 290 --------------- hercules.c | 260 ++++++------- hercules.go | 400 -------------------- monitor/config.go | 128 +++++++ monitor/go.mod | 2 +- monitor/http_api.go | 67 ++++ monitor/monitor.go | 621 +++---------------------------- monitor/pathmanager.go | 37 +- monitor/pathpicker.go | 18 +- monitor/pathstodestination.go | 104 ++---- monitor/sampleconf.toml | 33 ++ monitor/scionheader.go | 359 ++++++++++++++++++ stats.go | 286 -------------- 16 files changed, 825 insertions(+), 3019 deletions(-) delete mode 100644 config.go delete mode 100644 cutils.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 hercules.go create mode 100644 monitor/config.go create mode 100644 monitor/http_api.go create mode 100644 monitor/sampleconf.toml create mode 100644 monitor/scionheader.go delete mode 100644 stats.go diff --git a/config.go b/config.go deleted file mode 100644 index abcdc4e..0000000 --- a/config.go +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "errors" - "fmt" - "net" - "os" - "path/filepath" - "regexp" - "strings" - "time" - - log "github.com/inconshreveable/log15" - "github.com/scionproto/scion/pkg/snet" -) - -type HerculesGeneralConfig struct { - Direction string - DumpInterval time.Duration - Interfaces []string - Mode string - MTU int - Queue int - NumThreads int - Verbosity string - LocalAddress string - PerPathStatsFile string - PCCBenchMarkDuration time.Duration -} - -type SiteConfig struct { - HostAddr string - NumPaths int - PathSpec []PathSpec -} - -type HerculesReceiverConfig struct { - HerculesGeneralConfig - OutputFile string - ConfigureQueues bool - AcceptTimeout int - ExpectNumPaths int -} - -type HerculesSenderConfig struct { - HerculesGeneralConfig - TransmitFile string - FileOffset int - FileLength int - EnablePCC bool - RateLimit int - NumPathsPerDest int - Destinations []SiteConfig -} - -var ( - localAddrRegexp = regexp.MustCompile(`^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):([0-9]{1,5})$`) - configurableInterfaceRegexp = regexp.MustCompile(`^[a-zA-Z0-9]+$`) -) - -// receiver related - -func (config *HerculesReceiverConfig) initializeDefaults() { - config.HerculesGeneralConfig.initializeDefaults() - config.OutputFile = "" - config.ConfigureQueues = false - config.AcceptTimeout = 0 - config.ExpectNumPaths = 1 -} - -// Validates configuration parameters that have been provided, does not validate for presence of mandatory arguments. -func (config *HerculesReceiverConfig) validateLoose() error { - if config.Direction != "" && config.Direction != "download" { - return errors.New("field Direction must either be empty or 'download'") - } - if err := config.HerculesGeneralConfig.validateLoose(); err != nil { - return err - } - - // check if output file exists (or folder) - if config.OutputFile != "" { - if stat, err := os.Stat(config.OutputFile); err != nil { - if !os.IsNotExist(err) { - return err - } - } else if stat.IsDir() { - return fmt.Errorf("output file %s is a directory", config.OutputFile) - } else { - log.Info(fmt.Sprintf("output file %s exists: will be overwritten", config.OutputFile)) - } - dir := filepath.Dir(config.OutputFile) - stat, err := os.Stat(dir) - if err != nil { - return err - } - if !stat.IsDir() { - return fmt.Errorf("not a directory: %s", dir) - } - } - - if config.ConfigureQueues { - for _, ifName := range config.Interfaces { - if !configurableInterfaceRegexp.MatchString(ifName) { - return fmt.Errorf("cannot configure interface '%s' - escaping not implemented", ifName) - } - } - } - return nil -} - -// Validates all configuration parameters, also checks presence of mandatory parameters. -func (config *HerculesReceiverConfig) validateStrict() error { - if err := config.HerculesGeneralConfig.validateStrict(); err != nil { - return err - } - if err := config.validateLoose(); err != nil { - return err - } - - if config.OutputFile == "" { - return errors.New("no output file specified") - } - return nil -} - -// Merge commandline arguments into the current configuration. -func (config *HerculesReceiverConfig) mergeFlags(flags *Flags) error { - if err := forbidFlags([]string{"pcc", "p", "d", "t", "np", "be", "resv"}, "receiving"); err != nil { - return err - } - if err := config.HerculesGeneralConfig.mergeFlags(flags); err != nil { - return nil - } - if isFlagPassed("o") { - config.OutputFile = flags.outputFilename - } - if isFlagPassed("timeout") { - config.AcceptTimeout = flags.acceptTimeout - } - if isFlagPassed("ep") { - config.ExpectNumPaths = flags.expectPaths - } - return nil -} - -// sender related - -func (config *HerculesSenderConfig) initializeDefaults() { - config.HerculesGeneralConfig.initializeDefaults() - config.TransmitFile = "" - config.FileOffset = -1 // no offset - config.FileLength = -1 // use the whole file - config.EnablePCC = true - config.RateLimit = 3333333 - config.NumPathsPerDest = 1 - config.Destinations = nil -} - -// Validates configuration parameters that have been provided, does not validate for presence of mandatory arguments. -func (config *HerculesSenderConfig) validateLoose() error { - if config.Direction != "" && config.Direction != "upload" { - return errors.New("field Direction must either be empty or 'upload'") - } - if err := config.HerculesGeneralConfig.validateLoose(); err != nil { - return err - } - - // check that the file exists - if config.TransmitFile != "" { - stat, err := os.Stat(config.TransmitFile) - if err != nil { - return err - } - if stat.IsDir() { - return errors.New("file to transmit is a directory") - } - } - - if config.FileOffset > 0 && config.FileLength < 0 { - return errors.New("must provide a valid file length") - } - - if config.RateLimit < 100 { - log.Warn(fmt.Sprintf("rate limit is really low (%d packets per second)", config.RateLimit)) - } - - if config.NumPathsPerDest > maxPathsPerReceiver { - return fmt.Errorf("can use at most %d paths per destination; configured limit (%d) too large", maxPathsPerReceiver, config.NumPathsPerDest) - } - - // validate destinations - for d := range config.Destinations { - if config.Destinations[d].NumPaths > maxPathsPerReceiver { - return fmt.Errorf("can use at most %d paths per destination; max for destination %d is too large (%d)", maxPathsPerReceiver, d, config.Destinations[d].NumPaths) - } - - udpAddress, err := snet.ParseUDPAddr(config.Destinations[d].HostAddr) - if err != nil { - return err - } - if udpAddress.Host.Port == 0 { - return errors.New("must specify a destination port") - } - if udpAddress.IA == 0 { - return errors.New("must provide IA for destination address") - } - } - return nil -} - -// Validates all configuration parameters and checks the presence of mandatory parameters -func (config *HerculesSenderConfig) validateStrict() error { - if err := config.HerculesGeneralConfig.validateStrict(); err != nil { - return err - } - if err := config.validateLoose(); err != nil { - return err - } - - if config.TransmitFile == "" { - return errors.New("you must specify a file to send") - } - - if len(config.Destinations) == 0 { - return errors.New("you must specify at least one destination") - } - return nil -} - -// Merge commandline arguments into the current configuration. -func (config *HerculesSenderConfig) mergeFlags(flags *Flags) error { - if err := forbidFlags([]string{"o", "timeout", "ep"}, "sending"); err != nil { - return err - } - if err := config.HerculesGeneralConfig.mergeFlags(flags); err != nil { - return nil - } - if isFlagPassed("pcc") { - config.EnablePCC = flags.enablePCC - } - if isFlagPassed("p") { - config.RateLimit = flags.maxRateLimit - } - if isFlagPassed("d") { - sites := make([]SiteConfig, 0) - for _, remoteAddr := range flags.remoteAddrs { - sites = append(sites, SiteConfig{ - HostAddr: remoteAddr, - }) - } - config.Destinations = sites - } - if isFlagPassed("t") { - config.TransmitFile = flags.transmitFilename - } - if isFlagPassed("foffset") { - config.FileOffset = flags.fileOffset - } - if isFlagPassed(("flength")) { - config.FileLength = flags.fileLength - } - if isFlagPassed("np") { - config.NumPathsPerDest = flags.numPaths - } - return nil -} - -// Converts config.Destinations into []*Destination for use by herculesTx. -// Assumes config (strictly) is valid. -func (config *HerculesSenderConfig) destinations() []*Destination { - var dests []*Destination - for d, dst := range config.Destinations { - // since config is valid, there can be no error - hostAddr, _ := snet.ParseUDPAddr(dst.HostAddr) - dest := &Destination{ - hostAddr: hostAddr, - pathSpec: &config.Destinations[d].PathSpec, - numPaths: config.NumPathsPerDest, - } - if config.Destinations[d].NumPaths > 0 { - dest.numPaths = config.Destinations[d].NumPaths - } - dests = append(dests, dest) - } - return dests -} - -// for both, sender and receiver - -func (config *HerculesGeneralConfig) initializeDefaults() { - config.Direction = "" - config.DumpInterval = 1 * time.Second - config.Mode = "" - config.MTU = 1500 - config.NumThreads = 1 - config.Queue = 0 - config.Verbosity = "" - config.LocalAddress = "" - config.PerPathStatsFile = "" - config.PCCBenchMarkDuration = 0 -} - -func (config *HerculesGeneralConfig) validateLoose() error { - var ifaces []*net.Interface - if config.Direction != "" && config.Direction != "upload" && config.Direction != "download" { - return errors.New("field Direction must either be 'upload', 'download' or empty") - } - if config.DumpInterval <= 0 { - return errors.New("field DumpInterval must be strictly positive") - } - if len(config.Interfaces) != 0 { - for _, ifName := range config.Interfaces { - var err error - iface, err := net.InterfaceByName(ifName) - if err != nil { - return err - } - if iface.Flags&net.FlagUp == 0 { - return fmt.Errorf("interface %s is not up", iface.Name) - } - ifaces = append(ifaces, iface) - } - } - if config.Mode != "z" && config.Mode != "c" && config.Mode != "" { - return fmt.Errorf("unknown mode %s", config.Mode) - } - - // check LocalAddress - if config.LocalAddress != "" { - udpAddress, err := snet.ParseUDPAddr(config.LocalAddress) - if err != nil { - return err - } - if udpAddress.Host.Port == 0 { - return errors.New("must specify a source port") - } - if udpAddress.IA == 0 { - return errors.New("must provide IA for local address") - } - for _, iface := range ifaces { - if err := checkAssignedIP(iface, udpAddress.Host.IP); err != nil { - return err - } - } - } - - if config.MTU < minFrameSize { - return fmt.Errorf("MTU too small: %d < %d", config.MTU, minFrameSize) - } - if config.MTU > 9038 { - return fmt.Errorf("can not use jumbo frames of size %d > 9038", config.MTU) - } - - if config.Queue < 0 { - return errors.New("queue number must be non-negative") - } - - if config.NumThreads < 1 { - return errors.New("must at least use 1 worker thread") - } - - if config.Verbosity != "" && config.Verbosity != "v" && config.Verbosity != "vv" { - return errors.New("verbosity must be empty or one of 'v', 'vv'") - } - return nil -} - -// Check that the mandatory general configuration has been set. -// -// WARNING: this function does not validate the contents of the options to avoid duplicate calls to validateLoose(), -// as this function is called within Hercules(Sender|Receiver)Config.validateLoose() already. -func (config *HerculesGeneralConfig) validateStrict() error { - if len(config.Interfaces) == 0 { - return errors.New("you must specify at least one network interface to use") - } - if config.LocalAddress == "" { - return errors.New("you must specify a local address") - } - if config.MTU > 8015 { - log.Warn(fmt.Sprintf("using frame size %d > 8015 (IEEE 802.11)", config.MTU)) - } - return nil -} - -func (config *HerculesGeneralConfig) mergeFlags(flags *Flags) error { - if isFlagPassed("n") { - config.DumpInterval = flags.dumpInterval * time.Second - } - if isFlagPassed("i") { - config.Interfaces = flags.ifNames - } - if isFlagPassed("m") { - config.Mode = flags.mode - } - if isFlagPassed("l") { - config.LocalAddress = flags.localAddr - } - if isFlagPassed("q") { - config.Queue = flags.queue - } - if isFlagPassed("nt") { - config.NumThreads = flags.numThreads - } - if isFlagPassed("v") { - config.Verbosity = flags.verbose - } - if isFlagPassed("mtu") { - config.MTU = flags.mtu - } - if isFlagPassed("ps") { - config.PerPathStatsFile = flags.perPathStats - } - if isFlagPassed("pccbd") { - config.PCCBenchMarkDuration = time.Duration(flags.pccBenchmarkDuration) * time.Second - } - return nil -} - -func (config *HerculesGeneralConfig) getXDPMode() (mode int) { - switch config.Mode { - case "z": - mode = XDP_ZEROCOPY - case "c": - mode = XDP_COPY - default: - mode = XDP_COPY - } - return mode -} - -func (config *HerculesGeneralConfig) interfaces() ([]*net.Interface, error) { - var interfaces []*net.Interface - for _, ifName := range config.Interfaces { - iface, err := net.InterfaceByName(ifName) - if err != nil { - return nil, err - } - interfaces = append(interfaces, iface) - } - return interfaces, nil -} - -// helpers - -// Checks that none of flags are passed by the command line. -// mode should either be "sending" or "receiving" and is only used in errors -// -// Returns an error if any of the provided flags was passed by the command line, nil otherwise -func forbidFlags(flags []string, mode string) error { - var illegalFlags []string - for _, f := range flags { - if isFlagPassed(f) { - illegalFlags = append(illegalFlags, f) - } - } - - if len(illegalFlags) > 0 { - return fmt.Errorf("-%s not permitted for %s", strings.Join(illegalFlags, ", -"), mode) - } else { - return nil - } -} - -func checkAssignedIP(iface *net.Interface, localAddr net.IP) (err error) { - // Determine src IP matches information on Interface - interfaceAddrs, err := iface.Addrs() - if err != nil { - return - } - for _, ifAddr := range interfaceAddrs { - ip, ok := ifAddr.(*net.IPNet) - if ok && ip.IP.To4() != nil && ip.IP.To4().Equal(localAddr) { - return nil - } - } - return fmt.Errorf("interface '%s' does not have the IP address '%s'", iface.Name, localAddr) -} diff --git a/cutils.go b/cutils.go deleted file mode 100644 index 523adc0..0000000 --- a/cutils.go +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -// #cgo CFLAGS: -O1 -Wall -DDEBUG -D_GNU_SOURCE -// #cgo LDFLAGS: ${SRCDIR}/bpf/src/libbpf.a -lm -lelf -pthread -lz -// #pragma GCC diagnostic ignored "-Wunused-variable" // Hide warning in cgo-gcc-prolog -// #include "hercules.h" -// #include -// #include -// #include -// #include -import "C" -import ( - "encoding/binary" - "errors" - "fmt" - "net" - "net/netip" - "syscall" - "time" - "unsafe" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - log "github.com/inconshreveable/log15" - "github.com/scionproto/scion/pkg/addr" - "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/private/topology" - "github.com/vishvananda/netlink" -) - -type CPathManagement struct { - numPathsPerDst []C.int - maxNumPathsPerDst C.int - pathsPerDest []C.struct_hercules_path -} - -type layerWithOpts struct { - Layer gopacket.SerializableLayer - Opts gopacket.SerializeOptions -} - -type HerculesSession struct { - session *C.struct_hercules_session -} - -type HerculesServer struct { - server *C.struct_hercules_server -} - -type pathStats struct { - statsBuffer *C.struct_path_stats -} - -const XDP_ZEROCOPY = C.XDP_ZEROCOPY -const XDP_COPY = C.XDP_COPY -const minFrameSize = int(C.HERCULES_MAX_HEADERLEN) + 213 // sizeof(struct rbudp_initial_pkt) + rbudp_headerlen - -func herculesInit(interfaces []*net.Interface, local *snet.UDPAddr, queue int, MTU int) *HerculesSession { - var ifIndices []int - for _, iface := range interfaces { - ifIndices = append(ifIndices, iface.Index) - } - fmt.Println("init: ifindices", ifIndices); - // ifacesC := toCIntArray(ifIndices) - herculesSession := &HerculesSession{ - // session: C.hercules_init(&ifacesC[0], C.int(len(interfaces)), toCAddr(local), C.int(queue), C.int(MTU)), - } - return herculesSession -} - -func herculesInitServer(interfaces []*net.Interface, local *snet.UDPAddr, queue int, MTU int) *HerculesServer { - var ifIndices []int - for _, iface := range interfaces { - ifIndices = append(ifIndices, iface.Index) - } - ifacesC := toCIntArray(ifIndices) - // fmt.Println("init: ifindices", ifIndices); - herculesServer := &HerculesServer{ - server: C.hercules_init_server(&ifacesC[0], C.int(len(interfaces)), toCAddr(local), C.int(queue), C.int(MTU)), - } - return herculesServer -} - -func herculesTx(session *HerculesSession, filename string, offset int, length int, destinations []*Destination, - pm *PathManager, maxRateLimit int, enablePCC bool, xdpMode int, numThreads int) herculesStats { - cFilename := C.CString(filename) - defer C.free(unsafe.Pointer(cFilename)) - - cDests := make([]C.struct_hercules_app_addr, len(destinations)) - for d, dest := range destinations { - cDests[d] = toCAddr(dest.hostAddr) - } - return herculesStatsFromC(C.hercules_tx( - session.session, - cFilename, - C.int(offset), - C.int(length), - &cDests[0], - &pm.cStruct.pathsPerDest[0], - C.int(len(destinations)), - &pm.cStruct.numPathsPerDst[0], - pm.cStruct.maxNumPathsPerDst, - C.int(maxRateLimit), - C.bool(enablePCC), - C.int(xdpMode), - C.int(numThreads), - ), nil) -} -func herculesMainServer(server *HerculesServer, filename string, offset int, length int, destinations []*Destination, - pm *PathManager, maxRateLimit int, enablePCC bool, xdpMode int, numThreads int) { - cFilename := C.CString(filename) - defer C.free(unsafe.Pointer(cFilename)) - - cDests := make([]C.struct_hercules_app_addr, len(destinations)) - for d, dest := range destinations { - cDests[d] = toCAddr(dest.hostAddr) - } - C.hercules_main_sender(server.server, C.int(xdpMode), &cDests[0], &pm.cStruct.pathsPerDest[0], C.int(len(destinations)), &pm.cStruct.numPathsPerDst[0], pm.cStruct.maxNumPathsPerDst, C.int(maxRateLimit), C.bool(enablePCC)) -} - -func herculesRx(session *HerculesSession, filename string, xdpMode int, numThreads int, configureQueues bool, acceptTimeout int, isPCCBenchmark bool) herculesStats { - cFilename := C.CString(filename) - defer C.free(unsafe.Pointer(cFilename)) - return herculesStatsFromC( - C.hercules_rx(session.session, cFilename, C.int(xdpMode), C.bool(configureQueues), C.int(acceptTimeout), C.int(numThreads), C.bool(isPCCBenchmark)), - nil, - ) -} - -func herculesMain(server *HerculesServer, filename string, xdpMode int, numThreads int, configureQueues bool, acceptTimeout int) { - // XXX ignoring arguments - C.hercules_main(server.server, C.int(xdpMode)) -} - -func herculesClose(session *HerculesSession) { - // C.hercules_close(session.session) -} - -func makePerPathStatsBuffer(numPaths int) *pathStats { - return &pathStats{ - statsBuffer: C.make_path_stats_buffer(C.int(numPaths)), - } -} - -func herculesGetStats(session *HerculesSession, pStats *pathStats) herculesStats { - var statsBuffer *C.struct_path_stats = nil - if pStats != nil { - statsBuffer = pStats.statsBuffer - } - return herculesStatsFromC(C.hercules_get_stats(session.session, statsBuffer), pStats) -} - -func herculesStatsFromC(stats C.struct_hercules_stats, pStats *pathStats) herculesStats { - var ppStats []perPathStats - if pStats != nil { - numPaths := int(pStats.statsBuffer.num_paths) - ppStats = make([]perPathStats, numPaths, numPaths) - // circumvent Go range checking and pStats.statsBuffer.paths as dynamic struct member - statsBuffer := (*[1 << 30]C.struct_path_stats_path)(unsafe.Pointer(&pStats.statsBuffer.paths[0]))[:numPaths:numPaths] - for i := 0; i < numPaths; i++ { - ppStats[i].pps_target = int64(statsBuffer[i].pps_target) - ppStats[i].total_packets = int64(statsBuffer[i].total_packets) - } - } - return herculesStats{ - startTime: uint64(stats.start_time), - endTime: uint64(stats.end_time), - now: uint64(stats.now), - txNpkts: uint64(stats.tx_npkts), - rxNpkts: uint64(stats.rx_npkts), - filesize: uint64(stats.filesize), - frameLen: uint32(stats.framelen), - chunkLen: uint32(stats.chunklen), - totalChunks: uint32(stats.total_chunks), - completedChunks: uint32(stats.completed_chunks), - rateLimit: uint32(stats.rate_limit), - paths: ppStats, - } -} - -func (cpm *CPathManagement) initialize(numDestinations int, numPathsPerDestination int) { - cpm.numPathsPerDst = make([]C.int, numDestinations) - cpm.maxNumPathsPerDst = C.int(numPathsPerDestination) - cpm.pathsPerDest = make([]C.struct_hercules_path, numDestinations*numPathsPerDestination) -} - -// HerculesGetReplyPath creates a reply path header for the packet header in headerPtr with given length. -// Returns 0 iff successful. -// This function is exported to C and called to obtain a reply path to send NACKs from the receiver (slow path). -// -//export HerculesGetReplyPath -func HerculesGetReplyPath(headerPtr unsafe.Pointer, length C.int, replyPathStruct *C.struct_hercules_path) C.int { - buf := C.GoBytes(headerPtr, length) - replyPath, err := getReplyPathHeader(buf) - if err != nil { - log.Debug("HerculesGetReplyPath", "err", err) - return 1 - } - // the interface index is handled C-internally - toCPath(nil, replyPath, replyPathStruct, false, false) - return 0 -} - -func getReplyPathHeader(buf []byte) (*HerculesPathHeader, error) { - packet := gopacket.NewPacket(buf, layers.LayerTypeEthernet, gopacket.Default) - if err := packet.ErrorLayer(); err != nil { - return nil, fmt.Errorf("error decoding some part of the packet: %v", err) - } - eth := packet.Layer(layers.LayerTypeEthernet) - if eth == nil { - return nil, errors.New("error decoding ETH layer") - } - dstMAC, srcMAC := eth.(*layers.Ethernet).SrcMAC, eth.(*layers.Ethernet).DstMAC - - ip4 := packet.Layer(layers.LayerTypeIPv4) - if ip4 == nil { - return nil, errors.New("error decoding IPv4 layer") - } - dstIP, srcIP := ip4.(*layers.IPv4).SrcIP, ip4.(*layers.IPv4).DstIP - - udp := packet.Layer(layers.LayerTypeUDP) - if udp == nil { - return nil, errors.New("error decoding IPv4/UDP layer") - } - udpPayload := udp.(*layers.UDP).Payload - udpDstPort := udp.(*layers.UDP).SrcPort - - if len(udpPayload) < 8 { // Guard against bug in ParseScnPkt - return nil, errors.New("error decoding SCION packet: payload too small") - } - - sourcePkt := snet.Packet{ - Bytes: udpPayload, - } - if err := sourcePkt.Decode(); err != nil { - return nil, fmt.Errorf("error decoding SCION packet: %v", err) - } - - rpath, ok := sourcePkt.Path.(snet.RawPath) - if !ok { - return nil, fmt.Errorf("error decoding SCION packet: unexpected dataplane path type") - } - if len(rpath.Raw) != 0 { - replyPath, err := snet.DefaultReplyPather{}.ReplyPath(rpath) - if err != nil { - return nil, fmt.Errorf("failed to reverse SCION path: %v", err) - } - sourcePkt.Path = replyPath - } - - sourcePkt.Path = path.Empty{} - - udpPkt, ok := sourcePkt.Payload.(snet.UDPPayload) - if !ok { - return nil, errors.New("error decoding SCION/UDP") - } - - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort)) - if err != nil { - return nil, err - } - - payload := snet.UDPPayload{ - SrcPort: udpPkt.DstPort, - DstPort: udpPkt.SrcPort, - Payload: nil, - } - - destPkt := &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Destination: sourcePkt.Source, - Source: sourcePkt.Destination, - Path: sourcePkt.Path, - Payload: payload, - }, - } - - if err = destPkt.Serialize(); err != nil { - return nil, err - } - scionHeaderLen := len(destPkt.Bytes) - payloadLen := etherLen - len(underlayHeader) - scionHeaderLen - payload.Payload = make([]byte, payloadLen) - destPkt.Payload = payload - - if err = destPkt.Serialize(); err != nil { - return nil, err - } - scionHeader := destPkt.Bytes[:scionHeaderLen] - scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) - headerBuf := append(underlayHeader, scionHeader...) - herculesPath := HerculesPathHeader{ - Header: headerBuf, - PartialChecksum: scionChecksum, - } - return &herculesPath, nil -} - -// Assumes that the path header memory has already been set up; call allocateCPathHeaderMemory before, if needed -func toCPath(iface *net.Interface, from *HerculesPathHeader, to *C.struct_hercules_path, replaced, enabled bool) { - headerLen := len(from.Header) - if len(from.Header) > C.HERCULES_MAX_HEADERLEN { - panic(fmt.Sprintf("Header too long (%d), can't invoke hercules C API.", len(from.Header))) - } - // XXX(matzf): is there a nicer way to do this? - C.memcpy(unsafe.Pointer(&to.header.header), - unsafe.Pointer(&from.Header[0]), - C.ulong(len(from.Header))) - to.header.checksum = C.ushort(from.PartialChecksum) - to.headerlen = C.int(headerLen) - to.payloadlen = C.int(etherLen - headerLen) // TODO(matzf): take actual MTU into account, also when building header - to.framelen = C.int(etherLen) // TODO(matzf): " - if iface != nil { - to.ifid = C.int(iface.Index) - } - to.replaced = C.atomic_bool(replaced) - to.enabled = C.atomic_bool(enabled) -} - -func SerializePath(from *HerculesPathHeader) []byte { - out := make([]byte, 0, 1500); - fmt.Println(out) - out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) - fmt.Println(out) - out = append(out, from.Header...) - fmt.Println(out) - out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) - fmt.Println(out) - return out; -} - -func toCAddr(addr *snet.UDPAddr) C.struct_hercules_app_addr { - out := C.struct_hercules_app_addr{} - bufIA := toCIA(addr.IA) - bufIP := addr.Host.IP.To4() - bufPort := make([]byte, 2) - binary.BigEndian.PutUint16(bufPort, uint16(addr.Host.Port)) - - C.memcpy(unsafe.Pointer(&out.ia), unsafe.Pointer(&bufIA), C.sizeof_ia) - C.memcpy(unsafe.Pointer(&out.ip), unsafe.Pointer(&bufIP[0]), 4) - C.memcpy(unsafe.Pointer(&out.port), unsafe.Pointer(&bufPort[0]), 2) - fmt.Println(out) - return out -} - -func toCIA(in addr.IA) C.ia { - var out C.ia - bufIA := make([]byte, 8) - binary.BigEndian.PutUint64(bufIA, uint64(in)) - C.memcpy(unsafe.Pointer(&out), unsafe.Pointer(&bufIA[0]), 8) - return out -} - -func toCIntArray(in []int) []C.int { - out := make([]C.int, 0, len(in)) - for _, i := range in { - out = append(out, C.int(i)) - } - return out -} - -func prepareSCIONPacketHeader(src, dst *snet.UDPAddr, iface *net.Interface) (*HerculesPathHeader, error) { - dstMAC, srcMAC, err := getAddrs(iface, dst.NextHop.IP) - if err != nil { - return nil, err - } - - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, src.Host.IP, dst.NextHop.IP, uint16(dst.NextHop.Port)) - if err != nil { - return nil, err - } - - payload := snet.UDPPayload{ - SrcPort: uint16(src.Host.Port), - DstPort: uint16(dst.Host.Port), - Payload: nil, - } - - dstHostIP, ok := netip.AddrFromSlice(dst.Host.IP) - if !ok { - return nil, errors.New("invalid dst host IP") - } - srcHostIP, ok := netip.AddrFromSlice(src.Host.IP) - if !ok { - return nil, errors.New("invalid src host IP") - } - scionPkt := &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Destination: snet.SCIONAddress{IA: dst.IA, Host: addr.HostIP(dstHostIP)}, - Source: snet.SCIONAddress{IA: src.IA, Host: addr.HostIP(srcHostIP)}, - Path: dst.Path, - Payload: payload, - }, - } - if err := scionPkt.Serialize(); err != nil { - return nil, err - } - scionHeaderLen := len(scionPkt.Bytes) - payloadLen := etherLen - len(underlayHeader) - scionHeaderLen - payload.Payload = make([]byte, payloadLen) - scionPkt.Payload = payload - if err := scionPkt.Serialize(); err != nil { - return nil, err - } - - scionHeader := scionPkt.Bytes[:scionHeaderLen] - scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) - buf := append(underlayHeader, scionHeader...) - herculesPath := HerculesPathHeader{ - Header: buf, - PartialChecksum: scionChecksum, - } - return &herculesPath, nil -} - -func prepareUnderlayPacketHeader(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, dstPort uint16) ([]byte, error) { - ethHeader := 14 - ipHeader := 20 - udpHeader := 8 - - eth := layers.Ethernet{ - SrcMAC: srcMAC, - DstMAC: dstMAC, - EthernetType: layers.EthernetTypeIPv4, - } - - ip := layers.IPv4{ - Version: 4, - IHL: 5, // Computed at serialization when FixLengths option set - TOS: 0x0, - Length: uint16(etherLen - ethHeader), // Computed at serialization when FixLengths option set - Id: 0, - Flags: layers.IPv4DontFragment, - FragOffset: 0, - TTL: 0xFF, - Protocol: layers.IPProtocolUDP, - //Checksum: 0, // Set at serialization with the ComputeChecksums option - SrcIP: srcIP, - DstIP: dstIP, - Options: nil, - } - - srcPort := uint16(topology.EndhostPort) - udp := layers.UDP{ - SrcPort: layers.UDPPort(srcPort), - DstPort: layers.UDPPort(dstPort), - Length: uint16(etherLen - ethHeader - ipHeader), - Checksum: 0, - } - - buf := gopacket.NewSerializeBuffer() - serializeOpts := gopacket.SerializeOptions{ - FixLengths: false, - ComputeChecksums: false, - } - serializeOptsChecked := gopacket.SerializeOptions{ - FixLengths: false, - ComputeChecksums: true, - } - err := serializeLayersWOpts(buf, - layerWithOpts{ð, serializeOpts}, - layerWithOpts{&ip, serializeOptsChecked}, - layerWithOpts{&udp, serializeOpts}) - if err != nil { - return nil, err - } - - // return only the header - return buf.Bytes()[:ethHeader+ipHeader+udpHeader], nil -} - -func serializeLayersWOpts(w gopacket.SerializeBuffer, layersWOpts ...layerWithOpts) error { - err := w.Clear() - if err != nil { - return err - } - for i := len(layersWOpts) - 1; i >= 0; i-- { - layerWOpt := layersWOpts[i] - err := layerWOpt.Layer.SerializeTo(w, layerWOpt.Opts) - if err != nil { - return err - } - w.PushLayer(layerWOpt.Layer.LayerType()) - } - return nil -} - -// getAddrs returns dstMAC, srcMAC and srcIP for a packet to be sent over interface to destination. -func getAddrs(iface *net.Interface, destination net.IP) (dstMAC, srcMAC net.HardwareAddr, err error) { - - srcMAC = iface.HardwareAddr - - // Get destination MAC (address of either destination or gateway) using netlink - // n is the handle (i.e. the main entrypoint) for netlink - n, err := netlink.NewHandle() - if err != nil { - return - } - defer n.Delete() - - routes, err := n.RouteGet(destination) - if err != nil { - return - } - route := routes[0] - for _, r := range routes { - if r.LinkIndex == iface.Index { - route = r - break - } - } - if route.LinkIndex != iface.Index { - err = errors.New("no route found to destination on specified interface") - } - - dstIP := destination - if route.Gw != nil { - dstIP = route.Gw - } - dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) - if err != nil { - if err.Error() == "missing ARP entry" { - // Handle missing ARP entry - fmt.Printf("Sending ICMP echo to %v over %v and retrying...\n", dstIP, iface.Name) - - // Send ICMP - if err = sendICMP(iface, route.Src, dstIP); err != nil { - return - } - // Poll for 3 seconds - for start := time.Now(); time.Since(start) < time.Duration(3)*time.Second; { - dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) - if err == nil { - break - } - } - } - if err != nil { - return - } - } - - return -} - -// getNeighborMAC returns the HardwareAddr for the neighbor (ARP table entry) with the given IP -func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAddr, error) { - neighbors, err := n.NeighList(linkIndex, netlink.FAMILY_ALL) - if err != nil { - return nil, err - } - for _, neigh := range neighbors { - if neigh.IP.Equal(ip) && neigh.HardwareAddr != nil { - return neigh.HardwareAddr, nil - } - } - return nil, errors.New("missing ARP entry") -} - -func sendICMP(iface *net.Interface, srcIP net.IP, dstIP net.IP) (err error) { - icmp := layers.ICMPv4{ - TypeCode: layers.ICMPv4TypeEchoRequest, - } - buf := gopacket.NewSerializeBuffer() - serializeOpts := gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - } - err = gopacket.SerializeLayers(buf, serializeOpts, &icmp) - if err != nil { - return err - } - - fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP) - if err != nil { - fmt.Println("Creating raw socket failed.") - return err - } - defer syscall.Close(fd) - dstIPRaw := [4]byte{} - copy(dstIPRaw[:4], dstIP.To4()) - ipSockAddr := syscall.SockaddrInet4{ - Port: 0, - Addr: dstIPRaw, - } - if err = syscall.Sendto(fd, buf.Bytes(), 0, &ipSockAddr); err != nil { - fmt.Printf("Sending ICMP echo to %v over %v failed.\n", dstIP, iface.Name) - return err - } - return nil -} - -// TODO rewrite path pushing: prepare in Go buffers then have a single call where C fetches them -func (pm *PathManager) pushPaths(session *HerculesSession) { - C.acquire_path_lock() - defer C.free_path_lock() - syncTime := time.Now() - - // prepare and copy header to C - for d, dst := range pm.dsts { - if pm.syncTime.After(dst.modifyTime) { - continue - } - - dst.pushPaths(d, d*pm.numPathSlotsPerDst) - } - - pm.syncTime = syncTime - //C.push_hercules_tx_paths(herculesSession.session) not needed atm -} - -func (pm *PathManager) pushPathsServer(server *HerculesServer) { - C.acquire_path_lock() - defer C.free_path_lock() - syncTime := time.Now() - - // prepare and copy header to C - for d, dst := range pm.dsts { - if pm.syncTime.After(dst.modifyTime) { - continue - } - - dst.pushPaths(d, d*pm.numPathSlotsPerDst) - } - - pm.syncTime = syncTime - //C.push_hercules_tx_paths(herculesSession.session) not needed atm -} - -// TODO move back to pathstodestination.go -func (ptd *PathsToDestination) pushPaths(pwdIdx, firstSlot int) { - n := 0 - slot := 0 - if ptd.paths == nil { - for _, iface := range ptd.pm.interfaces { - ptd.canSendLocally = ptd.pushPath(&PathMeta{updated: true, enabled: true, iface: iface}, firstSlot) - } - } else { - for p := range ptd.paths { - path := &ptd.paths[p] - if path.updated || path.enabled { - n = slot - } - if !ptd.pushPath(path, firstSlot+slot) { - path.enabled = false - } - slot += 1 - path.updated = false - } - } - ptd.pm.cStruct.numPathsPerDst[pwdIdx] = C.int(n + 1) -} - -// TODO move back to pathstodestination.go -func (ptd *PathsToDestination) pushPath(path *PathMeta, slot int) bool { - if path.updated { - herculesPath, err := ptd.preparePath(path) - if err != nil { - log.Error(err.Error() + " - path disabled") - ptd.pm.cStruct.pathsPerDest[slot].enabled = false - return false - } - toCPath(path.iface, herculesPath, &ptd.pm.cStruct.pathsPerDest[slot], true, path.enabled) - } else { - ptd.pm.cStruct.pathsPerDest[slot].enabled = C.atomic_bool(path.enabled) - } - return true -} diff --git a/go.mod b/go.mod deleted file mode 100644 index ad71a2b..0000000 --- a/go.mod +++ /dev/null @@ -1,65 +0,0 @@ -module hercules - -go 1.21 - -toolchain go1.21.6 - -require ( - github.com/BurntSushi/toml v1.3.2 - github.com/google/gopacket v1.1.19 - github.com/inconshreveable/log15 v2.16.0+incompatible - github.com/scionproto/scion v0.10.0 - github.com/vishvananda/netlink v1.2.1-beta.2 -) - -require ( - github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/dchest/cmac v1.0.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-stack/stack v1.8.1 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.5.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.19 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect - github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/prometheus/client_golang v1.18.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect - github.com/uber/jaeger-lib v2.4.1+incompatible // indirect - github.com/vishvananda/netns v0.0.4 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.17.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect - google.golang.org/grpc v1.60.1 // indirect - google.golang.org/protobuf v1.32.0 // indirect - lukechampine.com/uint128 v1.3.0 // indirect - modernc.org/cc/v3 v3.41.0 // indirect - modernc.org/ccgo/v3 v3.16.15 // indirect - modernc.org/libc v1.40.1 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.28.0 // indirect - modernc.org/strutil v1.2.0 // indirect - modernc.org/token v1.1.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 556c2eb..0000000 --- a/go.sum +++ /dev/null @@ -1,290 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= -github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/cmac v1.0.0 h1:Vaorm9FVpO2P+YmRdH0RVCUB1XF3Ge1yg9scPvJphyk= -github.com/dchest/cmac v1.0.0/go.mod h1:0zViPqHm8iZwwMl1cuK3HqK7Tu4Q7DV4EuMIOUwBVQ0= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= -github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw= -github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= -github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/scionproto/scion v0.10.0 h1:OcjLpOaaT8uoTGsOAj1TRcqz4BJzLw/uKcWLNRfrUWY= -github.com/scionproto/scion v0.10.0/go.mod h1:N5p5gAbL5is+q85ohxSjo+WzFn8u5NM0Y0YwocXRF7U= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= -github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= -github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= -google.golang.org/grpc/examples v0.0.0-20230222033013-5353eaa44095 h1:ijVKWXLMbG/RK63KfOQ1lEVpEApj174fkw073gxZf3w= -google.golang.org/grpc/examples v0.0.0-20230222033013-5353eaa44095/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= -lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= -modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= -modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= -modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.40.1 h1:ZhRylEBcj3GyQbPVC8JxIg7SdrT4JOxIDJoUon0NfF8= -modernc.org/libc v1.40.1/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ= -modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= -modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= -modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/hercules.c b/hercules.c index c483447..f4128e8 100644 --- a/hercules.c +++ b/hercules.c @@ -88,7 +88,7 @@ static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, static struct hercules_session *make_session(struct hercules_server *server); -/// OK COMMON +/// COMMON // Check the SCION UDP address matches the session's peer static inline bool src_matches_address(struct hercules_session *session, @@ -158,7 +158,6 @@ static void send_eth_frame(struct hercules_server *server, } } -/* #define DEBUG_PRINT_PKTS */ #ifdef DEBUG_PRINT_PKTS // recv indicates whether printed packets should be prefixed with TX or RX void debug_print_rbudp_pkt(const char *pkt, bool recv) { @@ -333,7 +332,7 @@ struct hercules_server *hercules_init_server( return server; } -/// OK PACKET PARSING +/// PACKET PARSING // XXX: from lib/scion/udp.c /* * Calculate UDP checksum @@ -709,7 +708,7 @@ static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, return true; } -/// OK RECEIVER +/// RECEIVER static bool rx_received_all(const struct receiver_state *rx_state) { @@ -1190,7 +1189,7 @@ static void rx_send_nacks(struct hercules_server *server, struct receiver_state } } -/// OK SENDER +/// SENDER static bool tx_acked_all(const struct sender_state *tx_state) { if (tx_state->acked_chunks.num_set != tx_state->total_chunks) { return false; @@ -1676,8 +1675,6 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 return num_chunks_prepared; } - - // Initialise new sender state. Returns null in case of error. static struct sender_state *init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, @@ -1846,7 +1843,8 @@ static char *tx_mmap(char *fname, size_t *filesize) { *filesize = fsize; return mem; } -/// OK PCC + +/// PCC #define NACK_TRACE_SIZE (1024*1024) static u32 nack_trace_count = 0; static struct { @@ -1906,7 +1904,6 @@ static void pcc_trace_push(u64 time, sequence_number range_start, sequence_numbe pcc_trace[idx].actual_duration = actual_duration; } - static bool pcc_mi_elapsed(struct ccontrol_state *cc_state) { if(cc_state->state == pcc_uninitialized) { @@ -2000,7 +1997,8 @@ static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) cc_state->state != pcc_uninitialized && cc_state->mi_start + (u64)((cc_state->pcc_mi_duration) * 1e9) >= now; } -/// workers + +/// WORKER THREADS struct tx_send_p_args { struct hercules_server *server; struct xsk_socket_info *xsks[]; @@ -2131,6 +2129,127 @@ static void rx_p(void *arg) { } } +/** + * Transmit and retransmit chunks that have not been ACKed. + * For each retransmit chunk, wait (at least) one round trip time for the ACK to arrive. + * For large files transfers, this naturally allows to start retransmitting chunks at the beginning + * of the file, while chunks of the previous round at the end of the file are still in flight. + * + * Transmission through different paths is batched (i.e. use the same path within a batch) to prevent the receiver from + * ACKing individual chunks. + * + * The estimates for the ACK-arrival time dont need to be accurate for correctness, i.e. regardless + * of how bad our estimate is, all chunks will be (re-)transmitted eventually. + * - if we *under-estimate* the RTT, we may retransmit chunks unnecessarily + * - waste bandwidth, waste sender disk reads & CPU time, waste receiver CPU time + * - potentially increase overall transmit time because necessary retransmit may be delayed by + * wasted resources + * - if we *over-estimate* the RTT, we wait unnecessarily + * This is only constant overhead per retransmit round, independent of number of packets or send + * rate. + * Thus it seems preferrable to *over-estimate* the ACK-arrival time. + * + * To avoid recording transmit time per chunk, only record start and end time of a transmit round + * and linearly interpolate for each receiver separately. + * This assumes a uniform send rate and that chunks that need to be retransmitted (i.e. losses) + * occur uniformly. + */ +static void *tx_p(void *arg) { + struct hercules_server *server = arg; + while (1) { + /* pthread_spin_lock(&server->biglock); */ + pop_completion_rings(server); + u32 chunks[BATCH_SIZE]; + u8 chunk_rcvr[BATCH_SIZE]; + struct hercules_session *session_tx = atomic_load(&server->session_tx); + if (session_tx != NULL && + atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { + struct sender_state *tx_state = session_tx->tx_state; + /* debug_printf("Start transmit round"); */ + tx_state->prev_rate_check = get_nsecs(); + + pop_completion_rings(server); + send_path_handshakes(server, tx_state); + u64 next_ack_due = 0; + + // in each iteration, we send packets on a single path to each receiver + // collect the rate limits for each active path + u32 allowed_chunks = compute_max_chunks_current_path(tx_state); + + if (allowed_chunks == + 0) { // we hit the rate limits on every path; switch paths + iterate_paths(tx_state); + continue; + } + + // TODO re-enable? + // sending rates might add up to more than BATCH_SIZE, shrink + // proportionally, if needed + /* shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); */ + + const u64 now = get_nsecs(); + u32 num_chunks = 0; + if (!tx_state->finished) { + u64 ack_due = 0; + // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks to + // send + u32 cur_num_chunks = prepare_rcvr_chunks( + tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, + &ack_due, allowed_chunks); + num_chunks += cur_num_chunks; + if (tx_state->finished) { + terminate_cc(tx_state); + kick_cc(tx_state); + } else { + // only wait for the nearest ack + if (next_ack_due) { + if (next_ack_due > ack_due) { + next_ack_due = ack_due; + } + } else { + next_ack_due = ack_due; + } + } + } + + if (num_chunks > 0) { + u8 rcvr_path[1]; + prepare_rcvr_paths(tx_state, rcvr_path); + produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, + num_chunks); + tx_state->tx_npkts_queued += num_chunks; + rate_limit_tx(tx_state); + + // update book-keeping + struct path_set *pathset = tx_state->pathset; + u32 path_idx = pathset->path_index; + struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; + if (cc_state != NULL) { + // FIXME allowed_chunks below is not correct (3x) + atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); + atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); + if (pcc_has_active_mi(cc_state, now)) { + atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, + allowed_chunks); + } + } + } + + iterate_paths(tx_state); + + if (now < next_ack_due) { + // XXX if the session vanishes in the meantime, we might wait + // unnecessarily + sleep_until(next_ack_due); + } + } + } + + return NULL; +} + +/// Event handler tasks + // Check if the monitor has new transfer jobs available and, if so, start one static void new_tx_if_available(struct hercules_server *server) { char fname[1000]; @@ -2640,125 +2759,6 @@ static void events_p(void *arg) { } } -/** - * Transmit and retransmit chunks that have not been ACKed. - * For each retransmit chunk, wait (at least) one round trip time for the ACK to arrive. - * For large files transfers, this naturally allows to start retransmitting chunks at the beginning - * of the file, while chunks of the previous round at the end of the file are still in flight. - * - * Transmission through different paths is batched (i.e. use the same path within a batch) to prevent the receiver from - * ACKing individual chunks. - * - * The estimates for the ACK-arrival time dont need to be accurate for correctness, i.e. regardless - * of how bad our estimate is, all chunks will be (re-)transmitted eventually. - * - if we *under-estimate* the RTT, we may retransmit chunks unnecessarily - * - waste bandwidth, waste sender disk reads & CPU time, waste receiver CPU time - * - potentially increase overall transmit time because necessary retransmit may be delayed by - * wasted resources - * - if we *over-estimate* the RTT, we wait unnecessarily - * This is only constant overhead per retransmit round, independent of number of packets or send - * rate. - * Thus it seems preferrable to *over-estimate* the ACK-arrival time. - * - * To avoid recording transmit time per chunk, only record start and end time of a transmit round - * and linearly interpolate for each receiver separately. - * This assumes a uniform send rate and that chunks that need to be retransmitted (i.e. losses) - * occur uniformly. - */ -static void *tx_p(void *arg) { - struct hercules_server *server = arg; - while (1) { - /* pthread_spin_lock(&server->biglock); */ - pop_completion_rings(server); - u32 chunks[BATCH_SIZE]; - u8 chunk_rcvr[BATCH_SIZE]; - struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx != NULL && - atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { - struct sender_state *tx_state = session_tx->tx_state; - /* debug_printf("Start transmit round"); */ - tx_state->prev_rate_check = get_nsecs(); - - pop_completion_rings(server); - send_path_handshakes(server, tx_state); - u64 next_ack_due = 0; - - // in each iteration, we send packets on a single path to each receiver - // collect the rate limits for each active path - u32 allowed_chunks = compute_max_chunks_current_path(tx_state); - - if (allowed_chunks == - 0) { // we hit the rate limits on every path; switch paths - iterate_paths(tx_state); - continue; - } - - // TODO re-enable? - // sending rates might add up to more than BATCH_SIZE, shrink - // proportionally, if needed - /* shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); */ - - const u64 now = get_nsecs(); - u32 num_chunks = 0; - if (!tx_state->finished) { - u64 ack_due = 0; - // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks to - // send - u32 cur_num_chunks = prepare_rcvr_chunks( - tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, - &ack_due, allowed_chunks); - num_chunks += cur_num_chunks; - if (tx_state->finished) { - terminate_cc(tx_state); - kick_cc(tx_state); - } else { - // only wait for the nearest ack - if (next_ack_due) { - if (next_ack_due > ack_due) { - next_ack_due = ack_due; - } - } else { - next_ack_due = ack_due; - } - } - } - - if (num_chunks > 0) { - u8 rcvr_path[1]; - prepare_rcvr_paths(tx_state, rcvr_path); - produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, - num_chunks); - tx_state->tx_npkts_queued += num_chunks; - rate_limit_tx(tx_state); - - // update book-keeping - struct path_set *pathset = tx_state->pathset; - u32 path_idx = pathset->path_index; - struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; - if (cc_state != NULL) { - // FIXME allowed_chunks below is not correct (3x) - atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); - atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); - if (pcc_has_active_mi(cc_state, now)) { - atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, - allowed_chunks); - } - } - } - - iterate_paths(tx_state); - - if (now < next_ack_due) { - // XXX if the session vanishes in the meantime, we might wait - // unnecessarily - sleep_until(next_ack_due); - } - } - } - - return NULL; -} - static pthread_t start_thread(struct hercules_server *server, void *(start_routine), void *arg) { pthread_t pt; @@ -2903,7 +2903,7 @@ void usage(){ exit(1); } -// TODO what's up with multiple interfaces? +// TODO Test multiple interfaces #define HERCULES_MAX_INTERFACES 1 int main(int argc, char *argv[]) { // Options: diff --git a/hercules.go b/hercules.go deleted file mode 100644 index 5606b73..0000000 --- a/hercules.go +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "errors" - "flag" - "fmt" - "net" - "os" - "os/signal" - "strings" - "time" - - "github.com/BurntSushi/toml" - log "github.com/inconshreveable/log15" - "github.com/scionproto/scion/pkg/snet" -) - -type arrayFlags []string - -type Flags struct { - dumpInterval time.Duration - enablePCC bool - ifNames arrayFlags - localAddr string - maxRateLimit int - mode string - mtu int - queue int - numThreads int - remoteAddrs arrayFlags - transmitFilename string - fileOffset int - fileLength int - outputFilename string - verbose string - numPaths int - acceptTimeout int - perPathStats string - expectPaths int - pccBenchmarkDuration int -} - -const ( - maxPathsPerReceiver int = 255 // the maximum path index needs to fit into a uint8, value 255 is reserved for "don't track" -) - -var ( - startupVersion string // Add detailed version information to binary for reproducible tests - etherLen int -) - -func (i *arrayFlags) String() string { - return "[\n\t\"" + strings.Join(*i, "\",\n\t\"") + "\"\n]" -} - -func (i *arrayFlags) Set(value string) error { - *i = append(*i, value) - return nil -} - -func isFlagPassed(name string) bool { - found := false - flag.Visit(func(f *flag.Flag) { - if f.Name == name { - found = true - } - }) - return found -} - -func main() { - err := realMain() - if err != nil { - fmt.Println(os.Stderr, err.Error()) - os.Exit(1) - } -} - -func realMain() error { - var ( - configFile string - flags Flags - senderConfig HerculesSenderConfig - receiverConfig HerculesReceiverConfig - version bool - ) - flag.DurationVar(&flags.dumpInterval, "n", time.Second, "Print stats at given interval") - flag.BoolVar(&flags.enablePCC, "pcc", true, "Enable performance-oriented congestion control (PCC)") - flag.Var(&flags.ifNames, "i", "interface") - flag.StringVar(&flags.localAddr, "l", "", "local address") - flag.IntVar(&flags.maxRateLimit, "p", 3333333, "Maximum allowed send rate in Packets per Second (default: 3'333'333, ~40Gbps)") - flag.StringVar(&flags.mode, "m", "", "XDP socket bind mode (Zero copy: z; Copy mode: c)") - flag.IntVar(&flags.queue, "q", 0, "Use queue n") - flag.IntVar(&flags.numThreads, "nt", 0, "Maximum number of worker threads to use") - flag.Var(&flags.remoteAddrs, "d", "destination host address(es); omit the ia part of the address to add a receiver IP to the previous destination") - flag.StringVar(&flags.transmitFilename, "t", "", "transmit file (sender)") - flag.IntVar(&flags.fileOffset, "foffset", -1, "file offset") - flag.IntVar(&flags.fileLength, "flength", -1, "file length (needed if you specify an offset)") - flag.StringVar(&flags.outputFilename, "o", "", "output file (receiver)") - flag.StringVar(&flags.verbose, "v", "", "verbose output (from '' to vv)") - flag.IntVar(&flags.numPaths, "np", 1, "Maximum number of different paths per destination to use at the same time") - flag.StringVar(&configFile, "c", "", "File to parse configuration from, you may overwrite any configuration using command line arguemnts") - flag.IntVar(&flags.mtu, "mtu", 0, "Set the frame size to use") - flag.IntVar(&flags.acceptTimeout, "timeout", 0, "Abort accepting connections after this timeout (seconds)") - flag.BoolVar(&version, "version", false, "Output version and exit") - flag.StringVar(&flags.perPathStats, "ps", "", "Write per-path statistics to this file (CSV)") - flag.IntVar(&flags.expectPaths, "ep", 1, "Number of paths to expect for collecting per-path statistics (receiver only)") - flag.IntVar(&flags.pccBenchmarkDuration, "pccbd", 0, "PCC benchmark duration in (seconds). ") - flag.Parse() - - if version { - fmt.Printf("Build version: %s\n", startupVersion) - os.Exit(0) - } - - etherLen = 1200; - /// XXX BREAK - daemon, err := net.ResolveUnixAddr("unixgram", "/var/hercules.sock") - fmt.Println(daemon, err) - local, err := net.ResolveUnixAddr("unixgram", "/var/herculesmon.sock") - fmt.Println(local, err) - os.Remove("/var/herculesmon.sock") - usock, err := net.ListenUnixgram("unixgram", local) - fmt.Println(usock, err) - - for { - buf := make([]byte, 2000) - fmt.Println("read...") - n, a, err := usock.ReadFromUnix(buf) - fmt.Println(n, a, err) - if n > 0 { - replyPath, err := getReplyPathHeader(buf) - fmt.Println(replyPath, err) - // the interface index is handled C-internally - b := SerializePath(replyPath) - usock.WriteToUnix(b, a) - } - } - - if err := configureLogger(flags.verbose); err != nil { - return err - } - - // decide whether to send or to receive based on flags - sendMode := false - recvMode := false - if isFlagPassed("t") { - sendMode = true - } - if isFlagPassed("o") { - recvMode = true - } - if sendMode && recvMode { - return errors.New("you can not pass -o and -t at the same time") - } - - // parse config file, if provided - senderConfig.initializeDefaults() - receiverConfig.initializeDefaults() - if isFlagPassed("c") { - undecoded := make(map[string]struct{}) - if meta, err := toml.DecodeFile(configFile, &senderConfig); err != nil { - return err - } else { - for _, key := range meta.Undecoded() { - undecoded[strings.Join(key, ".")] = struct{}{} - } - } - if meta, err := toml.DecodeFile(configFile, &receiverConfig); err != nil { - return err - } else { - for _, key := range meta.Undecoded() { - key := strings.Join(key, ".") - if _, ok := undecoded[key]; ok { - log.Warn(fmt.Sprintf("Configuration file contains key \"%s\" which is unknown for both, sending and receiving", key)) - } - } - } - } - - // if not clear yet, decide whether to send or receive based on config file - if !sendMode && !recvMode { - if senderConfig.Direction == "upload" { - sendMode = true - } else if senderConfig.Direction == "download" { - recvMode = true - } else if senderConfig.Direction == "" { - if senderConfig.TransmitFile != "" { - sendMode = true - } - if receiverConfig.OutputFile != "" { - recvMode = true - } - if sendMode && recvMode { - return errors.New("unclear whether to send or to receive, use -t or -o on the command line or set Direction in the configuration file") - } - if !sendMode && !recvMode { - return errors.New("unclear whether to send or to receive, use -t or -o on the command line or at least one of Direction, OutputFile and TransmitFile in the configuration file") - } - } else { - return fmt.Errorf("'%s' is not a valid value for Direction", senderConfig.Direction) - } - } - - if sendMode { - if senderConfig.PerPathStatsFile != "" && !senderConfig.EnablePCC { - return errors.New("in send mode, path stats are currently only available with PCC") - } - if err := senderConfig.validateLoose(); err != nil { - return errors.New("in config file: " + err.Error()) - } - if err := senderConfig.mergeFlags(&flags); err != nil { - return errors.New("on command line: " + err.Error()) - } - if err := configureLogger(senderConfig.Verbosity); err != nil { - return err - } - if err := senderConfig.validateStrict(); err != nil { - return err - } - return mainTx(&senderConfig) - } else if recvMode { - if err := receiverConfig.validateLoose(); err != nil { - return errors.New("in config file: " + err.Error()) - } - if err := receiverConfig.mergeFlags(&flags); err != nil { - return errors.New("on command line: " + err.Error()) - } - if err := configureLogger(receiverConfig.Verbosity); err != nil { - return err - } - if err := receiverConfig.validateStrict(); err != nil { - return err - } - return mainRx(&receiverConfig) - } else { - // we should not end up here... - return errors.New("unclear whether to send or receive") - } -} - -func configureLogger(verbosity string) error { - // Setup logger - h := log.CallerFileHandler(log.StdoutHandler) - if verbosity == "vv" { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, h)) - } else if verbosity == "v" { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, h)) - } else if verbosity == "" { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlError, h)) - } else { - return errors.New("-v can only be vv, v or empty") - } - return nil -} - -// Assumes config to be strictly valid. -func mainTx(config *HerculesSenderConfig) (err error) { - mainServerSend(config) - // since config is valid, there can be no errors here: - etherLen = config.MTU - localAddress, _ := snet.ParseUDPAddr(config.LocalAddress) - interfaces, _ := config.interfaces() - destinations := config.destinations() - - pm, err := initNewPathManager( - interfaces, - destinations, - localAddress, - uint64(config.RateLimit)*uint64(config.MTU)) - if err != nil { - return err - } - - pm.choosePaths() - session := herculesInit(interfaces, localAddress, config.Queue, config.MTU) - pm.pushPaths(session) - if !pm.canSendToAllDests() { - return errors.New("some destinations are unreachable, abort") - } - - aggregateStats := aggregateStats{} - done := make(chan struct{}, 1) - go statsDumper(session, true, config.DumpInterval, &aggregateStats, config.PerPathStatsFile, config.NumPathsPerDest*len(config.Destinations), done, config.PCCBenchMarkDuration) - go cleanupOnSignal(session) - stats := herculesTx(session, config.TransmitFile, config.FileOffset, config.FileLength, - destinations, pm, config.RateLimit, config.EnablePCC, config.getXDPMode(), - config.NumThreads) - done <- struct{}{} - printSummary(stats, aggregateStats) - <-done // wait for path stats to be flushed - herculesClose(session) - return nil -} - -// Assumes config to be strictly valid. -func mainRx(config *HerculesReceiverConfig) error { - return mainServer(config) - // since config is valid, there can be no errors here: - etherLen = config.MTU - interfaces, _ := config.interfaces() - localAddr, _ := snet.ParseUDPAddr(config.LocalAddress) - - isPCCBenchmark := false - if config.PCCBenchMarkDuration > 0 { - isPCCBenchmark = true - } - session := herculesInit(interfaces, localAddr, config.Queue, config.MTU) - aggregateStats := aggregateStats{} - done := make(chan struct{}, 1) - go statsDumper(session, false, config.DumpInterval, &aggregateStats, config.PerPathStatsFile, config.ExpectNumPaths, done, config.PCCBenchMarkDuration) - go cleanupOnSignal(session) - stats := herculesRx(session, config.OutputFile, config.getXDPMode(), config.NumThreads, config.ConfigureQueues, - config.AcceptTimeout, isPCCBenchmark) - done <- struct{}{} - printSummary(stats, aggregateStats) - <-done // wait for path stats to be flushed - herculesClose(session) - return nil -} - -func mainServer(config *HerculesReceiverConfig) error { - // since config is valid, there can be no errors here: - etherLen = config.MTU - interfaces, _ := config.interfaces() - localAddr, _ := snet.ParseUDPAddr(config.LocalAddress) - server := herculesInitServer(interfaces, localAddr, config.Queue, config.MTU) - - fmt.Println("mainServer(go)", etherLen, interfaces, localAddr) - - // aggregateStats := aggregateStats{} - done := make(chan struct{}, 1) - // go statsDumper(session, false, config.DumpInterval, &aggregateStats, config.PerPathStatsFile, config.ExpectNumPaths, done, config.PCCBenchMarkDuration) - // go cleanupOnSignal(session) - herculesMain(server, config.OutputFile, config.getXDPMode(), config.NumThreads, config.ConfigureQueues, config.AcceptTimeout) - done <- struct{}{} - // printSummary(stats, aggregateStats) - <-done // wait for path stats to be flushed - // herculesClose(server) - return nil -} -func mainServerSend(config *HerculesSenderConfig) (err error) { - // since config is valid, there can be no errors here: - etherLen = config.MTU - localAddress, _ := snet.ParseUDPAddr(config.LocalAddress) - interfaces, _ := config.interfaces() - destinations := config.destinations() - - pm, err := initNewPathManager( - interfaces, - destinations, - localAddress, - uint64(config.RateLimit)*uint64(config.MTU)) - if err != nil { - return err - } - - pm.choosePaths() - server := herculesInitServer(interfaces, localAddress, config.Queue, config.MTU) - pm.pushPathsServer(server) - if !pm.canSendToAllDests() { - return errors.New("some destinations are unreachable, abort") - } - - // aggregateStats := aggregateStats{} - done := make(chan struct{}, 1) - // go statsDumper(session, true, config.DumpInterval, &aggregateStats, config.PerPathStatsFile, config.NumPathsPerDest*len(config.Destinations), done, config.PCCBenchMarkDuration) - // go cleanupOnSignal(session) - herculesMainServer(server, config.TransmitFile, config.FileOffset, config.FileLength, - destinations, pm, config.RateLimit, config.EnablePCC, config.getXDPMode(), - config.NumThreads) - done <- struct{}{} - // printSummary(stats, aggregateStats) - <-done // wait for path stats to be flushed - // herculesClose(session) - return nil -} - -func cleanupOnSignal(session *HerculesSession) { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, os.Kill) - // Block until any signal is received. - <-c - herculesClose(session) - os.Exit(128 + 15) // Customary exit code after SIGTERM -} diff --git a/monitor/config.go b/monitor/config.go new file mode 100644 index 0000000..4e5bf60 --- /dev/null +++ b/monitor/config.go @@ -0,0 +1,128 @@ +package main + +import ( + "fmt" + "os" + + "github.com/BurntSushi/toml" + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/snet" +) + +// These specify how to read the config file +type HostConfig struct { + HostAddr addr.Addr + NumPaths int + PathSpec []PathSpec +} +type ASConfig struct { + IA addr.IA + NumPaths int + PathSpec []PathSpec +} +type MonitorConfig struct { + DestinationHosts []HostConfig + DestinationASes []ASConfig + DefaultNumPaths int + MonitorSocket string +} + +type PathRules struct { + Hosts map[addr.Addr]HostConfig + ASes map[addr.IA]ASConfig + DefaultNumPaths int +} + +func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { + a := addr.Addr{ + IA: dest.IA, + Host: addr.MustParseHost(dest.Host.IP.String()), + } + confHost, ok := p.Hosts[a] + if ok { + return Destination{ + hostAddr: dest, + pathSpec: &confHost.PathSpec, + numPaths: confHost.NumPaths, + } + } + conf, ok := p.ASes[dest.IA] + if ok { + return Destination{ + hostAddr: dest, + pathSpec: &conf.PathSpec, + numPaths: conf.NumPaths, + } + } + return Destination{ + hostAddr: dest, + pathSpec: &[]PathSpec{}, + numPaths: p.DefaultNumPaths, + } +} + +func readConfig(configFile string) (MonitorConfig, PathRules) { + var config MonitorConfig + meta, err := toml.DecodeFile(configFile, &config) + if err != nil { + fmt.Printf("Error reading configuration file (%v): %v\n", configFile, err) + os.Exit(1) + } + if len(meta.Undecoded()) > 0 { + fmt.Printf("Unknown element(s) in config file: %v\n", meta.Undecoded()) + os.Exit(1) + } + + if config.DefaultNumPaths == 0 { + fmt.Println("Config: Default number of paths to use not set, using 1.") + config.DefaultNumPaths = 1 + } + + if config.MonitorSocket == "" { + config.MonitorSocket = "/var/herculesmon.sock" // TODO should be in /var/run + } + + fmt.Println(config.MonitorSocket, config.MonitorSocket == "", config.MonitorSocket == "") + + pathRules := PathRules{} + // TODO Would be nice not to have to do this dance and specify the maps directly in the config file, + // but the toml package crashes if the keys are addr.Addr + + pathRules.Hosts = map[addr.Addr]HostConfig{} + for _, host := range config.DestinationHosts { + numpaths := config.DefaultNumPaths + if host.NumPaths != 0 { + numpaths = host.NumPaths + } + pathspec := []PathSpec{} + if host.PathSpec != nil { + pathspec = host.PathSpec + } + pathRules.Hosts[host.HostAddr] = HostConfig{ + HostAddr: host.HostAddr, + NumPaths: numpaths, + PathSpec: pathspec, + } + } + + pathRules.ASes = map[addr.IA]ASConfig{} + for _, as := range config.DestinationASes { + numpaths := config.DefaultNumPaths + if as.NumPaths != 0 { + numpaths = as.NumPaths + } + pathspec := []PathSpec{} + if as.PathSpec != nil { + pathspec = as.PathSpec + } + pathRules.ASes[as.IA] = ASConfig{ + IA: as.IA, + NumPaths: numpaths, + PathSpec: pathspec, + } + } + + pathRules.DefaultNumPaths = config.DefaultNumPaths + + return config, pathRules +} diff --git a/monitor/go.mod b/monitor/go.mod index 05ab4a7..2134875 100644 --- a/monitor/go.mod +++ b/monitor/go.mod @@ -10,7 +10,6 @@ require ( github.com/inconshreveable/log15 v2.16.0+incompatible github.com/scionproto/scion v0.10.0 github.com/vishvananda/netlink v1.2.1-beta.2 - go.uber.org/atomic v1.9.0 ) require ( @@ -40,6 +39,7 @@ require ( github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.0.0+incompatible // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.12.0 // indirect diff --git a/monitor/http_api.go b/monitor/http_api.go new file mode 100644 index 0000000..39e0f7a --- /dev/null +++ b/monitor/http_api.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "strconv" + + "github.com/scionproto/scion/pkg/snet" +) + +// Handle submission of a new transfer +// GET params: +// file (File to transfer) +// dest (Destination IA+Host) +func http_submit(w http.ResponseWriter, r *http.Request) { + if !r.URL.Query().Has("file") || !r.URL.Query().Has("dest") { + io.WriteString(w, "missing parameter") + return + } + file := r.URL.Query().Get("file") + dest := r.URL.Query().Get("dest") + fmt.Println(file, dest) + destParsed, err := snet.ParseUDPAddr(dest) + if err != nil { + io.WriteString(w, "parse err") + return + } + destination := findPathRule(&pathRules, destParsed) + pm, _ := initNewPathManager(interfaces, &destination, localAddress) + + transfersLock.Lock() + jobid := nextID + transfers[nextID] = &HerculesTransfer{ + id: nextID, + status: Queued, + file: file, + dest: *destParsed, + pm: pm, + } + nextID += 1 + transfersLock.Unlock() + + io.WriteString(w, fmt.Sprintf("OK %d\n", jobid)) +} + +// Handle querying a transfer's status +// GET Params: +// id: An ID obtained by submitting a transfer +// Returns OK status state err seconds_elapsed chucks_acked +func http_status(w http.ResponseWriter, r *http.Request) { + if !r.URL.Query().Has("id") { + io.WriteString(w, "missing parameter") + return + } + id, err := strconv.Atoi(r.URL.Query().Get("id")) + if err != nil { + return + } + transfersLock.Lock() + info, ok := transfers[id] + transfersLock.Unlock() + if !ok { + return + } + io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.chunks_acked)) +} diff --git a/monitor/monitor.go b/monitor/monitor.go index 78655f4..c9ee72b 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -1,382 +1,24 @@ package main import ( - "bytes" "encoding/binary" - "errors" "flag" "fmt" - "io" "net" "net/http" "os" - "strconv" "sync" - "syscall" - "time" - - "github.com/BurntSushi/toml" - "github.com/google/gopacket" - "github.com/google/gopacket/layers" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/snet" - "github.com/scionproto/scion/pkg/snet/path" - "github.com/scionproto/scion/private/topology" - "github.com/vishvananda/netlink" ) // #include "../monitor.h" import "C" +const HerculesMaxPktsize = C.HERCULES_MAX_PKTSIZE -type layerWithOpts struct { - Layer gopacket.SerializableLayer - Opts gopacket.SerializeOptions -} - -func prepareUnderlayPacketHeader(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, dstPort uint16, etherLen int) ([]byte, error) { - ethHeader := 14 - ipHeader := 20 - udpHeader := 8 - - eth := layers.Ethernet{ - SrcMAC: srcMAC, - DstMAC: dstMAC, - EthernetType: layers.EthernetTypeIPv4, - } - - ip := layers.IPv4{ - Version: 4, - IHL: 5, // Computed at serialization when FixLengths option set - TOS: 0x0, - Length: uint16(etherLen - ethHeader), // Computed at serialization when FixLengths option set - Id: 0, - Flags: layers.IPv4DontFragment, - FragOffset: 0, - TTL: 0xFF, - Protocol: layers.IPProtocolUDP, - //Checksum: 0, // Set at serialization with the ComputeChecksums option - SrcIP: srcIP, - DstIP: dstIP, - Options: nil, - } - - srcPort := uint16(topology.EndhostPort) - udp := layers.UDP{ - SrcPort: layers.UDPPort(srcPort), - DstPort: layers.UDPPort(dstPort), - Length: uint16(etherLen - ethHeader - ipHeader), - Checksum: 0, - } - - buf := gopacket.NewSerializeBuffer() - serializeOpts := gopacket.SerializeOptions{ - FixLengths: false, - ComputeChecksums: false, - } - serializeOptsChecked := gopacket.SerializeOptions{ - FixLengths: false, - ComputeChecksums: true, - } - err := serializeLayersWOpts(buf, - layerWithOpts{ð, serializeOpts}, - layerWithOpts{&ip, serializeOptsChecked}, - layerWithOpts{&udp, serializeOpts}) - if err != nil { - return nil, err - } - - // return only the header - return buf.Bytes()[:ethHeader+ipHeader+udpHeader], nil -} - -func serializeLayersWOpts(w gopacket.SerializeBuffer, layersWOpts ...layerWithOpts) error { - err := w.Clear() - if err != nil { - return err - } - for i := len(layersWOpts) - 1; i >= 0; i-- { - layerWOpt := layersWOpts[i] - err := layerWOpt.Layer.SerializeTo(w, layerWOpt.Opts) - if err != nil { - return err - } - w.PushLayer(layerWOpt.Layer.LayerType()) - } - return nil -} - -func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, net.IP, error) { - packet := gopacket.NewPacket(buf, layers.LayerTypeEthernet, gopacket.Default) - if err := packet.ErrorLayer(); err != nil { - return nil, nil, fmt.Errorf("error decoding some part of the packet: %v", err) - } - eth := packet.Layer(layers.LayerTypeEthernet) - if eth == nil { - return nil, nil, errors.New("error decoding ETH layer") - } - dstMAC, srcMAC := eth.(*layers.Ethernet).SrcMAC, eth.(*layers.Ethernet).DstMAC - - ip4 := packet.Layer(layers.LayerTypeIPv4) - if ip4 == nil { - return nil, nil, errors.New("error decoding IPv4 layer") - } - dstIP, srcIP := ip4.(*layers.IPv4).SrcIP, ip4.(*layers.IPv4).DstIP - - udp := packet.Layer(layers.LayerTypeUDP) - if udp == nil { - return nil, nil, errors.New("error decoding IPv4/UDP layer") - } - udpPayload := udp.(*layers.UDP).Payload - udpDstPort := udp.(*layers.UDP).SrcPort - - if len(udpPayload) < 8 { // Guard against bug in ParseScnPkt - return nil, nil, errors.New("error decoding SCION packet: payload too small") - } - - sourcePkt := snet.Packet{ - Bytes: udpPayload, - } - if err := sourcePkt.Decode(); err != nil { - return nil, nil, fmt.Errorf("error decoding SCION packet: %v", err) - } - - rpath, ok := sourcePkt.Path.(snet.RawPath) - if !ok { - return nil, nil, fmt.Errorf("error decoding SCION packet: unexpected dataplane path type") - } - if len(rpath.Raw) != 0 { - replyPath, err := snet.DefaultReplyPather{}.ReplyPath(rpath) - if err != nil { - return nil, nil, fmt.Errorf("failed to reverse SCION path: %v", err) - } - sourcePkt.Path = replyPath - } - - udpPkt, ok := sourcePkt.Payload.(snet.UDPPayload) - if !ok { - return nil, nil, errors.New("error decoding SCION/UDP") - } - - if (sourcePkt.Source.IA == sourcePkt.Destination.IA){ - sourcePkt.Path = path.Empty{} - } - - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort), etherLen) - if err != nil { - return nil, nil, err - } - - payload := snet.UDPPayload{ - SrcPort: udpPkt.DstPort, - DstPort: udpPkt.SrcPort, - Payload: nil, - } - - destPkt := &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Destination: sourcePkt.Source, - Source: sourcePkt.Destination, - Path: sourcePkt.Path, - Payload: payload, - }, - } - - if err = destPkt.Serialize(); err != nil { - return nil, nil, err - } - scionHeaderLen := len(destPkt.Bytes) - payloadLen := etherLen - len(underlayHeader) - scionHeaderLen - payload.Payload = make([]byte, payloadLen) - destPkt.Payload = payload - - if err = destPkt.Serialize(); err != nil { - return nil, nil, err - } - scionHeader := destPkt.Bytes[:scionHeaderLen] - scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) - headerBuf := append(underlayHeader, scionHeader...) - herculesPath := HerculesPathHeader{ - Header: headerBuf, - PartialChecksum: scionChecksum, - } - return &herculesPath, dstIP, nil -} - -func SerializePath(from *HerculesPathHeader, ifid int) []byte { - fmt.Println("serialize") - out := make([]byte, 0, 1500) - fmt.Println(out) - out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) - out = binary.LittleEndian.AppendUint16(out, uint16(ifid)) - fmt.Println(out) - out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) - if len(from.Header) > C.HERCULES_MAX_HEADERLEN { - // Header does not fit in the C struct - fmt.Println("!! Header too long !!") - // TODO This will panic in the below call to bytes.Repeat(), do something more appropriate - } - fmt.Println(out) - out = append(out, from.Header...) - out = append(out, bytes.Repeat([]byte{0x00}, C.HERCULES_MAX_HEADERLEN-len(from.Header))...) - fmt.Println("Serialized header", out, len(out)) - fmt.Printf("hex header % x\n", out) - return out -} - -// getAddrs returns dstMAC, srcMAC and srcIP for a packet to be sent over interface to destination. -func getAddrs(iface *net.Interface, destination net.IP) (dstMAC, srcMAC net.HardwareAddr, err error) { - - srcMAC = iface.HardwareAddr - - // Get destination MAC (address of either destination or gateway) using netlink - // n is the handle (i.e. the main entrypoint) for netlink - n, err := netlink.NewHandle() - if err != nil { - return - } - defer n.Delete() - - routes, err := n.RouteGet(destination) - if err != nil { - return - } - route := routes[0] - for _, r := range routes { - if r.LinkIndex == iface.Index { - route = r - break - } - } - if route.LinkIndex != iface.Index { - err = errors.New("no route found to destination on specified interface") - } - - dstIP := destination - if route.Gw != nil { - dstIP = route.Gw - } - dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) - if err != nil { - if err.Error() == "missing ARP entry" { - // Handle missing ARP entry - fmt.Printf("Sending ICMP echo to %v over %v and retrying...\n", dstIP, iface.Name) - - // Send ICMP - if err = sendICMP(iface, route.Src, dstIP); err != nil { - return - } - // Poll for 3 seconds - for start := time.Now(); time.Since(start) < time.Duration(3)*time.Second; { - dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) - if err == nil { - break - } - } - } - if err != nil { - return - } - } - - return -} - -func sendICMP(iface *net.Interface, srcIP net.IP, dstIP net.IP) (err error) { - icmp := layers.ICMPv4{ - TypeCode: layers.ICMPv4TypeEchoRequest, - } - buf := gopacket.NewSerializeBuffer() - serializeOpts := gopacket.SerializeOptions{ - FixLengths: true, - ComputeChecksums: true, - } - err = gopacket.SerializeLayers(buf, serializeOpts, &icmp) - if err != nil { - return err - } - - fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP) - if err != nil { - fmt.Println("Creating raw socket failed.") - return err - } - defer syscall.Close(fd) - dstIPRaw := [4]byte{} - copy(dstIPRaw[:4], dstIP.To4()) - ipSockAddr := syscall.SockaddrInet4{ - Port: 0, - Addr: dstIPRaw, - } - if err = syscall.Sendto(fd, buf.Bytes(), 0, &ipSockAddr); err != nil { - fmt.Printf("Sending ICMP echo to %v over %v failed.\n", dstIP, iface.Name) - return err - } - return nil -} - -// getNeighborMAC returns the HardwareAddr for the neighbor (ARP table entry) with the given IP -func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAddr, error) { - neighbors, err := n.NeighList(linkIndex, netlink.FAMILY_ALL) - if err != nil { - return nil, err - } - for _, neigh := range neighbors { - if neigh.IP.Equal(ip) && neigh.HardwareAddr != nil { - return neigh.HardwareAddr, nil - } - } - return nil, errors.New("missing ARP entry") -} - -// TODO no reason to pass in both net.udpaddr and addr.Addr, but the latter does not include the port -func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcAddr, dstAddr addr.Addr) HerculesPathHeader { - iface := path.iface - dstMAC, srcMAC, err := getAddrs(iface, path.path.UnderlayNextHop().IP) - fmt.Println(dstMAC, srcMAC, err) - - underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcUDP.IP, path.path.UnderlayNextHop().IP, uint16(path.path.UnderlayNextHop().Port), etherLen) - fmt.Println(underlayHeader, err) - - payload := snet.UDPPayload{ - SrcPort: srcUDP.AddrPort().Port(), - DstPort: dstUDP.AddrPort().Port(), - Payload: nil, - } - - destPkt := &snet.Packet{ - PacketInfo: snet.PacketInfo{ - Destination: dstAddr, - Source: srcAddr, - Path: path.path.Dataplane(), - Payload: payload, - }, - } - - if err = destPkt.Serialize(); err != nil { - fmt.Println("serializer err") - } - scionHeaderLen := len(destPkt.Bytes) - payloadLen := etherLen - len(underlayHeader) - scionHeaderLen - payload.Payload = make([]byte, payloadLen) - destPkt.Payload = payload - - if err = destPkt.Serialize(); err != nil { - fmt.Println("serrializer err2") - } - scionHeader := destPkt.Bytes[:scionHeaderLen] - scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) - headerBuf := append(underlayHeader, scionHeader...) - herculesPath := HerculesPathHeader{ - Header: headerBuf, - PartialChecksum: scionChecksum, - } - fmt.Println(herculesPath) - return herculesPath -} - +// Select paths and serialize headers for a given transfer func headersToDestination(transfer HerculesTransfer) (int, []byte) { - fmt.Println("making headers", transfer.pm.src, transfer.dest, transfer.nPaths) srcA := addr.Addr{ IA: localAddress.IA, Host: addr.MustParseHost(localAddress.Host.IP.String()), @@ -396,8 +38,14 @@ func headersToDestination(transfer HerculesTransfer) (int, []byte) { numSelectedPaths := len(enabledPaths) headers_ser := []byte{} for _, p := range enabledPaths { - preparedHeader := prepareHeader(p, transfer.mtu, *transfer.pm.src.Host, *transfer.dest.Host, srcA, dstA) - headers_ser = append(headers_ser, SerializePath(&preparedHeader, p.iface.Index)...) + preparedHeader := prepareHeader(p, transfer.pm.mtu, *transfer.pm.src.Host, *transfer.dest.Host, srcA, dstA) + serializedHeader := SerializePath(&preparedHeader, p.iface.Index, C.HERCULES_MAX_HEADERLEN) + if serializedHeader == nil { + fmt.Printf("Unable to serialize header for path: %v\n", p.path) + numSelectedPaths-- + continue + } + headers_ser = append(headers_ser, serializedHeader...) } return numSelectedPaths, headers_ser } @@ -405,160 +53,36 @@ func headersToDestination(transfer HerculesTransfer) (int, []byte) { type TransferStatus int const ( - Queued TransferStatus = iota - Submitted - Done + Queued TransferStatus = iota // Received by the monitor, enqueued, not yet known to the server + Submitted // The server is processing the transfer + Done // The server is done with the transfer (not necessarily successfully) ) +// Note that the monitor's transfer status is distinct from the server's session state. +// The monitor's status is used to distinguish queued jobs from ones already submitted to the server, +// since the server has no concept of pending jobs. type HerculesTransfer struct { id int // ID identifying this transfer status TransferStatus // Status as seen by the monitor file string // Name of the file to transfer dest snet.UDPAddr // Destination - mtu int // MTU to use - nPaths int // Maximum number of paths to use pm *PathManager // The following two fields are meaningless if the job's status is 'Queued' + // They are updated when the server sends messages of type 'update_job' state C.enum_session_state // The state returned by the server err C.enum_session_error // The error returned by the server time_elapsed int // Seconds the transfer has been running chunks_acked int // Number of successfully transferred chunks } -var transfersLock sync.Mutex +var transfersLock sync.Mutex // To protect the map below var transfers = map[int]*HerculesTransfer{} -var nextID int = 1 - -// GET params: -// file (File to transfer) -// dest (Destination IA+Host) -func http_submit(w http.ResponseWriter, r *http.Request) { - fmt.Println(r) - if !r.URL.Query().Has("file") || !r.URL.Query().Has("dest") { - io.WriteString(w, "missing parameter") - return - } - file := r.URL.Query().Get("file") - dest := r.URL.Query().Get("dest") - fmt.Println(file, dest) - destParsed, err := snet.ParseUDPAddr(dest) - if err != nil { - io.WriteString(w, "parse err") - return - } - mtu := 1200 - if r.URL.Query().Has("mtu") { - mtu, err = strconv.Atoi(r.URL.Query().Get("mtu")) - if err != nil { - io.WriteString(w, "parse err") - return - } - } - nPaths := 1 - if r.URL.Query().Has("np") { - nPaths, err = strconv.Atoi(r.URL.Query().Get("np")) - if err != nil { - io.WriteString(w, "parse err") - return - } - } - fmt.Println(destParsed) - destination := findPathRule(&pathRules, destParsed) - pm, _ := initNewPathManager(interfaces, &destination, localAddress, uint64(mtu)) - transfersLock.Lock() - jobid := nextID - transfers[nextID] = &HerculesTransfer{ - id: nextID, - status: Queued, - file: file, - dest: *destParsed, - mtu: mtu, - nPaths: nPaths, - pm: pm, - } - nextID += 1 - transfersLock.Unlock() - - io.WriteString(w, fmt.Sprintf("OK %d\n", jobid)) -} - -// GET Params: -// id: An ID obtained by submitting a transfer -// Returns OK status state err seconds_elapsed chucks_acked -func http_status(w http.ResponseWriter, r *http.Request) { - if !r.URL.Query().Has("id") { - io.WriteString(w, "missing parameter") - return - } - id, err := strconv.Atoi(r.URL.Query().Get("id")) - if err != nil { - return - } - transfersLock.Lock() - info, ok := transfers[id] - transfersLock.Unlock() - if !ok { - return - } - io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.chunks_acked)) -} +var nextID int = 1 // ID to use for the next transfer var localAddress *snet.UDPAddr var interfaces []*net.Interface -type HostConfig struct { - HostAddr addr.Addr - NumPaths int - PathSpec []PathSpec -} -type ASConfig struct { - IA addr.IA - NumPaths int - PathSpec []PathSpec -} - -type MonitorConfig struct { - DestinationHosts []HostConfig - DestinationASes []ASConfig - DefaultNumPaths int -} - -type PathRules struct { - Hosts map[addr.Addr]HostConfig - ASes map[addr.IA]ASConfig - DefaultNumPaths int -} - -var config MonitorConfig -var pathRules PathRules = PathRules{} - -func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { - a := addr.Addr{ - IA: dest.IA, - Host: addr.MustParseHost(dest.Host.IP.String()), - } - confHost, ok := p.Hosts[a] - if ok { - return Destination{ - hostAddr: dest, - pathSpec: &confHost.PathSpec, - numPaths: confHost.NumPaths, - } - } - conf, ok := p.ASes[dest.IA] - if ok { - return Destination{ - hostAddr: dest, - pathSpec: &conf.PathSpec, - numPaths: conf.NumPaths, - } - } - return Destination{ - hostAddr: dest, - pathSpec: &[]PathSpec{}, - numPaths: p.DefaultNumPaths, - } -} +var pathRules PathRules func main() { var localAddr string @@ -567,54 +91,9 @@ func main() { flag.StringVar(&configFile, "c", "herculesmon.conf", "Path to the monitor configuration file") flag.Parse() - meta, err := toml.DecodeFile(configFile, &config) - if err != nil { - fmt.Printf("Error reading configuration file (%v): %v\n", configFile, err) - os.Exit(1) - } - if len(meta.Undecoded()) > 0 { - fmt.Printf("Unknown element(s) in config file: %v\n", meta.Undecoded()) - os.Exit(1) - } - // TODO Would be nice not to have to do this dance and specify the maps directly in the config file, - // but the toml package crashes if the keys are addr.Addr - if config.DefaultNumPaths == 0 { - fmt.Println("Config: Default number of paths to use not set, using 1.") - config.DefaultNumPaths = 1 - } - pathRules.Hosts = map[addr.Addr]HostConfig{} - for _, host := range config.DestinationHosts { - numpaths := config.DefaultNumPaths - if host.NumPaths != 0 { - numpaths = host.NumPaths - } - pathspec := []PathSpec{} - if host.PathSpec != nil { - pathspec = host.PathSpec - } - pathRules.Hosts[host.HostAddr] = HostConfig{ - HostAddr: host.HostAddr, - NumPaths: numpaths, - PathSpec: pathspec, - } - } - pathRules.ASes = map[addr.IA]ASConfig{} - for _, as := range config.DestinationASes { - numpaths := config.DefaultNumPaths - if as.NumPaths != 0 { - numpaths = as.NumPaths - } - pathspec := []PathSpec{} - if as.PathSpec != nil { - pathspec = as.PathSpec - } - pathRules.ASes[as.IA] = ASConfig{ - IA: as.IA, - NumPaths: numpaths, - PathSpec: pathspec, - } - } - pathRules.DefaultNumPaths = config.DefaultNumPaths + var config MonitorConfig + config, pathRules = readConfig(configFile) + fmt.Println(config) src, err := snet.ParseUDPAddr(localAddr) if err != nil || src.Host.Port == 0 { @@ -622,17 +101,22 @@ func main() { os.Exit(1) } localAddress = src - GlobalQuerier = newPathQuerier() + GlobalQuerier = newPathQuerier() // TODO can the connection time out? // TODO make socket paths congfigurable - daemon, err := net.ResolveUnixAddr("unixgram", "/var/hercules.sock") - fmt.Println(daemon, err) - local, err := net.ResolveUnixAddr("unixgram", "/var/herculesmon.sock") - fmt.Println(local, err) - os.Remove("/var/herculesmon.sock") - usock, err := net.ListenUnixgram("unixgram", local) - fmt.Println(usock, err) + monitorSocket, err := net.ResolveUnixAddr("unixgram", config.MonitorSocket) + if err != nil { + os.Exit(1) + } + + os.Remove(config.MonitorSocket) + usock, err := net.ListenUnixgram("unixgram", monitorSocket) + if err != nil { + fmt.Printf("Error binding to monitor socket (%s): %v\n", config.MonitorSocket, err) + os.Exit(1) + } + // TODO interfaces from config ifs, _ := net.Interfaces() iffs := []*net.Interface{} for i, _ := range ifs { @@ -643,35 +127,40 @@ func main() { // used for looking up reply path interface pm, err := initNewPathManager(iffs, &Destination{ hostAddr: localAddress, - }, src, 0) - fmt.Println(err) + }, src) + if err != nil { + fmt.Printf("Error initialising path manager: %v\n", err) + os.Exit(1) + } + // Start HTTP API http.HandleFunc("/submit", http_submit) http.HandleFunc("/status", http_status) go http.ListenAndServe(":8000", nil) // TODO remove finished sessions after a while + // Communication is always initiated by the server, + // the monitor's job is to respond to queries from the server for { buf := make([]byte, C.SOCKMSG_SIZE) fmt.Println("read...", C.SOCKMSG_SIZE) n, a, err := usock.ReadFromUnix(buf) - fmt.Println(n, a, err, buf) + if err != nil { + fmt.Println("Error reading from socket!", err) + } if n > 0 { msgtype := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] switch msgtype { case C.SOCKMSG_TYPE_GET_REPLY_PATH: - fmt.Println("reply path") sample_len := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] etherlen := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] - fmt.Println("smaple len", sample_len) replyPath, nextHop, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) + fmt.Println(err) // TODO signal error to server? iface, _ := pm.interfaceForRoute(nextHop) - fmt.Println("reply iface", iface) - fmt.Println(replyPath, err) - b := SerializePath(replyPath, iface.Index) + b := SerializePath(replyPath, iface.Index, C.HERCULES_MAX_HEADERLEN) usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_NEW_JOB: @@ -688,16 +177,16 @@ func main() { var b []byte if selectedJob != nil { fmt.Println("sending file to daemon:", selectedJob.file, selectedJob.id) + _, _ = headersToDestination(*selectedJob) // look up paths to fix mtu // TODO Conversion between go and C strings? strlen := len(selectedJob.file) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) - b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.mtu)) + b = binary.LittleEndian.AppendUint16(b, uint16(1200)) b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) b = append(b, []byte(selectedJob.file)...) - fmt.Println(b) } else { - fmt.Println("no new jobs") + // no new jobs b = append(b, 0) } usock.WriteToUnix(b, a) @@ -705,7 +194,6 @@ func main() { case C.SOCKMSG_TYPE_GET_PATHS: job_id := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] - fmt.Println("fetch path, job", job_id) transfersLock.Lock() job, _ := transfers[int(job_id)] n_headers, headers := headersToDestination(*job) @@ -730,18 +218,17 @@ func main() { job, _ := transfers[int(job_id)] job.state = status job.err = errorcode - if (job.state == C.SESSION_STATE_DONE){ + if job.state == C.SESSION_STATE_DONE { job.status = Done } job.chunks_acked = int(bytes_acked) // FIXME job.time_elapsed = int(seconds) - fmt.Println(job, job.state) transfersLock.Unlock() b := binary.LittleEndian.AppendUint16(nil, uint16(1)) usock.WriteToUnix(b, a) default: - fmt.Println("unknown message?") + fmt.Println("Received unknown message?") } } } diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 44c4cf2..853739d 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -16,10 +16,10 @@ package main import ( "fmt" + "net" + "github.com/scionproto/scion/pkg/snet" "github.com/vishvananda/netlink" - "net" - "time" ) type Destination struct { @@ -29,12 +29,10 @@ type Destination struct { } type PathManager struct { - numPathSlotsPerDst int - interfaces map[int]*net.Interface - dst *PathsToDestination - src *snet.UDPAddr - syncTime time.Time - maxBps uint64 + interfaces map[int]*net.Interface + dst *PathsToDestination + src *snet.UDPAddr + mtu int } type PathWithInterface struct { @@ -44,9 +42,7 @@ type PathWithInterface struct { type AppPathSet map[snet.PathFingerprint]PathWithInterface -const numPathsResolved = 20 - -func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr, maxBps uint64) (*PathManager, error) { +func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr) (*PathManager, error) { ifMap := make(map[int]*net.Interface) for _, iface := range interfaces { ifMap[iface.Index] = iface @@ -56,8 +52,7 @@ func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet interfaces: ifMap, src: src, dst: &PathsToDestination{}, - syncTime: time.Unix(0, 0), - maxBps: maxBps, + mtu: 0, // Will be set later, after the first path lookup } if src.IA == dst.hostAddr.IA { @@ -81,13 +76,23 @@ func (pm *PathManager) choosePaths() bool { return pm.dst.choosePaths() } -func (pm *PathManager) filterPathsByActiveInterfaces(pathsAvail []snet.Path) AppPathSet { - pathsFiltered := make(AppPathSet) +func (pm *PathManager) filterPathsByActiveInterfaces(pathsAvail []snet.Path) []PathWithInterface { + pathsFiltered := []PathWithInterface{} for _, path := range pathsAvail { iface, err := pm.interfaceForRoute(path.UnderlayNextHop().IP) if err != nil { } else { - pathsFiltered[snet.Fingerprint(path)] = PathWithInterface{path, iface} + pathsFiltered = append(pathsFiltered, PathWithInterface{path, iface}) + } + } + return pathsFiltered +} + +func (pm *PathManager) filterPathsByMTU(pathsAvail []PathWithInterface) []PathWithInterface { + pathsFiltered := []PathWithInterface{} + for _, path := range pathsAvail { + if path.path.Metadata().MTU >= uint16(pm.mtu){ + pathsFiltered = append(pathsFiltered, path) } } return pathsFiltered diff --git a/monitor/pathpicker.go b/monitor/pathpicker.go index 315a702..a96a50f 100644 --- a/monitor/pathpicker.go +++ b/monitor/pathpicker.go @@ -27,7 +27,7 @@ type PathPickDescriptor struct { type PathPicker struct { pathSpec *[]PathSpec - availablePaths []snet.Path + availablePaths []PathWithInterface currentPathPick []PathPickDescriptor } @@ -38,18 +38,14 @@ func min(a, b int) int { return b } -func makePathPicker(spec *[]PathSpec, pathSet *AppPathSet, numPaths int) *PathPicker { +func makePathPicker(spec *[]PathSpec, pathSet []PathWithInterface, numPaths int) *PathPicker { if len(*spec) == 0 { defaultSpec := make([]PathSpec, numPaths) spec = &defaultSpec } - paths := make([]snet.Path, 0, len(*pathSet)) - for _, path := range *pathSet { - paths = append(paths, path.path) - } picker := &PathPicker{ pathSpec: spec, - availablePaths: paths, + availablePaths: pathSet, } picker.reset(numPaths) return picker @@ -155,7 +151,7 @@ func (picker *PathPicker) nextPickIterate(idx int) bool { func (picker *PathPicker) matches(pathIdx, ruleIdx int) bool { pathSpec := (*picker.pathSpec)[ruleIdx] - pathInterfaces := picker.availablePaths[pathIdx].Metadata().Interfaces + pathInterfaces := picker.availablePaths[pathIdx].path.Metadata().Interfaces idx := 0 for _, iface := range pathSpec { for len(pathInterfaces) > idx && !iface.match(pathInterfaces[idx]) { @@ -184,7 +180,7 @@ func (picker *PathPicker) disjointnessScore() int { interfaces := map[snet.PathInterface]int{} score := 0 for _, pick := range picker.currentPathPick { - for _, path := range picker.availablePaths[pick.pathIndex].Metadata().Interfaces { + for _, path := range picker.availablePaths[pick.pathIndex].path.Metadata().Interfaces { score -= interfaces[path] interfaces[path]++ } @@ -192,8 +188,8 @@ func (picker *PathPicker) disjointnessScore() int { return score } -func (picker *PathPicker) getPaths() []snet.Path { - paths := make([]snet.Path, 0, len(picker.currentPathPick)) +func (picker *PathPicker) getPaths() []PathWithInterface { + paths := make([]PathWithInterface, 0, len(picker.currentPathPick)) for _, pick := range picker.currentPathPick { paths = append(paths, picker.availablePaths[pick.pathIndex]) } diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index e407523..a00ec71 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -18,13 +18,11 @@ import ( "context" "fmt" "net" - "time" log "github.com/inconshreveable/log15" "github.com/scionproto/scion/pkg/snet" "github.com/scionproto/scion/pkg/snet/path" "github.com/scionproto/scion/private/topology" - "go.uber.org/atomic" ) var GlobalQuerier snet.PathQuerier @@ -32,8 +30,6 @@ var GlobalQuerier snet.PathQuerier type PathsToDestination struct { pm *PathManager dst *Destination - modifyTime time.Time - ExtnUpdated atomic.Bool allPaths []snet.Path paths []PathMeta // nil indicates that the destination is in the same AS as the sender and we can use an empty path canSendLocally bool // (only if destination in same AS) indicates if we can send packets @@ -61,7 +57,6 @@ func initNewPathsToDestinationWithEmptyPath(pm *PathManager, dst *Destination) * pm: pm, dst: dst, paths: make([]PathMeta, 1), - modifyTime: time.Now(), } } @@ -71,7 +66,6 @@ func initNewPathsToDestination(pm *PathManager, src *snet.UDPAddr, dst *Destinat dst: dst, allPaths: nil, paths: make([]PathMeta, dst.numPaths), - modifyTime: time.Unix(0, 0), }, nil } @@ -107,57 +101,44 @@ func (ptd *PathsToDestination) choosePaths() bool { } } - fmt.Println("all paths", ptd.allPaths) availablePaths := ptd.pm.filterPathsByActiveInterfaces(ptd.allPaths) if len(availablePaths) == 0 { log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) } - // TODO Ensure this still does the right thing when the number of paths decreases (how to test?) - ptd.chooseNewPaths(&availablePaths) - - fmt.Println("chosen paths", ptd.paths) - return true -} + if ptd.pm.mtu != 0 { + // MTU fixed by a previous path lookup, we need to pick paths compatible with it + availablePaths = ptd.pm.filterPathsByMTU(availablePaths) + } -func (ptd *PathsToDestination) choosePreviousPaths(previousPathAvailable *[]bool, availablePaths *AppPathSet) bool { - updated := false - for newFingerprint := range *availablePaths { - for i := range ptd.paths { - pathMeta := &ptd.paths[i] - if newFingerprint == pathMeta.fingerprint { - if !pathMeta.enabled { - log.Info(fmt.Sprintf("[Destination %s] re-enabling path %d\n", ptd.dst.hostAddr.IA, i)) - pathMeta.enabled = true - updated = true - } - (*previousPathAvailable)[i] = true - break + // TODO Ensure this still does the right thing when the number of paths decreases (how to test?) + ptd.chooseNewPaths(availablePaths) + + if ptd.pm.mtu == 0{ + // No MTU set yet, we set it to the maximum that all selected paths and interfaces support + minMTU := HerculesMaxPktsize + for _, path := range ptd.paths { + if path.path.Metadata().MTU < uint16(minMTU){ + minMTU = int(path.path.Metadata().MTU) + } + if path.iface.MTU < minMTU { + minMTU = path.iface.MTU } } + ptd.pm.mtu = minMTU + ptd.pm.mtu = 1200 // FIXME temp } - return updated -} -func (ptd *PathsToDestination) disableVanishedPaths(previousPathAvailable *[]bool) bool { - updated := false - for i, inUse := range *previousPathAvailable { - pathMeta := &ptd.paths[i] - if inUse == false && pathMeta.enabled { - log.Info(fmt.Sprintf("[Destination %s] disabling path %d\n", ptd.dst.hostAddr.IA, i)) - pathMeta.enabled = false - updated = true - } - } - return updated + return true } -func (ptd *PathsToDestination) chooseNewPaths(availablePaths *AppPathSet) bool { +func (ptd *PathsToDestination) chooseNewPaths(availablePaths []PathWithInterface) bool { updated := false // pick paths picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) - var pathSet []snet.Path + fmt.Println(availablePaths) + var pathSet []PathWithInterface disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) maxRuleIdx := 0 // the highest index of a PathSpec that is used (to be minimized) for i := ptd.dst.numPaths; i > 0; i-- { @@ -186,47 +167,12 @@ func (ptd *PathsToDestination) chooseNewPaths(availablePaths *AppPathSet) bool { return false } for i, path := range pathSet { - log.Info(fmt.Sprintf("\t%s", path)) - fingerprint := snet.Fingerprint(path) - ptd.paths[i].path = path - ptd.paths[i].fingerprint = fingerprint + log.Info(fmt.Sprintf("\t%s", path.path)) + ptd.paths[i].path = path.path ptd.paths[i].enabled = true ptd.paths[i].updated = true - ptd.paths[i].iface = (*availablePaths)[fingerprint].iface + ptd.paths[i].iface = path.iface updated = true } return updated } - -func (ptd *PathsToDestination) preparePath(p *PathMeta) (*HerculesPathHeader, error) { - var err error - var iface *net.Interface - curDst := ptd.dst.hostAddr - fmt.Println("preparepath", curDst, iface) - if (*p).path == nil { - // in order to use a static empty path, we need to set the next hop on dst - fmt.Println("empty path") - curDst.NextHop = &net.UDPAddr{ - IP: ptd.dst.hostAddr.Host.IP, - Port: topology.EndhostPort, - } - fmt.Println("nexthop", curDst.NextHop) - iface, err = ptd.pm.interfaceForRoute(ptd.dst.hostAddr.Host.IP) - if err != nil { - return nil, err - } - curDst.Path = path.Empty{} - } else { - curDst.Path = (*p).path.Dataplane() - - curDst.NextHop = (*p).path.UnderlayNextHop() - iface = p.iface - } - - // path, err := prepareSCIONPacketHeader(ptd.pm.src, curDst, iface) - // if err != nil { - // return nil, err - // } - // return path, nil - return nil, fmt.Errorf("NOPE") -} diff --git a/monitor/sampleconf.toml b/monitor/sampleconf.toml new file mode 100644 index 0000000..38f08a9 --- /dev/null +++ b/monitor/sampleconf.toml @@ -0,0 +1,33 @@ +# Sample config for Hercules monitor + +# By default, up to `DefaultNumPaths` paths will be used. +DefaultNumPaths = 1 + +# The number and choice of paths can be overridden on a destination-host +# or destination-AS basis. In case both an AS and Host rule match, the Host +# rule takes precedence. + +# This Host rule specifies that, for the host 17-ffaa:1:fe2,1.1.1.1, +# - Transfers may use up to 42 paths +# - The paths must contain either the AS-interface sequence +# 17-f:f:f 1 > 17:f:f:a 2 +# OR 1-f:0:0 22 +[[DestinationHosts]] +HostAddr = "17-ffa:1:fe2,1.1.1.1" +NumPaths = 42 +PathSpec = [ +["17-f:f:f 1", "17-f:f:a 2"], +["1-f:0:0 22"], +] + +# In this case the number of paths is set to 2, but the exact set of paths +# is left unspecified +[[DestinationHosts]] +HostAddr = "18-a:b:c,2.2.2.2" +NumPaths = 2 + + +# Similarly, but for an entire destination AS instead of a specific host +[[DestinationASes]] +IA = "17-a:b:c" +NumPaths = 2 diff --git a/monitor/scionheader.go b/monitor/scionheader.go new file mode 100644 index 0000000..72bba12 --- /dev/null +++ b/monitor/scionheader.go @@ -0,0 +1,359 @@ +package main + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "net" + "syscall" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/scionproto/scion/pkg/addr" + "github.com/scionproto/scion/pkg/snet" + "github.com/scionproto/scion/pkg/snet/path" + "github.com/scionproto/scion/private/topology" + "github.com/vishvananda/netlink" +) + +type layerWithOpts struct { + Layer gopacket.SerializableLayer + Opts gopacket.SerializeOptions +} + +func prepareUnderlayPacketHeader(srcMAC, dstMAC net.HardwareAddr, srcIP, dstIP net.IP, dstPort uint16, etherLen int) ([]byte, error) { + ethHeader := 14 + ipHeader := 20 + udpHeader := 8 + + eth := layers.Ethernet{ + SrcMAC: srcMAC, + DstMAC: dstMAC, + EthernetType: layers.EthernetTypeIPv4, + } + + ip := layers.IPv4{ + Version: 4, + IHL: 5, // Computed at serialization when FixLengths option set + TOS: 0x0, + Length: uint16(etherLen - ethHeader), // Computed at serialization when FixLengths option set + Id: 0, + Flags: layers.IPv4DontFragment, + FragOffset: 0, + TTL: 0xFF, + Protocol: layers.IPProtocolUDP, + //Checksum: 0, // Set at serialization with the ComputeChecksums option + SrcIP: srcIP, + DstIP: dstIP, + Options: nil, + } + + srcPort := uint16(topology.EndhostPort) + udp := layers.UDP{ + SrcPort: layers.UDPPort(srcPort), + DstPort: layers.UDPPort(dstPort), + Length: uint16(etherLen - ethHeader - ipHeader), + Checksum: 0, + } + + buf := gopacket.NewSerializeBuffer() + serializeOpts := gopacket.SerializeOptions{ + FixLengths: false, + ComputeChecksums: false, + } + serializeOptsChecked := gopacket.SerializeOptions{ + FixLengths: false, + ComputeChecksums: true, + } + err := serializeLayersWOpts(buf, + layerWithOpts{ð, serializeOpts}, + layerWithOpts{&ip, serializeOptsChecked}, + layerWithOpts{&udp, serializeOpts}) + if err != nil { + return nil, err + } + + // return only the header + return buf.Bytes()[:ethHeader+ipHeader+udpHeader], nil +} + +func serializeLayersWOpts(w gopacket.SerializeBuffer, layersWOpts ...layerWithOpts) error { + err := w.Clear() + if err != nil { + return err + } + for i := len(layersWOpts) - 1; i >= 0; i-- { + layerWOpt := layersWOpts[i] + err := layerWOpt.Layer.SerializeTo(w, layerWOpt.Opts) + if err != nil { + return err + } + w.PushLayer(layerWOpt.Layer.LayerType()) + } + return nil +} + +// Determine the reply path by reversing the path of a received packet +func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, net.IP, error) { + packet := gopacket.NewPacket(buf, layers.LayerTypeEthernet, gopacket.Default) + if err := packet.ErrorLayer(); err != nil { + return nil, nil, fmt.Errorf("error decoding some part of the packet: %v", err) + } + eth := packet.Layer(layers.LayerTypeEthernet) + if eth == nil { + return nil, nil, errors.New("error decoding ETH layer") + } + dstMAC, srcMAC := eth.(*layers.Ethernet).SrcMAC, eth.(*layers.Ethernet).DstMAC + + ip4 := packet.Layer(layers.LayerTypeIPv4) + if ip4 == nil { + return nil, nil, errors.New("error decoding IPv4 layer") + } + dstIP, srcIP := ip4.(*layers.IPv4).SrcIP, ip4.(*layers.IPv4).DstIP + + udp := packet.Layer(layers.LayerTypeUDP) + if udp == nil { + return nil, nil, errors.New("error decoding IPv4/UDP layer") + } + udpPayload := udp.(*layers.UDP).Payload + udpDstPort := udp.(*layers.UDP).SrcPort + + if len(udpPayload) < 8 { // Guard against bug in ParseScnPkt + return nil, nil, errors.New("error decoding SCION packet: payload too small") + } + + sourcePkt := snet.Packet{ + Bytes: udpPayload, + } + if err := sourcePkt.Decode(); err != nil { + return nil, nil, fmt.Errorf("error decoding SCION packet: %v", err) + } + + rpath, ok := sourcePkt.Path.(snet.RawPath) + if !ok { + return nil, nil, fmt.Errorf("error decoding SCION packet: unexpected dataplane path type") + } + if len(rpath.Raw) != 0 { + replyPath, err := snet.DefaultReplyPather{}.ReplyPath(rpath) + if err != nil { + return nil, nil, fmt.Errorf("failed to reverse SCION path: %v", err) + } + sourcePkt.Path = replyPath + } + + udpPkt, ok := sourcePkt.Payload.(snet.UDPPayload) + if !ok { + return nil, nil, errors.New("error decoding SCION/UDP") + } + + if sourcePkt.Source.IA == sourcePkt.Destination.IA { + sourcePkt.Path = path.Empty{} + } + + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcIP, dstIP, uint16(udpDstPort), etherLen) + if err != nil { + return nil, nil, err + } + + payload := snet.UDPPayload{ + SrcPort: udpPkt.DstPort, + DstPort: udpPkt.SrcPort, + Payload: nil, + } + + destPkt := &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Destination: sourcePkt.Source, + Source: sourcePkt.Destination, + Path: sourcePkt.Path, + Payload: payload, + }, + } + + if err = destPkt.Serialize(); err != nil { + return nil, nil, err + } + scionHeaderLen := len(destPkt.Bytes) + payloadLen := etherLen - len(underlayHeader) - scionHeaderLen + payload.Payload = make([]byte, payloadLen) + destPkt.Payload = payload + + if err = destPkt.Serialize(); err != nil { + return nil, nil, err + } + scionHeader := destPkt.Bytes[:scionHeaderLen] + scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) + headerBuf := append(underlayHeader, scionHeader...) + herculesPath := HerculesPathHeader{ + Header: headerBuf, + PartialChecksum: scionChecksum, + } + return &herculesPath, dstIP, nil +} + +func SerializePath(from *HerculesPathHeader, ifid int, maxHeaderLen int) []byte { + out := []byte{} + out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) + out = binary.LittleEndian.AppendUint16(out, uint16(ifid)) + out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) + if len(from.Header) > maxHeaderLen { + // Header does not fit in the C struct + return nil + } + out = append(out, from.Header...) + out = append(out, bytes.Repeat([]byte{0x00}, maxHeaderLen-len(from.Header))...) + return out +} + +// getAddrs returns dstMAC, srcMAC and srcIP for a packet to be sent over interface to destination. +func getAddrs(iface *net.Interface, destination net.IP) (dstMAC, srcMAC net.HardwareAddr, err error) { + + srcMAC = iface.HardwareAddr + + // Get destination MAC (address of either destination or gateway) using netlink + // n is the handle (i.e. the main entrypoint) for netlink + n, err := netlink.NewHandle() + if err != nil { + return + } + defer n.Delete() + + routes, err := n.RouteGet(destination) + if err != nil { + return + } + route := routes[0] + for _, r := range routes { + if r.LinkIndex == iface.Index { + route = r + break + } + } + if route.LinkIndex != iface.Index { + err = errors.New("no route found to destination on specified interface") + } + + dstIP := destination + if route.Gw != nil { + dstIP = route.Gw + } + dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) + if err != nil { + if err.Error() == "missing ARP entry" { + // Handle missing ARP entry + fmt.Printf("Sending ICMP echo to %v over %v and retrying...\n", dstIP, iface.Name) + + // Send ICMP + if err = sendICMP(iface, route.Src, dstIP); err != nil { + return + } + // Poll for 3 seconds + for start := time.Now(); time.Since(start) < time.Duration(3)*time.Second; { + dstMAC, err = getNeighborMAC(n, iface.Index, dstIP) + if err == nil { + break + } + } + } + if err != nil { + return + } + } + + return +} + +func sendICMP(iface *net.Interface, srcIP net.IP, dstIP net.IP) (err error) { + icmp := layers.ICMPv4{ + TypeCode: layers.ICMPv4TypeEchoRequest, + } + buf := gopacket.NewSerializeBuffer() + serializeOpts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + err = gopacket.SerializeLayers(buf, serializeOpts, &icmp) + if err != nil { + return err + } + + fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_ICMP) + if err != nil { + fmt.Println("Creating raw socket failed.") + return err + } + defer syscall.Close(fd) + dstIPRaw := [4]byte{} + copy(dstIPRaw[:4], dstIP.To4()) + ipSockAddr := syscall.SockaddrInet4{ + Port: 0, + Addr: dstIPRaw, + } + if err = syscall.Sendto(fd, buf.Bytes(), 0, &ipSockAddr); err != nil { + fmt.Printf("Sending ICMP echo to %v over %v failed.\n", dstIP, iface.Name) + return err + } + return nil +} + +// getNeighborMAC returns the HardwareAddr for the neighbor (ARP table entry) with the given IP +func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAddr, error) { + neighbors, err := n.NeighList(linkIndex, netlink.FAMILY_ALL) + if err != nil { + return nil, err + } + for _, neigh := range neighbors { + if neigh.IP.Equal(ip) && neigh.HardwareAddr != nil { + return neigh.HardwareAddr, nil + } + } + return nil, errors.New("missing ARP entry") +} + +// TODO no reason to pass in both net.udpaddr and addr.Addr, but the latter does not include the port +func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcAddr, dstAddr addr.Addr) HerculesPathHeader { + iface := path.iface + dstMAC, srcMAC, err := getAddrs(iface, path.path.UnderlayNextHop().IP) + fmt.Println(dstMAC, srcMAC, err) + + underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcUDP.IP, path.path.UnderlayNextHop().IP, uint16(path.path.UnderlayNextHop().Port), etherLen) + fmt.Println(underlayHeader, err) + + payload := snet.UDPPayload{ + SrcPort: srcUDP.AddrPort().Port(), + DstPort: dstUDP.AddrPort().Port(), + Payload: nil, + } + + destPkt := &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Destination: dstAddr, + Source: srcAddr, + Path: path.path.Dataplane(), + Payload: payload, + }, + } + + if err = destPkt.Serialize(); err != nil { + fmt.Println("serializer err") + } + scionHeaderLen := len(destPkt.Bytes) + payloadLen := etherLen - len(underlayHeader) - scionHeaderLen + payload.Payload = make([]byte, payloadLen) + destPkt.Payload = payload + + if err = destPkt.Serialize(); err != nil { + fmt.Println("serrializer err2") + } + scionHeader := destPkt.Bytes[:scionHeaderLen] + scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) + headerBuf := append(underlayHeader, scionHeader...) + herculesPath := HerculesPathHeader{ + Header: headerBuf, + PartialChecksum: scionChecksum, + } + fmt.Println(herculesPath) + return herculesPath +} diff --git a/stats.go b/stats.go deleted file mode 100644 index c142289..0000000 --- a/stats.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2019 ETH Zurich -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "encoding/csv" - "fmt" - "math" - "os" - "strconv" - "time" -) - -type herculesStats struct { - startTime uint64 - endTime uint64 - now uint64 - - txNpkts uint64 - rxNpkts uint64 - - filesize uint64 - frameLen uint32 - chunkLen uint32 - totalChunks uint32 - completedChunks uint32 //!< either number of acked (for sender) or received (for receiver) chunks - - rateLimit uint32 - paths []perPathStats -} - -type aggregateStats struct { - maxPps float64 - maxBpsThru float64 - maxBpsGood float64 -} - -type perPathStats struct { - total_packets, pps_target int64 -} - -func statsDumper(session *HerculesSession, tx bool, interval time.Duration, aggregate *aggregateStats, pathStatsFile string, numPaths int, done chan struct{}, benchmarkDuration time.Duration) { - if interval == 0 { - return - } - - statsAwaitStart(session) - - if tx { - fmt.Printf("\n%-6s %10s %10s %20s %20s %20s %11s %11s\n", - "Time", - "Completion", - "Goodput", - "Throughput now", - "Throughput target", - "Throughput avg", - "Pkts sent", - "Pkts rcvd", - ) - } else { - fmt.Printf("\n%-6s %10s %10s %20s %20s %11s %11s\n", - "Time", - "Completion", - "Goodput", - "Throughput now", - "Throughput avg", - "Pkts rcvd", - "Pkts sent", - ) - } - - var pStats *pathStats - var psWriter *csv.Writer - if pathStatsFile != "" { - pStats = makePerPathStatsBuffer(numPaths) - file, err := os.Create(pathStatsFile) - if err != nil { - fmt.Fprintf(os.Stderr, "Cannot open %s for writing", pathStatsFile) - os.Exit(1) - } - - psWriter = csv.NewWriter(file) - defer func() { - psWriter.Flush() - if err := psWriter.Error(); err != nil { - fmt.Println(os.Stderr, err) - } - _ = file.Close() - done <- struct{}{} - }() - - header := make([]string, 1, 1+2*numPaths) - header[0] = "Time" - for i := 0; i < numPaths; i++ { - header = append(header, - fmt.Sprintf("Path %d target [bit/s]", i), - fmt.Sprintf("Path %d throughput [bit/s]", i), - ) - } - if err = psWriter.Write(header); err != nil { - fmt.Fprintf(os.Stderr, "Cannot write header") - os.Exit(1) - } - } else { - defer func() { done <- struct{}{} }() - } - - prevStats := herculesGetStats(session, pStats) - - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - select { - case <-done: - return - case <-ticker.C: - stats := herculesGetStats(session, pStats) - - // elapsed time in seconds - t := stats.now - if stats.endTime > 0 { - t = stats.endTime - } - dt := float64(t-prevStats.now) / 1e9 - dttot := float64(t-stats.startTime) / 1e9 - - chunklen := float64(stats.chunkLen) - framelen := float64(stats.frameLen) - completion := float64(stats.completedChunks) / float64(stats.totalChunks) - - if stats.paths != nil { - record := make([]string, 1, 1+2*len(stats.paths)) - record[0] = strconv.FormatFloat(dttot, 'f', 1, 64) - for i, ps := range stats.paths { - record = append(record, - strconv.FormatInt(8*int64(framelen)*ps.pps_target, 10), - strconv.FormatInt(8*int64(framelen)*(ps.total_packets-prevStats.paths[i].total_packets), 10), - ) - } - if err := psWriter.Write(record); err != nil { - fmt.Fprintf(os.Stderr, "could not write path stats record: %s", err) - os.Exit(1) - } - } - - if tx { - - ppsNow := float64(stats.txNpkts-prevStats.txNpkts) / dt - ppsAvg := float64(stats.txNpkts) / dttot - ppsTrg := float64(stats.rateLimit) - - bpsGoodNow := 8 * chunklen * ppsNow - bpsThruNow := 8 * framelen * ppsNow - bpsThruAvg := 8 * framelen * ppsAvg - bpsThruTrg := 8 * framelen * ppsTrg - - fmt.Printf("%5.1fs %9.2f%% %10s %10s %9s %10s %9s %10s %9s %11d %11d\n", - dttot, - completion*100, - humanReadable(bpsGoodNow, "bps"), - humanReadable(bpsThruNow, "bps"), - humanReadable(ppsNow, "pps"), - humanReadable(bpsThruTrg, "bps"), - humanReadable(ppsTrg, "pps"), - humanReadable(bpsThruAvg, "bps"), - humanReadable(ppsAvg, "pps"), - stats.txNpkts, - stats.rxNpkts, - ) - aggregate.maxPps = math.Max(aggregate.maxPps, ppsNow) - aggregate.maxBpsGood = math.Max(aggregate.maxBpsGood, bpsGoodNow) - aggregate.maxBpsThru = math.Max(aggregate.maxBpsThru, bpsThruNow) - } else { - - ppsNow := float64(stats.rxNpkts-prevStats.rxNpkts) / dt - ppsAvg := float64(stats.rxNpkts) / dttot - - bpsGoodNow := 8 * chunklen * ppsNow - bpsThruNow := 8 * framelen * ppsNow - bpsThruAvg := 8 * framelen * ppsAvg - - fmt.Printf("%5.1fs %9.2f%% %10s %10s %9s %10s %9s %11d %11d\n", - dttot, - completion*100, - humanReadable(bpsGoodNow, "bps"), - humanReadable(bpsThruNow, "bps"), - humanReadable(ppsNow, "pps"), - humanReadable(bpsThruAvg, "bps"), - humanReadable(ppsAvg, "pps"), - stats.rxNpkts, - stats.txNpkts, - ) - aggregate.maxPps = math.Max(aggregate.maxPps, ppsNow) - aggregate.maxBpsGood = math.Max(aggregate.maxBpsGood, bpsGoodNow) - aggregate.maxBpsThru = math.Max(aggregate.maxBpsThru, bpsThruNow) - } - - if stats.endTime > 0 || stats.startTime == 0 { // explicitly finished or already de-initialized - <-done // wait for signal before returning (signalling done back) - return - } - if benchmarkDuration > 0 && dttot > float64(benchmarkDuration/time.Second) { // benchmark over - herculesClose(session) - return - } - prevStats = stats - } - } -} - -// statsAwaitStart busy-waits until hercules_get_stats indicates that the transfer has started. -func statsAwaitStart(session *HerculesSession) { - for { - stats := herculesGetStats(session, nil) - if stats.startTime > 0 { - return - } - time.Sleep(100 * time.Millisecond) - } -} - -func printSummary(stats herculesStats, aggregate aggregateStats) { - - dttot := float64(stats.endTime-stats.startTime) / 1e9 - filesize := stats.filesize - goodputBytePS := float64(filesize) / dttot - fmt.Printf("\nTransfer completed:\n %-12s%10.3fs\n %-12s%11s\n %-13s%11s (%s)\n %-11s%11.3f\n %-11s%11.3f\n %-13s%11s (%s)\n %-13s%11s\n %-11s%10d\n %-11s%10d\n %-11s%10d\n %-13s%10d\n %-13s%10d\n", - "Duration:", dttot, - "Filesize:", humanReadableSize(filesize, "B"), - "Rate:", humanReadable(8*goodputBytePS, "b/s"), humanReadableSize(uint64(goodputBytePS), "B/s"), - "Sent/Chunk:", float64(stats.txNpkts)/float64(stats.totalChunks), - "Rcvd/Chunk:", float64(stats.rxNpkts)/float64(stats.totalChunks), - "Max thr.put:", humanReadable(aggregate.maxBpsThru, "b/s"), humanReadable(aggregate.maxPps, "P/s"), - "Max goodput:", humanReadable(aggregate.maxBpsGood, "b/s"), - "Chks:", stats.totalChunks, - "Sent:", stats.txNpkts, - "Rcvd:", stats.rxNpkts, - "LChunk:", stats.chunkLen, - "LFrame:", stats.frameLen, - ) -} - -func humanReadable(n float64, unit string) string { - switch { - case n >= 1e9: - return fmt.Sprintf("%.1fG%s", n/1e9, unit) - case n >= 1e6: - return fmt.Sprintf("%.1fM%s", n/1e6, unit) - default: - return fmt.Sprintf("%.1fK%s", n/1e3, unit) - } -} - -func humanReadableSize(n uint64, unit string) string { - const ( - Ki = 1 << 10 - Mi = 1 << 20 - Gi = 1 << 30 - Ti = 1 << 40 - ) - - switch { - case n >= Ti: - return fmt.Sprintf("%.1fTi%s", float64(n)/float64(Ti), unit) - case n >= Gi: - return fmt.Sprintf("%.1fGi%s", float64(n)/float64(Gi), unit) - case n >= Mi: - return fmt.Sprintf("%.1fMi%s", float64(n)/float64(Mi), unit) - case n >= Ki: - return fmt.Sprintf("%.1fKi", float64(n)/float64(Ki)) - default: - return fmt.Sprintf("%d%s", n, unit) - } -} From 3a139353d2e25ad3449214021bf7e5b244bf32be Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 9 Jun 2024 10:28:46 +0200 Subject: [PATCH 034/112] implement cancel handler --- hercules.c | 6 ++++-- hercules.h | 3 ++- monitor/http_api.go | 25 +++++++++++++++++++++++++ monitor/monitor.go | 12 +++++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/hercules.c b/hercules.c index f4128e8..2098e06 100644 --- a/hercules.c +++ b/hercules.c @@ -2518,11 +2518,13 @@ static void print_session_stats(struct hercules_server *server, static void tx_update_monitor(struct hercules_server *server, u64 now) { struct hercules_session *session_tx = server->session_tx; if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { - monitor_update_job(server->usock, session_tx->jobid, session_tx->state, 0, + bool ret = monitor_update_job(server->usock, session_tx->jobid, session_tx->state, 0, ( now - session_tx->tx_state->start_time ) / (int)1e9, session_tx->tx_state->chunklen * session_tx->tx_state->acked_chunks.num_set); - debug_printf("elapsed %llu", (now - session_tx->tx_state->start_time)/(u64)1e9); + if (!ret) { + quit_session(session_tx, SESSION_ERROR_CANCELLED); + } } } diff --git a/hercules.h b/hercules.h index 267e1db..32bc13c 100644 --- a/hercules.h +++ b/hercules.h @@ -160,7 +160,8 @@ enum session_error { SESSION_ERROR_STALE, //< Packets are being received, but none are new SESSION_ERROR_PCC, //< Something wrong with PCC SESSION_ERROR_SEQNO_OVERFLOW, - SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination + SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination + SESSION_ERROR_CANCELLED, //< Transfer cancelled by monitor }; // A session is a transfer between one sender and one receiver diff --git a/monitor/http_api.go b/monitor/http_api.go index 39e0f7a..4cefd4f 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -65,3 +65,28 @@ func http_status(w http.ResponseWriter, r *http.Request) { } io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.chunks_acked)) } + +// Handle cancelling a transfer +// GET Params: +// id: An ID obtained by submitting a transfer +// Returns OK +func http_cancel(w http.ResponseWriter, r *http.Request) { + if !r.URL.Query().Has("id") { + io.WriteString(w, "missing parameter") + return + } + id, err := strconv.Atoi(r.URL.Query().Get("id")) + if err != nil { + return + } + transfersLock.Lock() + info, ok := transfers[id] + if !ok { + transfersLock.Unlock() + return + } + info.status = Cancelled + transfersLock.Unlock() + + io.WriteString(w, "OK\n") +} diff --git a/monitor/monitor.go b/monitor/monitor.go index c9ee72b..fdc75b2 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -15,6 +15,7 @@ import ( // #include "../monitor.h" import "C" + const HerculesMaxPktsize = C.HERCULES_MAX_PKTSIZE // Select paths and serialize headers for a given transfer @@ -55,8 +56,10 @@ type TransferStatus int const ( Queued TransferStatus = iota // Received by the monitor, enqueued, not yet known to the server Submitted // The server is processing the transfer + Cancelled // The monitor has received a cancellation request Done // The server is done with the transfer (not necessarily successfully) ) + // Note that the monitor's transfer status is distinct from the server's session state. // The monitor's status is used to distinguish queued jobs from ones already submitted to the server, // since the server has no concept of pending jobs. @@ -136,6 +139,7 @@ func main() { // Start HTTP API http.HandleFunc("/submit", http_submit) http.HandleFunc("/status", http_status) + http.HandleFunc("/cancel", http_cancel) go http.ListenAndServe(":8000", nil) // TODO remove finished sessions after a while @@ -223,8 +227,14 @@ func main() { } job.chunks_acked = int(bytes_acked) // FIXME job.time_elapsed = int(seconds) + isCancelled := job.status == Cancelled transfersLock.Unlock() - b := binary.LittleEndian.AppendUint16(nil, uint16(1)) + var b []byte + if isCancelled { + b = binary.LittleEndian.AppendUint16(b, uint16(0)) + } else { + b = binary.LittleEndian.AppendUint16(b, uint16(1)) + } usock.WriteToUnix(b, a) default: From 52a8eeb418d74cd98ac9396f0e67c5c16fcac0a9 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 9 Jun 2024 17:21:59 +0200 Subject: [PATCH 035/112] cleanup some todos --- hercules.c | 38 ++++++++++++++++++-------------------- hercules.h | 11 +++++++---- monitor.c | 4 ++-- monitor.h | 2 +- monitor/monitor.go | 3 +-- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/hercules.c b/hercules.c index 2098e06..9f47094 100644 --- a/hercules.c +++ b/hercules.c @@ -283,7 +283,8 @@ static void destroy_session_rx(struct hercules_session *session) { // there's no point in continuing. struct hercules_server *hercules_init_server( int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, - int queue, int xdp_mode, int n_threads, bool configure_queues) { + int queue, int xdp_mode, int n_threads, bool configure_queues, + bool enable_pcc) { struct hercules_server *server; server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); if (server == NULL) { @@ -310,8 +311,7 @@ struct hercules_server *hercules_init_server( server->config.xdp_mode = xdp_mode; /* server->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; */ // FIXME with flags set, setup may fail and we don't catch it? - server->enable_pcc = - true; // TODO this should be per-path or at least per-transfer + server->enable_pcc = enable_pcc; for (int i = 0; i < num_ifaces; i++) { server->ifaces[i] = (struct hercules_interface){ @@ -932,14 +932,11 @@ static char *rx_mmap(const char *pathname, size_t filesize) { close(f); return NULL; } - // TODO why shared mapping? char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); if (mem == MAP_FAILED) { close(f); return NULL; } - // TODO Shouldn't we keep the file open until the transfer is finished to - // prevent it being messed with? close(f); return mem; } @@ -1175,7 +1172,6 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s atomic_fetch_add(&rx_state->session->tx_npkts, 1); } libbpf_smp_wmb(); - // FIXME spurious segfault on unlock pthread_spin_unlock(&path_state->seq_rcvd.lock); path_state->nack_end = nack_end; } @@ -2043,8 +2039,6 @@ static void tx_send_p(void *arg) { allocate_tx_frames(server, frame_addrs); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, frame_addrs, &unit); - kick_tx_server( - server); // FIXME should not be needed and probably inefficient } } @@ -2268,26 +2262,25 @@ static void new_tx_if_available(struct hercules_server *server) { rbudp_headerlen > (size_t)mtu) { debug_printf("supplied MTU too small"); - // TODO update_job with err + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_BAD_MTU, 0, 0); return; } if (mtu > HERCULES_MAX_PKTSIZE){ debug_printf("supplied MTU too large"); - // TODO update_job with err + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_BAD_MTU, 0, 0); return; } size_t filesize; char *mem = tx_mmap(fname, &filesize); if (mem == NULL){ debug_printf("mmap failed"); - // TODO update_job with err + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_MAP_FAILED, 0, 0); return; } struct hercules_session *session = make_session(server); if (session == NULL){ - // TODO update_job with err - debug_printf("error creating session"); - munmap(mem, filesize); + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_INIT, 0, 0); + munmap(mem, filesize); // FIXME when to unmap? return; } session->state = SESSION_STATE_PENDING; @@ -2301,7 +2294,7 @@ static void new_tx_if_available(struct hercules_server *server) { debug_printf("error getting paths"); munmap(mem, filesize); /* destroy_session(session); */ // FIXME - // TODO update job err + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_NO_PATHS, 0, 0); return; } // TODO free paths @@ -2331,8 +2324,12 @@ static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { struct hercules_session *session_tx = atomic_load(&server->session_tx); if (session_tx && session_tx->state == SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { - monitor_update_job(server->usock, session_tx->jobid, session_tx->state, - session_tx->error, 0, 0); // FIXME 0 0 + u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; + u64 bytes_acked = session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set; + monitor_update_job(server->usock, session_tx->jobid, + session_tx->state, session_tx->error, + sec_elapsed, bytes_acked); struct hercules_session *current = server->session_tx; atomic_store(&server->session_tx, NULL); fprintf(stderr, "Cleaning up TX session...\n"); @@ -2915,7 +2912,7 @@ int main(int argc, char *argv[]) { // -q queue // -t TX worker threads // -r RX worker threads - unsigned int if_idxs[HERCULES_MAX_INTERFACES]; // XXX 10 should be enough + unsigned int if_idxs[HERCULES_MAX_INTERFACES]; int n_interfaces = 0; struct hercules_app_addr listen_addr = {.ia = 0, .ip = 0, .port = 0}; int xdp_mode = XDP_COPY; @@ -3003,7 +3000,8 @@ int main(int argc, char *argv[]) { queue, rx_threads, tx_threads, xdp_mode); struct hercules_server *server = - hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, rx_threads, false); + hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, + rx_threads, false, true); hercules_main(server); } diff --git a/hercules.h b/hercules.h index 32bc13c..3b1dc02 100644 --- a/hercules.h +++ b/hercules.h @@ -86,7 +86,7 @@ struct receiver_state { // The reply path to use for contacting the sender. This is the reversed // path of the last initial packet with the SET_RETURN_PATH flag set. - // TODO needs atomic? + // TODO needs atomic? -> perf? struct hercules_path reply_path; // Start/end time of the current transfer @@ -124,7 +124,7 @@ struct sender_state { struct bitset acked_chunks; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns - u32 return_path_idx; // TODO set where? + u32 return_path_idx; // TODO set where? struct path_set *_Atomic pathset; // Paths currently in use /** Filesize in bytes */ size_t filesize; @@ -160,8 +160,11 @@ enum session_error { SESSION_ERROR_STALE, //< Packets are being received, but none are new SESSION_ERROR_PCC, //< Something wrong with PCC SESSION_ERROR_SEQNO_OVERFLOW, - SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination - SESSION_ERROR_CANCELLED, //< Transfer cancelled by monitor + SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination + SESSION_ERROR_CANCELLED, //< Transfer cancelled by monitor + SESSION_ERROR_BAD_MTU, //< Invalid MTU supplied by the monitor + SESSION_ERROR_MAP_FAILED, //< Could not mmap file + SESSION_ERROR_INIT, //< Could not initialise session }; // A session is a transfer between one sender and one receiver diff --git a/monitor.c b/monitor.c index f25d1be..e0d128b 100644 --- a/monitor.c +++ b/monitor.c @@ -31,8 +31,8 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample path->headerlen = reply.payload.reply_path.path.headerlen; path->header.checksum = reply.payload.reply_path.path.chksum; path->enabled = true; - path->payloadlen = 1200 - path->headerlen; // TODO set correctly - path->framelen = 1200; + path->payloadlen = etherlen - path->headerlen; + path->framelen = etherlen; path->ifid = reply.payload.reply_path.path.ifid; return true; } diff --git a/monitor.h b/monitor.h index d3e983e..7a31d0e 100644 --- a/monitor.h +++ b/monitor.h @@ -76,7 +76,7 @@ struct sockmsg_new_job_A { uint16_t job_id; uint16_t mtu; uint16_t filename_len; - uint8_t filename[SOCKMSG_MAX_PAYLOAD]; + uint8_t filename[SOCKMSG_MAX_PAYLOAD]; // Filename *without* terminating 0-byte }; // Get paths to use for a given job ID diff --git a/monitor/monitor.go b/monitor/monitor.go index fdc75b2..42e333e 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -182,11 +182,10 @@ func main() { if selectedJob != nil { fmt.Println("sending file to daemon:", selectedJob.file, selectedJob.id) _, _ = headersToDestination(*selectedJob) // look up paths to fix mtu - // TODO Conversion between go and C strings? strlen := len(selectedJob.file) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) - b = binary.LittleEndian.AppendUint16(b, uint16(1200)) + b = binary.LittleEndian.AppendUint16(b, uint16(1200)) // FIXME b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) b = append(b, []byte(selectedJob.file)...) } else { From e960cfd1b633c8939c7bbe7ffd7684225c077f76 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 10 Jun 2024 14:35:40 +0200 Subject: [PATCH 036/112] add stat for gfal --- monitor/http_api.go | 27 +++++++++++++++++++++++++++ monitor/monitor.go | 1 + 2 files changed, 28 insertions(+) diff --git a/monitor/http_api.go b/monitor/http_api.go index 4cefd4f..6e7ba85 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "net/http" + "os" "strconv" "github.com/scionproto/scion/pkg/snet" @@ -90,3 +91,29 @@ func http_cancel(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "OK\n") } + +// Handle gfal's stat command +// GET Params: +// file: a file path +// Returns OK exists? size +func http_stat(w http.ResponseWriter, r *http.Request) { + if !r.URL.Query().Has("file") { + io.WriteString(w, "missing parameter") + return + } + file := r.URL.Query().Get("file") + info, err := os.Stat(file) + if os.IsNotExist(err){ + io.WriteString(w, "OK 0 0\n"); + return + } else if err != nil { + io.WriteString(w, "err\n") + return + } + if !info.Mode().IsRegular() { + io.WriteString(w, "err\n") + return + } + + io.WriteString(w, fmt.Sprintf("OK 1 %d\n", info.Size())) +} diff --git a/monitor/monitor.go b/monitor/monitor.go index 42e333e..b0d7a32 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -140,6 +140,7 @@ func main() { http.HandleFunc("/submit", http_submit) http.HandleFunc("/status", http_status) http.HandleFunc("/cancel", http_cancel) + http.HandleFunc("/stat", http_stat) go http.ListenAndServe(":8000", nil) // TODO remove finished sessions after a while From b0340c5c61255ab5be8e09e210765498e7772289 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 11 Jun 2024 12:04:31 +0200 Subject: [PATCH 037/112] more todos --- hercules.c | 21 +++++++++++++-------- hercules.h | 1 - 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/hercules.c b/hercules.c index 9f47094..355b96f 100644 --- a/hercules.c +++ b/hercules.c @@ -1053,7 +1053,7 @@ static void rx_handle_initial(struct hercules_server *server, // Send an empty ACK, indicating to the sender that it may start sending data // packets. -// XXX This is not strictly necessary, I think. Once the ACK sender thread sees +// This is not strictly necessary. Once the ACK sender thread sees // the session it will start sending ACKs, which will also be empty. static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) { @@ -1730,7 +1730,8 @@ static void tx_retransmit_initial(struct hercules_server *server, u64 now) { session_tx->last_pkt_sent + session_hs_retransmit_interval) { struct sender_state *tx_state = session_tx->tx_state; struct path_set *pathset = tx_state->pathset; - tx_send_initial(server, &pathset->paths[tx_state->return_path_idx], + // We always use the first path as the return path + tx_send_initial(server, &pathset->paths[0], tx_state->filename, tx_state->filesize, tx_state->chunklen, now, 0, session_tx->etherlen, true, true); session_tx->last_pkt_sent = now; @@ -1766,7 +1767,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, } ccontrol_update_rtt(pathset->paths[0].cc_state, tx_state->handshake_rtt); - // TODO assumption: return path is always idx 0 + // Return path is always idx 0 fprintf(stderr, "[receiver %d] [path 0] handshake_rtt: " "%fs, MI: %fs\n", @@ -2339,7 +2340,6 @@ static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { // until after the next session has completed. At that point, no // references to the deferred session should be around, so we then // free it. - // XXX Is this really good enough? destroy_session_tx(server->deferred_tx); server->deferred_tx = current; } @@ -2422,8 +2422,7 @@ static void tx_update_paths(struct hercules_server *server) { } else { debug_printf("Path %d changed, resetting CC", i); if (i == 0) { - // TODO assumption (also in other places): return path is - // always idx 0 + // Return path is always idx 0 replaced_return_path = true; } // TODO whether to use pcc should be decided on a per-path basis @@ -2736,10 +2735,16 @@ static void events_p(void *arg) { count_received_pkt(server->session_tx, h->path); nack_trace_push(cp->payload.ack.timestamp, cp->payload.ack.ack_nr); + struct path_set *pathset = + server->session_tx->tx_state->pathset; + if (h->path > pathset->n_paths) { + // The pathset was updated in the meantime and + // there are now fewer paths, so ignore this + break; + } tx_register_nacks( &cp->payload.ack, - // TODO h->path may be too large if pathset changed - server->session_tx->tx_state->pathset->paths[h->path].cc_state); + pathset->paths[h->path].cc_state); } break; default: diff --git a/hercules.h b/hercules.h index 3b1dc02..3be0b39 100644 --- a/hercules.h +++ b/hercules.h @@ -124,7 +124,6 @@ struct sender_state { struct bitset acked_chunks; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns - u32 return_path_idx; // TODO set where? struct path_set *_Atomic pathset; // Paths currently in use /** Filesize in bytes */ size_t filesize; From 45b3277661a52243e676e7e8065cba671f0381a5 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 11 Jun 2024 18:01:02 +0200 Subject: [PATCH 038/112] split umem lock --- hercules.c | 18 +++++++++++------- hercules.h | 5 ++++- xdp.c | 3 ++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/hercules.c b/hercules.c index 355b96f..aff4012 100644 --- a/hercules.c +++ b/hercules.c @@ -836,12 +836,12 @@ static void submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames) { u32 idx_fq = 0; - pthread_spin_lock(&umem->lock); + pthread_spin_lock(&umem->fq_lock); size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); while(reserved != num_frames) { reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); if(session == NULL || session->state != SESSION_STATE_RUNNING) { - pthread_spin_unlock(&umem->lock); + pthread_spin_unlock(&umem->fq_lock); return; } } @@ -850,7 +850,7 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c *xsk_ring_prod__fill_addr(&umem->fq, idx_fq++) = addrs[i]; } xsk_ring_prod__submit(&umem->fq, num_frames); - pthread_spin_unlock(&umem->lock); + pthread_spin_unlock(&umem->fq_lock); } // Read a batch of data packets from the XSK @@ -1387,7 +1387,7 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { // TODO FIXME Lock contention, significantly affects performance - pthread_spin_lock(&iface->umem->lock); + pthread_spin_lock(&iface->umem->frames_lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); while(reserved != num_frames) { // When we're not getting any frames, we might need to... @@ -1398,7 +1398,7 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_inte struct hercules_session *s = atomic_load(&server->session_tx); if(!s || atomic_load(&s->state) != SESSION_STATE_RUNNING) { debug_printf("STOP"); - pthread_spin_unlock(&iface->umem->lock); + pthread_spin_unlock(&iface->umem->frames_lock); return; } } @@ -1407,7 +1407,7 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_inte addrs[i] = frame_queue__cons_fetch(&iface->umem->available_frames, i); } frame_queue__pop(&iface->umem->available_frames, num_frames); - pthread_spin_unlock(&iface->umem->lock); + pthread_spin_unlock(&iface->umem->frames_lock); } static char *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_idx, size_t framelen) @@ -2862,7 +2862,11 @@ struct path_stats *make_path_stats_buffer(int num_paths) { void hercules_main(struct hercules_server *server) { debug_printf("Hercules main"); - xdp_setup(server); + int ret = xdp_setup(server); + if (ret != 0){ + fprintf(stderr, "Error in XDP setup!\n"); + exit(1); + } // Start event receiver thread debug_printf("Starting event receiver thread"); diff --git a/hercules.h b/hercules.h index 3be0b39..3fea063 100644 --- a/hercules.h +++ b/hercules.h @@ -236,7 +236,10 @@ struct xsk_umem_info { struct xsk_ring_prod fq; struct xsk_ring_cons cq; struct frame_queue available_frames; - pthread_spinlock_t lock; + // TODO ok to have locks closeby? + pthread_spinlock_t fq_lock; // Lock for the fill queue (fq) + pthread_spinlock_t + frames_lock; // Lock for the frame queue (available_frames) struct xsk_umem *umem; void *buffer; struct hercules_interface *iface; diff --git a/xdp.c b/xdp.c index 1c7f8e9..76459b9 100644 --- a/xdp.c +++ b/xdp.c @@ -58,7 +58,8 @@ struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, if (ret) { return NULL; } - pthread_spin_init(&umem->lock, 0); + pthread_spin_init(&umem->fq_lock, 0); + pthread_spin_init(&umem->frames_lock, 0); return umem; } From 997506051efd9341367bfeffb96230ac9830094a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 11 Jun 2024 18:59:40 +0200 Subject: [PATCH 039/112] add monitor socket, interfaces and listen addr to config --- monitor/config.go | 45 ++++++++++++++++++++++++++++++++--- monitor/http_api.go | 2 +- monitor/monitor.go | 40 ++++++++++++++----------------- monitor/pathstodestination.go | 1 + monitor/sampleconf.toml | 10 ++++++++ 5 files changed, 72 insertions(+), 26 deletions(-) diff --git a/monitor/config.go b/monitor/config.go index 4e5bf60..293c0d2 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "net" "os" "github.com/BurntSushi/toml" @@ -20,11 +21,35 @@ type ASConfig struct { NumPaths int PathSpec []PathSpec } + +// This wraps snet.UDPAddr to make the config parsing work +type UDPAddr struct { + addr *snet.UDPAddr +} + +func (a *UDPAddr) UnmarshalText(text []byte) error { + var err error + a.addr, err = snet.ParseUDPAddr(string(text)) + return err +} + +type Interface struct { + iface *net.Interface +} + +func (i *Interface) UnmarshalText(text []byte) error { + var err error + i.iface, err = net.InterfaceByName(string(text)) + return err +} + type MonitorConfig struct { DestinationHosts []HostConfig DestinationASes []ASConfig DefaultNumPaths int MonitorSocket string + ListenAddress UDPAddr + Interfaces []Interface } type PathRules struct { @@ -61,6 +86,11 @@ func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { } } +const defaultConfigPath = "herculesmon.conf" +const defaultMonitorSocket = "var/run/herculesmon.sock" + +// Decode the config file and fill in any unspecified values with defaults. +// Will exit if an error occours or a required value is not specified. func readConfig(configFile string) (MonitorConfig, PathRules) { var config MonitorConfig meta, err := toml.DecodeFile(configFile, &config) @@ -79,13 +109,22 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { } if config.MonitorSocket == "" { - config.MonitorSocket = "/var/herculesmon.sock" // TODO should be in /var/run + config.MonitorSocket = defaultMonitorSocket } - fmt.Println(config.MonitorSocket, config.MonitorSocket == "", config.MonitorSocket == "") + // This is required + if config.ListenAddress.addr == nil { + fmt.Println("Error: Listening address not specified") + os.Exit(1) + } + + if len(config.Interfaces) == 0 { + fmt.Println("Error: No interfaces specified") + os.Exit(1) + } pathRules := PathRules{} - // TODO Would be nice not to have to do this dance and specify the maps directly in the config file, + // It would be nice not to have to do this dance and specify the maps directly in the config file, // but the toml package crashes if the keys are addr.Addr pathRules.Hosts = map[addr.Addr]HostConfig{} diff --git a/monitor/http_api.go b/monitor/http_api.go index 6e7ba85..643601a 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -28,7 +28,7 @@ func http_submit(w http.ResponseWriter, r *http.Request) { return } destination := findPathRule(&pathRules, destParsed) - pm, _ := initNewPathManager(interfaces, &destination, localAddress) + pm, _ := initNewPathManager(activeInterfaces, &destination, listenAddress) transfersLock.Lock() jobid := nextID diff --git a/monitor/monitor.go b/monitor/monitor.go index b0d7a32..b9786e0 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -21,8 +21,8 @@ const HerculesMaxPktsize = C.HERCULES_MAX_PKTSIZE // Select paths and serialize headers for a given transfer func headersToDestination(transfer HerculesTransfer) (int, []byte) { srcA := addr.Addr{ - IA: localAddress.IA, - Host: addr.MustParseHost(localAddress.Host.IP.String()), + IA: listenAddress.IA, + Host: addr.MustParseHost(listenAddress.Host.IP.String()), } dstA := addr.Addr{ IA: transfer.dest.IA, @@ -82,33 +82,32 @@ var transfersLock sync.Mutex // To protect the map below var transfers = map[int]*HerculesTransfer{} var nextID int = 1 // ID to use for the next transfer -var localAddress *snet.UDPAddr -var interfaces []*net.Interface +// These are needed by the HTTP handlers +var listenAddress *snet.UDPAddr +var activeInterfaces []*net.Interface var pathRules PathRules func main() { - var localAddr string var configFile string - flag.StringVar(&localAddr, "l", "", "local address") - flag.StringVar(&configFile, "c", "herculesmon.conf", "Path to the monitor configuration file") + flag.StringVar(&configFile, "c", defaultConfigPath, "Path to the monitor configuration file") flag.Parse() var config MonitorConfig config, pathRules = readConfig(configFile) fmt.Println(config) - src, err := snet.ParseUDPAddr(localAddr) - if err != nil || src.Host.Port == 0 { - flag.Usage() + if config.ListenAddress.addr.Host.Port == 0 { + fmt.Println("No listening port specified") os.Exit(1) } - localAddress = src - GlobalQuerier = newPathQuerier() // TODO can the connection time out? + listenAddress = config.ListenAddress.addr + + GlobalQuerier = newPathQuerier() // TODO can the connection time out or break? - // TODO make socket paths congfigurable monitorSocket, err := net.ResolveUnixAddr("unixgram", config.MonitorSocket) if err != nil { + fmt.Printf("Error resolving socket address: %s\n", config.MonitorSocket) os.Exit(1) } @@ -119,18 +118,15 @@ func main() { os.Exit(1) } - // TODO interfaces from config - ifs, _ := net.Interfaces() - iffs := []*net.Interface{} - for i, _ := range ifs { - iffs = append(iffs, &ifs[i]) + activeInterfaces = []*net.Interface{} + for _, i := range config.Interfaces { + activeInterfaces = append(activeInterfaces, i.iface) } - interfaces = iffs // used for looking up reply path interface - pm, err := initNewPathManager(iffs, &Destination{ - hostAddr: localAddress, - }, src) + pm, err := initNewPathManager(activeInterfaces, &Destination{ + hostAddr: config.ListenAddress.addr, + }, config.ListenAddress.addr) if err != nil { fmt.Printf("Error initialising path manager: %v\n", err) os.Exit(1) diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index a00ec71..532d6ac 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -85,6 +85,7 @@ func (ptd *PathsToDestination) choosePaths() bool { var err error ptd.allPaths, err = GlobalQuerier.Query(context.Background(), ptd.dst.hostAddr.IA) if err != nil { + fmt.Println("Error querying paths:", err) return false } diff --git a/monitor/sampleconf.toml b/monitor/sampleconf.toml index 38f08a9..8e8aeff 100644 --- a/monitor/sampleconf.toml +++ b/monitor/sampleconf.toml @@ -3,6 +3,16 @@ # By default, up to `DefaultNumPaths` paths will be used. DefaultNumPaths = 1 +# Path to the monitor socket +MonitorSocket = "var/run/herculesmon.sock" + +# SCION address the Hercules server should listen on +ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" + +Interfaces = [ +"eth0", +] + # The number and choice of paths can be overridden on a destination-host # or destination-AS basis. In case both an AS and Host rule match, the Host # rule takes precedence. From f8580cba63308b3e739c64656a23325fa24d6c7b Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 13 Jun 2024 10:37:55 +0200 Subject: [PATCH 040/112] Determine payload length based on selected paths --- Makefile | 2 +- hercules.c | 66 ++++------ hercules.h | 6 +- monitor.c | 241 ++++++++++++++++++---------------- monitor.h | 6 +- monitor/config.go | 5 + monitor/monitor.go | 24 ++-- monitor/pathmanager.go | 21 ++- monitor/pathstodestination.go | 98 +++++++------- monitor/scionheader.go | 73 +++++++--- packet.h | 1 - 11 files changed, 289 insertions(+), 254 deletions(-) diff --git a/Makefile b/Makefile index 15598f1..576fc13 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TARGET_SERVER := runHercules TARGET_MONITOR := runMonitor CC := gcc -CFLAGS = -O3 -std=gnu11 -D_GNU_SOURCE +CFLAGS = -O0 -std=gnu11 -D_GNU_SOURCE # CFLAGS += -Wall -Wextra # for debugging: CFLAGS += -g3 -DDEBUG diff --git a/hercules.c b/hercules.c index aff4012..c17727c 100644 --- a/hercules.c +++ b/hercules.c @@ -945,7 +945,6 @@ static char *rx_mmap(const char *pathname, size_t filesize) { static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t namelen, size_t filesize, int chunklen, - int etherlen, bool is_pcc_benchmark) { struct receiver_state *rx_state; rx_state = calloc(1, sizeof(*rx_state)); @@ -967,7 +966,6 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, free(rx_state); return NULL; } - rx_state->etherlen = etherlen; return rx_state; } @@ -976,6 +974,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, // path reversed. static bool rx_update_reply_path( struct hercules_server *server, struct receiver_state *rx_state, int ifid, + int etherlen, int rx_sample_len, const char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]) { debug_printf("Updating reply path"); if (!rx_state) { @@ -988,7 +987,7 @@ static bool rx_update_reply_path( // TODO writing to reply path needs sync? int ret = monitor_get_reply_path(server->usock, rx_sample_buf, rx_sample_len, - rx_state->etherlen, &rx_state->reply_path); + etherlen, &rx_state->reply_path); if (!ret) { return false; } @@ -1045,7 +1044,10 @@ static void rx_handle_initial(struct hercules_server *server, debug_printf("handling initial"); const int headerlen = (int)(payload - buf); if (initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - rx_update_reply_path(server, rx_state, ifid, headerlen + payloadlen, buf); + debug_printf("initial headerlen, payloadlen: %d, %d", headerlen, payloadlen); + // XXX Why use both initial->chunklen (transmitted) and the size of the received packet? + // Are they ever not the same? + rx_update_reply_path(server, rx_state, ifid, initial->chunklen + headerlen, headerlen + payloadlen, buf); } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize @@ -1298,7 +1300,7 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, u32 etherlen, bool set_return_path, bool new_transfer) +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) { debug_printf("Sending initial"); char buf[HERCULES_MAX_PKTSIZE]; @@ -1321,7 +1323,6 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path .path_index = path_index, .flags = flags, .name_len = strlen(filename), - .etherlen = etherlen, }, }; assert(strlen(filename) < 100); // TODO @@ -1375,11 +1376,10 @@ void send_path_handshakes(struct hercules_server *server, struct sender_state *t now + PATH_HANDSHAKE_TIMEOUT_NS)) { debug_printf("sending hs on path %d", p); // FIXME file name below? - tx_send_initial(server, path, "abcd", tx_state->filesize, - tx_state->chunklen, get_nsecs(), p, 0, - false, false); - } - } + tx_send_initial(server, path, "abcd", tx_state->filesize, + tx_state->chunklen, get_nsecs(), p, false, false); + } + } } } } @@ -1733,7 +1733,7 @@ static void tx_retransmit_initial(struct hercules_server *server, u64 now) { // We always use the first path as the return path tx_send_initial(server, &pathset->paths[0], tx_state->filename, tx_state->filesize, - tx_state->chunklen, now, 0, session_tx->etherlen, true, true); + tx_state->chunklen, now, 0, true, true); session_tx->last_pkt_sent = now; } } @@ -2251,26 +2251,21 @@ static void new_tx_if_available(struct hercules_server *server) { memset(fname, 0, 1000); int count; u16 jobid; - u16 mtu; + u16 payloadlen; struct hercules_app_addr dest; - int ret = monitor_get_new_job(server->usock, fname, &jobid, &dest, &mtu); + int ret = monitor_get_new_job(server->usock, fname, &jobid, &dest, &payloadlen); if (!ret) { return; } debug_printf("new job: %s", fname); - if (HERCULES_MAX_HEADERLEN + sizeof(struct rbudp_initial_pkt) + - rbudp_headerlen > - (size_t)mtu) { - debug_printf("supplied MTU too small"); - monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_BAD_MTU, 0, 0); - return; - } - if (mtu > HERCULES_MAX_PKTSIZE){ - debug_printf("supplied MTU too large"); + + if (sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)payloadlen) { + debug_printf("supplied payloadlen too small"); monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_BAD_MTU, 0, 0); return; } + size_t filesize; char *mem = tx_mmap(fname, &filesize); if (mem == NULL){ @@ -2285,12 +2280,12 @@ static void new_tx_if_available(struct hercules_server *server) { return; } session->state = SESSION_STATE_PENDING; - session->etherlen = mtu; + session->payloadlen = payloadlen; session->jobid = jobid; int n_paths; struct hercules_path *paths; - ret = monitor_get_paths(server->usock, jobid, &n_paths, &paths); + ret = monitor_get_paths(server->usock, jobid, payloadlen, &n_paths, &paths); if (!ret || n_paths == 0){ debug_printf("error getting paths"); munmap(mem, filesize); @@ -2301,17 +2296,14 @@ static void new_tx_if_available(struct hercules_server *server) { // TODO free paths debug_printf("received %d paths", n_paths); - // TODO If the paths don't have the same header length this does not work: - // If the first path has a shorter header than the second, chunks won't fit - // on the second path - u32 chunklen = paths[0].payloadlen - rbudp_headerlen; - atomic_store(&server->session_tx, session); + int chunklen = payloadlen - rbudp_headerlen; struct sender_state *tx_state = init_tx_state( - server->session_tx, filesize, chunklen, server->rate_limit, mem, + session, filesize, chunklen, server->rate_limit, mem, &session->peer, paths, 1, n_paths, server->max_paths); strncpy(tx_state->filename, fname, 99); - server->session_tx->tx_state = tx_state; + session->tx_state = tx_state; + atomic_store(&server->session_tx, session); } // Remove and free finished sessions @@ -2386,8 +2378,8 @@ static void tx_update_paths(struct hercules_server *server) { struct path_set *old_pathset = tx_state->pathset; int n_paths; struct hercules_path *paths; - bool ret = - monitor_get_paths(server->usock, session_tx->jobid, &n_paths, &paths); + bool ret = monitor_get_paths(server->usock, session_tx->jobid, + session_tx->payloadlen, &n_paths, &paths); if (!ret) { debug_printf("error getting paths"); return; @@ -2678,9 +2670,9 @@ static void events_p(void *arg) { server->session_rx = session; session->state = SESSION_STATE_NEW; struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt->name, parsed_pkt->name_len, - parsed_pkt->filesize, parsed_pkt->chunklen, - parsed_pkt->etherlen, false); + session, parsed_pkt->name, + parsed_pkt->name_len, parsed_pkt->filesize, + parsed_pkt->chunklen, false); session->rx_state = rx_state; rx_handle_initial(server, rx_state, parsed_pkt, buf, addr.sll_ifindex, diff --git a/hercules.h b/hercules.h index 3fea063..4997b18 100644 --- a/hercules.h +++ b/hercules.h @@ -79,8 +79,6 @@ struct receiver_state { u32 total_chunks; /** Memory mapped file for receive */ char *mem; - /** Packet size */ - u32 etherlen; struct bitset received_chunks; @@ -180,7 +178,9 @@ struct hercules_session { // already-seen chunks for a while something is // probably wrong u32 jobid; //< The monitor's ID for this job - u32 etherlen; + u32 payloadlen; //< The payload length used for this transfer. Note that + // the payload length includes the rbudp header while the + // chunk length does not. struct hercules_app_addr peer; diff --git a/monitor.c b/monitor.c index e0d128b..3b748f4 100644 --- a/monitor.c +++ b/monitor.c @@ -1,6 +1,5 @@ #include "monitor.h" -#include "hercules.h" -#include "utils.h" + #include #include #include @@ -8,126 +7,138 @@ #include #include -bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, int etherlen, - struct hercules_path *path) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - - struct hercules_sockmsg_Q msg; - msg.msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; - msg.payload.reply_path.etherlen = etherlen; - msg.payload.reply_path.sample_len = rx_sample_len; - memcpy(msg.payload.reply_path.sample, rx_sample_buf, rx_sample_len); - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); // TODO return val - - struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); - debug_printf("Read %d bytes", n); - if (n <= 0) { - return false; - } - memcpy(&path->header, reply.payload.reply_path.path.header, reply.payload.reply_path.path.headerlen); - path->headerlen = reply.payload.reply_path.path.headerlen; - path->header.checksum = reply.payload.reply_path.path.chksum; - path->enabled = true; - path->payloadlen = etherlen - path->headerlen; - path->framelen = etherlen; - path->ifid = reply.payload.reply_path.path.ifid; - return true; +#include "hercules.h" +#include "utils.h" + +bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, + int rx_sample_len, int etherlen, + struct hercules_path *path) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + + struct hercules_sockmsg_Q msg; + msg.msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; + msg.payload.reply_path.etherlen = etherlen; + msg.payload.reply_path.sample_len = rx_sample_len; + memcpy(msg.payload.reply_path.sample, rx_sample_buf, rx_sample_len); + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, + sizeof(monitor)); // TODO return val + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); + debug_printf("Read %d bytes", n); + if (n <= 0) { + return false; + } + memcpy(&path->header, reply.payload.reply_path.path.header, + reply.payload.reply_path.path.headerlen); + path->headerlen = reply.payload.reply_path.path.headerlen; + path->header.checksum = reply.payload.reply_path.path.chksum; + path->enabled = true; + path->payloadlen = etherlen - path->headerlen; + path->framelen = etherlen; + path->ifid = reply.payload.reply_path.path.ifid; + return true; } -bool monitor_get_paths(int sockfd, int job_id, int *n_paths, - struct hercules_path **paths) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - - struct hercules_sockmsg_Q msg; - msg.msgtype = SOCKMSG_TYPE_GET_PATHS; - msg.payload.paths.job_id = job_id; - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); - - struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); - debug_printf("receive %d bytes", n); - - int received_paths = reply.payload.paths.n_paths; - struct hercules_path *p = - calloc(received_paths, sizeof(struct hercules_path)); - - for (int i = 0; i < received_paths; i++) { - p[i].headerlen = reply.payload.paths.paths[i].headerlen; - memcpy(&p[i].header, reply.payload.paths.paths[i].header, - p[i].headerlen); - p[i].header.checksum = reply.payload.paths.paths[i].chksum; - p[i].enabled = true; - p[i].payloadlen = 1200 - p[i].headerlen; // TODO set correctly - p[i].framelen = 1200; - p[i].ifid = reply.payload.paths.paths[i].ifid; - } - - *n_paths = received_paths; - *paths = p; - return true; +// The payload length is fixed when first fetching the job, we pass it in here +// to compute the paths payload and frame lengths. +bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, + struct hercules_path **paths) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + + struct hercules_sockmsg_Q msg; + msg.msgtype = SOCKMSG_TYPE_GET_PATHS; + msg.payload.paths.job_id = job_id; + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); + debug_printf("receive %d bytes", n); + + int received_paths = reply.payload.paths.n_paths; + struct hercules_path *p = + calloc(received_paths, sizeof(struct hercules_path)); + + for (int i = 0; i < received_paths; i++) { + p[i].headerlen = reply.payload.paths.paths[i].headerlen; + memcpy(&p[i].header, reply.payload.paths.paths[i].header, + p[i].headerlen); + p[i].header.checksum = reply.payload.paths.paths[i].chksum; + p[i].enabled = true; + p[i].payloadlen = payloadlen; + p[i].framelen = p[i].headerlen + payloadlen; + p[i].ifid = reply.payload.paths.paths[i].ifid; + } + + *n_paths = received_paths; + *paths = p; + return true; } -bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, struct hercules_app_addr *dest, u16 *mtu) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - - struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); - - struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); - if (!reply.payload.newjob.has_job){ - return false; - } - // XXX name needs to be allocated large enough by caller - strncpy(name, reply.payload.newjob.filename, reply.payload.newjob.filename_len); - *job_id = reply.payload.newjob.job_id; - debug_printf("received job id %d", reply.payload.newjob.job_id); - *mtu = reply.payload.newjob.mtu; - return true; +bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, + struct hercules_app_addr *dest, u16 *payloadlen) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + + struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); + if (!reply.payload.newjob.has_job) { + return false; + } + // XXX name needs to be allocated large enough by caller + strncpy(name, reply.payload.newjob.filename, + reply.payload.newjob.filename_len); + *job_id = reply.payload.newjob.job_id; + debug_printf("received job id %d", reply.payload.newjob.job_id); + *payloadlen = reply.payload.newjob.payloadlen; + return true; } -bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum session_error err, u64 seconds_elapsed, u64 bytes_acked){ - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - - struct hercules_sockmsg_Q msg; - msg.msgtype = SOCKMSG_TYPE_UPDATE_JOB; - msg.payload.job_update.job_id = job_id; - msg.payload.job_update.status = state; - msg.payload.job_update.error = err; - msg.payload.job_update.seconds_elapsed = seconds_elapsed; - msg.payload.job_update.bytes_acked = bytes_acked; - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); - - struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); - if (!reply.payload.job_update.ok){ - return false; - } - return true; +bool monitor_update_job(int sockfd, int job_id, enum session_state state, + enum session_error err, u64 seconds_elapsed, + u64 bytes_acked) { + struct sockaddr_un monitor; + monitor.sun_family = AF_UNIX; + strcpy(monitor.sun_path, "/var/herculesmon.sock"); + + struct hercules_sockmsg_Q msg; + msg.msgtype = SOCKMSG_TYPE_UPDATE_JOB; + msg.payload.job_update.job_id = job_id; + msg.payload.job_update.status = state; + msg.payload.job_update.error = err; + msg.payload.job_update.seconds_elapsed = seconds_elapsed; + msg.payload.job_update.bytes_acked = bytes_acked; + sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + + struct hercules_sockmsg_A reply; + int n = recv(sockfd, &reply, sizeof(reply), 0); + if (!reply.payload.job_update.ok) { + return false; + } + return true; } #define HERCULES_DAEMON_SOCKET_PATH "/var/hercules.sock" -int monitor_bind_daemon_socket(){ - int usock = socket(AF_UNIX, SOCK_DGRAM, 0); - if (usock <= 0){ - return 0; - } - struct sockaddr_un name; - name.sun_family = AF_UNIX; - strcpy(name.sun_path, HERCULES_DAEMON_SOCKET_PATH); - unlink(HERCULES_DAEMON_SOCKET_PATH); - int ret = bind(usock, &name, sizeof(name)); - if (ret){ - return 0; - } - return usock; +int monitor_bind_daemon_socket() { + int usock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (usock <= 0) { + return 0; + } + struct sockaddr_un name; + name.sun_family = AF_UNIX; + strcpy(name.sun_path, HERCULES_DAEMON_SOCKET_PATH); + unlink(HERCULES_DAEMON_SOCKET_PATH); + int ret = bind(usock, &name, sizeof(name)); + if (ret) { + return 0; + } + return usock; } diff --git a/monitor.h b/monitor.h index 7a31d0e..3755a72 100644 --- a/monitor.h +++ b/monitor.h @@ -14,14 +14,14 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, // Get SCION paths from the monitor. The caller is responsible for freeing // **paths. -bool monitor_get_paths(int sockfd, int job_id, int *n_paths, +bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, struct hercules_path **paths); // Check if the monitor has a new job available. // If so the function returns true and the job's details are filled into the // arguments. bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, - struct hercules_app_addr *dest, u16 *mtu); + struct hercules_app_addr *dest, u16 *payloadlen); // Inform the monitor about a transfer's (new) status. bool monitor_update_job(int sockfd, int job_id, enum session_state state, @@ -74,7 +74,7 @@ struct sockmsg_new_job_Q {}; struct sockmsg_new_job_A { uint8_t has_job; // The other fields are only valid if this is set to 1 uint16_t job_id; - uint16_t mtu; + uint16_t payloadlen; uint16_t filename_len; uint8_t filename[SOCKMSG_MAX_PAYLOAD]; // Filename *without* terminating 0-byte }; diff --git a/monitor/config.go b/monitor/config.go index 293c0d2..7ba4f51 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -118,6 +118,11 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { os.Exit(1) } + if config.ListenAddress.addr.Host.Port == 0 { + fmt.Println("No listening port specified") + os.Exit(1) + } + if len(config.Interfaces) == 0 { fmt.Println("Error: No interfaces specified") os.Exit(1) diff --git a/monitor/monitor.go b/monitor/monitor.go index b9786e0..d774ec7 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -39,8 +39,13 @@ func headersToDestination(transfer HerculesTransfer) (int, []byte) { numSelectedPaths := len(enabledPaths) headers_ser := []byte{} for _, p := range enabledPaths { - preparedHeader := prepareHeader(p, transfer.pm.mtu, *transfer.pm.src.Host, *transfer.dest.Host, srcA, dstA) - serializedHeader := SerializePath(&preparedHeader, p.iface.Index, C.HERCULES_MAX_HEADERLEN) + preparedHeader, err := prepareHeader(p, transfer.pm.payloadLen, *transfer.pm.src.Host, *transfer.dest.Host, srcA, dstA) + if err != nil { + fmt.Println("Error preparing header!", err) + numSelectedPaths-- + continue + } + serializedHeader := SerializePathHeader(&preparedHeader, p.iface.Index, C.HERCULES_MAX_HEADERLEN) if serializedHeader == nil { fmt.Printf("Unable to serialize header for path: %v\n", p.path) numSelectedPaths-- @@ -85,7 +90,6 @@ var nextID int = 1 // ID to use for the next transfer // These are needed by the HTTP handlers var listenAddress *snet.UDPAddr var activeInterfaces []*net.Interface - var pathRules PathRules func main() { @@ -95,12 +99,7 @@ func main() { var config MonitorConfig config, pathRules = readConfig(configFile) - fmt.Println(config) - if config.ListenAddress.addr.Host.Port == 0 { - fmt.Println("No listening port specified") - os.Exit(1) - } listenAddress = config.ListenAddress.addr GlobalQuerier = newPathQuerier() // TODO can the connection time out or break? @@ -148,27 +147,30 @@ func main() { n, a, err := usock.ReadFromUnix(buf) if err != nil { fmt.Println("Error reading from socket!", err) + os.Exit(1) } if n > 0 { msgtype := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] switch msgtype { + case C.SOCKMSG_TYPE_GET_REPLY_PATH: sample_len := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] etherlen := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] + fmt.Println("sampel len, etherlen", sample_len, etherlen) replyPath, nextHop, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) fmt.Println(err) // TODO signal error to server? iface, _ := pm.interfaceForRoute(nextHop) - b := SerializePath(replyPath, iface.Index, C.HERCULES_MAX_HEADERLEN) + b := SerializePathHeader(replyPath, iface.Index, C.HERCULES_MAX_HEADERLEN) usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_NEW_JOB: transfersLock.Lock() var selectedJob *HerculesTransfer = nil for _, job := range transfers { - if job.status == 0 { + if job.status == Queued { selectedJob = job job.status = Submitted break @@ -182,7 +184,7 @@ func main() { strlen := len(selectedJob.file) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) - b = binary.LittleEndian.AppendUint16(b, uint16(1200)) // FIXME + b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.pm.payloadLen)) b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) b = append(b, []byte(selectedJob.file)...) } else { diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 853739d..49ed1c3 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -32,7 +32,7 @@ type PathManager struct { interfaces map[int]*net.Interface dst *PathsToDestination src *snet.UDPAddr - mtu int + payloadLen int // The payload length to use for this transfer. Paths must be able to transfer payloads of at least this size. } type PathWithInterface struct { @@ -40,8 +40,6 @@ type PathWithInterface struct { iface *net.Interface } -type AppPathSet map[snet.PathFingerprint]PathWithInterface - func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr) (*PathManager, error) { ifMap := make(map[int]*net.Interface) for _, iface := range interfaces { @@ -52,14 +50,14 @@ func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet interfaces: ifMap, src: src, dst: &PathsToDestination{}, - mtu: 0, // Will be set later, after the first path lookup + payloadLen: 0, // Will be set later, after the first path lookup } if src.IA == dst.hostAddr.IA { pm.dst = initNewPathsToDestinationWithEmptyPath(pm, dst) } else { var err error - pm.dst, err = initNewPathsToDestination(pm, src, dst) + pm.dst, err = initNewPathsToDestination(pm, dst) if err != nil { return nil, err } @@ -68,10 +66,6 @@ func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet return pm, nil } -func (pm *PathManager) canSendToDest() bool { - return pm.dst.hasUsablePaths() -} - func (pm *PathManager) choosePaths() bool { return pm.dst.choosePaths() } @@ -88,10 +82,16 @@ func (pm *PathManager) filterPathsByActiveInterfaces(pathsAvail []snet.Path) []P return pathsFiltered } +// Don't consider paths that cannot fit the required payload length func (pm *PathManager) filterPathsByMTU(pathsAvail []PathWithInterface) []PathWithInterface { pathsFiltered := []PathWithInterface{} for _, path := range pathsAvail { - if path.path.Metadata().MTU >= uint16(pm.mtu){ + // The path MTU refers to the maximum length of the SCION headers and payload, + // but not including the lower-level (ethernet/ip/udp) headers + pathMTU := int(path.path.Metadata().MTU) + _, pathScionHeaderlen := getPathHeaderlen(path.path) + pathPayloadlen := pathMTU - pathScionHeaderlen + if pathPayloadlen >= pm.payloadLen { pathsFiltered = append(pathsFiltered, path) } } @@ -104,7 +104,6 @@ func (pm *PathManager) interfaceForRoute(ip net.IP) (*net.Interface, error) { return nil, fmt.Errorf("could not find route for destination %s: %s", ip, err) } - fmt.Println(pm.interfaces) for _, route := range routes { if iface, ok := pm.interfaces[route.LinkIndex]; ok { fmt.Printf("sending via #%d (%s) to %s\n", route.LinkIndex, pm.interfaces[route.LinkIndex].Name, ip) diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 532d6ac..fb3dad9 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -28,21 +28,18 @@ import ( var GlobalQuerier snet.PathQuerier type PathsToDestination struct { - pm *PathManager - dst *Destination - allPaths []snet.Path - paths []PathMeta // nil indicates that the destination is in the same AS as the sender and we can use an empty path - canSendLocally bool // (only if destination in same AS) indicates if we can send packets + pm *PathManager + dst *Destination + paths []PathMeta // Paths to use for sending } type PathMeta struct { - path snet.Path - fingerprint snet.PathFingerprint - iface *net.Interface - enabled bool // Indicates whether this path can be used at the moment - updated bool // Indicates whether this path needs to be synced to the C path + path snet.Path + iface *net.Interface + enabled bool // Indicates whether this path can be used at the moment } +// Packet header (including lower-level headers) as used by the C part type HerculesPathHeader struct { Header []byte //!< C.HERCULES_MAX_HEADERLEN bytes PartialChecksum uint16 //SCION L4 checksum over header with 0 payload @@ -54,47 +51,35 @@ func initNewPathsToDestinationWithEmptyPath(pm *PathManager, dst *Destination) * Port: topology.EndhostPort, } return &PathsToDestination{ - pm: pm, - dst: dst, - paths: make([]PathMeta, 1), + pm: pm, + dst: dst, + paths: make([]PathMeta, 1), } } -func initNewPathsToDestination(pm *PathManager, src *snet.UDPAddr, dst *Destination) (*PathsToDestination, error) { +func initNewPathsToDestination(pm *PathManager, dst *Destination) (*PathsToDestination, error) { return &PathsToDestination{ - pm: pm, - dst: dst, - allPaths: nil, - paths: make([]PathMeta, dst.numPaths), + pm: pm, + dst: dst, + paths: make([]PathMeta, dst.numPaths), }, nil } -func (ptd *PathsToDestination) hasUsablePaths() bool { - if ptd.paths == nil { - return ptd.canSendLocally - } - for _, path := range ptd.paths { - if path.enabled { - return true - } - } - return false -} - func (ptd *PathsToDestination) choosePaths() bool { var err error - ptd.allPaths, err = GlobalQuerier.Query(context.Background(), ptd.dst.hostAddr.IA) + allPaths, err := GlobalQuerier.Query(context.Background(), ptd.dst.hostAddr.IA) if err != nil { fmt.Println("Error querying paths:", err) return false } - if ptd.allPaths == nil { + if allPaths == nil { return false } - if ptd.allPaths[0].UnderlayNextHop() == nil { - ptd.allPaths[0] = path.Path{ + // This is a transfer within the same AS, use empty path + if allPaths[0].UnderlayNextHop() == nil { + allPaths[0] = path.Path{ Src: ptd.pm.src.IA, Dst: ptd.dst.hostAddr.IA, DataplanePath: path.Empty{}, @@ -102,32 +87,33 @@ func (ptd *PathsToDestination) choosePaths() bool { } } - availablePaths := ptd.pm.filterPathsByActiveInterfaces(ptd.allPaths) - if len(availablePaths) == 0 { - log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) - } + // Restrict to paths that use one of the specified interfaces + availablePaths := ptd.pm.filterPathsByActiveInterfaces(allPaths) - if ptd.pm.mtu != 0 { - // MTU fixed by a previous path lookup, we need to pick paths compatible with it + if ptd.pm.payloadLen != 0 { + // Chunk length fixed by a previous path lookup, we need to pick paths compatible with it availablePaths = ptd.pm.filterPathsByMTU(availablePaths) } + if len(availablePaths) == 0 { + log.Error(fmt.Sprintf("no paths to destination %s", ptd.dst.hostAddr.IA.String())) + return false + } - // TODO Ensure this still does the right thing when the number of paths decreases (how to test?) ptd.chooseNewPaths(availablePaths) - if ptd.pm.mtu == 0{ - // No MTU set yet, we set it to the maximum that all selected paths and interfaces support - minMTU := HerculesMaxPktsize + if ptd.pm.payloadLen == 0 { + // No payloadlen set yet, we set it to the maximum that all selected paths support + maxPayloadlen := HerculesMaxPktsize for _, path := range ptd.paths { - if path.path.Metadata().MTU < uint16(minMTU){ - minMTU = int(path.path.Metadata().MTU) - } - if path.iface.MTU < minMTU { - minMTU = path.iface.MTU - } + pathMTU := int(path.path.Metadata().MTU) + underlayHeaderLen, scionHeaderLen := getPathHeaderlen(path.path) + pathPayloadlen := pathMTU - scionHeaderLen + maxPayloadlen = min(maxPayloadlen, pathPayloadlen) + // Cap to Hercules' max pkt size + maxPayloadlen = min(maxPayloadlen, HerculesMaxPktsize-scionHeaderLen-underlayHeaderLen) } - ptd.pm.mtu = minMTU - ptd.pm.mtu = 1200 // FIXME temp + ptd.pm.payloadLen = maxPayloadlen + fmt.Println("Set chunk length to", ptd.pm.payloadLen) } return true @@ -163,15 +149,19 @@ func (ptd *PathsToDestination) chooseNewPaths(availablePaths []PathWithInterface } log.Info(fmt.Sprintf("[Destination %s] using %d paths:", ptd.dst.hostAddr.IA, len(pathSet))) - if (len(pathSet) == 0){ + if len(pathSet) == 0 { ptd.paths = []PathMeta{} return false } + for i, _ := range ptd.paths { + // Ensures unused paths slots are not accidentally marked enabled if + // the number of paths has decreased since the last time + ptd.paths[i].enabled = false + } for i, path := range pathSet { log.Info(fmt.Sprintf("\t%s", path.path)) ptd.paths[i].path = path.path ptd.paths[i].enabled = true - ptd.paths[i].updated = true ptd.paths[i].iface = path.iface updated = true } diff --git a/monitor/scionheader.go b/monitor/scionheader.go index 72bba12..7bdc771 100644 --- a/monitor/scionheader.go +++ b/monitor/scionheader.go @@ -193,16 +193,18 @@ func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, net.IP, return &herculesPath, dstIP, nil } -func SerializePath(from *HerculesPathHeader, ifid int, maxHeaderLen int) []byte { +// Serialize the path header for transmission via the unix socket +func SerializePathHeader(from *HerculesPathHeader, ifid int, maxHeaderLen int) []byte { out := []byte{} out = binary.LittleEndian.AppendUint16(out, from.PartialChecksum) out = binary.LittleEndian.AppendUint16(out, uint16(ifid)) out = binary.LittleEndian.AppendUint32(out, uint32(len(from.Header))) if len(from.Header) > maxHeaderLen { - // Header does not fit in the C struct + fmt.Println("Header does not fit in the C struct!") return nil } out = append(out, from.Header...) + // Pad to C struct size out = append(out, bytes.Repeat([]byte{0x00}, maxHeaderLen-len(from.Header))...) return out } @@ -312,11 +314,14 @@ func getNeighborMAC(n *netlink.Handle, linkIndex int, ip net.IP) (net.HardwareAd return nil, errors.New("missing ARP entry") } -// TODO no reason to pass in both net.udpaddr and addr.Addr, but the latter does not include the port -func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcAddr, dstAddr addr.Addr) HerculesPathHeader { - iface := path.iface - dstMAC, srcMAC, err := getAddrs(iface, path.path.UnderlayNextHop().IP) - fmt.Println(dstMAC, srcMAC, err) +// XXX no reason to pass in both net.udpaddr and addr.Addr, but the latter does not include the port +// Serialize the header into its on-wire format +func prepareHeader(path PathMeta, payloadLen int, srcUDP, dstUDP net.UDPAddr, srcAddr, dstAddr addr.Addr) (HerculesPathHeader, error) { + dstMAC, srcMAC, err := getAddrs(path.iface, path.path.UnderlayNextHop().IP) + + // We need to know the final size of packets to fill the length fields in the IP/UDP headers + underlayHeaderLen, scionHdrLen := getPathHeaderlen(path.path) + etherLen := underlayHeaderLen + scionHdrLen + payloadLen underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcUDP.IP, path.path.UnderlayNextHop().IP, uint16(path.path.UnderlayNextHop().Port), etherLen) fmt.Println(underlayHeader, err) @@ -324,7 +329,7 @@ func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcA payload := snet.UDPPayload{ SrcPort: srcUDP.AddrPort().Port(), DstPort: dstUDP.AddrPort().Port(), - Payload: nil, + Payload: make([]byte, payloadLen), } destPkt := &snet.Packet{ @@ -337,23 +342,55 @@ func prepareHeader(path PathMeta, etherLen int, srcUDP, dstUDP net.UDPAddr, srcA } if err = destPkt.Serialize(); err != nil { - fmt.Println("serializer err") + return HerculesPathHeader{}, err } - scionHeaderLen := len(destPkt.Bytes) - payloadLen := etherLen - len(underlayHeader) - scionHeaderLen - payload.Payload = make([]byte, payloadLen) - destPkt.Payload = payload - if err = destPkt.Serialize(); err != nil { - fmt.Println("serrializer err2") - } + scionHeaderLen := len(destPkt.Bytes) - payloadLen scionHeader := destPkt.Bytes[:scionHeaderLen] scionChecksum := binary.BigEndian.Uint16(scionHeader[scionHeaderLen-2:]) headerBuf := append(underlayHeader, scionHeader...) + herculesPath := HerculesPathHeader{ Header: headerBuf, PartialChecksum: scionChecksum, } - fmt.Println(herculesPath) - return herculesPath + return herculesPath, nil +} + +// XXX Is there a nicer way to get the header's on-wire length than serialising it? +// Return the path's underlay and scion header length by creating a bogus packet. +func getPathHeaderlen(path snet.Path) (int, int) { + nilMAC := []byte{0, 0, 0, 0, 0, 0} + nilIP := []byte{0,0,0,0} + underlayHeader, err := prepareUnderlayPacketHeader(nilMAC, nilMAC, nilIP, path.UnderlayNextHop().IP, uint16(path.UnderlayNextHop().Port), 9000) + if err != nil { + return 0, 0 + } + + payload := snet.UDPPayload{ + SrcPort: 0, + DstPort: 0, + Payload: nil, + } + + nilAddr := addr.Addr{ + IA: 0, + Host: addr.MustParseHost("0.0.0.0"), + } + destPkt := &snet.Packet{ + PacketInfo: snet.PacketInfo{ + Destination: nilAddr, + Source: nilAddr, + Path: path.Dataplane(), + Payload: payload, + }, + } + + if err = destPkt.Serialize(); err != nil { + fmt.Println("serializer err", err) + return 0,0 + } + fmt.Println("Header length: ", len(destPkt.Bytes)) + fmt.Println("Underlay Header length: ", len(underlayHeader)) + return len(underlayHeader), len(destPkt.Bytes) } diff --git a/packet.h b/packet.h index 2279820..ac227ea 100644 --- a/packet.h +++ b/packet.h @@ -111,7 +111,6 @@ struct hercules_header { struct rbudp_initial_pkt { __u64 filesize; __u32 chunklen; - __u32 etherlen; __u64 timestamp; __u8 path_index; __u8 flags; From 638eac9643a7e1a6e97f56a5720f051591317160 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 13 Jun 2024 12:17:59 +0200 Subject: [PATCH 041/112] Cap payload length to interface MTU --- monitor/pathmanager.go | 18 +++++++++++++----- monitor/pathstodestination.go | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 49ed1c3..3bd5ef5 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -32,7 +32,7 @@ type PathManager struct { interfaces map[int]*net.Interface dst *PathsToDestination src *snet.UDPAddr - payloadLen int // The payload length to use for this transfer. Paths must be able to transfer payloads of at least this size. + payloadLen int // The payload length to use for this transfer. Paths must be able to transfer payloads of at least this size. } type PathWithInterface struct { @@ -50,7 +50,7 @@ func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet interfaces: ifMap, src: src, dst: &PathsToDestination{}, - payloadLen: 0, // Will be set later, after the first path lookup + payloadLen: 0, // Will be set later, after the first path lookup } if src.IA == dst.hostAddr.IA { @@ -89,9 +89,17 @@ func (pm *PathManager) filterPathsByMTU(pathsAvail []PathWithInterface) []PathWi // The path MTU refers to the maximum length of the SCION headers and payload, // but not including the lower-level (ethernet/ip/udp) headers pathMTU := int(path.path.Metadata().MTU) - _, pathScionHeaderlen := getPathHeaderlen(path.path) - pathPayloadlen := pathMTU - pathScionHeaderlen - if pathPayloadlen >= pm.payloadLen { + underlayHeaderLen, scionHeaderLen := getPathHeaderlen(path.path) + if pathMTU == 0 { + // Empty path has length 0, let's just use the interface's MTU + pathMTU = path.iface.MTU - scionHeaderLen - underlayHeaderLen + } + pathPayloadlen := pathMTU - scionHeaderLen + // The interface MTU refers to the maximum length of the entire packet, + // excluding the ethernet header (14B) + ifacePayloadLen := path.iface.MTU - (scionHeaderLen + underlayHeaderLen - 14) + + if pathPayloadlen >= pm.payloadLen && ifacePayloadLen >= pm.payloadLen { pathsFiltered = append(pathsFiltered, path) } } diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index fb3dad9..073bda7 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -102,15 +102,28 @@ func (ptd *PathsToDestination) choosePaths() bool { ptd.chooseNewPaths(availablePaths) if ptd.pm.payloadLen == 0 { - // No payloadlen set yet, we set it to the maximum that all selected paths support + // No payloadlen set yet, we set it to the maximum that all selected paths and interfaces support maxPayloadlen := HerculesMaxPktsize for _, path := range ptd.paths { pathMTU := int(path.path.Metadata().MTU) underlayHeaderLen, scionHeaderLen := getPathHeaderlen(path.path) + if pathMTU == 0 { + // Empty path has length 0, let's just use the interface's MTU + // XXX If the path's MTU is smaller than the interface's, the packet will end up too large + // TODO allow specifying MTU at submission time + pathMTU = path.iface.MTU - scionHeaderLen - underlayHeaderLen + } pathPayloadlen := pathMTU - scionHeaderLen maxPayloadlen = min(maxPayloadlen, pathPayloadlen) // Cap to Hercules' max pkt size maxPayloadlen = min(maxPayloadlen, HerculesMaxPktsize-scionHeaderLen-underlayHeaderLen) + // Check the interface's MTU is large enough + if maxPayloadlen + scionHeaderLen + underlayHeaderLen - 14 > path.iface.MTU { + // Packet exceeds the interface MTU + // 14 is the size of the ethernet header, which is not included in the interface's MTU + fmt.Printf("Interface (%v) MTU too low, decreasing payload length", path.iface.Name) + maxPayloadlen = path.iface.MTU - underlayHeaderLen - scionHeaderLen + } } ptd.pm.payloadLen = maxPayloadlen fmt.Println("Set chunk length to", ptd.pm.payloadLen) From 1e97502ac9ea84f4acf9d069f855be876c61b27a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 14 Jun 2024 10:37:09 +0200 Subject: [PATCH 042/112] pathset freeing --- Makefile | 5 +- hercules.c | 156 +++++++++++++++++++++++++++++++++-------------------- hercules.h | 23 ++++++-- xdp.c | 5 +- xdp.h | 2 +- 5 files changed, 122 insertions(+), 69 deletions(-) diff --git a/Makefile b/Makefile index 576fc13..cd53da7 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,11 @@ CC := gcc CFLAGS = -O0 -std=gnu11 -D_GNU_SOURCE # CFLAGS += -Wall -Wextra # for debugging: -CFLAGS += -g3 -DDEBUG +# ASAN_FLAG := -fsanitize=address,leak,undefined,pointer-compare,pointer-subtract +CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets # CFLAGS += -DPRINT_STATS -LDFLAGS = -lbpf -Lbpf/src -lm -lelf -pthread -lz -z noexecstack +LDFLAGS = -lbpf -Lbpf/src -lm -lelf -pthread -lz -z noexecstack $(ASAN_FLAG) DEPFLAGS:=-MP -MD SRCS := $(wildcard *.c) diff --git a/hercules.c b/hercules.c index c17727c..dca5ed0 100644 --- a/hercules.c +++ b/hercules.c @@ -302,7 +302,7 @@ struct hercules_server *hercules_init_server( server->n_threads = n_threads; server->session_rx = NULL; server->session_tx = NULL; - server->worker_args = calloc(server->n_threads, sizeof(struct rx_p_args *)); + server->worker_args = calloc(server->n_threads, sizeof(struct worker_args *)); if (server->worker_args == NULL){ exit_with_error(NULL, ENOMEM); } @@ -708,6 +708,14 @@ static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, return true; } +// Load the pathset currently in use and publish its epoch so the freeing thread +// knows when it's safe to free +static struct path_set *pathset_read(struct sender_state *tx_state, u32 id) { + struct path_set *pathset = atomic_load(&tx_state->pathset); + atomic_store(&tx_state->epochs[id].epoch, pathset->epoch); + return pathset; +} + /// RECEIVER static bool rx_received_all(const struct receiver_state *rx_state) @@ -1362,26 +1370,27 @@ static void rate_limit_tx(struct sender_state *tx_state) tx_state->prev_tx_npkts_queued = tx_state->tx_npkts_queued; } -void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state) { - u64 now = get_nsecs(); - - struct path_set *pathset = tx_state->pathset; - for (u32 p = 0; p < pathset->n_paths; p++) { - struct hercules_path *path = &pathset->paths[p]; - if (path->enabled) { - u64 handshake_at = atomic_load(&path->next_handshake_at); - if (handshake_at < now) { - if (atomic_compare_exchange_strong(&path->next_handshake_at, - &handshake_at, - now + PATH_HANDSHAKE_TIMEOUT_NS)) { - debug_printf("sending hs on path %d", p); - // FIXME file name below? - tx_send_initial(server, path, "abcd", tx_state->filesize, - tx_state->chunklen, get_nsecs(), p, false, false); +void send_path_handshakes(struct hercules_server *server, + struct sender_state *tx_state, + struct path_set *pathset) { + u64 now = get_nsecs(); + for (u32 p = 0; p < pathset->n_paths; p++) { + struct hercules_path *path = &pathset->paths[p]; + if (path->enabled) { + u64 handshake_at = atomic_load(&path->next_handshake_at); + if (handshake_at < now) { + if (atomic_compare_exchange_strong( + &path->next_handshake_at, &handshake_at, + now + PATH_HANDSHAKE_TIMEOUT_NS)) { + debug_printf("sending hs on path %d", p); + // FIXME file name below? + tx_send_initial(server, path, "abcd", tx_state->filesize, + tx_state->chunklen, get_nsecs(), p, false, + false); + } + } } - } - } - } + } } static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) @@ -1422,12 +1431,12 @@ static char *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_id static short flowIdCtr = 0; #endif -static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_state, struct xsk_socket_info *xsk, - int ifid, u64 frame_addrs[SEND_QUEUE_ENTRIES_PER_UNIT], - struct send_queue_unit *unit) -{ +static inline void tx_handle_send_queue_unit_for_iface( + struct sender_state *tx_state, struct xsk_socket_info *xsk, int ifid, + u64 frame_addrs[SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, + u32 thread_id) { u32 num_chunks_in_unit = 0; - struct path_set *pathset = tx_state->pathset; + struct path_set *pathset = pathset_read(tx_state, thread_id); for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { if(unit->paths[i] == UINT8_MAX) { break; @@ -1483,11 +1492,11 @@ static inline void tx_handle_send_queue_unit_for_iface(struct sender_state *tx_s static inline void tx_handle_send_queue_unit(struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], - struct send_queue_unit *unit) + struct send_queue_unit *unit, u32 thread_id) { for(int i = 0; i < server->num_ifaces; i++) { - tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit); + tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, thread_id); } } @@ -1542,12 +1551,10 @@ static inline void allocate_tx_frames(struct hercules_server *server, } // Compute rate limit for the path currently marked active -static u32 compute_max_chunks_current_path(struct sender_state *tx_state) { +static u32 compute_max_chunks_current_path(struct sender_state *tx_state, struct path_set *pathset) { u32 allowed_chunks = 0; u64 now = get_nsecs(); - // TODO pass in pathset instead? (atomics/free) - struct path_set *pathset = tx_state->pathset; // TODO make sure path_index is reset correctly on pathset change struct hercules_path *path = &pathset->paths[pathset->path_index]; if (!path->enabled) { @@ -1586,8 +1593,7 @@ static inline void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_pa } // Mark the next available path as active -static void iterate_paths(struct sender_state *tx_state) { - struct path_set *pathset = tx_state->pathset; +static void iterate_paths(struct sender_state *tx_state, struct path_set *pathset) { if (pathset->n_paths == 0) { return; } @@ -1603,18 +1609,16 @@ static void iterate_paths(struct sender_state *tx_state) { pathset->path_index != prev_path_index); } -static void terminate_cc(const struct sender_state *tx_state) { - struct path_set *pathset = tx_state->pathset; +static void terminate_cc(struct path_set *pathset) { for (u32 i = 0; i < pathset->n_paths; i++) { terminate_ccontrol(pathset->paths[i].cc_state); } } -static void kick_cc(struct sender_state *tx_state) { +static void kick_cc(struct sender_state *tx_state, struct path_set *pathset) { if (tx_state->finished) { return; } - struct path_set *pathset = tx_state->pathset; for (u32 p = 0; p < pathset->n_paths; p++) { kick_ccontrol(pathset->paths[p].cc_state); } @@ -1678,7 +1682,7 @@ static struct sender_state *init_tx_state(struct hercules_session *session, const struct hercules_app_addr *dests, struct hercules_path *paths, u32 num_dests, const int num_paths, - u32 max_paths_per_dest) { + u32 max_paths_per_dest, u32 num_threads) { u64 total_chunks = (filesize + chunklen - 1) / chunklen; if (total_chunks >= UINT_MAX) { fprintf(stderr, @@ -1712,6 +1716,17 @@ static struct sender_state *init_tx_state(struct hercules_session *session, memcpy(pathset->paths, paths, sizeof(*paths)*num_paths); tx_state->pathset = pathset; tx_state->session->peer = *dests; + + // tx_p uses index 0, tx_send_p threads start at index 1 + int err = posix_memalign((void **)&tx_state->epochs, CACHELINE_SIZE, + sizeof(*tx_state->epochs)*(num_threads+1)); + if (err != 0) { + free(pathset); + free(tx_state); + return NULL; + } + memset(tx_state->epochs, 0, sizeof(*tx_state->epochs) * (num_threads + 1)); + tx_state->next_epoch = 1; return tx_state; } @@ -1996,15 +2011,11 @@ static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) } /// WORKER THREADS -struct tx_send_p_args { - struct hercules_server *server; - struct xsk_socket_info *xsks[]; -}; // Read chunk ids from the send queue, fill in packets accorindgly and actually // send them. This is the function run by the TX worker thread(s). static void tx_send_p(void *arg) { - struct tx_send_p_args *args = arg; + struct worker_args *args = arg; struct hercules_server *server = args->server; while (1) { struct hercules_session *session_tx = atomic_load(&server->session_tx); @@ -2039,7 +2050,7 @@ static void tx_send_p(void *arg) { } allocate_tx_frames(server, frame_addrs); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, - frame_addrs, &unit); + frame_addrs, &unit, args->id); } } @@ -2102,7 +2113,7 @@ static void rx_trickle_nacks(void *arg) { // Receive data packets on the XDP sockets. Runs in the RX worker thread(s). static void rx_p(void *arg) { - struct rx_p_args *args = arg; + struct worker_args *args = arg; struct hercules_server *server = args->server; int num_ifaces = server->num_ifaces; u32 i = 0; @@ -2160,20 +2171,21 @@ static void *tx_p(void *arg) { if (session_tx != NULL && atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { struct sender_state *tx_state = session_tx->tx_state; + struct path_set *pathset = pathset_read(tx_state, 0); /* debug_printf("Start transmit round"); */ tx_state->prev_rate_check = get_nsecs(); pop_completion_rings(server); - send_path_handshakes(server, tx_state); + send_path_handshakes(server, tx_state, pathset); u64 next_ack_due = 0; // in each iteration, we send packets on a single path to each receiver // collect the rate limits for each active path - u32 allowed_chunks = compute_max_chunks_current_path(tx_state); + u32 allowed_chunks = compute_max_chunks_current_path(tx_state, pathset); if (allowed_chunks == 0) { // we hit the rate limits on every path; switch paths - iterate_paths(tx_state); + iterate_paths(tx_state, pathset); continue; } @@ -2193,8 +2205,8 @@ static void *tx_p(void *arg) { &ack_due, allowed_chunks); num_chunks += cur_num_chunks; if (tx_state->finished) { - terminate_cc(tx_state); - kick_cc(tx_state); + terminate_cc(pathset); + kick_cc(tx_state, pathset); } else { // only wait for the nearest ack if (next_ack_due) { @@ -2216,7 +2228,6 @@ static void *tx_p(void *arg) { rate_limit_tx(tx_state); // update book-keeping - struct path_set *pathset = tx_state->pathset; u32 path_idx = pathset->path_index; struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; if (cc_state != NULL) { @@ -2230,7 +2241,7 @@ static void *tx_p(void *arg) { } } - iterate_paths(tx_state); + iterate_paths(tx_state, pathset); if (now < next_ack_due) { // XXX if the session vanishes in the meantime, we might wait @@ -2300,7 +2311,7 @@ static void new_tx_if_available(struct hercules_server *server) { struct sender_state *tx_state = init_tx_state( session, filesize, chunklen, server->rate_limit, mem, &session->peer, paths, 1, n_paths, - server->max_paths); + server->max_paths, server->n_threads); strncpy(tx_state->filename, fname, 99); session->tx_state = tx_state; atomic_store(&server->session_tx, session); @@ -2392,13 +2403,26 @@ static void tx_update_paths(struct hercules_server *server) { } struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); if (new_pathset == NULL) { + // FIXME leak? return; } + u32 new_epoch = tx_state->next_epoch; + new_pathset->epoch = new_epoch; + tx_state->next_epoch++; new_pathset->n_paths = n_paths; memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); u32 path_lim = (old_pathset->n_paths > (u32)n_paths) ? (u32)n_paths : old_pathset->n_paths; bool replaced_return_path = false; + struct ccontrol_state **replaced_cc = + calloc(old_pathset->n_paths, sizeof(*replaced_cc)); + if (replaced_cc == NULL) { + // FIXME leak? + return; + } + for (u32 i = 0; i < old_pathset->n_paths; i++) { + replaced_cc[i] = old_pathset->paths[i].cc_state; + } for (u32 i = 0; i < path_lim; i++) { // Set these two values before the comparison or it would fail even // if paths are the same. @@ -2406,11 +2430,16 @@ static void tx_update_paths(struct hercules_server *server) { old_pathset->paths[i].next_handshake_at; new_pathset->paths[i].cc_state = old_pathset->paths[i].cc_state; + // XXX This works, but it means we restart CC even if the path has + // not changed (but the header has, eg. because the old one + // expired). We could avoid this by having the monitor tell us + // whether the path changed, as it used to. if (memcmp(&old_pathset->paths[i], &new_pathset->paths[i], sizeof(struct hercules_path)) == 0) { // Old and new path are the same, CC state carries over. // Since we copied the CC state before just leave as-is. debug_printf("Path %d not changed", i); + replaced_cc[i] = NULL; } else { debug_printf("Path %d changed, resetting CC", i); if (i == 0) { @@ -2440,12 +2469,20 @@ static void tx_update_paths(struct hercules_server *server) { } // Finally, swap in the new pathset tx_state->pathset = new_pathset; - free(paths); // These were *copied* into the new pathset - // TODO when it's safe (when?), free: - // - the old pathset - // - its cc states that were not carried over - // - ! If we have fewer paths in the new set than in the old one don't - // forget to free ALL old states + free(paths); // These were *copied* into the new pathset + for (int i = 0; i < server->n_threads + 1; i++) { + do { + // Wait until the thread has seen the new pathset + } while (tx_state->epochs[i].epoch != new_epoch); + } + for (u32 i = 0; i < old_pathset->n_paths; i++) { + // If CC was replaced, this contains the pointer to the old CC + // state. Otherwise it contains NULL, and we don't need to free + // anything. + free(replaced_cc[i]); + } + free(replaced_cc); + free(old_pathset); } } @@ -2549,8 +2586,9 @@ static void events_p(void *arg) { /* tx_update_paths(server); */ /* lastpoll = now; */ /* } */ - if (now > lastpoll + 2e9){ + if (now > lastpoll + 3e9){ tx_update_monitor(server, now); + tx_update_paths(server); lastpoll = now; } /* lastpoll = now; */ diff --git a/hercules.h b/hercules.h index 4997b18..439d125 100644 --- a/hercules.h +++ b/hercules.h @@ -52,8 +52,8 @@ struct hercules_path { int framelen; //!< length of ethernet frame; headerlen + payloadlen int ifid; // Interface to use for sending struct hercules_path_header header; - atomic_bool enabled; // e.g. when a path has been revoked and no - // replacement is available, this will be set to false + atomic_bool enabled; // Paths can be disabled, e.g. in response to + // receiving SCMP errors struct ccontrol_state *cc_state; // This path's PCC state }; @@ -100,11 +100,19 @@ struct receiver_state { // Used to atomically swap in new paths struct path_set { + u64 epoch; u32 n_paths; u8 path_index; // Path to use for sending next batch (used by tx_p) struct hercules_path paths[256]; }; +struct thread_epoch { + _Atomic u64 epoch; + u64 _[7]; +}; +_Static_assert(sizeof(struct thread_epoch) == 64, + "struct thread_epoch must be cacheline-sized"); + struct sender_state { struct hercules_session *session; @@ -123,6 +131,10 @@ struct sender_state { atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns struct path_set *_Atomic pathset; // Paths currently in use + struct thread_epoch + *epochs; // Used for threads to publish their current pathset epoch + u32 next_epoch; // Used by the thread updating the pathsets + /** Filesize in bytes */ size_t filesize; char filename[100]; @@ -216,8 +228,8 @@ struct hercules_server { int usock; // Unix socket used for communication with the monitor int max_paths; int rate_limit; - int n_threads; // Number of RX/TX worker threads - struct rx_p_args **worker_args; // Args passed to RX workers + int n_threads; // Number of RX/TX worker threads + struct worker_args **worker_args; // Args passed to RX/TX workers struct hercules_session *_Atomic session_tx; // Current TX session struct hercules_session *deferred_tx; // Previous TX session, no longer @@ -255,7 +267,8 @@ struct xsk_socket_info { typedef int xskmap; /// Thread args -struct rx_p_args { +struct worker_args { + u32 id; struct hercules_server *server; struct xsk_socket_info *xsks[]; }; diff --git a/xdp.c b/xdp.c index 76459b9..73161b7 100644 --- a/xdp.c +++ b/xdp.c @@ -249,7 +249,7 @@ int xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, * Load a BPF program redirecting IP traffic to the XSK. */ int load_xsk_redirect_userspace(struct hercules_server *server, - struct rx_p_args *args[], int num_threads) { + struct worker_args *args[], int num_threads) { debug_printf("Loading XDP program for redirection"); for (int i = 0; i < server->num_ifaces; i++) { struct bpf_object *obj; @@ -353,12 +353,13 @@ int xdp_setup(struct hercules_server *server) { } for (int t = 0; t < server->n_threads; t++) { server->worker_args[t] = - malloc(sizeof(*server->worker_args) + + malloc(sizeof(**server->worker_args) + server->num_ifaces * sizeof(*server->worker_args[t]->xsks)); if (server->worker_args[t] == NULL) { return ENOMEM; } server->worker_args[t]->server = server; + server->worker_args[t]->id = t+1; for (int i = 0; i < server->num_ifaces; i++) { server->worker_args[t]->xsks[i] = server->ifaces[i].xsks[t]; } diff --git a/xdp.h b/xdp.h index 0fee3a8..29aab32 100644 --- a/xdp.h +++ b/xdp.h @@ -38,7 +38,7 @@ int xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, struct xsk_socket_info *xsk); int load_xsk_redirect_userspace(struct hercules_server *server, - struct rx_p_args *args[], int num_threads); + struct worker_args *args[], int num_threads); int xdp_setup(struct hercules_server *server); From 574d5801e23180e21dc445fe6d641c102491f56f Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 14 Jun 2024 11:09:13 +0200 Subject: [PATCH 043/112] fix mtu bug --- hercules.c | 17 +++++++++-------- monitor/pathstodestination.go | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/hercules.c b/hercules.c index dca5ed0..43d181c 100644 --- a/hercules.c +++ b/hercules.c @@ -175,12 +175,11 @@ void debug_print_rbudp_pkt(const char *pkt, bool recv) { printf( "%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx " "%u, Flags " - "0x%x, MTU %d, Name length %u [%s]\n", + "0x%x, Name length %u [%s]\n", prefix, cp->payload.initial.filesize, cp->payload.initial.chunklen, cp->payload.initial.timestamp, cp->payload.initial.path_index, cp->payload.initial.flags, - cp->payload.initial.etherlen, cp->payload.initial.name_len, - cp->payload.initial.name); + cp->payload.initial.name_len, cp->payload.initial.name); break; case CONTROL_PACKET_TYPE_ACK: printf("%s ACK (%d) ", prefix, cp->payload.ack.num_acks); @@ -978,7 +977,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, } // Update the reply path using the header from a received packet. -// The packet is sent to the monior, which will return a new header with the +// The packet is sent to the monitor, which will return a new header with the // path reversed. static bool rx_update_reply_path( struct hercules_server *server, struct receiver_state *rx_state, int ifid, @@ -1048,14 +1047,16 @@ static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, const char *buf, int ifid, const char *payload, - int payloadlen) { + int framelen) { debug_printf("handling initial"); - const int headerlen = (int)(payload - buf); + // Payload points to the rbudp payload (after the rbudp header) + const int headerlen = (int)(payload - buf); // Length of ALL headers (including rbudp) if (initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { - debug_printf("initial headerlen, payloadlen: %d, %d", headerlen, payloadlen); + debug_printf("initial headerlen, framelen: %d, %d", headerlen, framelen); + debug_printf("initial chunklen: %d", initial->chunklen); // XXX Why use both initial->chunklen (transmitted) and the size of the received packet? // Are they ever not the same? - rx_update_reply_path(server, rx_state, ifid, initial->chunklen + headerlen, headerlen + payloadlen, buf); + rx_update_reply_path(server, rx_state, ifid, initial->chunklen + headerlen, framelen, buf); } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 073bda7..17dc3e0 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -126,7 +126,7 @@ func (ptd *PathsToDestination) choosePaths() bool { } } ptd.pm.payloadLen = maxPayloadlen - fmt.Println("Set chunk length to", ptd.pm.payloadLen) + fmt.Println("Set payload length to", ptd.pm.payloadLen) } return true From 9daca06cbd1896d88e5cf583b9f83833309be61b Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 14 Jun 2024 13:21:32 +0200 Subject: [PATCH 044/112] fix fill_rbudp_pkt --- Makefile | 1 + hercules.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cd53da7..989e653 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ CFLAGS = -O0 -std=gnu11 -D_GNU_SOURCE # CFLAGS += -Wall -Wextra # for debugging: # ASAN_FLAG := -fsanitize=address,leak,undefined,pointer-compare,pointer-subtract +ASAN_FLAG := -fsanitize=address CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets # CFLAGS += -DPRINT_STATS diff --git a/hercules.c b/hercules.c index 43d181c..6097c9e 100644 --- a/hercules.c +++ b/hercules.c @@ -685,9 +685,9 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, void *rbudp_seqnr = mempcpy(rbudp_path_idx, &path_idx, sizeof(path_idx)); void *rbudp_payload = mempcpy(rbudp_seqnr, &seqnr, sizeof(seqnr)); void *start_pad = mempcpy(rbudp_payload, data, n); - if (sizeof(chunk_idx) + sizeof(path_idx) + n < payloadlen) { + if (rbudp_headerlen + n < payloadlen) { memset(start_pad, 0, - payloadlen - sizeof(chunk_idx) - sizeof(path_idx) - n); + payloadlen - rbudp_headerlen - n); } debug_print_rbudp_pkt(rbudp_pkt, false); } From 7585de701b92eb926f831d1c36c9e02ba2127367 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sat, 15 Jun 2024 11:23:35 +0200 Subject: [PATCH 045/112] implement directory transfer add rbudp phase for long directory index transfer: If the directory index does not fit in the first packet, an additional round of rbudp is performed to transfer the index, then the transfer starts as normal. --- bitset.h | 4 + hercules.c | 875 ++++++++++++++++++++++++++++++++++++++--------------- hercules.h | 19 +- packet.h | 24 +- 4 files changed, 678 insertions(+), 244 deletions(-) diff --git a/bitset.h b/bitset.h index cda6ac5..3f238c9 100644 --- a/bitset.h +++ b/bitset.h @@ -114,6 +114,10 @@ static inline void bitset__reset(struct bitset *s) static inline u32 bitset__scan(struct bitset *s, u32 pos) { // TODO: profile the entire application and rewrite this function to use bitscan ops + if (s->num == 1 && pos == 0) { + // Needed for the edge case where the bitset has only 1 entry + return !bitset__check(s, 0); + } for(u32 i = pos; i < s->max_set; ++i) { if(bitset__check(s, i)) { return i; diff --git a/hercules.c b/hercules.c index 6097c9e..d22477b 100644 --- a/hercules.c +++ b/hercules.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -75,7 +76,7 @@ static const u64 session_stale_timeout = 30e9; // 30 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path // Fill packet with n bytes from data and pad with zeros to payloadlen. -static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, +static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, u8 flags, sequence_number seqnr, const char *data, size_t n, size_t payloadlen); @@ -158,28 +159,47 @@ static void send_eth_frame(struct hercules_server *server, } } +static inline bool session_state_is_running(enum session_state s) { + if (s == SESSION_STATE_RUNNING_IDX || s == SESSION_STATE_RUNNING_DATA) { + return true; + } + return false; +} + #ifdef DEBUG_PRINT_PKTS // recv indicates whether printed packets should be prefixed with TX or RX void debug_print_rbudp_pkt(const char *pkt, bool recv) { struct hercules_header *h = (struct hercules_header *)pkt; const char *prefix = (recv) ? "RX->" : "<-TX"; - printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, - h->path, h->seqno); + printf("%s Header: IDX %u, Path %u, Flags %s, Seqno %u\n", prefix, + h->chunk_idx, h->path, + (h->flags & PKT_FLAG_IS_INDEX) ? "IDX" : "DATA", h->seqno); if (h->chunk_idx == UINT_MAX) { // Control packets - const char *pl = pkt + 9; + const char *pl = pkt + rbudp_headerlen; struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; switch (cp->type) { case CONTROL_PACKET_TYPE_INITIAL: printf( "%s HS: Filesize %llu, Chunklen %u, TS %llu, Path idx " - "%u, Flags " - "0x%x, Name length %u [%s]\n", + "%u, Index size %llu, Flags %s|%s|%s|%s\n", prefix, cp->payload.initial.filesize, cp->payload.initial.chunklen, cp->payload.initial.timestamp, - cp->payload.initial.path_index, cp->payload.initial.flags, - cp->payload.initial.name_len, cp->payload.initial.name); + cp->payload.initial.path_index, + cp->payload.initial.index_len, + (cp->payload.initial.flags & HANDSHAKE_FLAG_SET_RETURN_PATH) + ? "RP" + : "--", + (cp->payload.initial.flags & HANDSHAKE_FLAG_HS_CONFIRM) + ? "HC" + : "--", + (cp->payload.initial.flags & HANDSHAKE_FLAG_NEW_TRANSFER) + ? "NT" + : "--", + (cp->payload.initial.flags & HANDSHAKE_FLAG_INDEX_FOLLOWS) + ? "IF" + : "--"); break; case CONTROL_PACKET_TYPE_ACK: printf("%s ACK (%d) ", prefix, cp->payload.ack.num_acks); @@ -678,13 +698,15 @@ static void stitch_checksum(const struct hercules_path *path, u16 precomputed_ch } // Fill packet with n bytes from data and pad with zeros to payloadlen. -static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, +static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, u8 flags, sequence_number seqnr, const char *data, size_t n, size_t payloadlen) { - void *rbudp_path_idx = mempcpy(rbudp_pkt, &chunk_idx, sizeof(chunk_idx)); - void *rbudp_seqnr = mempcpy(rbudp_path_idx, &path_idx, sizeof(path_idx)); - void *rbudp_payload = mempcpy(rbudp_seqnr, &seqnr, sizeof(seqnr)); - void *start_pad = mempcpy(rbudp_payload, data, n); + struct hercules_header *hdr = (struct hercules_header *)rbudp_pkt; + hdr->chunk_idx = chunk_idx; + hdr->path = path_idx; + hdr->flags = flags; + hdr->seqno = seqnr; + void *start_pad = mempcpy(hdr->data, data, n); if (rbudp_headerlen + n < payloadlen) { memset(start_pad, 0, payloadlen - rbudp_headerlen - n); @@ -717,8 +739,12 @@ static struct path_set *pathset_read(struct sender_state *tx_state, u32 id) { /// RECEIVER -static bool rx_received_all(const struct receiver_state *rx_state) -{ +static bool rx_received_all(const struct receiver_state *rx_state, + const bool is_index_transfer) { + if (is_index_transfer) { + return (rx_state->received_chunks_index.num_set == + rx_state->index_chunks); + } return (rx_state->received_chunks.num_set == rx_state->total_chunks); } @@ -729,23 +755,37 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p return false; } - u32 chunk_idx; - memcpy(&chunk_idx, pkt, sizeof(u32)); - if(chunk_idx >= rx_state->total_chunks) { - if(chunk_idx == UINT_MAX) { - // control packet is handled elsewhere - } else { - fprintf(stderr, "ERROR: chunk_idx larger than expected: %u >= %u\n", - chunk_idx, rx_state->total_chunks); + struct hercules_header *hdr = (struct hercules_header *)pkt; + bool is_index_transfer = hdr->flags & PKT_FLAG_IS_INDEX; + + u32 chunk_idx = hdr->chunk_idx; + if (is_index_transfer) { + if (chunk_idx >= rx_state->index_chunks) { + if (chunk_idx == UINT_MAX) { + // control packet is handled elsewhere + } else { + fprintf(stderr, + "ERROR: IDX chunk_idx larger than expected: %u >= %u\n", + chunk_idx, rx_state->index_chunks); + } + return false; + } + } else { + if (chunk_idx >= rx_state->total_chunks) { + if (chunk_idx == UINT_MAX) { + // control packet is handled elsewhere + } else { + fprintf(stderr, + "ERROR: DATA chunk_idx larger than expected: %u >= %u\n", + chunk_idx, rx_state->total_chunks); + } + return false; } - return false; } - u8 path_idx; - mempcpy(&path_idx, &pkt[4], sizeof(u8)); + u8 path_idx = hdr->path; if(path_idx < PCC_NO_PATH) { - sequence_number seqnr; - memcpy(&seqnr, &pkt[5], sizeof(sequence_number)); + sequence_number seqnr = hdr->seqno; if(rx_state->path_state[path_idx].seq_rcvd.bitmap == NULL) { // TODO compute correct number here bitset__create(&rx_state->path_state[path_idx].seq_rcvd, 200 * rx_state->total_chunks); @@ -754,7 +794,7 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p if(seqnr >= rx_state->path_state[path_idx].seq_rcvd.num) { // XXX: currently we cannot track these sequence numbers, as a consequence congestion control breaks at this // point, abort. - if(rx_state->session->state != SESSION_STATE_RUNNING) { + if(!session_state_is_running(rx_state->session->state)) { return true; } else { fprintf(stderr, "sequence number overflow %d / %d\n", seqnr, @@ -779,30 +819,47 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p // break PCC because that takes NACKs send on a per-path basis as feedback } else { // mark as received in received_chunks bitmap - prev = bitset__set_mt_safe(&rx_state->received_chunks, chunk_idx); + if (is_index_transfer) { + prev = bitset__set_mt_safe(&rx_state->received_chunks_index, + chunk_idx); + } else { + prev = bitset__set_mt_safe(&rx_state->received_chunks, chunk_idx); + } } if(!prev) { + char *target_ptr = rx_state->mem; const char *payload = pkt + rbudp_headerlen; const size_t chunk_start = (size_t)chunk_idx * rx_state->chunklen; - const size_t len = umin64(rx_state->chunklen, rx_state->filesize - chunk_start); - memcpy(rx_state->mem + chunk_start, payload, len); + size_t len = + umin64(rx_state->chunklen, rx_state->filesize - chunk_start); + if (is_index_transfer) { + target_ptr = rx_state->index; + len = umin64(rx_state->chunklen, rx_state->index_size - chunk_start); + } + memcpy(target_ptr + chunk_start, payload, len); // Update last new pkt timestamp atomic_store(&rx_state->session->last_new_pkt_rcvd, get_nsecs()); } return true; } -static u32 fill_ack_pkt(struct receiver_state *rx_state, u32 first, struct rbudp_ack_pkt *ack, size_t max_num_acks) +static u32 fill_ack_pkt(struct receiver_state *rx_state, u32 first, struct rbudp_ack_pkt *ack, size_t max_num_acks, bool is_index_transfer) { size_t e = 0; u32 curr = first; + struct bitset *set = &rx_state->received_chunks; + u32 num = rx_state->received_chunks.num; + if (is_index_transfer){ + set = &rx_state->received_chunks_index; + num = rx_state->received_chunks_index.num; + } for(; e < max_num_acks;) { - u32 begin = bitset__scan(&rx_state->received_chunks, curr); - if(begin == rx_state->received_chunks.num) { + u32 begin = bitset__scan(set, curr); + if(begin == num) { curr = begin; break; } - u32 end = bitset__scan_neg(&rx_state->received_chunks, begin + 1); + u32 end = bitset__scan_neg(set, begin + 1); curr = end + 1; ack->acks[e].begin = begin; ack->acks[e].end = end; @@ -847,7 +904,7 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); while(reserved != num_frames) { reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - if(session == NULL || session->state != SESSION_STATE_RUNNING) { + if(session == NULL || !session_state_is_running(session->state)) { pthread_spin_unlock(&umem->fq_lock); return; } @@ -920,37 +977,71 @@ static void rx_receive_and_drop(struct xsk_socket_info *xsk){ } // Prepare a file and memory mapping to receive a file -static char *rx_mmap(const char *pathname, size_t filesize) { - debug_printf("mmap file: %s", pathname); - int ret; - /*ret = unlink(pathname); - if(ret && errno != ENOENT) { - exit_with_error(server, errno); - }*/ - int f = open(pathname, O_RDWR | O_CREAT | O_EXCL, 0664); - if (f == -1 && errno == EEXIST) { - f = open(pathname, O_RDWR | O_EXCL); - } - if (f == -1) { - return NULL; - } - ret = fallocate(f, 0, 0, filesize); // Will fail on old filesystems (ext3) - if (ret) { - close(f); - return NULL; - } - char *mem = mmap(NULL, filesize, PROT_WRITE, MAP_SHARED, f, 0); +static char *rx_mmap(const char *index, size_t index_size, size_t total_filesize) { + debug_printf("total filesize %ld", total_filesize); + debug_printf("total entry size %ld", index_size); + char *mem = mmap(NULL, total_filesize, PROT_READ, MAP_PRIVATE | MAP_ANON, 0, 0); if (mem == MAP_FAILED) { - close(f); return NULL; } - close(f); + char *next_mapping = mem; + + struct dir_index_entry *p = (struct dir_index_entry *)index; + while (1) { + debug_printf("Read: %s (%d) %dB", p->path, p->type, p->filesize); + int ret; + if (p->type == INDEX_TYPE_FILE) { + int f = open(p->path, O_RDWR | O_CREAT | O_EXCL, 0664); + if (f == -1 && errno == EEXIST) { + f = open(p->path, O_RDWR | O_EXCL); + } + if (f == -1) { + return NULL; + } + ret = + fallocate(f, 0, 0, + p->filesize); // Will fail on old filesystems (ext3) + if (ret) { + close(f); + return NULL; + } + char *filemap = mmap(next_mapping, p->filesize, PROT_WRITE, + MAP_SHARED | MAP_FIXED, f, 0); + if (mem == MAP_FAILED) { + debug_printf("filemap err!"); + return NULL; + } + next_mapping += p->filesize; + close(f); + } + else if (p->type == INDEX_TYPE_DIR){ + ret = mkdir(p->path, 0664); + if (ret != 0) { + // XXX should an already existing directory be an error? + if (errno == EEXIST) { + struct stat statbuf; + stat(p->path, &statbuf); + if (!S_ISDIR(statbuf.st_mode)){ + debug_printf("path exists but is not a directory?"); + return NULL; + } + } else { + debug_printf("mkdir err"); + return NULL; + } + } + } + p = ((char *)p) + sizeof(*p) + p->path_len; + if (p >= index + index_size) { + break; + } + } return mem; } // Create new receiver state. Returns null in case of error. static struct receiver_state *make_rx_state(struct hercules_session *session, - char *filename, size_t namelen, + char *index, size_t index_size, size_t filesize, int chunklen, bool is_pcc_benchmark) { struct receiver_state *rx_state; @@ -967,8 +1058,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; - filename[namelen+1] = 0; // HACK FIXME - rx_state->mem = rx_mmap(filename, filesize); + rx_state->mem = rx_mmap(index, index_size, filesize); if (rx_state->mem == NULL) { free(rx_state); return NULL; @@ -976,6 +1066,33 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, return rx_state; } +// For index transfer: Create new receiver state without mapping a file. Returns +// null in case of error. +static struct receiver_state *make_rx_state_nomap( + struct hercules_session *session, size_t index_size, + size_t filesize, int chunklen, bool is_pcc_benchmark) { + struct receiver_state *rx_state; + rx_state = calloc(1, sizeof(*rx_state)); + if (rx_state == NULL) { + return NULL; + } + rx_state->session = session; + rx_state->filesize = filesize; + rx_state->chunklen = chunklen; + rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; + rx_state->index_chunks = (index_size + chunklen - 1) / chunklen; + bitset__create(&rx_state->received_chunks, rx_state->total_chunks); + bitset__create(&rx_state->received_chunks_index, rx_state->index_chunks); + rx_state->start_time = 0; + rx_state->end_time = 0; + rx_state->handshake_rtt = 0; + rx_state->is_pcc_benchmark = is_pcc_benchmark; + // XXX We cannot map the file(s) yet since we don't have the index, + // but we could already reserve the required range (to check if there's even + // enough memory available) + return rx_state; +} + // Update the reply path using the header from a received packet. // The packet is sent to the monitor, which will return a new header with the // path reversed. @@ -1033,7 +1150,7 @@ static void rx_send_rtt_ack(struct hercules_server *server, /* strncpy(control_pkt.payload.initial.name, pld->name, pld->name_len); */ control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&control_pkt, sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); @@ -1083,7 +1200,7 @@ static void rx_send_cts_ack(struct hercules_server *server, .payload.ack.num_acks = 0, }; - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); @@ -1095,12 +1212,17 @@ static void rx_send_cts_ack(struct hercules_server *server, static void send_control_pkt(struct hercules_server *server, struct hercules_session *session, struct hercules_control_packet *control_pkt, - struct hercules_path *path) { + struct hercules_path *path, + bool is_index_transfer) { char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + u8 flag = 0; + if (is_index_transfer) { + flag |= PKT_FLAG_IS_INDEX; + } fill_rbudp_pkt( - rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, + rbudp_pkt, UINT_MAX, PCC_NO_PATH, flag, 0, (char *)control_pkt, sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), path->payloadlen); stitch_checksum(path, path->header.checksum, buf); @@ -1110,7 +1232,7 @@ static void send_control_pkt(struct hercules_server *server, } // Send as many ACK packets as necessary to convey all received packet ranges -static void rx_send_acks(struct hercules_server *server, struct receiver_state *rx_state) +static void rx_send_acks(struct hercules_server *server, struct receiver_state *rx_state, bool is_index_transfer) { struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { @@ -1126,17 +1248,17 @@ static void rx_send_acks(struct hercules_server *server, struct receiver_state * const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); // send an empty ACK to keep connection alive until first packet arrives - u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries); - send_control_pkt(server, rx_state->session, &control_pkt, &path); + u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries, is_index_transfer); + send_control_pkt(server, rx_state->session, &control_pkt, &path, is_index_transfer); for(; curr < rx_state->total_chunks;) { - curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries); + curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries, is_index_transfer); if(control_pkt.payload.ack.num_acks == 0) break; - send_control_pkt(server, rx_state->session, &control_pkt, &path); + send_control_pkt(server, rx_state->session, &control_pkt, &path, is_index_transfer); } } -static void rx_send_path_nacks(struct hercules_server *server, struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr) +static void rx_send_path_nacks(struct hercules_server *server, struct receiver_state *rx_state, struct receiver_state_per_path *path_state, u8 path_idx, u64 time, u32 nr, bool is_index_transfer) { struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { @@ -1175,7 +1297,11 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s if(control_pkt.payload.ack.num_acks != 0) { nack_end = control_pkt.payload.ack.acks[control_pkt.payload.ack.num_acks - 1].end; } - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, path_idx, 0, (char *)&control_pkt, + u8 flag = 0; + if (is_index_transfer) { + flag |= PKT_FLAG_IS_INDEX; + } + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, path_idx, flag, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); @@ -1188,11 +1314,11 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s } // sends the NACKs used for congestion control by the sender -static void rx_send_nacks(struct hercules_server *server, struct receiver_state *rx_state, u64 time, u32 nr) +static void rx_send_nacks(struct hercules_server *server, struct receiver_state *rx_state, u64 time, u32 nr, bool is_index_transfer) { u8 num_paths = atomic_load(&rx_state->num_tracked_paths); for(u8 p = 0; p < num_paths; p++) { - rx_send_path_nacks(server, rx_state, &rx_state->path_state[p], p, time, nr); + rx_send_path_nacks(server, rx_state, &rx_state->path_state[p], p, time, nr, is_index_transfer); } } @@ -1204,6 +1330,13 @@ static bool tx_acked_all(const struct sender_state *tx_state) { return true; } +static bool tx_acked_all_index(const struct sender_state *tx_state) { + if (tx_state->acked_chunks_index.num_set != tx_state->index_chunks) { + return false; + } + return true; +} + // Submitting the frames to the TX ring does not mean they will be sent immediately, // this forces all submitted packets to be sent so we can get the frames back static void kick_tx(struct hercules_server *server, struct xsk_socket_info *xsk) @@ -1246,6 +1379,20 @@ static void tx_register_acks(const struct rbudp_ack_pkt *ack, struct sender_stat } } +static void tx_register_acks_index(const struct rbudp_ack_pkt *ack, struct sender_state *tx_state) +{ + for(uint16_t e = 0; e < ack->num_acks; ++e) { + const u32 begin = ack->acks[e].begin; + const u32 end = ack->acks[e].end; + if(begin >= end || end > tx_state->acked_chunks_index.num) { + return; // Abort + } + for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized + bitset__set(&tx_state->acked_chunks_index, i); // don't need thread-safety here, all updates in same thread + } + } +} + // Pop entries from completion ring and store them in umem->available_frames. static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_info *umem) { @@ -1309,7 +1456,7 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, void *index, u64 index_size, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) { debug_printf("Sending initial"); char buf[HERCULES_MAX_PKTSIZE]; @@ -1324,20 +1471,42 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path } struct hercules_control_packet pld = { - .type = CONTROL_PACKET_TYPE_INITIAL, - .payload.initial = { - .filesize = filesize, - .chunklen = chunklen, - .timestamp = timestamp, - .path_index = path_index, - .flags = flags, - .name_len = strlen(filename), + .type = CONTROL_PACKET_TYPE_INITIAL, + .payload.initial = + { + .filesize = filesize, + .chunklen = chunklen, + .timestamp = timestamp, + .path_index = path_index, + .flags = flags, + .index_len = index_size, }, }; - assert(strlen(filename) < 100); // TODO - strncpy(pld.payload.initial.name, filename, 100); - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, sizeof(pld.type) + sizeof(pld.payload.initial) + pld.payload.initial.name_len, - path->payloadlen); + // Using sizeof(pld) would give fewer bytes than actually available due + // to the union in struct hercules_control_packet + u64 initial_pl_size = sizeof(pld.type) + sizeof(pld.payload.initial); + + // Only include directory index in the very first HS packet + if (new_transfer) { + u64 index_bytes_available = path->payloadlen - initial_pl_size; + + debug_printf("bytes for index: %lld, size %lld", index_bytes_available, + index_size); + if (index_size > index_bytes_available) { + // Index won't fit, will be transferred separately + debug_printf("index too long for HS packet!"); + pld.payload.initial.flags |= HANDSHAKE_FLAG_INDEX_FOLLOWS; + } else { + // Index is small enough to fit in the HS packet, include it + debug_printf("Index contained in HS packet"); + memcpy(pld.payload.initial.index, index, index_size); + initial_pl_size += index_size; + } + } + debug_printf("aaa %x", pld.payload.initial.flags); + + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&pld, + initial_pl_size, path->payloadlen); stitch_checksum(path, path->header.checksum, buf); send_eth_frame(server, path, buf); @@ -1385,7 +1554,7 @@ void send_path_handshakes(struct hercules_server *server, now + PATH_HANDSHAKE_TIMEOUT_NS)) { debug_printf("sending hs on path %d", p); // FIXME file name below? - tx_send_initial(server, path, "abcd", tx_state->filesize, + tx_send_initial(server, path, NULL, 0, tx_state->filesize, tx_state->chunklen, get_nsecs(), p, false, false); } @@ -1406,7 +1575,7 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_inte /* debug_printf("reserved %ld, wanted %ld", reserved, num_frames); */ // XXX FIXME struct hercules_session *s = atomic_load(&server->session_tx); - if(!s || atomic_load(&s->state) != SESSION_STATE_RUNNING) { + if(!s || !session_state_is_running(atomic_load(&s->state))) { debug_printf("STOP"); pthread_spin_unlock(&iface->umem->frames_lock); return; @@ -1435,7 +1604,7 @@ static short flowIdCtr = 0; static inline void tx_handle_send_queue_unit_for_iface( struct sender_state *tx_state, struct xsk_socket_info *xsk, int ifid, u64 frame_addrs[SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, - u32 thread_id) { + u32 thread_id, bool is_index_transfer) { u32 num_chunks_in_unit = 0; struct path_set *pathset = pathset_read(tx_state, thread_id); for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { @@ -1465,9 +1634,26 @@ static inline void tx_handle_send_queue_unit_for_iface( if(path->ifid != ifid) { continue; } - const u32 chunk_idx = unit->chunk_idx[i]; + u32 chunk_idx = unit->chunk_idx[i]; + if (!is_index_transfer && + chunk_idx >= tx_state->total_chunks){ + // Since we use the same send queue for both index and data + // transfer, we don't know which one the dequeued chunk idx refers + // to. This is only a problem right after the swap from index to + // data transfer (when there might still be items in the send queue + // that refer to the index transfer even though we've moved on) and + // there are more index than data packets. + // We need to send something though, since we've allocated the frame + // already, so we just pretend it's chunk 0. + debug_printf("Chunk idx too large, index leftover?"); + chunk_idx = 0; + } const size_t chunk_start = (size_t)chunk_idx * tx_state->chunklen; - const size_t len = umin64(tx_state->chunklen, tx_state->filesize - chunk_start); + size_t len = + umin64(tx_state->chunklen, tx_state->filesize - chunk_start); + if (is_index_transfer) { + len = umin64(tx_state->chunklen, tx_state->index_size - chunk_start); + } void *pkt = prepare_frame(xsk, frame_addrs[current_frame], idx + current_frame, path->framelen); frame_addrs[current_frame] = -1; @@ -1485,7 +1671,13 @@ static inline void tx_handle_send_queue_unit_for_iface( track_path = unit->paths[i]; seqnr = atomic_fetch_add(&path->cc_state->last_seqnr, 1); } - fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, seqnr, tx_state->mem + chunk_start, len, path->payloadlen); + u8 flags = 0; + char *payload = tx_state->mem; + if (is_index_transfer){ + flags |= PKT_FLAG_IS_INDEX; + payload = tx_state->index; + } + fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, payload + chunk_start, len, path->payloadlen); stitch_checksum(path, path->header.checksum, pkt); } xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); @@ -1493,11 +1685,11 @@ static inline void tx_handle_send_queue_unit_for_iface( static inline void tx_handle_send_queue_unit(struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], - struct send_queue_unit *unit, u32 thread_id) + struct send_queue_unit *unit, u32 thread_id, bool is_index_transfer) { for(int i = 0; i < server->num_ifaces; i++) { - tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, thread_id); + tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, thread_id, is_index_transfer); } } @@ -1632,15 +1824,23 @@ static void kick_cc(struct sender_state *tx_state, struct path_set *pathset) { // is set to the timestamp by which that ACK should arrive. Otherwise, wait_until // is not modified. static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 *chunks, u8 *chunk_rcvr, const u64 now, - u64 *wait_until, u32 num_chunks) + u64 *wait_until, u32 num_chunks, bool is_index_transfer) { u32 num_chunks_prepared = 0; u32 chunk_idx = tx_state->prev_chunk_idx; /* debug_printf("n chunks %d", num_chunks); */ for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ - chunk_idx = bitset__scan_neg(&tx_state->acked_chunks, chunk_idx); - if(chunk_idx == tx_state->total_chunks) { + u32 total_chunks; + if (is_index_transfer) { + chunk_idx = + bitset__scan_neg(&tx_state->acked_chunks_index, chunk_idx); + total_chunks = tx_state->index_chunks; + } else { + chunk_idx = bitset__scan_neg(&tx_state->acked_chunks, chunk_idx); + total_chunks = tx_state->total_chunks; + } + if (chunk_idx == total_chunks) { /* debug_printf("prev %d", rcvr->prev_chunk_idx); */ if(tx_state->prev_chunk_idx == 0) { // this receiver has finished debug_printf("receiver has finished"); @@ -1679,6 +1879,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 // Initialise new sender state. Returns null in case of error. static struct sender_state *init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, + size_t index_chunks, char *index, int max_rate_limit, char *mem, const struct hercules_app_addr *dests, struct hercules_path *paths, @@ -1690,6 +1891,7 @@ static struct sender_state *init_tx_state(struct hercules_session *session, "File too big, not enough chunks available (chunks needed: " "%llu, chunks available: %u)\n", total_chunks, UINT_MAX - 1); + // TODO update monitor err return NULL; } @@ -1701,12 +1903,15 @@ static struct sender_state *init_tx_state(struct hercules_session *session, tx_state->filesize = filesize; tx_state->chunklen = chunklen; tx_state->total_chunks = total_chunks; + tx_state->index_chunks = index_chunks; tx_state->mem = mem; + tx_state->index = index; tx_state->rate_limit = max_rate_limit; tx_state->start_time = 0; tx_state->end_time = 0; bitset__create(&tx_state->acked_chunks, tx_state->total_chunks); + bitset__create(&tx_state->acked_chunks_index, index_chunks); tx_state->handshake_rtt = 0; struct path_set *pathset = calloc(1, sizeof(*tx_state->pathset)); if (pathset == NULL){ @@ -1731,6 +1936,11 @@ static struct sender_state *init_tx_state(struct hercules_session *session, return tx_state; } +static void reset_tx_state(struct sender_state *tx_state) { + tx_state->finished = false; + tx_state->prev_chunk_idx = 0; +} + static void destroy_tx_state(struct sender_state *tx_state) { bitset__destroy(&tx_state->acked_chunks); // TODO freeing pathset @@ -1747,8 +1957,8 @@ static void tx_retransmit_initial(struct hercules_server *server, u64 now) { struct sender_state *tx_state = session_tx->tx_state; struct path_set *pathset = tx_state->pathset; // We always use the first path as the return path - tx_send_initial(server, &pathset->paths[0], - tx_state->filename, tx_state->filesize, + tx_send_initial(server, &pathset->paths[0], tx_state->index, + tx_state->index_size, tx_state->filesize, tx_state->chunklen, now, 0, true, true); session_tx->last_pkt_sent = now; } @@ -1799,12 +2009,19 @@ static void tx_handle_hs_confirm(struct hercules_server *server, } } tx_state->start_time = get_nsecs(); - session_tx->state = SESSION_STATE_WAIT_CTS; + if (parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS) { + // Need to do index transfer first + // XXX relying on data echoed back by receiver instead of local + // state + session_tx->state = SESSION_STATE_RUNNING_IDX; + } else { + // Index transfer not needed, straight to data transfer + session_tx->state = SESSION_STATE_WAIT_CTS; + } return; } - if (session_tx != NULL && - session_tx->state == SESSION_STATE_RUNNING) { + if (session_tx != NULL && session_state_is_running(session_tx->state)) { struct sender_state *tx_state = session_tx->tx_state; struct path_set *pathset = tx_state->pathset; // This is a reply to some handshake we sent during an already @@ -1835,25 +2052,116 @@ static void tx_handle_hs_confirm(struct hercules_server *server, // Map the provided file into memory for reading. Returns pointer to the mapped // area, or null on error. -static char *tx_mmap(char *fname, size_t *filesize) { - int f = open(fname, O_RDONLY); - if (f == -1) { +static char *tx_mmap(char *fname, size_t *filesize, void **index_o, u64 *index_size_o) { + FTS *fts = NULL; + FTSENT *ent = NULL; + debug_printf("opening"); + char* fts_arg[2] = {fname, NULL}; + fts = fts_open(fts_arg, FTS_PHYSICAL, NULL); // Don't follow symlinks + if (fts == NULL) { return NULL; } - struct stat stat; - int ret = fstat(f, &stat); - if (ret) { - close(f); + debug_printf("fts open"); + int index_cap = 4096; + void *index = malloc(index_cap); + if (index == NULL){ + fts_close(fts); return NULL; } - const size_t fsize = stat.st_size; - char *mem = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, f, 0); + int index_size = 0; + + int total_filesize = 0; + + while ((ent = fts_read(fts)) != NULL) { + switch (ent->fts_info) { + case FTS_F:; // Regular file + int entry_size = + sizeof(struct dir_index_entry) + ent->fts_pathlen + 1; + debug_printf("entry size %d", entry_size); + if (index_size + entry_size >= index_cap) { + debug_printf("need realloc"); + index = realloc(index, index_cap + 4096); + if (index == NULL) { + fts_close(fts); + return NULL; + } + index_cap += 4096; + } + debug_printf("adding to index: %s (%ldB)", ent->fts_path, ent->fts_statp->st_size); + struct dir_index_entry *newentry = + (struct dir_index_entry *)(index + index_size); + + newentry->filesize = ent->fts_statp->st_size; + newentry->type = INDEX_TYPE_FILE; + newentry->path_len = ent->fts_pathlen + 1; + debug_printf("pathlen %d", ent->fts_pathlen); + strncpy(newentry->path, ent->fts_path, newentry->path_len); + debug_printf("Readback: %s (%d) %dB", newentry->path, + newentry->type, newentry->filesize); + index_size += entry_size; + total_filesize += newentry->filesize; + break; + case FTS_D:; // Directory + entry_size = + sizeof(struct dir_index_entry) + ent->fts_pathlen + 1; + if (index_size + entry_size >= index_cap) { + debug_printf("need realloc"); + index = realloc(index, index_cap + 4096); + if (index == NULL) { + fts_close(fts); + return NULL; + } + index_cap += 4096; + } + debug_printf("adding to index: %s (%ldB)", ent->fts_path, ent->fts_statp->st_size); + newentry = + (struct dir_index_entry *)(index + index_size); + newentry->filesize = 0; + newentry->type = INDEX_TYPE_DIR; + newentry->path_len = ent->fts_pathlen + 1; + strncpy(newentry->path, ent->fts_path, newentry->path_len); + index_size += entry_size; + break; + default: + break; + } + } + + fts_close(fts); + debug_printf("total filesize %d", total_filesize); + debug_printf("total entry size %d", index_size); + char *mem = mmap(NULL, total_filesize, PROT_READ, MAP_PRIVATE | MAP_ANON, 0, 0); if (mem == MAP_FAILED) { - close(f); return NULL; } - close(f); - *filesize = fsize; + char *next_mapping = mem; + + struct dir_index_entry *p = index; + while (1) { + debug_printf("Read: %s (%d) %dB", p->path, p->type, p->filesize); + if (p->type == INDEX_TYPE_FILE) { + int f = open(p->path, O_RDONLY); + if (f == -1) { + return NULL; + } + char *filemap = mmap(next_mapping, p->filesize, PROT_READ, + MAP_PRIVATE | MAP_FIXED, f, 0); + if (mem == MAP_FAILED) { + debug_printf("filemap err!"); + return NULL; + } + next_mapping += p->filesize; + close(f); + } + p = ((char *)p) + sizeof(*p) + p->path_len; + if (p >= index + index_size) { + break; + } + } + + *filesize = total_filesize; + *index_o = index; + *index_size_o = index_size; return mem; } @@ -2021,10 +2329,11 @@ static void tx_send_p(void *arg) { while (1) { struct hercules_session *session_tx = atomic_load(&server->session_tx); if (session_tx == NULL || - atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { + !session_state_is_running(session_tx->state)) { kick_tx_server(server); // flush any pending packets continue; } + bool is_index_transfer = (session_tx->state == SESSION_STATE_RUNNING_IDX); struct send_queue_unit unit; int ret = send_queue_pop(session_tx->send_queue, &unit); if (!ret) { @@ -2051,7 +2360,7 @@ static void tx_send_p(void *arg) { } allocate_tx_frames(server, frame_addrs); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, - frame_addrs, &unit, args->id); + frame_addrs, &unit, args->id, is_index_transfer); } } @@ -2060,10 +2369,11 @@ static void rx_trickle_acks(void *arg) { struct hercules_server *server = arg; while (1) { struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + if (session_rx != NULL && session_state_is_running(session_rx->state)) { struct receiver_state *rx_state = session_rx->rx_state; + bool is_index_transfer = (session_rx->state == SESSION_STATE_RUNNING_IDX); // XXX: data races in access to shared rx_state! - atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); + atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); // FIXME what? if (atomic_load(&rx_state->last_pkt_rcvd) + umax64(100 * ACK_RATE_TIME_MS * 1e6, 3 * rx_state->handshake_rtt) < @@ -2071,12 +2381,17 @@ static void rx_trickle_acks(void *arg) { // Transmission timed out quit_session(session_rx, SESSION_ERROR_TIMEOUT); } - rx_send_acks(server, rx_state); - if (rx_received_all( - rx_state)) { // TODO move this check to ack receive? - debug_printf("Received all, done."); - quit_session(session_rx, SESSION_ERROR_OK); - rx_send_acks(server, rx_state); + rx_send_acks(server, rx_state, is_index_transfer); + if (rx_received_all(rx_state, is_index_transfer)) { + if (is_index_transfer) { + debug_printf("Received entire index"); + rx_send_acks(server, rx_state, is_index_transfer); + session_rx->state = SESSION_STATE_INDEX_READY; + } else { + debug_printf("Received all, done."); + rx_send_acks(server, rx_state, is_index_transfer); + quit_session(session_rx, SESSION_ERROR_OK); + } } } sleep_nsecs(ACK_RATE_TIME_MS * 1e6); @@ -2088,14 +2403,15 @@ static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; while (1) { struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + if (session_rx != NULL && session_state_is_running(session_rx->state)) { u32 ack_nr = 0; + bool is_index_transfer = (session_rx->state == SESSION_STATE_RUNNING_IDX); struct receiver_state *rx_state = session_rx->rx_state; // TODO remove this inner loop? - while (rx_state->session->state == SESSION_STATE_RUNNING && - !rx_received_all(rx_state)) { + while (session_state_is_running(rx_state->session->state) && + !rx_received_all(rx_state, is_index_transfer)) { u64 ack_round_start = get_nsecs(); - rx_send_nacks(server, rx_state, ack_round_start, ack_nr); + rx_send_nacks(server, rx_state, ack_round_start, ack_nr, is_index_transfer); u64 ack_round_end = get_nsecs(); if (ack_round_end > ack_round_start + rx_state->handshake_rtt * 1000 / 4) { @@ -2120,7 +2436,7 @@ static void rx_p(void *arg) { u32 i = 0; while (1) { struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { + if (session_rx != NULL && session_state_is_running(session_rx->state)) { rx_receive_batch(session_rx->rx_state, args->xsks[i % num_ifaces]); i++; } @@ -2169,87 +2485,88 @@ static void *tx_p(void *arg) { u32 chunks[BATCH_SIZE]; u8 chunk_rcvr[BATCH_SIZE]; struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx != NULL && - atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { - struct sender_state *tx_state = session_tx->tx_state; - struct path_set *pathset = pathset_read(tx_state, 0); - /* debug_printf("Start transmit round"); */ - tx_state->prev_rate_check = get_nsecs(); - - pop_completion_rings(server); - send_path_handshakes(server, tx_state, pathset); - u64 next_ack_due = 0; - - // in each iteration, we send packets on a single path to each receiver - // collect the rate limits for each active path - u32 allowed_chunks = compute_max_chunks_current_path(tx_state, pathset); - - if (allowed_chunks == - 0) { // we hit the rate limits on every path; switch paths - iterate_paths(tx_state, pathset); - continue; - } + if (session_tx != NULL && + session_state_is_running(atomic_load(&session_tx->state))) { + bool is_index_transfer = (session_tx->state == SESSION_STATE_RUNNING_IDX); + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *pathset = pathset_read(tx_state, 0); + /* debug_printf("Start transmit round"); */ + tx_state->prev_rate_check = get_nsecs(); - // TODO re-enable? - // sending rates might add up to more than BATCH_SIZE, shrink - // proportionally, if needed - /* shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); */ - - const u64 now = get_nsecs(); - u32 num_chunks = 0; - if (!tx_state->finished) { - u64 ack_due = 0; - // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks to - // send - u32 cur_num_chunks = prepare_rcvr_chunks( - tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, - &ack_due, allowed_chunks); - num_chunks += cur_num_chunks; - if (tx_state->finished) { - terminate_cc(pathset); - kick_cc(tx_state, pathset); - } else { - // only wait for the nearest ack - if (next_ack_due) { - if (next_ack_due > ack_due) { - next_ack_due = ack_due; - } - } else { - next_ack_due = ack_due; - } - } - } + pop_completion_rings(server); + send_path_handshakes(server, tx_state, pathset); + u64 next_ack_due = 0; - if (num_chunks > 0) { - u8 rcvr_path[1]; - prepare_rcvr_paths(tx_state, rcvr_path); - produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, - num_chunks); - tx_state->tx_npkts_queued += num_chunks; - rate_limit_tx(tx_state); - - // update book-keeping - u32 path_idx = pathset->path_index; - struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; - if (cc_state != NULL) { - // FIXME allowed_chunks below is not correct (3x) - atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); - atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); - if (pcc_has_active_mi(cc_state, now)) { - atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, - allowed_chunks); - } - } - } - - iterate_paths(tx_state, pathset); - - if (now < next_ack_due) { - // XXX if the session vanishes in the meantime, we might wait - // unnecessarily - sleep_until(next_ack_due); - } - } + // in each iteration, we send packets on a single path to each receiver + // collect the rate limits for each active path + u32 allowed_chunks = compute_max_chunks_current_path(tx_state, pathset); + + if (allowed_chunks == + 0) { // we hit the rate limits on every path; switch paths + iterate_paths(tx_state, pathset); + continue; + } + + // TODO re-enable? + // sending rates might add up to more than BATCH_SIZE, shrink + // proportionally, if needed + /* shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); */ + + const u64 now = get_nsecs(); + u32 num_chunks = 0; + if (!tx_state->finished) { + u64 ack_due = 0; + // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks + // to send + u32 cur_num_chunks = prepare_rcvr_chunks( + tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, + &ack_due, allowed_chunks, is_index_transfer); + num_chunks += cur_num_chunks; + if (tx_state->finished && !is_index_transfer) { + terminate_cc(pathset); + kick_cc(tx_state, pathset); + } else { + // only wait for the nearest ack + if (next_ack_due) { + if (next_ack_due > ack_due) { + next_ack_due = ack_due; + } + } else { + next_ack_due = ack_due; + } + } + } + + if (num_chunks > 0) { + u8 rcvr_path[1]; + prepare_rcvr_paths(tx_state, rcvr_path); + produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, + num_chunks); + tx_state->tx_npkts_queued += num_chunks; + rate_limit_tx(tx_state); + + // update book-keeping + u32 path_idx = pathset->path_index; + struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; + if (cc_state != NULL) { + // FIXME allowed_chunks below is not correct (3x) + atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); + atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); + if (pcc_has_active_mi(cc_state, now)) { + atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, + allowed_chunks); + } + } + } + + iterate_paths(tx_state, pathset); + + if (now < next_ack_due) { + // XXX if the session vanishes in the meantime, we might wait + // unnecessarily + sleep_until(next_ack_due); + } + } } return NULL; @@ -2279,12 +2596,26 @@ static void new_tx_if_available(struct hercules_server *server) { } size_t filesize; - char *mem = tx_mmap(fname, &filesize); + void *index; + u64 index_size; + char *mem = tx_mmap(fname, &filesize, &index, &index_size); if (mem == NULL){ debug_printf("mmap failed"); monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_MAP_FAILED, 0, 0); return; } + debug_printf("Index totals %llu bytes, data size %llu bytes", index_size, filesize); + debug_printf("Transfer index in %f packets", index_size/(double)payloadlen); + u64 chunklen = payloadlen - rbudp_headerlen; + u64 chunks_for_index = (index_size + chunklen - 1) / chunklen; + if (chunks_for_index >= UINT_MAX) { + fprintf(stderr, + "Index too big, not enough chunks available (chunks needed: " + "%llu, chunks available: %u)\n", + chunks_for_index, UINT_MAX - 1); + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_TOO_LARGE, 0, 0); + return; + } struct hercules_session *session = make_session(server); if (session == NULL){ monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_INIT, 0, 0); @@ -2308,12 +2639,13 @@ static void new_tx_if_available(struct hercules_server *server) { // TODO free paths debug_printf("received %d paths", n_paths); - int chunklen = payloadlen - rbudp_headerlen; struct sender_state *tx_state = init_tx_state( - session, filesize, chunklen, server->rate_limit, mem, + session, filesize, chunklen, chunks_for_index, index, server->rate_limit, mem, &session->peer, paths, 1, n_paths, server->max_paths, server->n_threads); strncpy(tx_state->filename, fname, 99); + tx_state->index = index; + tx_state->index_size = index_size; session->tx_state = tx_state; atomic_store(&server->session_tx, session); } @@ -2385,7 +2717,7 @@ static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { static void tx_update_paths(struct hercules_server *server) { struct hercules_session *session_tx = server->session_tx; - if (session_tx && session_tx->state == SESSION_STATE_RUNNING) { + if (session_tx && session_state_is_running(session_tx->state)) { struct sender_state *tx_state = session_tx->tx_state; struct path_set *old_pathset = tx_state->pathset; int n_paths; @@ -2543,7 +2875,7 @@ static void print_session_stats(struct hercules_server *server, static void tx_update_monitor(struct hercules_server *server, u64 now) { struct hercules_session *session_tx = server->session_tx; - if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { + if (session_tx != NULL && session_state_is_running(session_tx->state)) { bool ret = monitor_update_job(server->usock, session_tx->jobid, session_tx->state, 0, ( now - session_tx->tx_state->start_time ) / (int)1e9, session_tx->tx_state->chunklen * @@ -2554,6 +2886,21 @@ static void tx_update_monitor(struct hercules_server *server, u64 now) { } } +static void rx_send_cts(struct hercules_server *server, u64 now){ + struct hercules_session *session_rx = server->session_rx; + if (session_rx != NULL && session_rx->state == SESSION_STATE_INDEX_READY){ + struct receiver_state *rx_state = session_rx->rx_state; + rx_state->mem = + rx_mmap(rx_state->index, rx_state->index_size, rx_state->filesize); + if (rx_state->mem == NULL){ + quit_session(session_rx, SESSION_ERROR_MAP_FAILED); + return; + } + rx_send_cts_ack(server, rx_state); + server->session_rx->state = SESSION_STATE_RUNNING_DATA; + } +} + #define PRINT_STATS // Read control packets from the control socket and process them; also handles @@ -2580,6 +2927,7 @@ static void events_p(void *arg) { mark_timed_out_sessions(server, now); cleanup_finished_sessions(server, now); tx_retransmit_initial(server, now); + rx_send_cts(server, now); #ifdef PRINT_STATS print_session_stats(server, &prints); #endif @@ -2587,7 +2935,7 @@ static void events_p(void *arg) { /* tx_update_paths(server); */ /* lastpoll = now; */ /* } */ - if (now > lastpoll + 3e9){ + if (now > lastpoll + 20e9){ tx_update_monitor(server, now); tx_update_paths(server); lastpoll = now; @@ -2632,7 +2980,7 @@ static void events_p(void *arg) { // failure and the session abandoned immediately? struct hercules_session *session_tx = server->session_tx; if (session_tx != NULL && - session_tx->state == SESSION_STATE_RUNNING) { + session_state_is_running(session_tx->state)) { struct path_set *pathset = session_tx->tx_state->pathset; if (scmp_bad_path < pathset->n_paths) { @@ -2682,8 +3030,8 @@ static void events_p(void *arg) { } // Otherwise, we process and reflect the packet if (server->session_rx != NULL && - server->session_rx->state == - SESSION_STATE_RUNNING) { + session_state_is_running( + server->session_rx->state)) { if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { // This is a handshake that tries to open a new @@ -2708,18 +3056,57 @@ static void events_p(void *arg) { make_session(server); server->session_rx = session; session->state = SESSION_STATE_NEW; - struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt->name, - parsed_pkt->name_len, parsed_pkt->filesize, - parsed_pkt->chunklen, false); - session->rx_state = rx_state; - rx_handle_initial(server, rx_state, parsed_pkt, - buf, addr.sll_ifindex, - rbudp_pkt + rbudp_headerlen, - len); - rx_send_cts_ack(server, rx_state); - server->session_rx->state = - SESSION_STATE_RUNNING; + if (!(parsed_pkt->flags & + HANDSHAKE_FLAG_INDEX_FOLLOWS)) { + // Entire index contained in this packet, + // we can go ahead and proceed with transfer + struct receiver_state *rx_state = + make_rx_state( + session, parsed_pkt->index, + parsed_pkt->index_len, + parsed_pkt->filesize, + parsed_pkt->chunklen, false); + if (rx_state == NULL) { + debug_printf( + "Error creating RX state!"); + break; + } + session->rx_state = rx_state; + rx_handle_initial( + server, rx_state, parsed_pkt, buf, + addr.sll_ifindex, + rbudp_pkt + rbudp_headerlen, len); + rx_send_cts_ack(server, rx_state); + server->session_rx->state = + SESSION_STATE_RUNNING_DATA; + } + else { + // Index transferred separately + struct receiver_state *rx_state = + make_rx_state_nomap( + session, parsed_pkt->index_len, + parsed_pkt->filesize, + parsed_pkt->chunklen, false); + if (rx_state == NULL) { + debug_printf( + "Error creating RX state!"); + break; + } + rx_state->index_size = parsed_pkt->index_len; + rx_state->index = calloc(1, parsed_pkt->index_len); + if (rx_state->index == NULL){ + debug_printf("Error allocating index"); + break; + } + session->rx_state = rx_state; + rx_handle_initial( + server, rx_state, parsed_pkt, buf, + addr.sll_ifindex, + rbudp_pkt + rbudp_headerlen, len); + server->session_rx->state = + SESSION_STATE_RUNNING_IDX; + + } } } break; @@ -2735,15 +3122,13 @@ static void events_p(void *arg) { if (cp->payload.ack.num_acks == 0) { debug_printf("CTS received"); atomic_store(&server->session_tx->state, - SESSION_STATE_RUNNING); + SESSION_STATE_RUNNING_DATA); } } if (server->session_tx != NULL && - server->session_tx->state == - SESSION_STATE_RUNNING) { - tx_register_acks( - &cp->payload.ack, - server->session_tx->tx_state); + server->session_tx->state == SESSION_STATE_RUNNING_DATA) { + tx_register_acks(&cp->payload.ack, + server->session_tx->tx_state); count_received_pkt(server->session_tx, h->path); atomic_store(&server->session_tx->last_pkt_rcvd, get_nsecs()); if (tx_acked_all(server->session_tx->tx_state)) { @@ -2752,6 +3137,18 @@ static void events_p(void *arg) { SESSION_ERROR_OK); } } + if (server->session_tx != NULL && + server->session_tx->state == SESSION_STATE_RUNNING_IDX) { + tx_register_acks_index(&cp->payload.ack, + server->session_tx->tx_state); + count_received_pkt(server->session_tx, h->path); + atomic_store(&server->session_tx->last_pkt_rcvd, get_nsecs()); + if (tx_acked_all_index(server->session_tx->tx_state)) { + debug_printf("Index transfer done, received all acks"); + reset_tx_state(server->session_tx->tx_state); + server->session_tx->state = SESSION_STATE_WAIT_CTS; + } + } break; case CONTROL_PACKET_TYPE_NACK: @@ -2761,8 +3158,8 @@ static void events_p(void *arg) { break; } if (server->session_tx != NULL && - server->session_tx->state == - SESSION_STATE_RUNNING) { + session_state_is_running( + server->session_tx->state)) { count_received_pkt(server->session_tx, h->path); nack_trace_push(cp->payload.ack.timestamp, cp->payload.ack.ack_nr); diff --git a/hercules.h b/hercules.h index 439d125..f39d068 100644 --- a/hercules.h +++ b/hercules.h @@ -72,15 +72,19 @@ struct receiver_state { atomic_uint_least64_t handshake_rtt; /** Filesize in bytes */ size_t filesize; + size_t index_size; /** Size of file data (in byte) per packet */ u32 chunklen; /** Number of packets that will make up the entire file. Equal to * `ceil(filesize/chunklen)` */ u32 total_chunks; + u32 index_chunks; /** Memory mapped file for receive */ char *mem; + char *index; struct bitset received_chunks; + struct bitset received_chunks_index; // The reply path to use for contacting the sender. This is the reversed // path of the last initial packet with the SET_RETURN_PATH flag set. @@ -128,6 +132,7 @@ struct sender_state { u32 prev_chunk_idx; bool finished; struct bitset acked_chunks; //< Chunks we've received an ack for + struct bitset acked_chunks_index; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns struct path_set *_Atomic pathset; // Paths currently in use @@ -148,6 +153,10 @@ struct sender_state { // Start/end time of the current transfer u64 start_time; u64 end_time; + + u32 index_chunks; + char *index; + size_t index_size; }; /// SESSION @@ -158,9 +167,12 @@ enum session_state { // waiting for a reflected HS packet SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and // CTS - SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS - SESSION_STATE_RUNNING, //< Transfer in progress - SESSION_STATE_DONE, //< Transfer done (or cancelled with error) + SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS + SESSION_STATE_INDEX_READY, //< (RX) Index transfer complete, map files and + //send CTS + SESSION_STATE_RUNNING_DATA, //< Data transfer in progress + SESSION_STATE_RUNNING_IDX, //< Directory index transfer in progress + SESSION_STATE_DONE, //< Transfer done (or cancelled with error) }; enum session_error { @@ -173,6 +185,7 @@ enum session_error { SESSION_ERROR_CANCELLED, //< Transfer cancelled by monitor SESSION_ERROR_BAD_MTU, //< Invalid MTU supplied by the monitor SESSION_ERROR_MAP_FAILED, //< Could not mmap file + SESSION_ERROR_TOO_LARGE, //< File or index size too large SESSION_ERROR_INIT, //< Could not initialise session }; diff --git a/packet.h b/packet.h index ac227ea..c78e0d9 100644 --- a/packet.h +++ b/packet.h @@ -103,7 +103,24 @@ struct scmp_message { struct hercules_header { __u32 chunk_idx; __u8 path; + __u8 flags; __u32 seqno; + __u8 data[]; +}; +// The flags field is zero for regular data and (N)ACK packets +// The flags field has the lowest bit set for packets referring to the transfer +// of a directory index +// The flags field is zero for initial packets, since those don't refer to +// either in particular +#define PKT_FLAG_IS_INDEX (0x1u << 0) + +#define INDEX_TYPE_FILE 0 +#define INDEX_TYPE_DIR 1 +struct dir_index_entry { + __u32 filesize; + __u8 type; + __u32 path_len; + __u8 path[]; }; // Structure of first RBUDP packet sent by sender. @@ -114,8 +131,8 @@ struct rbudp_initial_pkt { __u64 timestamp; __u8 path_index; __u8 flags; - __u32 name_len; - __u8 name[]; + __u32 index_len; + __u8 index[]; }; // Indicates to the receiver this path should be used for (N)ACKs @@ -124,6 +141,9 @@ struct rbudp_initial_pkt { #define HANDSHAKE_FLAG_HS_CONFIRM (0x1u << 1) // Indicates that the packet is trying to start a new transfer #define HANDSHAKE_FLAG_NEW_TRANSFER (0x1u << 2) +// We're transferring a directory and the index is larger than the space +// available in the handshake packet +#define HANDSHAKE_FLAG_INDEX_FOLLOWS (0x1u << 3) // Structure of ACK RBUDP packets sent by the receiver. // Integers all transmitted in little endian (host endianness). From fd33abee75d74cbf868dd94ea4715919f717af13 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sat, 15 Jun 2024 18:25:19 +0200 Subject: [PATCH 046/112] enable multiple concurrent sessions --- bpf_prgm/redirect_userspace.c | 4 +- hercules.c | 814 ++++++++++++++++++++-------------- hercules.h | 21 +- monitor.c | 3 +- monitor.h | 3 +- monitor/monitor.go | 1 + monitor/scionheader.go | 6 +- packet.h | 2 +- 8 files changed, 505 insertions(+), 349 deletions(-) diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index ba18436..182f951 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -126,7 +126,9 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) if((void *)(l4udph + 1) > data_end) { return XDP_PASS; // too short after all } - if(l4udph->dest != addr->port) { + if (ntohs(l4udph->dest) < ntohs(addr->port) || + ntohs(l4udph->dest) > + ntohs(addr->port) + HERCULES_CONCURRENT_SESSIONS) { return XDP_PASS; } offset += sizeof(struct udphdr); diff --git a/hercules.c b/hercules.c index 6097c9e..8b8744b 100644 --- a/hercules.c +++ b/hercules.c @@ -94,9 +94,10 @@ static struct hercules_session *make_session(struct hercules_server *server); static inline bool src_matches_address(struct hercules_session *session, const struct scionaddrhdr_ipv4 *scionaddrhdr, const struct udphdr *udphdr) { - struct hercules_app_addr *addr = &session->peer; - return scionaddrhdr->src_ia == addr->ia && - scionaddrhdr->src_ip == addr->ip && udphdr->uh_sport == addr->port; + /* struct hercules_app_addr *addr = &session->peer; */ + /* return scionaddrhdr->src_ia == addr->ia && */ + /* scionaddrhdr->src_ip == addr->ip && udphdr->uh_sport == addr->port; */ + return true; } static void __exit_with_error(struct hercules_server *server, int error, const char *file, const char *func, int line) @@ -163,8 +164,10 @@ static void send_eth_frame(struct hercules_server *server, void debug_print_rbudp_pkt(const char *pkt, bool recv) { struct hercules_header *h = (struct hercules_header *)pkt; const char *prefix = (recv) ? "RX->" : "<-TX"; - printf("%s Header: IDX %u, Path %u, Seqno %u\n", prefix, h->chunk_idx, - h->path, h->seqno); + const u16 *src_port = (const u16 *) (pkt-8); + const u16 *dst_port = (const u16 *) (pkt-6); + printf("%s [%u -> %u] Header: IDX %u, Path %u, Seqno %u\n", prefix, + ntohs(*src_port), ntohs(*dst_port), h->chunk_idx, h->path, h->seqno); if (h->chunk_idx == UINT_MAX) { // Control packets const char *pl = pkt + 9; @@ -213,6 +216,28 @@ void debug_print_rbudp_pkt(const char * pkt, bool recv){ } #endif +static struct hercules_session *lookup_session_tx(struct hercules_server *server, u16 port){ + if (port < server->config.port_min || port > server->config.port_max){ + return NULL; + } + if (port == server->config.port_min){ + return NULL; + } + u32 off = port - server->config.port_min - 1; + return server->sessions_tx[off]; +} + +static struct hercules_session *lookup_session_rx(struct hercules_server *server, u16 port){ + if (port < server->config.port_min || port > server->config.port_max){ + return NULL; + } + if (port == server->config.port_min){ + return NULL; + } + u32 off = port - server->config.port_min - 1; + return server->sessions_rx[off]; +} + // Initialise a new session. Returns null in case of error. static struct hercules_session *make_session(struct hercules_server *server) { struct hercules_session *s; @@ -299,13 +324,15 @@ struct hercules_server *hercules_init_server( server->num_ifaces = num_ifaces; server->config.queue = queue; server->n_threads = n_threads; - server->session_rx = NULL; - server->session_tx = NULL; + memset(server->sessions_rx, 0, sizeof(server->sessions_rx[0])*HERCULES_CONCURRENT_SESSIONS); + memset(server->sessions_tx, 0, sizeof(server->sessions_tx[0])*HERCULES_CONCURRENT_SESSIONS); server->worker_args = calloc(server->n_threads, sizeof(struct worker_args *)); if (server->worker_args == NULL){ exit_with_error(NULL, ENOMEM); } server->config.local_addr = local_addr; + server->config.port_min = ntohs(local_addr.port); + server->config.port_max = server->config.port_min + HERCULES_CONCURRENT_SESSIONS; server->config.configure_queues = configure_queues; server->config.xdp_mode = xdp_mode; /* server->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; */ @@ -437,7 +464,7 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec // figure out which path the SCMP message is referring to. // Returns the offending path's id, or PCC_NO_PATH on failure. // XXX Not checking dst or source ia/addr/port in reflected packet -static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length) { +static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, u16 *offending_dst_port) { size_t offset = 0; const char *pkt = NULL; debug_printf("SCMP type %d", scmp->type); @@ -523,6 +550,9 @@ static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length) { offset += sizeof(struct udphdr); const struct hercules_header *rbudp_hdr = (const struct hercules_header *)(pkt + offset); + if (offending_dst_port) { + *offending_dst_port = ntohs(l4udph->uh_dport); + } return rbudp_hdr->path; } @@ -535,7 +565,8 @@ static const char *parse_pkt(const struct hercules_server *server, const char *pkt, size_t length, bool check, const struct scionaddrhdr_ipv4 **scionaddrh_o, const struct udphdr **udphdr_o, - u8 *scmp_offending_path_o) { + u8 *scmp_offending_path_o, + u16 *scmp_offending_dst_port_o) { // Parse Ethernet frame if(sizeof(struct ether_header) > length) { debug_printf("too short for eth header: %zu", length); @@ -621,7 +652,7 @@ static const char *parse_pkt(const struct hercules_server *server, const struct scmp_message *scmp_msg = (const struct scmp_message *)(pkt + next_offset); *scmp_offending_path_o = - parse_scmp_packet(scmp_msg, length - next_offset); + parse_scmp_packet(scmp_msg, length - next_offset, scmp_offending_dst_port_o); } else { debug_printf("unknown SCION L4: %u", next_header); } @@ -648,8 +679,10 @@ static const char *parse_pkt(const struct hercules_server *server, } const struct udphdr *l4udph = (const struct udphdr *)(pkt + offset); - if(l4udph->dest != server->config.local_addr.port) { - debug_printf("not addressed to us (L4 UDP port): %u", ntohs(l4udph->dest)); + if (ntohs(l4udph->dest) < server->config.port_min || + ntohs(l4udph->dest) > server->config.port_max) { + debug_printf("not addressed to us (L4 UDP port): %u", + ntohs(l4udph->dest)); return NULL; } @@ -663,14 +696,46 @@ static const char *parse_pkt(const struct hercules_server *server, return parse_pkt_fast_path(pkt, length, check, offset); } +static inline void stitch_src_port(const struct hercules_path *path, u16 port, char *pkt){ + char *payload = pkt + path->headerlen; + u16 *udp_src = (u16 *)(payload-8); + *udp_src = htons(port); +} + +static inline void stitch_dst_port(const struct hercules_path *path, u16 port, char *pkt){ + char *payload = pkt + path->headerlen; + u16 *udp_dst = (u16 *)(payload-6); + *udp_dst = htons(port); +} + +static void stitch_checksum_with_dst(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) +{ + chk_input chk_input_s; + chk_input *chksum_struc = init_chk_input(&chk_input_s, 4); + assert(chksum_struc); + char *payload = pkt + path->headerlen; + u16 udp_src_le = ntohs(*(u16*)(payload - 8)); // Why in host order? + u16 udp_dst_le = ntohs(*(u16*)(payload - 6)); + precomputed_checksum = ~precomputed_checksum; // take one complement of precomputed checksum + chk_add_chunk(chksum_struc, (u8 *)&precomputed_checksum, 2); // add precomputed header checksum + chk_add_chunk(chksum_struc, (u8 *)&udp_src_le, 2); + chk_add_chunk(chksum_struc, (u8 *)&udp_dst_le, 2); + chk_add_chunk(chksum_struc, (u8 *)payload, path->payloadlen); // add payload + u16 pkt_checksum = checksum(chksum_struc); + + mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); +} + static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) { chk_input chk_input_s; - chk_input *chksum_struc = init_chk_input(&chk_input_s, 2); + chk_input *chksum_struc = init_chk_input(&chk_input_s, 3); assert(chksum_struc); char *payload = pkt + path->headerlen; + u16 udp_src_le = ntohs(*(u16*)(payload - 8)); // Why in host order? precomputed_checksum = ~precomputed_checksum; // take one complement of precomputed checksum chk_add_chunk(chksum_struc, (u8 *)&precomputed_checksum, 2); // add precomputed header checksum + chk_add_chunk(chksum_struc, (u8 *)&udp_src_le, 2); chk_add_chunk(chksum_struc, (u8 *)payload, path->payloadlen); // add payload u16 pkt_checksum = checksum(chksum_struc); @@ -840,17 +905,17 @@ static bool has_more_nacks(sequence_number curr, struct bitset *seqs) } static void -submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames) +submit_rx_frames(struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames) { u32 idx_fq = 0; pthread_spin_lock(&umem->fq_lock); size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); while(reserved != num_frames) { reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - if(session == NULL || session->state != SESSION_STATE_RUNNING) { - pthread_spin_unlock(&umem->fq_lock); - return; - } + /* if(session == NULL || session->state != SESSION_STATE_RUNNING) { */ + /* pthread_spin_unlock(&umem->fq_lock); */ + /* return; */ + /* } */ } for(size_t i = 0; i < num_frames; i++) { @@ -861,7 +926,7 @@ submit_rx_frames(struct hercules_session *session, struct xsk_umem_info *umem, c } // Read a batch of data packets from the XSK -static void rx_receive_batch(struct receiver_state *rx_state, +static void rx_receive_batch(struct hercules_server *server, struct xsk_socket_info *xsk) { u32 idx_rx = 0; int ignored = 0; @@ -872,14 +937,6 @@ static void rx_receive_batch(struct receiver_state *rx_state, } // optimistically update receive timestamp - u64 now = get_nsecs(); - u64 old_last_pkt_rcvd = atomic_load(&rx_state->last_pkt_rcvd); - if (old_last_pkt_rcvd < now) { - atomic_compare_exchange_strong(&rx_state->last_pkt_rcvd, - &old_last_pkt_rcvd, now); - } - // TODO timestamps in multiple places... - atomic_store(&rx_state->session->last_pkt_rcvd, now); u64 frame_addrs[BATCH_SIZE]; for (size_t i = 0; i < rcvd; i++) { @@ -888,35 +945,32 @@ static void rx_receive_batch(struct receiver_state *rx_state, u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); + u16 pkt_dst_port = ntohs(*(u16 *)(rbudp_pkt - 6)); + struct hercules_session *session_rx = lookup_session_rx(server, pkt_dst_port); + if (session_rx == NULL || session_rx->state != SESSION_STATE_RUNNING){ + continue; + } + u64 now = get_nsecs(); + u64 old_last_pkt_rcvd = atomic_load(&session_rx->rx_state->last_pkt_rcvd); + if (old_last_pkt_rcvd < now) { + atomic_compare_exchange_strong(&session_rx->rx_state->last_pkt_rcvd, + &old_last_pkt_rcvd, now); + } + atomic_store(&session_rx->last_pkt_rcvd, now); if (rbudp_pkt) { debug_print_rbudp_pkt(rbudp_pkt, true); - if (!handle_rbudp_data_pkt(rx_state, rbudp_pkt, + if (!handle_rbudp_data_pkt(session_rx->rx_state, rbudp_pkt, len - (rbudp_pkt - pkt))) { debug_printf("Non-data packet on XDP socket? Ignoring."); - ignored++; } } else { debug_printf("Unparseable packet on XDP socket, ignoring"); - ignored++; } - } - xsk_ring_cons__release(&xsk->rx, rcvd); - atomic_fetch_add(&rx_state->session->rx_npkts, (rcvd - ignored)); - submit_rx_frames(rx_state->session, xsk->umem, frame_addrs, rcvd); -} + atomic_fetch_add(&session_rx->rx_npkts, 1); -static void rx_receive_and_drop(struct xsk_socket_info *xsk){ - u32 idx_rx = 0; - size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); - u64 frame_addrs[BATCH_SIZE]; - for (size_t i = 0; i < rcvd; i++) { - u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; - frame_addrs[i] = addr; - u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; - const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); } xsk_ring_cons__release(&xsk->rx, rcvd); - submit_rx_frames(NULL, xsk->umem, frame_addrs, rcvd); + submit_rx_frames(xsk->umem, frame_addrs, rcvd); } // Prepare a file and memory mapping to receive a file @@ -952,6 +1006,7 @@ static char *rx_mmap(const char *pathname, size_t filesize) { static struct receiver_state *make_rx_state(struct hercules_session *session, char *filename, size_t namelen, size_t filesize, int chunklen, + u16 src_port, bool is_pcc_benchmark) { struct receiver_state *rx_state; rx_state = calloc(1, sizeof(*rx_state)); @@ -967,6 +1022,7 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; + rx_state->src_port = src_port; filename[namelen+1] = 0; // HACK FIXME rx_state->mem = rx_mmap(filename, filesize); if (rx_state->mem == NULL) { @@ -1015,7 +1071,7 @@ static bool rx_get_reply_path(struct receiver_state *rx_state, // Reflect the received initial packet back to the sender. The sent packet is // identical to the one received, but has the HS_CONFIRM flag set. static void rx_send_rtt_ack(struct hercules_server *server, - struct receiver_state *rx_state, + struct receiver_state *rx_state, int rx_slot, struct rbudp_initial_pkt *pld) { struct hercules_path path; if(!rx_get_reply_path(rx_state, &path)) { @@ -1030,11 +1086,13 @@ static void rx_send_rtt_ack(struct hercules_server *server, .type = CONTROL_PACKET_TYPE_INITIAL, .payload.initial = *pld, }; - /* strncpy(control_pkt.payload.initial.name, pld->name, pld->name_len); */ control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, - sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); + stitch_src_port(&path, server->config.port_min + rx_slot + 1, buf); + fill_rbudp_pkt( + rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, + sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), + path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); @@ -1045,7 +1103,7 @@ static void rx_send_rtt_ack(struct hercules_server *server, // the session's reply path if the corresponding flag was set static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, - struct rbudp_initial_pkt *initial, + struct rbudp_initial_pkt *initial, int rx_slot, const char *buf, int ifid, const char *payload, int framelen) { debug_printf("handling initial"); @@ -1058,7 +1116,7 @@ static void rx_handle_initial(struct hercules_server *server, // Are they ever not the same? rx_update_reply_path(server, rx_state, ifid, initial->chunklen + headerlen, framelen, buf); } - rx_send_rtt_ack(server, rx_state, + rx_send_rtt_ack(server, rx_state, rx_slot, initial); // echo back initial pkt to ACK filesize } @@ -1083,6 +1141,7 @@ static void rx_send_cts_ack(struct hercules_server *server, .payload.ack.num_acks = 0, }; + stitch_src_port(&path, rx_state->src_port, buf); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); @@ -1095,10 +1154,11 @@ static void rx_send_cts_ack(struct hercules_server *server, static void send_control_pkt(struct hercules_server *server, struct hercules_session *session, struct hercules_control_packet *control_pkt, - struct hercules_path *path) { + struct hercules_path *path, u16 src_port) { char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + stitch_src_port(path, src_port, buf); fill_rbudp_pkt( rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)control_pkt, sizeof(control_pkt->type) + ack__len(&control_pkt->payload.ack), @@ -1127,11 +1187,11 @@ static void rx_send_acks(struct hercules_server *server, struct receiver_state * // send an empty ACK to keep connection alive until first packet arrives u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries); - send_control_pkt(server, rx_state->session, &control_pkt, &path); + send_control_pkt(server, rx_state->session, &control_pkt, &path, rx_state->src_port); for(; curr < rx_state->total_chunks;) { curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries); if(control_pkt.payload.ack.num_acks == 0) break; - send_control_pkt(server, rx_state->session, &control_pkt, &path); + send_control_pkt(server, rx_state->session, &control_pkt, &path, rx_state->src_port); } } @@ -1175,6 +1235,7 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s if(control_pkt.payload.ack.num_acks != 0) { nack_end = control_pkt.payload.ack.acks[control_pkt.payload.ack.num_acks - 1].end; } + stitch_src_port(&path, rx_state->src_port, buf); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, path_idx, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); @@ -1262,7 +1323,6 @@ static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_ } frame_queue__push(&umem->available_frames, num); xsk_ring_cons__release(&umem->cq, entries); - atomic_fetch_add(&server->session_tx->tx_npkts, entries); } } @@ -1309,7 +1369,7 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) +tx_send_initial(struct hercules_server *server, const struct hercules_path *path, int tx_slot, u16 dst_port, char *filename, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) { debug_printf("Sending initial"); char buf[HERCULES_MAX_PKTSIZE]; @@ -1336,12 +1396,16 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path }; assert(strlen(filename) < 100); // TODO strncpy(pld.payload.initial.name, filename, 100); - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, sizeof(pld.type) + sizeof(pld.payload.initial) + pld.payload.initial.name_len, - path->payloadlen); - stitch_checksum(path, path->header.checksum, buf); + stitch_src_port(path, server->config.port_min + tx_slot + 1, buf); + stitch_dst_port(path, dst_port, buf); + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, (char *)&pld, + sizeof(pld.type) + sizeof(pld.payload.initial) + + pld.payload.initial.name_len, + path->payloadlen); + stitch_checksum_with_dst(path, path->header.checksum, buf); send_eth_frame(server, path, buf); - atomic_fetch_add(&server->session_tx->tx_npkts, 1); + atomic_fetch_add(&server->sessions_tx[tx_slot]->tx_npkts, 1); } // TODO do something instead of spinning until time is up @@ -1373,6 +1437,7 @@ static void rate_limit_tx(struct sender_state *tx_state) void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state, + int tx_slot, struct path_set *pathset) { u64 now = get_nsecs(); for (u32 p = 0; p < pathset->n_paths; p++) { @@ -1385,9 +1450,10 @@ void send_path_handshakes(struct hercules_server *server, now + PATH_HANDSHAKE_TIMEOUT_NS)) { debug_printf("sending hs on path %d", p); // FIXME file name below? - tx_send_initial(server, path, "abcd", tx_state->filesize, - tx_state->chunklen, get_nsecs(), p, false, - false); + tx_send_initial(server, path, tx_slot, + tx_state->session->dst_port, "abcd", + tx_state->filesize, tx_state->chunklen, + get_nsecs(), p, false, false); } } } @@ -1405,7 +1471,7 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_inte reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); /* debug_printf("reserved %ld, wanted %ld", reserved, num_frames); */ // XXX FIXME - struct hercules_session *s = atomic_load(&server->session_tx); + struct hercules_session *s = server->sessions_tx[0]; // FIXME idx 0 if(!s || atomic_load(&s->state) != SESSION_STATE_RUNNING) { debug_printf("STOP"); pthread_spin_unlock(&iface->umem->frames_lock); @@ -1435,7 +1501,7 @@ static short flowIdCtr = 0; static inline void tx_handle_send_queue_unit_for_iface( struct sender_state *tx_state, struct xsk_socket_info *xsk, int ifid, u64 frame_addrs[SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, - u32 thread_id) { + u32 thread_id, u16 dst_port) { u32 num_chunks_in_unit = 0; struct path_set *pathset = pathset_read(tx_state, thread_id); for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { @@ -1485,19 +1551,21 @@ static inline void tx_handle_send_queue_unit_for_iface( track_path = unit->paths[i]; seqnr = atomic_fetch_add(&path->cc_state->last_seqnr, 1); } + stitch_dst_port(path, dst_port, pkt); + stitch_src_port(path, tx_state->src_port, pkt); fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, seqnr, tx_state->mem + chunk_start, len, path->payloadlen); - stitch_checksum(path, path->header.checksum, pkt); + stitch_checksum_with_dst(path, path->header.checksum, pkt); } xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); } -static inline void tx_handle_send_queue_unit(struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], - u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], - struct send_queue_unit *unit, u32 thread_id) -{ - +static inline void tx_handle_send_queue_unit( + struct hercules_server *server, struct sender_state *tx_state, + struct xsk_socket_info *xsks[], + u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], + struct send_queue_unit *unit, u32 thread_id, u16 dst_port) { for(int i = 0; i < server->num_ifaces; i++) { - tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, thread_id); + tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, thread_id, dst_port); } } @@ -1680,10 +1748,9 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 static struct sender_state *init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, int max_rate_limit, char *mem, - const struct hercules_app_addr *dests, struct hercules_path *paths, u32 num_dests, const int num_paths, - u32 max_paths_per_dest, u32 num_threads) { + u32 max_paths_per_dest, u32 num_threads, u16 src_port) { u64 total_chunks = (filesize + chunklen - 1) / chunklen; if (total_chunks >= UINT_MAX) { fprintf(stderr, @@ -1705,6 +1772,7 @@ static struct sender_state *init_tx_state(struct hercules_session *session, tx_state->rate_limit = max_rate_limit; tx_state->start_time = 0; tx_state->end_time = 0; + tx_state->src_port = src_port; bitset__create(&tx_state->acked_chunks, tx_state->total_chunks); tx_state->handshake_rtt = 0; @@ -1716,7 +1784,6 @@ static struct sender_state *init_tx_state(struct hercules_session *session, pathset->n_paths = num_paths; memcpy(pathset->paths, paths, sizeof(*paths)*num_paths); tx_state->pathset = pathset; - tx_state->session->peer = *dests; // tx_p uses index 0, tx_send_p threads start at index 1 int err = posix_memalign((void **)&tx_state->epochs, CACHELINE_SIZE, @@ -1740,24 +1807,28 @@ static void destroy_tx_state(struct sender_state *tx_state) { // (Re)send HS if needed static void tx_retransmit_initial(struct hercules_server *server, u64 now) { - struct hercules_session *session_tx = server->session_tx; - if (session_tx && session_tx->state == SESSION_STATE_PENDING) { - if (now > - session_tx->last_pkt_sent + session_hs_retransmit_interval) { - struct sender_state *tx_state = session_tx->tx_state; - struct path_set *pathset = tx_state->pathset; - // We always use the first path as the return path - tx_send_initial(server, &pathset->paths[0], - tx_state->filename, tx_state->filesize, - tx_state->chunklen, now, 0, true, true); - session_tx->last_pkt_sent = now; + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx && session_tx->state == SESSION_STATE_PENDING) { + if (now > + session_tx->last_pkt_sent + session_hs_retransmit_interval) { + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *pathset = tx_state->pathset; + // We always use the first path as the return path + tx_send_initial(server, &pathset->paths[0], s, + session_tx->dst_port, tx_state->filename, + tx_state->filesize, tx_state->chunklen, now, 0, + true, true); + session_tx->last_pkt_sent = now; + } } } } static void tx_handle_hs_confirm(struct hercules_server *server, - struct rbudp_initial_pkt *parsed_pkt) { - struct hercules_session *session_tx = server->session_tx; + struct rbudp_initial_pkt *parsed_pkt, + u16 dst_port, u16 src_port) { + struct hercules_session *session_tx = lookup_session_tx(server, dst_port); if (session_tx != NULL && session_tx->state == SESSION_STATE_PENDING) { struct sender_state *tx_state = session_tx->tx_state; @@ -1790,6 +1861,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, 0, tx_state->handshake_rtt / 1e9, pathset->paths[0].cc_state->pcc_mi_duration); + // TODO setting HS ok can be moved outside the if-pcc block // make sure we later perform RTT estimation // on every enabled path pathset->paths[0].next_handshake_at = @@ -1799,6 +1871,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, } } tx_state->start_time = get_nsecs(); + session_tx->dst_port = src_port; session_tx->state = SESSION_STATE_WAIT_CTS; return; } @@ -2018,8 +2091,10 @@ static inline bool pcc_has_active_mi(struct ccontrol_state *cc_state, u64 now) static void tx_send_p(void *arg) { struct worker_args *args = arg; struct hercules_server *server = args->server; + int cur_session = 0; while (1) { - struct hercules_session *session_tx = atomic_load(&server->session_tx); + cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; + struct hercules_session *session_tx = server->sessions_tx[cur_session]; if (session_tx == NULL || atomic_load(&session_tx->state) != SESSION_STATE_RUNNING) { kick_tx_server(server); // flush any pending packets @@ -2028,6 +2103,10 @@ static void tx_send_p(void *arg) { struct send_queue_unit unit; int ret = send_queue_pop(session_tx->send_queue, &unit); if (!ret) { + pathset_read( + session_tx->tx_state, + args->id); // Necessary to prevent prevent pathset updater from + // getting stuck in an infinite loop; kick_tx_server(server); continue; } @@ -2051,15 +2130,18 @@ static void tx_send_p(void *arg) { } allocate_tx_frames(server, frame_addrs); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, - frame_addrs, &unit, args->id); + frame_addrs, &unit, args->id, session_tx->dst_port); + atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); } } // Send ACKs to the sender. Runs in its own thread. static void rx_trickle_acks(void *arg) { struct hercules_server *server = arg; + int cur_session = 0; while (1) { - struct hercules_session *session_rx = atomic_load(&server->session_rx); + cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; + struct hercules_session *session_rx = server->sessions_rx[cur_session]; if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { struct receiver_state *rx_state = session_rx->rx_state; // XXX: data races in access to shared rx_state! @@ -2086,8 +2168,10 @@ static void rx_trickle_acks(void *arg) { // Send NACKs to the sender. Runs in its own thread. static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; + int cur_session = 0; while (1) { - struct hercules_session *session_rx = atomic_load(&server->session_rx); + cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; + struct hercules_session *session_rx = server->sessions_rx[cur_session]; if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { u32 ack_nr = 0; struct receiver_state *rx_state = session_rx->rx_state; @@ -2119,20 +2203,8 @@ static void rx_p(void *arg) { int num_ifaces = server->num_ifaces; u32 i = 0; while (1) { - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx != NULL && session_rx->state == SESSION_STATE_RUNNING) { - rx_receive_batch(session_rx->rx_state, args->xsks[i % num_ifaces]); - i++; - } - else { - // Even though we don't currently have a running session, we might - // not have processed all received packets before stopping the - // previous session (or they might still be in flight). Drain any - // received packets to avoid erroneously assigning them to the next - // session. - rx_receive_and_drop(args->xsks[i % num_ifaces]); - i++; - } + rx_receive_batch(server, args->xsks[i % num_ifaces]); + i++; } } @@ -2163,12 +2235,14 @@ static void rx_p(void *arg) { */ static void *tx_p(void *arg) { struct hercules_server *server = arg; + int cur_session = 0; while (1) { /* pthread_spin_lock(&server->biglock); */ + cur_session = (cur_session +1) % HERCULES_CONCURRENT_SESSIONS; pop_completion_rings(server); u32 chunks[BATCH_SIZE]; u8 chunk_rcvr[BATCH_SIZE]; - struct hercules_session *session_tx = atomic_load(&server->session_tx); + struct hercules_session *session_tx = server->sessions_tx[cur_session]; if (session_tx != NULL && atomic_load(&session_tx->state) == SESSION_STATE_RUNNING) { struct sender_state *tx_state = session_tx->tx_state; @@ -2177,7 +2251,7 @@ static void *tx_p(void *arg) { tx_state->prev_rate_check = get_nsecs(); pop_completion_rings(server); - send_path_handshakes(server, tx_state, pathset); + send_path_handshakes(server, tx_state, cur_session, pathset); u64 next_ack_due = 0; // in each iteration, we send packets on a single path to each receiver @@ -2257,20 +2331,46 @@ static void *tx_p(void *arg) { /// Event handler tasks +static int find_free_tx_slot(struct hercules_server *server){ + for (int i = 0; i < HERCULES_CONCURRENT_SESSIONS; i++){ + if (server->sessions_tx[i] == NULL){ + return i; + } + } + return -1; +} + +static int find_free_rx_slot(struct hercules_server *server){ + for (int i = 0; i < HERCULES_CONCURRENT_SESSIONS; i++){ + if (server->sessions_rx[i] == NULL){ + return i; + } + } + return -1; +} + // Check if the monitor has new transfer jobs available and, if so, start one static void new_tx_if_available(struct hercules_server *server) { + int session_slot = find_free_tx_slot(server); + if (session_slot == -1){ + // no free tx slot + return; + } + // We're the only thread adding/removing sessions, so if we found a free + // slot it will still be free when we assign to it later on char fname[1000]; memset(fname, 0, 1000); int count; u16 jobid; u16 payloadlen; - struct hercules_app_addr dest; + u16 dst_port; - int ret = monitor_get_new_job(server->usock, fname, &jobid, &dest, &payloadlen); + int ret = monitor_get_new_job(server->usock, fname, &jobid, &dst_port, &payloadlen); if (!ret) { return; } debug_printf("new job: %s", fname); + debug_printf("using tx slot %d", session_slot); if (sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)payloadlen) { debug_printf("supplied payloadlen too small"); @@ -2294,6 +2394,7 @@ static void new_tx_if_available(struct hercules_server *server) { session->state = SESSION_STATE_PENDING; session->payloadlen = payloadlen; session->jobid = jobid; + session->dst_port = dst_port; int n_paths; struct hercules_path *paths; @@ -2309,181 +2410,199 @@ static void new_tx_if_available(struct hercules_server *server) { debug_printf("received %d paths", n_paths); int chunklen = payloadlen - rbudp_headerlen; + u16 src_port = server->config.port_min + session_slot + 1; struct sender_state *tx_state = init_tx_state( - session, filesize, chunklen, server->rate_limit, mem, - &session->peer, paths, 1, n_paths, - server->max_paths, server->n_threads); + session, filesize, chunklen, server->rate_limit, mem, paths, 1, n_paths, + server->max_paths, server->n_threads, src_port); strncpy(tx_state->filename, fname, 99); session->tx_state = tx_state; - atomic_store(&server->session_tx, session); + atomic_store(&server->sessions_tx[session_slot], session); } // Remove and free finished sessions static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { - // Wait for twice the session timeout before removing the finished - // session (and thus before accepting new sessions). This ensures the - // other party has also quit or timed out its session and won't send - // packets that would then be mixed into future sessions. - // XXX This depends on both endpoints sharing the same timeout value, - // which is not negotiated but defined at the top of this file. - struct hercules_session *session_tx = atomic_load(&server->session_tx); - if (session_tx && session_tx->state == SESSION_STATE_DONE) { - if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { - u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; - u64 bytes_acked = session_tx->tx_state->chunklen * - session_tx->tx_state->acked_chunks.num_set; - monitor_update_job(server->usock, session_tx->jobid, - session_tx->state, session_tx->error, - sec_elapsed, bytes_acked); - struct hercules_session *current = server->session_tx; - atomic_store(&server->session_tx, NULL); - fprintf(stderr, "Cleaning up TX session...\n"); - // At this point we don't know if some other thread still has a - // pointer to the session that it might dereference, so we cannot - // safely free it. So, we record the pointer and defer freeing it - // until after the next session has completed. At that point, no - // references to the deferred session should be around, so we then - // free it. - destroy_session_tx(server->deferred_tx); - server->deferred_tx = current; - } - } - struct hercules_session *session_rx = atomic_load(&server->session_rx); - if (session_rx && session_rx->state == SESSION_STATE_DONE) { - if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { - struct hercules_session *current = server->session_rx; - atomic_store(&server->session_rx, NULL); - fprintf(stderr, "Cleaning up RX session...\n"); - // See the note above on deferred freeing - destroy_session_rx(server->deferred_rx); - server->deferred_rx = current; + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + // Wait for twice the session timeout before removing the finished + // session (and thus before accepting new sessions). This ensures the + // other party has also quit or timed out its session and won't send + // packets that would then be mixed into future sessions. + // XXX This depends on both endpoints sharing the same timeout value, + // which is not negotiated but defined at the top of this file. + struct hercules_session *session_tx = atomic_load(&server->sessions_tx[s]); + if (session_tx && session_tx->state == SESSION_STATE_DONE) { + if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { + u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; + u64 bytes_acked = session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set; + monitor_update_job(server->usock, session_tx->jobid, + session_tx->state, session_tx->error, + sec_elapsed, bytes_acked); + struct hercules_session *current = session_tx; + atomic_store(&server->sessions_tx[s], NULL); + fprintf(stderr, "Cleaning up TX session %d...\n", s); + // At this point we don't know if some other thread still has a + // pointer to the session that it might dereference, so we + // cannot safely free it. So, we record the pointer and defer + // freeing it until after the next session has completed. At + // that point, no references to the deferred session should be + // around, so we then free it. + destroy_session_tx(server->deferreds_tx[s]); + server->deferreds_tx[s] = current; + } + } + struct hercules_session *session_rx = atomic_load(&server->sessions_rx[s]); + if (session_rx && session_rx->state == SESSION_STATE_DONE) { + if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { + struct hercules_session *current = session_rx; + atomic_store(&server->sessions_rx[s], NULL); + fprintf(stderr, "Cleaning up RX session %d...\n", s); + // See the note above on deferred freeing + destroy_session_rx(server->deferreds_rx[s]); + server->deferreds_rx[s] = current; + } } } } // Time out if no packets received for a while static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { - struct hercules_session *session_tx = server->session_tx; - if (session_tx && session_tx->state != SESSION_STATE_DONE) { - if (now > session_tx->last_pkt_rcvd + session_timeout) { - quit_session(session_tx, SESSION_ERROR_TIMEOUT); - debug_printf("Session (TX) timed out!"); - } - } - struct hercules_session *session_rx = server->session_rx; - if (session_rx && session_rx->state != SESSION_STATE_DONE) { - if (now > session_rx->last_pkt_rcvd + session_timeout) { - quit_session(session_rx, SESSION_ERROR_TIMEOUT); - debug_printf("Session (RX) timed out!"); + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx && session_tx->state != SESSION_STATE_DONE) { + if (now > session_tx->last_pkt_rcvd + session_timeout) { + quit_session(session_tx, SESSION_ERROR_TIMEOUT); + debug_printf("Session (TX %2d) timed out!", s); + } } - else if (now > session_rx->last_new_pkt_rcvd + session_stale_timeout){ - quit_session(session_rx, SESSION_ERROR_STALE); - debug_printf("Session (RX) stale!"); + struct hercules_session *session_rx = server->sessions_rx[s]; + if (session_rx && session_rx->state != SESSION_STATE_DONE) { + if (now > session_rx->last_pkt_rcvd + session_timeout) { + quit_session(session_rx, SESSION_ERROR_TIMEOUT); + debug_printf("Session (RX %2d) timed out!", s); + } else if (now > + session_rx->last_new_pkt_rcvd + session_stale_timeout) { + quit_session(session_rx, SESSION_ERROR_STALE); + debug_printf("Session (RX %2d) stale!", s); + } } } } static void tx_update_paths(struct hercules_server *server) { - struct hercules_session *session_tx = server->session_tx; - if (session_tx && session_tx->state == SESSION_STATE_RUNNING) { - struct sender_state *tx_state = session_tx->tx_state; - struct path_set *old_pathset = tx_state->pathset; - int n_paths; - struct hercules_path *paths; - bool ret = monitor_get_paths(server->usock, session_tx->jobid, - session_tx->payloadlen, &n_paths, &paths); - if (!ret) { - debug_printf("error getting paths"); - return; - } - debug_printf("received %d paths", n_paths); - if (n_paths == 0) { - free(paths); - quit_session(session_tx, SESSION_ERROR_NO_PATHS); - return; - } - struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); - if (new_pathset == NULL) { - // FIXME leak? - return; - } - u32 new_epoch = tx_state->next_epoch; - new_pathset->epoch = new_epoch; - tx_state->next_epoch++; - new_pathset->n_paths = n_paths; - memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); - u32 path_lim = - (old_pathset->n_paths > (u32)n_paths) ? (u32)n_paths : old_pathset->n_paths; - bool replaced_return_path = false; - struct ccontrol_state **replaced_cc = - calloc(old_pathset->n_paths, sizeof(*replaced_cc)); - if (replaced_cc == NULL) { - // FIXME leak? - return; - } - for (u32 i = 0; i < old_pathset->n_paths; i++) { - replaced_cc[i] = old_pathset->paths[i].cc_state; - } - for (u32 i = 0; i < path_lim; i++) { - // Set these two values before the comparison or it would fail even - // if paths are the same. - new_pathset->paths[i].next_handshake_at = - old_pathset->paths[i].next_handshake_at; - new_pathset->paths[i].cc_state = old_pathset->paths[i].cc_state; - - // XXX This works, but it means we restart CC even if the path has - // not changed (but the header has, eg. because the old one - // expired). We could avoid this by having the monitor tell us - // whether the path changed, as it used to. - if (memcmp(&old_pathset->paths[i], &new_pathset->paths[i], - sizeof(struct hercules_path)) == 0) { - // Old and new path are the same, CC state carries over. - // Since we copied the CC state before just leave as-is. - debug_printf("Path %d not changed", i); - replaced_cc[i] = NULL; - } else { - debug_printf("Path %d changed, resetting CC", i); - if (i == 0) { - // Return path is always idx 0 - replaced_return_path = true; + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx && session_tx->state == SESSION_STATE_RUNNING) { + debug_printf("Updating paths for TX %d", s); + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *old_pathset = tx_state->pathset; + int n_paths; + struct hercules_path *paths; + bool ret = + monitor_get_paths(server->usock, session_tx->jobid, + session_tx->payloadlen, &n_paths, &paths); + if (!ret) { + debug_printf("error getting paths"); + return; + } + debug_printf("received %d paths", n_paths); + if (n_paths == 0) { + free(paths); + quit_session(session_tx, SESSION_ERROR_NO_PATHS); + return; + } + struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); + if (new_pathset == NULL) { + // FIXME leak? + return; + } + u32 new_epoch = tx_state->next_epoch; + new_pathset->epoch = new_epoch; + tx_state->next_epoch++; + new_pathset->n_paths = n_paths; + memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); + u32 path_lim = (old_pathset->n_paths > (u32)n_paths) + ? (u32)n_paths + : old_pathset->n_paths; + bool replaced_return_path = false; + struct ccontrol_state **replaced_cc = + calloc(old_pathset->n_paths, sizeof(*replaced_cc)); + if (replaced_cc == NULL) { + // FIXME leak? + return; + } + for (u32 i = 0; i < old_pathset->n_paths; i++) { + replaced_cc[i] = old_pathset->paths[i].cc_state; + } + for (u32 i = 0; i < path_lim; i++) { + // Set these two values before the comparison or it would fail + // even if paths are the same. + new_pathset->paths[i].next_handshake_at = + old_pathset->paths[i].next_handshake_at; + new_pathset->paths[i].cc_state = old_pathset->paths[i].cc_state; + + // XXX This works, but it means we restart CC even if the path + // has not changed (but the header has, eg. because the old one + // expired). We could avoid this by having the monitor tell us + // whether the path changed, as it used to. + if (memcmp(&old_pathset->paths[i], &new_pathset->paths[i], + sizeof(struct hercules_path)) == 0) { + // Old and new path are the same, CC state carries over. + // Since we copied the CC state before just leave as-is. + debug_printf("Path %d not changed", i); + replaced_cc[i] = NULL; + } else { + debug_printf("Path %d changed, resetting CC", i); + if (i == 0) { + // Return path is always idx 0 + replaced_return_path = true; + } + // TODO whether to use pcc should be decided on a per-path + // basis by the monitor + if (server->enable_pcc) { + // TODO assert chunk length fits onto path + // The new path is different, restart CC + // TODO where to get rate + u32 rate = 100; + new_pathset->paths[i].cc_state = init_ccontrol_state( + rate, tx_state->total_chunks, new_pathset->n_paths); + // Re-send a handshake to update path rtt + new_pathset->paths[i].next_handshake_at = 0; + } } - // TODO whether to use pcc should be decided on a per-path basis - // by the monitor - if (server->enable_pcc) { - // TODO assert chunk length fits onto path - // The new path is different, restart CC - // TODO where to get rate - u32 rate = 100; - new_pathset->paths[i].cc_state = init_ccontrol_state( - rate, tx_state->total_chunks, new_pathset->n_paths); - // Re-send a handshake to update path rtt + if (replaced_return_path) { + // If we changed the return path we re-send the handshake on + // all paths to update RTT + debug_printf( + "Re-sending HS on path %d because return path changed", + i); new_pathset->paths[i].next_handshake_at = 0; } } - if (replaced_return_path) { - // If we changed the return path we re-send the handshake on all - // paths to update RTT - debug_printf( - "Re-sending HS on path %d because return path changed", i); - new_pathset->paths[i].next_handshake_at = 0; + // Finally, swap in the new pathset + tx_state->pathset = new_pathset; + free(paths); // These were *copied* into the new pathset + for (int i = 0; i < server->n_threads + 1; i++) { + int attempts = 0; + do { + attempts++; + if (attempts > 1000000){ + debug_printf("something wrong with pathset swap loop"); + debug_printf("waiting on %d, epoch still %lld", i, tx_state->epochs[i].epoch); + attempts = 0; + } + // Wait until the thread has seen the new pathset + } while (tx_state->epochs[i].epoch != new_epoch); } + for (u32 i = 0; i < old_pathset->n_paths; i++) { + // If CC was replaced, this contains the pointer to the old CC + // state. Otherwise it contains NULL, and we don't need to free + // anything. + free(replaced_cc[i]); + } + free(replaced_cc); + free(old_pathset); + debug_printf("done with update"); } - // Finally, swap in the new pathset - tx_state->pathset = new_pathset; - free(paths); // These were *copied* into the new pathset - for (int i = 0; i < server->n_threads + 1; i++) { - do { - // Wait until the thread has seen the new pathset - } while (tx_state->epochs[i].epoch != new_epoch); - } - for (u32 i = 0; i < old_pathset->n_paths; i++) { - // If CC was replaced, this contains the pointer to the old CC - // state. Otherwise it contains NULL, and we don't need to free - // anything. - free(replaced_cc[i]); - } - free(replaced_cc); - free(old_pathset); } } @@ -2510,46 +2629,57 @@ static void print_session_stats(struct hercules_server *server, u64 tdiff = now - p->ts; p->ts = now; - struct hercules_session *session_tx = server->session_tx; - if (session_tx && session_tx->state != SESSION_STATE_DONE) { - u32 sent_now = session_tx->tx_npkts; - u32 acked_count = session_tx->tx_state->acked_chunks.num_set; - u32 total = session_tx->tx_state->acked_chunks.num; - double send_rate_pps = (sent_now - p->tx_sent) / ((double)tdiff / 1e9); - p->tx_sent = sent_now; - double send_rate = - 8 * send_rate_pps * server->session_tx->tx_state->chunklen / 1e6; - fprintf(stderr, "(TX) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", - acked_count, total, session_tx->rx_npkts, session_tx->tx_npkts, - send_rate); - } - - struct hercules_session *session_rx = server->session_rx; - if (session_rx && session_rx->state != SESSION_STATE_DONE) { - u32 begin = bitset__scan_neg(&session_rx->rx_state->received_chunks, 0); - u32 rec_count = session_rx->rx_state->received_chunks.num_set; - u32 total = session_rx->rx_state->received_chunks.num; - u32 rcvd_now = session_rx->rx_npkts; - double recv_rate_pps = - (rcvd_now - p->rx_received) / ((double)tdiff / 1e9); - p->rx_received = rcvd_now; - double recv_rate = - 8 * recv_rate_pps * server->session_rx->rx_state->chunklen / 1e6; - fprintf(stderr, "(RX) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", - rec_count, total, session_rx->rx_npkts, session_rx->tx_npkts, - recv_rate); + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx && session_tx->state != SESSION_STATE_DONE) { + u32 sent_now = session_tx->tx_npkts; + u32 acked_count = session_tx->tx_state->acked_chunks.num_set; + u32 total = session_tx->tx_state->acked_chunks.num; + double send_rate_pps = + (sent_now - p->tx_sent) / ((double)tdiff / 1e9); + p->tx_sent = sent_now; + double send_rate = 8 * send_rate_pps * + session_tx->tx_state->chunklen / 1e6; + fprintf(stderr, + "(TX %2d) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", + s, + acked_count, total, session_tx->rx_npkts, + session_tx->tx_npkts, send_rate); + } + + struct hercules_session *session_rx = server->sessions_rx[s]; + if (session_rx && session_rx->state != SESSION_STATE_DONE) { + u32 begin = + bitset__scan_neg(&session_rx->rx_state->received_chunks, 0); + u32 rec_count = session_rx->rx_state->received_chunks.num_set; + u32 total = session_rx->rx_state->received_chunks.num; + u32 rcvd_now = session_rx->rx_npkts; + double recv_rate_pps = + (rcvd_now - p->rx_received) / ((double)tdiff / 1e9); + p->rx_received = rcvd_now; + double recv_rate = 8 * recv_rate_pps * + session_rx->rx_state->chunklen / 1e6; + fprintf(stderr, + "(RX %2d) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", + s, + rec_count, total, session_rx->rx_npkts, + session_rx->tx_npkts, recv_rate); + } } } static void tx_update_monitor(struct hercules_server *server, u64 now) { - struct hercules_session *session_tx = server->session_tx; - if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { - bool ret = monitor_update_job(server->usock, session_tx->jobid, session_tx->state, 0, - ( now - session_tx->tx_state->start_time ) / (int)1e9, - session_tx->tx_state->chunklen * - session_tx->tx_state->acked_chunks.num_set); - if (!ret) { - quit_session(session_tx, SESSION_ERROR_CANCELLED); + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { + bool ret = monitor_update_job( + server->usock, session_tx->jobid, session_tx->state, 0, + (now - session_tx->tx_state->start_time) / (int)1e9, + session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set); + if (!ret) { + quit_session(session_tx, SESSION_ERROR_CANCELLED); + } } } } @@ -2574,9 +2704,8 @@ static void events_p(void *arg) { u64 now = get_nsecs(); /* if (now > lastpoll + 1e9){ */ // XXX run the following every n seconds or every n socket reads? - if (server->session_tx == NULL) { - new_tx_if_available(server); - } + // FIXME don't loop over all sessions, one at a time + new_tx_if_available(server); mark_timed_out_sessions(server, now); cleanup_finished_sessions(server, now); tx_retransmit_initial(server, now); @@ -2617,12 +2746,14 @@ static void events_p(void *arg) { } u8 scmp_bad_path = PCC_NO_PATH; - const char *rbudp_pkt = parse_pkt( - server, buf, len, true, &scionaddrhdr, &udphdr, &scmp_bad_path); + u16 scmp_bad_port = 0; + const char *rbudp_pkt = + parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr, + &scmp_bad_path, &scmp_bad_port); if (rbudp_pkt == NULL) { if (scmp_bad_path != PCC_NO_PATH) { - debug_printf("Received SCMP error on path %d, disabling", - scmp_bad_path); + debug_printf("Received SCMP error on path %d, dst port %u, disabling", + scmp_bad_path, scmp_bad_port); // XXX We disable the path that received an SCMP error. The // next time we fetch new paths from the monitor it will be // re-enabled, if it's still present. It may be desirable to @@ -2630,7 +2761,7 @@ static void events_p(void *arg) { // update paths and on the exact SCMP error. Also, should // "destination unreachable" be treated as a permanent // failure and the session abandoned immediately? - struct hercules_session *session_tx = server->session_tx; + struct hercules_session *session_tx = lookup_session_tx(server, scmp_bad_port); if (session_tx != NULL && session_tx->state == SESSION_STATE_RUNNING) { struct path_set *pathset = @@ -2642,6 +2773,8 @@ static void events_p(void *arg) { } continue; } + u16 pkt_dst_port = ntohs(*(u16 *)( rbudp_pkt - 6 )); + u16 pkt_src_port = ntohs(*(u16 *)( rbudp_pkt - 8 )); const size_t rbudp_len = len - (rbudp_pkt - buf); if (rbudp_len < sizeof(u32)) { @@ -2677,24 +2810,26 @@ static void events_p(void *arg) { // This is a confirmation for a handshake packet // we sent out earlier debug_printf("HS confirm packet"); - tx_handle_hs_confirm(server, parsed_pkt); + tx_handle_hs_confirm(server, parsed_pkt, pkt_dst_port, pkt_src_port); break; // Make sure we don't process this further } // Otherwise, we process and reflect the packet - if (server->session_rx != NULL && - server->session_rx->state == - SESSION_STATE_RUNNING) { + struct hercules_session *session_rx = lookup_session_rx(server, pkt_dst_port); + if (session_rx != NULL && + session_rx->state == SESSION_STATE_RUNNING) { if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { // This is a handshake that tries to open a new // path for the running transfer + // Source port does not matter, so we pass 0 rx_handle_initial( - server, server->session_rx->rx_state, - parsed_pkt, buf, addr.sll_ifindex, + server, session_rx->rx_state, + parsed_pkt, 0, buf, addr.sll_ifindex, rbudp_pkt + rbudp_headerlen, len); } } - if (server->session_rx == NULL && + int rx_slot = find_free_rx_slot(server); + if (rx_slot != -1 && (parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { // We don't have a running session and this is an // attempt to start a new one, go ahead and start a @@ -2706,19 +2841,22 @@ static void events_p(void *arg) { // path or we won't be able to reply struct hercules_session *session = make_session(server); - server->session_rx = session; + server->sessions_rx[rx_slot] = session; session->state = SESSION_STATE_NEW; + u16 src_port = + server->config.port_min + rx_slot + 1; + debug_printf("src port is %d", src_port); struct receiver_state *rx_state = make_rx_state( session, parsed_pkt->name, parsed_pkt->name_len, parsed_pkt->filesize, - parsed_pkt->chunklen, false); + parsed_pkt->chunklen, src_port, false); session->rx_state = rx_state; - rx_handle_initial(server, rx_state, parsed_pkt, + rx_handle_initial(server, rx_state, parsed_pkt, rx_slot, buf, addr.sll_ifindex, rbudp_pkt + rbudp_headerlen, len); rx_send_cts_ack(server, rx_state); - server->session_rx->state = + session->state = SESSION_STATE_RUNNING; } } @@ -2729,26 +2867,26 @@ static void events_p(void *arg) { debug_printf("ACK packet too short"); break; } - if (server->session_tx != NULL && - server->session_tx->state == + struct hercules_session *session_tx = + lookup_session_tx(server, pkt_dst_port); + if (session_tx != NULL && + session_tx->state == SESSION_STATE_WAIT_CTS) { if (cp->payload.ack.num_acks == 0) { debug_printf("CTS received"); - atomic_store(&server->session_tx->state, + atomic_store(&session_tx->state, SESSION_STATE_RUNNING); } } - if (server->session_tx != NULL && - server->session_tx->state == - SESSION_STATE_RUNNING) { - tx_register_acks( - &cp->payload.ack, - server->session_tx->tx_state); - count_received_pkt(server->session_tx, h->path); - atomic_store(&server->session_tx->last_pkt_rcvd, get_nsecs()); - if (tx_acked_all(server->session_tx->tx_state)) { - debug_printf("TX done, received all acks"); - quit_session(server->session_tx, + if (session_tx != NULL && + session_tx->state == SESSION_STATE_RUNNING) { + tx_register_acks(&cp->payload.ack, + session_tx->tx_state); + count_received_pkt(session_tx, h->path); + atomic_store(&session_tx->last_pkt_rcvd, get_nsecs()); + if (tx_acked_all(session_tx->tx_state)) { + debug_printf("TX done, received all acks (%d)", pkt_dst_port-server->config.port_min); + quit_session(session_tx, SESSION_ERROR_OK); } } @@ -2760,14 +2898,15 @@ static void events_p(void *arg) { debug_printf("NACK packet too short"); break; } - if (server->session_tx != NULL && - server->session_tx->state == + session_tx = lookup_session_tx(server, pkt_dst_port); + if (session_tx != NULL && + session_tx->state == SESSION_STATE_RUNNING) { - count_received_pkt(server->session_tx, h->path); + count_received_pkt(session_tx, h->path); nack_trace_push(cp->payload.ack.timestamp, cp->payload.ack.ack_nr); struct path_set *pathset = - server->session_tx->tx_state->pathset; + session_tx->tx_state->pathset; if (h->path > pathset->n_paths) { // The pathset was updated in the meantime and // there are now fewer paths, so ignore this @@ -2787,8 +2926,10 @@ static void events_p(void *arg) { // all data packets debug_printf("Non-control packet received on control socket"); } - if (server->session_tx){ - pcc_monitor(server->session_tx->tx_state); + struct hercules_session *session_tx = + lookup_session_tx(server, pkt_dst_port); + if (session_tx) { + pcc_monitor(session_tx->tx_state); } } } @@ -3039,9 +3180,10 @@ int main(int argc, char *argv[]) { "threads, xdp mode 0x%x", queue, rx_threads, tx_threads, xdp_mode); + bool enable_pcc = false; struct hercules_server *server = hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, - rx_threads, false, true); + rx_threads, false, enable_pcc); hercules_main(server); } diff --git a/hercules.h b/hercules.h index 439d125..a969875 100644 --- a/hercules.h +++ b/hercules.h @@ -94,6 +94,7 @@ struct receiver_state { u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; + u16 src_port; }; /// SENDER @@ -148,6 +149,7 @@ struct sender_state { // Start/end time of the current transfer u64 start_time; u64 end_time; + u16 src_port; }; /// SESSION @@ -194,7 +196,7 @@ struct hercules_session { // the payload length includes the rbudp header while the // chunk length does not. - struct hercules_app_addr peer; + u16 dst_port; // Number of sent/received packets (for stats) size_t rx_npkts; @@ -220,6 +222,9 @@ struct hercules_config { int queue; bool configure_queues; struct hercules_app_addr local_addr; + u16 port_min; // Lowest port on which to accept packets (in HOST + // endianness) + u16 port_max; // Highest port, host endianness }; struct hercules_server { @@ -231,11 +236,15 @@ struct hercules_server { int n_threads; // Number of RX/TX worker threads struct worker_args **worker_args; // Args passed to RX/TX workers - struct hercules_session *_Atomic session_tx; // Current TX session - struct hercules_session *deferred_tx; // Previous TX session, no longer - // active, waiting to be freed - struct hercules_session *_Atomic session_rx; // Current RX session - struct hercules_session *deferred_rx; + struct hercules_session *_Atomic + sessions_tx[HERCULES_CONCURRENT_SESSIONS]; // Current TX sessions + struct hercules_session + *deferreds_tx[HERCULES_CONCURRENT_SESSIONS]; // Previous TX sessions, + // no longer active, + // waiting to be freed + struct hercules_session *_Atomic + sessions_rx[HERCULES_CONCURRENT_SESSIONS]; // Current RX sessions + struct hercules_session *deferreds_rx[HERCULES_CONCURRENT_SESSIONS]; bool enable_pcc; // TODO make per path or session or something int *ifindices; diff --git a/monitor.c b/monitor.c index 3b748f4..6a9b468 100644 --- a/monitor.c +++ b/monitor.c @@ -80,7 +80,7 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, } bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, - struct hercules_app_addr *dest, u16 *payloadlen) { + u16 *dst_port, u16 *payloadlen) { struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; strcpy(monitor.sun_path, "/var/herculesmon.sock"); @@ -99,6 +99,7 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, *job_id = reply.payload.newjob.job_id; debug_printf("received job id %d", reply.payload.newjob.job_id); *payloadlen = reply.payload.newjob.payloadlen; + *dst_port = reply.payload.newjob.dest_port; return true; } diff --git a/monitor.h b/monitor.h index 3755a72..98fc586 100644 --- a/monitor.h +++ b/monitor.h @@ -21,7 +21,7 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, // If so the function returns true and the job's details are filled into the // arguments. bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, - struct hercules_app_addr *dest, u16 *payloadlen); + u16 *dst_port, u16 *payloadlen); // Inform the monitor about a transfer's (new) status. bool monitor_update_job(int sockfd, int job_id, enum session_state state, @@ -74,6 +74,7 @@ struct sockmsg_new_job_Q {}; struct sockmsg_new_job_A { uint8_t has_job; // The other fields are only valid if this is set to 1 uint16_t job_id; + uint16_t dest_port; uint16_t payloadlen; uint16_t filename_len; uint8_t filename[SOCKMSG_MAX_PAYLOAD]; // Filename *without* terminating 0-byte diff --git a/monitor/monitor.go b/monitor/monitor.go index d774ec7..2adcec6 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -184,6 +184,7 @@ func main() { strlen := len(selectedJob.file) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) + b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.dest.Host.Port)) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.pm.payloadLen)) b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) b = append(b, []byte(selectedJob.file)...) diff --git a/monitor/scionheader.go b/monitor/scionheader.go index 7bdc771..d130914 100644 --- a/monitor/scionheader.go +++ b/monitor/scionheader.go @@ -158,7 +158,7 @@ func getReplyPathHeader(buf []byte, etherLen int) (*HerculesPathHeader, net.IP, } payload := snet.UDPPayload{ - SrcPort: udpPkt.DstPort, + SrcPort: 0, // Will be filled by server, left empty for correct checksum computation DstPort: udpPkt.SrcPort, Payload: nil, } @@ -327,8 +327,8 @@ func prepareHeader(path PathMeta, payloadLen int, srcUDP, dstUDP net.UDPAddr, sr fmt.Println(underlayHeader, err) payload := snet.UDPPayload{ - SrcPort: srcUDP.AddrPort().Port(), - DstPort: dstUDP.AddrPort().Port(), + SrcPort: 0, // Will be filled by server, left empty for correct checksum computation + DstPort: 0, Payload: make([]byte, payloadLen), } diff --git a/packet.h b/packet.h index ac227ea..16bd3ff 100644 --- a/packet.h +++ b/packet.h @@ -167,5 +167,5 @@ struct hercules_app_addr { }; typedef __u64 ia; #define MAX_NUM_SOCKETS 256 - +#define HERCULES_CONCURRENT_SESSIONS 16 #endif // HERCULES_SCION_H From e1a02dd24c2ea14579dc26dbda7a56992f1baad4 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 20 Jun 2024 17:01:24 +0200 Subject: [PATCH 047/112] set destination path in directory index --- hercules.c | 47 +++++++++++++++++++++++++++++++++++++++------ monitor.c | 5 +++-- monitor.h | 5 +++-- monitor/http_api.go | 20 ++++++++++--------- monitor/monitor.go | 20 +++++++++++-------- 5 files changed, 70 insertions(+), 27 deletions(-) diff --git a/hercules.c b/hercules.c index 9c8f44a..d275217 100644 --- a/hercules.c +++ b/hercules.c @@ -2123,9 +2123,15 @@ static void tx_handle_hs_confirm(struct hercules_server *server, debug_printf("Dropping HS confirm packet, was not expecting one"); } +static void replace_dir(char *entry, char *src, char *dst) { + int oldlen = strlen(src); + int entry_size = strlen(entry) - oldlen + 1; + +} + // Map the provided file into memory for reading. Returns pointer to the mapped // area, or null on error. -static char *tx_mmap(char *fname, size_t *filesize, void **index_o, u64 *index_size_o) { +static char *tx_mmap(char *fname, char *dstname, size_t *filesize, void **index_o, u64 *index_size_o) { FTS *fts = NULL; FTSENT *ent = NULL; debug_printf("opening"); @@ -2227,6 +2233,32 @@ static char *tx_mmap(char *fname, size_t *filesize, void **index_o, u64 *index_s struct dir_index_entry *p = index; while (1) { debug_printf("Read: %s (%d) %dB", p->path, p->type, p->filesize); + int src_path_len = strlen(p->path); + int src_root_len = strlen(fname); + int dst_root_len = strlen(dstname); + int dst_path_len = src_path_len - src_root_len + dst_root_len; + debug_printf("src path %d, root %d. dst path %d, root %d", src_path_len, src_root_len, dst_path_len, dst_root_len); + int entry_size = sizeof(struct dir_index_entry) + dst_path_len + 1; + if (dst_index_size + entry_size >= dst_index_cap) { + debug_printf("need realloc"); + dst_index = realloc(dst_index, dst_index_cap + 4096); + if (dst_index == NULL) { + fts_close(fts); + return NULL; + } + dst_index_cap += 4096; + } + struct dir_index_entry *newentry = (struct dir_index_entry *)(dst_index + dst_index_size); + debug_printf("entry size %d, index size %d", entry_size, dst_index_size); + newentry->filesize = p->filesize; + newentry->type = p->type; + newentry->path_len = dst_path_len + 1; + strncpy(newentry->path, dstname, dst_root_len); + strncpy(&newentry->path[dst_root_len], &p->path[src_root_len], + dst_path_len - dst_root_len + 1); + debug_printf("Set dst path %s", newentry->path); + dst_index_size += entry_size; + if (p->type == INDEX_TYPE_FILE) { int f = open(p->path, O_RDONLY); if (f == -1) { @@ -2250,10 +2282,11 @@ static char *tx_mmap(char *fname, size_t *filesize, void **index_o, u64 *index_s break; } } + free(index); *filesize = total_filesize; - *index_o = index; - *index_size_o = index_size; + *index_o = dst_index; + *index_size_o = dst_index_size; return mem; } @@ -2696,16 +2729,18 @@ static void new_tx_if_available(struct hercules_server *server) { // slot it will still be free when we assign to it later on char fname[1000]; memset(fname, 0, 1000); + char destname[1000]; + memset(destname, 0, 1000); int count; u16 jobid; u16 payloadlen; u16 dst_port; - int ret = monitor_get_new_job(server->usock, fname, &jobid, &dst_port, &payloadlen); + int ret = monitor_get_new_job(server->usock, fname, destname, &jobid, &dst_port, &payloadlen); if (!ret) { return; } - debug_printf("new job: %s", fname); + debug_printf("new job: %s -> %s", fname, destname); debug_printf("using tx slot %d", session_slot); if (sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)payloadlen) { @@ -2717,7 +2752,7 @@ static void new_tx_if_available(struct hercules_server *server) { size_t filesize; void *index; u64 index_size; - char *mem = tx_mmap(fname, &filesize, &index, &index_size); + char *mem = tx_mmap(fname, destname, &filesize, &index, &index_size); if (mem == NULL){ debug_printf("mmap failed"); monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_MAP_FAILED, 0, 0); diff --git a/monitor.c b/monitor.c index 6a9b468..41e13ee 100644 --- a/monitor.c +++ b/monitor.c @@ -79,7 +79,7 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, return true; } -bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, +bool monitor_get_new_job(int sockfd, char *name, char *destname, u16 *job_id, u16 *dst_port, u16 *payloadlen) { struct sockaddr_un monitor; monitor.sun_family = AF_UNIX; @@ -94,8 +94,9 @@ bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, return false; } // XXX name needs to be allocated large enough by caller - strncpy(name, reply.payload.newjob.filename, + strncpy(name, reply.payload.newjob.names, reply.payload.newjob.filename_len); + strncpy(destname, reply.payload.newjob.names + reply.payload.newjob.filename_len, reply.payload.newjob.destname_len); *job_id = reply.payload.newjob.job_id; debug_printf("received job id %d", reply.payload.newjob.job_id); *payloadlen = reply.payload.newjob.payloadlen; diff --git a/monitor.h b/monitor.h index 98fc586..3f19d98 100644 --- a/monitor.h +++ b/monitor.h @@ -20,7 +20,7 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, // Check if the monitor has a new job available. // If so the function returns true and the job's details are filled into the // arguments. -bool monitor_get_new_job(int sockfd, char *name, u16 *job_id, +bool monitor_get_new_job(int sockfd, char *name, char *destname, u16 *job_id, u16 *dst_port, u16 *payloadlen); // Inform the monitor about a transfer's (new) status. @@ -77,7 +77,8 @@ struct sockmsg_new_job_A { uint16_t dest_port; uint16_t payloadlen; uint16_t filename_len; - uint8_t filename[SOCKMSG_MAX_PAYLOAD]; // Filename *without* terminating 0-byte + uint16_t destname_len; + uint8_t names[SOCKMSG_MAX_PAYLOAD]; // Filenames *without* terminating 0-byte }; // Get paths to use for a given job ID diff --git a/monitor/http_api.go b/monitor/http_api.go index 643601a..77ede2c 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -15,13 +15,14 @@ import ( // file (File to transfer) // dest (Destination IA+Host) func http_submit(w http.ResponseWriter, r *http.Request) { - if !r.URL.Query().Has("file") || !r.URL.Query().Has("dest") { + if !r.URL.Query().Has("file") || !r.URL.Query().Has("destfile") || !r.URL.Query().Has("dest") { io.WriteString(w, "missing parameter") return } file := r.URL.Query().Get("file") + destfile := r.URL.Query().Get("destfile") dest := r.URL.Query().Get("dest") - fmt.Println(file, dest) + fmt.Println(file, destfile, dest) destParsed, err := snet.ParseUDPAddr(dest) if err != nil { io.WriteString(w, "parse err") @@ -33,11 +34,12 @@ func http_submit(w http.ResponseWriter, r *http.Request) { transfersLock.Lock() jobid := nextID transfers[nextID] = &HerculesTransfer{ - id: nextID, - status: Queued, - file: file, - dest: *destParsed, - pm: pm, + id: nextID, + status: Queued, + file: file, + destFile: destfile, + dest: *destParsed, + pm: pm, } nextID += 1 transfersLock.Unlock() @@ -103,8 +105,8 @@ func http_stat(w http.ResponseWriter, r *http.Request) { } file := r.URL.Query().Get("file") info, err := os.Stat(file) - if os.IsNotExist(err){ - io.WriteString(w, "OK 0 0\n"); + if os.IsNotExist(err) { + io.WriteString(w, "OK 0 0\n") return } else if err != nil { io.WriteString(w, "err\n") diff --git a/monitor/monitor.go b/monitor/monitor.go index 2adcec6..69162da 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -70,11 +70,12 @@ const ( // since the server has no concept of pending jobs. type HerculesTransfer struct { - id int // ID identifying this transfer - status TransferStatus // Status as seen by the monitor - file string // Name of the file to transfer - dest snet.UDPAddr // Destination - pm *PathManager + id int // ID identifying this transfer + status TransferStatus // Status as seen by the monitor + file string // Name of the file to transfer on the source host + destFile string // Name of the file to transfer at destination host + dest snet.UDPAddr // Destination + pm *PathManager // The following two fields are meaningless if the job's status is 'Queued' // They are updated when the server sends messages of type 'update_job' state C.enum_session_state // The state returned by the server @@ -179,15 +180,18 @@ func main() { transfersLock.Unlock() var b []byte if selectedJob != nil { - fmt.Println("sending file to daemon:", selectedJob.file, selectedJob.id) + fmt.Println("sending file to daemon:", selectedJob.file, selectedJob.destFile, selectedJob.id) _, _ = headersToDestination(*selectedJob) // look up paths to fix mtu - strlen := len(selectedJob.file) + strlen_src := len(selectedJob.file) + strlen_dst := len(selectedJob.destFile) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.dest.Host.Port)) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.pm.payloadLen)) - b = binary.LittleEndian.AppendUint16(b, uint16(strlen)) + b = binary.LittleEndian.AppendUint16(b, uint16(strlen_src)) + b = binary.LittleEndian.AppendUint16(b, uint16(strlen_dst)) b = append(b, []byte(selectedJob.file)...) + b = append(b, []byte(selectedJob.destFile)...) } else { // no new jobs b = append(b, 0) From 3ba83067280282a9ab71081389e9d5943188fc7a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 20 Jun 2024 17:32:41 +0200 Subject: [PATCH 048/112] fix session stopping --- hercules.c | 25 ++++++++++++++++++++++++- hercules.h | 5 +++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/hercules.c b/hercules.c index d275217..791308f 100644 --- a/hercules.c +++ b/hercules.c @@ -124,10 +124,12 @@ static inline struct hercules_interface *get_interface_by_id(struct hercules_ser } // Mark the session as done and store why it was stopped. +// This may be called by any thread. +// Actually setting the session state to DONE should be done +// by the events_p thread. static inline void quit_session(struct hercules_session *s, enum session_error err) { s->error = err; - s->state = SESSION_STATE_DONE; } static u32 ack__max_num_entries(u32 len) @@ -265,6 +267,7 @@ static struct hercules_session *make_session(struct hercules_server *server) { return NULL; } s->state = SESSION_STATE_NONE; + s->error = SESSION_ERROR_NONE; int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, sizeof(*s->send_queue)); if (err != 0) { @@ -3088,6 +3091,25 @@ static void rx_send_cts(struct hercules_server *server, u64 now){ } } +static void stop_finished_sessions(struct hercules_server *server, u64 now) { + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx != NULL && session_tx->state != SESSION_STATE_DONE && + session_tx->error != SESSION_ERROR_NONE) { + debug_printf("Stopping TX %d", s); + session_tx->state = SESSION_STATE_DONE; + } + } + for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { + struct hercules_session *session_rx = server->sessions_rx[s]; + if (session_rx != NULL && session_rx->state != SESSION_STATE_DONE && + session_rx->error != SESSION_ERROR_NONE) { + debug_printf("Stopping RX %d", s); + session_rx->state = SESSION_STATE_DONE; + } + } +} + #define PRINT_STATS // Read control packets from the control socket and process them; also handles @@ -3111,6 +3133,7 @@ static void events_p(void *arg) { // FIXME don't loop over all sessions, one at a time new_tx_if_available(server); mark_timed_out_sessions(server, now); + stop_finished_sessions(server, now); cleanup_finished_sessions(server, now); tx_retransmit_initial(server, now); rx_send_cts(server, now); diff --git a/hercules.h b/hercules.h index 68616ab..5214b2d 100644 --- a/hercules.h +++ b/hercules.h @@ -133,7 +133,7 @@ struct sender_state { u32 prev_chunk_idx; bool finished; struct bitset acked_chunks; //< Chunks we've received an ack for - struct bitset acked_chunks_index; //< Chunks we've received an ack for + struct bitset acked_chunks_index; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns struct path_set *_Atomic pathset; // Paths currently in use @@ -171,13 +171,14 @@ enum session_state { // CTS SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS SESSION_STATE_INDEX_READY, //< (RX) Index transfer complete, map files and - //send CTS + // send CTS SESSION_STATE_RUNNING_DATA, //< Data transfer in progress SESSION_STATE_RUNNING_IDX, //< Directory index transfer in progress SESSION_STATE_DONE, //< Transfer done (or cancelled with error) }; enum session_error { + SESSION_ERROR_NONE, // Error not set yet SESSION_ERROR_OK, //< No error, transfer completed successfully SESSION_ERROR_TIMEOUT, //< Session timed out SESSION_ERROR_STALE, //< Packets are being received, but none are new From ae837f15001468d4d7b277f3c87112782f24b65b Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 20 Jun 2024 18:18:20 +0200 Subject: [PATCH 049/112] add underlay source port randomisation --- Makefile | 1 + hercules.c | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 989e653..a49d063 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ ASAN_FLAG := -fsanitize=address CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets # CFLAGS += -DPRINT_STATS +# CFLAGS += -DRANDOMIZE_UNDERLAY_SRC # Enabling this breaks SCMP packet parsing LDFLAGS = -lbpf -Lbpf/src -lm -lelf -pthread -lz -z noexecstack $(ASAN_FLAG) DEPFLAGS:=-MP -MD diff --git a/hercules.c b/hercules.c index 791308f..b7c1c10 100644 --- a/hercules.c +++ b/hercules.c @@ -1670,6 +1670,9 @@ static char *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_id #ifdef RANDOMIZE_FLOWID static short flowIdCtr = 0; #endif +#ifdef RANDOMIZE_UNDERLAY_SRC +static short src_port_ctr = 0; +#endif static inline void tx_handle_send_queue_unit_for_iface( struct sender_state *tx_state, struct xsk_socket_info *xsk, int ifid, @@ -1731,23 +1734,32 @@ static inline void tx_handle_send_queue_unit_for_iface( void *rbudp_pkt = mempcpy(pkt, path->header.header, path->headerlen); #ifdef RANDOMIZE_FLOWID - short *flowId = (short *)&((char *)pkt)[44]; // ethernet hdr (14), ip hdr (20), udp hdr (8), offset of flowId in scion hdr - // XXX ^ ignores first 4 bits of flowId - *flowId = atomic_fetch_add(&flowIdCtr, 1); + short *flowId = (short *)&( + (char *)pkt)[44]; // ethernet hdr (14), ip hdr (20), udp hdr (8), + // offset of flowId in scion hdr + // XXX ^ ignores first 4 bits of flowId + *flowId = atomic_fetch_add(&flowIdCtr, 1); +#endif +#ifdef RANDOMIZE_UNDERLAY_SRC + short *src_port = + (short *)&((char *)pkt)[34]; // Ethernet (14) + IP (20), src port + // is first 2 bytes of udp header + *src_port = atomic_fetch_add(&src_port_ctr, 1); #endif - u8 track_path = PCC_NO_PATH; // put path_idx iff PCC is enabled + u8 track_path = PCC_NO_PATH; // put path_idx iff PCC is enabled sequence_number seqnr = 0; - if(path->cc_state != NULL) { + if (path->cc_state != NULL) { track_path = unit->paths[i]; seqnr = atomic_fetch_add(&path->cc_state->last_seqnr, 1); } u8 flags = 0; char *payload = tx_state->mem; - if (is_index_transfer){ + if (is_index_transfer) { flags |= PKT_FLAG_IS_INDEX; payload = tx_state->index; } - fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, payload + chunk_start, len, path->payloadlen); + fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, + payload + chunk_start, len, path->payloadlen); stitch_dst_port(path, dst_port, pkt); stitch_src_port(path, tx_state->src_port, pkt); stitch_checksum_with_dst(path, path->header.checksum, pkt); From cd9e42f3b4e33fabe2dd93dd602a123b58f9ba04 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 21 Jun 2024 11:00:27 +0200 Subject: [PATCH 050/112] fix nacks with multiple sessions, minor improvements --- hercules.c | 558 ++++++++++++++++++++++++++--------------------------- hercules.h | 2 + 2 files changed, 274 insertions(+), 286 deletions(-) diff --git a/hercules.c b/hercules.c index b7c1c10..d658cca 100644 --- a/hercules.c +++ b/hercules.c @@ -1633,19 +1633,16 @@ void send_path_handshakes(struct hercules_server *server, } } -static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) +static void claim_tx_frames(struct hercules_server *server, struct hercules_session *session, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { - // TODO FIXME Lock contention, significantly affects performance pthread_spin_lock(&iface->umem->frames_lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); while(reserved != num_frames) { // When we're not getting any frames, we might need to... kick_all_tx(server, iface); reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); - /* debug_printf("reserved %ld, wanted %ld", reserved, num_frames); */ // XXX FIXME - struct hercules_session *s = server->sessions_tx[0]; // FIXME idx 0 - if(!s || !session_state_is_running(atomic_load(&s->state))) { + if(!session || !session_state_is_running(atomic_load(&session->state))) { debug_printf("STOP"); pthread_spin_unlock(&iface->umem->frames_lock); return; @@ -1814,6 +1811,7 @@ produce_batch(struct hercules_server *server, struct hercules_session *session, } static inline void allocate_tx_frames(struct hercules_server *server, + struct hercules_session *session, u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT]) { for(int i = 0; i < server->num_ifaces; i++) { @@ -1823,7 +1821,7 @@ static inline void allocate_tx_frames(struct hercules_server *server, break; } } - claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); + claim_tx_frames(server, session, &server->ifaces[i], frame_addrs[i], num_frames); } } @@ -2032,20 +2030,18 @@ static void destroy_tx_state(struct sender_state *tx_state) { } // (Re)send HS if needed -static void tx_retransmit_initial(struct hercules_server *server, u64 now) { - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_tx = server->sessions_tx[s]; - if (session_tx && session_tx->state == SESSION_STATE_PENDING) { - if (now > - session_tx->last_pkt_sent + session_hs_retransmit_interval) { - struct sender_state *tx_state = session_tx->tx_state; - struct path_set *pathset = tx_state->pathset; - // We always use the first path as the return path - tx_send_initial(server, &pathset->paths[0], tx_state->index, tx_state->index_size, s, - session_tx->dst_port, tx_state->filesize, tx_state->chunklen, now, 0, - true, true); - session_tx->last_pkt_sent = now; - } +static void tx_retransmit_initial(struct hercules_server *server, int s, u64 now) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx && session_tx->state == SESSION_STATE_PENDING) { + if (now > session_tx->last_pkt_sent + session_hs_retransmit_interval) { + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *pathset = tx_state->pathset; + // We always use the first path as the return path + tx_send_initial(server, &pathset->paths[0], tx_state->index, + tx_state->index_size, s, session_tx->dst_port, + tx_state->filesize, tx_state->chunklen, now, 0, + true, true); + session_tx->last_pkt_sent = now; } } } @@ -2479,10 +2475,6 @@ static void tx_send_p(void *arg) { struct send_queue_unit unit; int ret = send_queue_pop(session_tx->send_queue, &unit); if (!ret) { - pathset_read( - session_tx->tx_state, - args->id); // Necessary to prevent prevent pathset updater from - // getting stuck in an infinite loop; kick_tx_server(server); continue; } @@ -2504,7 +2496,7 @@ static void tx_send_p(void *arg) { frame_addrs[0][i] = 0; } } - allocate_tx_frames(server, frame_addrs); + allocate_tx_frames(server, session_tx, frame_addrs); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, frame_addrs, &unit, args->id, session_tx->dst_port, is_index_transfer); atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); // FIXME should this be here? @@ -2552,29 +2544,31 @@ static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; int cur_session = 0; while (1) { - cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; + cur_session = (cur_session + 1) % HERCULES_CONCURRENT_SESSIONS; struct hercules_session *session_rx = server->sessions_rx[cur_session]; if (session_rx != NULL && session_state_is_running(session_rx->state)) { - u32 ack_nr = 0; - bool is_index_transfer = (session_rx->state == SESSION_STATE_RUNNING_IDX); + bool is_index_transfer = + (session_rx->state == SESSION_STATE_RUNNING_IDX); struct receiver_state *rx_state = session_rx->rx_state; - // TODO remove this inner loop? - while (session_state_is_running(rx_state->session->state) && - !rx_received_all(rx_state, is_index_transfer)) { - u64 ack_round_start = get_nsecs(); - rx_send_nacks(server, rx_state, ack_round_start, ack_nr, is_index_transfer); - u64 ack_round_end = get_nsecs(); - if (ack_round_end > - ack_round_start + rx_state->handshake_rtt * 1000 / 4) { - /* fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", */ - /* ack_round_end - ack_round_start, */ - /* rx_state->handshake_rtt * 1000 / 4); */ - } else { - sleep_until(ack_round_start + - rx_state->handshake_rtt * 1000 / 4); - } - ack_nr++; + u32 ack_nr = rx_state->ack_nr; + u64 now = get_nsecs(); + if (now < rx_state->next_nack_round_start) { + continue; + } + u64 ack_round_start = now; + rx_send_nacks(server, rx_state, ack_round_start, ack_nr, + is_index_transfer); + u64 ack_round_end = get_nsecs(); + if (ack_round_end > + ack_round_start + rx_state->handshake_rtt * 1000 / 4) { + /* fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", */ + /* ack_round_end - ack_round_start, */ + /* rx_state->handshake_rtt * 1000 / 4); */ + } else { + rx_state->next_nack_round_start = + ack_round_start + rx_state->handshake_rtt * 1000 / 4; } + rx_state->ack_nr++; } } } @@ -2821,189 +2815,174 @@ static void new_tx_if_available(struct hercules_server *server) { } // Remove and free finished sessions -static void cleanup_finished_sessions(struct hercules_server *server, u64 now) { - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - // Wait for twice the session timeout before removing the finished - // session (and thus before accepting new sessions). This ensures the - // other party has also quit or timed out its session and won't send - // packets that would then be mixed into future sessions. - // XXX This depends on both endpoints sharing the same timeout value, - // which is not negotiated but defined at the top of this file. - struct hercules_session *session_tx = atomic_load(&server->sessions_tx[s]); - if (session_tx && session_tx->state == SESSION_STATE_DONE) { - if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { - u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; - u64 bytes_acked = session_tx->tx_state->chunklen * - session_tx->tx_state->acked_chunks.num_set; - monitor_update_job(server->usock, session_tx->jobid, - session_tx->state, session_tx->error, - sec_elapsed, bytes_acked); - struct hercules_session *current = session_tx; - atomic_store(&server->sessions_tx[s], NULL); - fprintf(stderr, "Cleaning up TX session %d...\n", s); - // At this point we don't know if some other thread still has a - // pointer to the session that it might dereference, so we - // cannot safely free it. So, we record the pointer and defer - // freeing it until after the next session has completed. At - // that point, no references to the deferred session should be - // around, so we then free it. - destroy_session_tx(server->deferreds_tx[s]); - server->deferreds_tx[s] = current; - } - } - struct hercules_session *session_rx = atomic_load(&server->sessions_rx[s]); - if (session_rx && session_rx->state == SESSION_STATE_DONE) { - if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { - struct hercules_session *current = session_rx; - atomic_store(&server->sessions_rx[s], NULL); - fprintf(stderr, "Cleaning up RX session %d...\n", s); - // See the note above on deferred freeing - destroy_session_rx(server->deferreds_rx[s]); - server->deferreds_rx[s] = current; - } +static void cleanup_finished_sessions(struct hercules_server *server, int s, u64 now) { + // Wait for twice the session timeout before removing the finished + // session (and thus before accepting new sessions). This ensures the + // other party has also quit or timed out its session and won't send + // packets that would then be mixed into future sessions. + // XXX This depends on both endpoints sharing the same timeout value, + // which is not negotiated but defined at the top of this file. + struct hercules_session *session_tx = atomic_load(&server->sessions_tx[s]); + if (session_tx && session_tx->state == SESSION_STATE_DONE) { + if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { + u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; + u64 bytes_acked = session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set; + monitor_update_job(server->usock, session_tx->jobid, + session_tx->state, session_tx->error, + sec_elapsed, bytes_acked); + struct hercules_session *current = session_tx; + atomic_store(&server->sessions_tx[s], NULL); + fprintf(stderr, "Cleaning up TX session %d...\n", s); + // At this point we don't know if some other thread still has a + // pointer to the session that it might dereference, so we + // cannot safely free it. So, we record the pointer and defer + // freeing it until after the next session has completed. At + // that point, no references to the deferred session should be + // around, so we then free it. + destroy_session_tx(server->deferreds_tx[s]); + server->deferreds_tx[s] = current; + } + } + struct hercules_session *session_rx = atomic_load(&server->sessions_rx[s]); + if (session_rx && session_rx->state == SESSION_STATE_DONE) { + if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { + struct hercules_session *current = session_rx; + atomic_store(&server->sessions_rx[s], NULL); + fprintf(stderr, "Cleaning up RX session %d...\n", s); + // See the note above on deferred freeing + destroy_session_rx(server->deferreds_rx[s]); + server->deferreds_rx[s] = current; } } } // Time out if no packets received for a while -static void mark_timed_out_sessions(struct hercules_server *server, u64 now) { - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_tx = server->sessions_tx[s]; - if (session_tx && session_tx->state != SESSION_STATE_DONE) { - if (now > session_tx->last_pkt_rcvd + session_timeout) { - quit_session(session_tx, SESSION_ERROR_TIMEOUT); - debug_printf("Session (TX %2d) timed out!", s); - } +static void mark_timed_out_sessions(struct hercules_server *server, int s, u64 now) { + struct hercules_session *session_tx = server->sessions_tx[s]; + if (session_tx && session_tx->state != SESSION_STATE_DONE) { + if (now > session_tx->last_pkt_rcvd + session_timeout) { + quit_session(session_tx, SESSION_ERROR_TIMEOUT); + debug_printf("Session (TX %2d) timed out!", s); } - struct hercules_session *session_rx = server->sessions_rx[s]; - if (session_rx && session_rx->state != SESSION_STATE_DONE) { - if (now > session_rx->last_pkt_rcvd + session_timeout) { - quit_session(session_rx, SESSION_ERROR_TIMEOUT); - debug_printf("Session (RX %2d) timed out!", s); - } else if (now > - session_rx->last_new_pkt_rcvd + session_stale_timeout) { - quit_session(session_rx, SESSION_ERROR_STALE); - debug_printf("Session (RX %2d) stale!", s); - } + } + struct hercules_session *session_rx = server->sessions_rx[s]; + if (session_rx && session_rx->state != SESSION_STATE_DONE) { + if (now > session_rx->last_pkt_rcvd + session_timeout) { + quit_session(session_rx, SESSION_ERROR_TIMEOUT); + debug_printf("Session (RX %2d) timed out!", s); + } else if (now > + session_rx->last_new_pkt_rcvd + session_stale_timeout) { + quit_session(session_rx, SESSION_ERROR_STALE); + debug_printf("Session (RX %2d) stale!", s); } } } -static void tx_update_paths(struct hercules_server *server) { - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_tx = server->sessions_tx[s]; +static void tx_update_paths(struct hercules_server *server, int s) { + struct hercules_session *session_tx = server->sessions_tx[s]; if (session_tx && session_state_is_running(session_tx->state)) { - debug_printf("Updating paths for TX %d", s); - struct sender_state *tx_state = session_tx->tx_state; - struct path_set *old_pathset = tx_state->pathset; - int n_paths; - struct hercules_path *paths; - bool ret = - monitor_get_paths(server->usock, session_tx->jobid, - session_tx->payloadlen, &n_paths, &paths); - if (!ret) { - debug_printf("error getting paths"); - return; - } - debug_printf("received %d paths", n_paths); - if (n_paths == 0) { - free(paths); - quit_session(session_tx, SESSION_ERROR_NO_PATHS); - return; - } - struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); - if (new_pathset == NULL) { - // FIXME leak? - return; - } - u32 new_epoch = tx_state->next_epoch; - new_pathset->epoch = new_epoch; - tx_state->next_epoch++; - new_pathset->n_paths = n_paths; - memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); - u32 path_lim = (old_pathset->n_paths > (u32)n_paths) - ? (u32)n_paths - : old_pathset->n_paths; - bool replaced_return_path = false; - struct ccontrol_state **replaced_cc = - calloc(old_pathset->n_paths, sizeof(*replaced_cc)); - if (replaced_cc == NULL) { - // FIXME leak? - return; - } - for (u32 i = 0; i < old_pathset->n_paths; i++) { - replaced_cc[i] = old_pathset->paths[i].cc_state; - } - for (u32 i = 0; i < path_lim; i++) { - // Set these two values before the comparison or it would fail - // even if paths are the same. - new_pathset->paths[i].next_handshake_at = - old_pathset->paths[i].next_handshake_at; - new_pathset->paths[i].cc_state = old_pathset->paths[i].cc_state; - - // XXX This works, but it means we restart CC even if the path - // has not changed (but the header has, eg. because the old one - // expired). We could avoid this by having the monitor tell us - // whether the path changed, as it used to. - if (memcmp(&old_pathset->paths[i], &new_pathset->paths[i], - sizeof(struct hercules_path)) == 0) { - // Old and new path are the same, CC state carries over. - // Since we copied the CC state before just leave as-is. - debug_printf("Path %d not changed", i); - replaced_cc[i] = NULL; - } else { - debug_printf("Path %d changed, resetting CC", i); - if (i == 0) { - // Return path is always idx 0 - replaced_return_path = true; - } - // TODO whether to use pcc should be decided on a per-path - // basis by the monitor - if (server->enable_pcc) { - // TODO assert chunk length fits onto path - // The new path is different, restart CC - // TODO where to get rate - u32 rate = 100; - new_pathset->paths[i].cc_state = init_ccontrol_state( - rate, tx_state->total_chunks, new_pathset->n_paths); - // Re-send a handshake to update path rtt - new_pathset->paths[i].next_handshake_at = 0; - } + debug_printf("Updating paths for TX %d", s); + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *old_pathset = tx_state->pathset; + int n_paths; + struct hercules_path *paths; + bool ret = monitor_get_paths(server->usock, session_tx->jobid, + session_tx->payloadlen, &n_paths, &paths); + if (!ret) { + debug_printf("error getting paths"); + return; + } + debug_printf("received %d paths", n_paths); + if (n_paths == 0) { + free(paths); + quit_session(session_tx, SESSION_ERROR_NO_PATHS); + return; + } + struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); + if (new_pathset == NULL) { + // FIXME leak? + return; + } + u32 new_epoch = tx_state->next_epoch; + new_pathset->epoch = new_epoch; + tx_state->next_epoch++; + new_pathset->n_paths = n_paths; + memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); + u32 path_lim = (old_pathset->n_paths > (u32)n_paths) + ? (u32)n_paths + : old_pathset->n_paths; + bool replaced_return_path = false; + struct ccontrol_state **replaced_cc = + calloc(old_pathset->n_paths, sizeof(*replaced_cc)); + if (replaced_cc == NULL) { + // FIXME leak? + return; + } + for (u32 i = 0; i < old_pathset->n_paths; i++) { + replaced_cc[i] = old_pathset->paths[i].cc_state; + } + for (u32 i = 0; i < path_lim; i++) { + // Set these two values before the comparison or it would fail + // even if paths are the same. + new_pathset->paths[i].next_handshake_at = + old_pathset->paths[i].next_handshake_at; + new_pathset->paths[i].cc_state = old_pathset->paths[i].cc_state; + + // XXX This works, but it means we restart CC even if the path + // has not changed (but the header has, eg. because the old one + // expired). We could avoid this by having the monitor tell us + // whether the path changed, as it used to. + if (memcmp(&old_pathset->paths[i], &new_pathset->paths[i], + sizeof(struct hercules_path)) == 0) { + // Old and new path are the same, CC state carries over. + // Since we copied the CC state before just leave as-is. + debug_printf("Path %d not changed", i); + replaced_cc[i] = NULL; + } else { + debug_printf("Path %d changed, resetting CC", i); + if (i == 0) { + // Return path is always idx 0 + replaced_return_path = true; } - if (replaced_return_path) { - // If we changed the return path we re-send the handshake on - // all paths to update RTT - debug_printf( - "Re-sending HS on path %d because return path changed", - i); + // TODO whether to use pcc should be decided on a per-path + // basis by the monitor + if (server->enable_pcc) { + // TODO assert chunk length fits onto path + // The new path is different, restart CC + // TODO where to get rate + u32 rate = 100; + new_pathset->paths[i].cc_state = init_ccontrol_state( + rate, tx_state->total_chunks, new_pathset->n_paths); + // Re-send a handshake to update path rtt new_pathset->paths[i].next_handshake_at = 0; } } - // Finally, swap in the new pathset - tx_state->pathset = new_pathset; - free(paths); // These were *copied* into the new pathset - for (int i = 0; i < server->n_threads + 1; i++) { - int attempts = 0; - do { - attempts++; - if (attempts > 1000000){ - debug_printf("something wrong with pathset swap loop"); - debug_printf("waiting on %d, epoch still %lld", i, tx_state->epochs[i].epoch); - attempts = 0; - } - // Wait until the thread has seen the new pathset - } while (tx_state->epochs[i].epoch != new_epoch); + if (replaced_return_path) { + // If we changed the return path we re-send the handshake on + // all paths to update RTT + debug_printf( + "Re-sending HS on path %d because return path changed", i); + new_pathset->paths[i].next_handshake_at = 0; } - for (u32 i = 0; i < old_pathset->n_paths; i++) { - // If CC was replaced, this contains the pointer to the old CC - // state. Otherwise it contains NULL, and we don't need to free - // anything. - free(replaced_cc[i]); - } - free(replaced_cc); - free(old_pathset); - debug_printf("done with update"); } + // Finally, swap in the new pathset + tx_state->pathset = new_pathset; + free(paths); // These were *copied* into the new pathset + for (int i = 0; i < server->n_threads + 1; i++) { + do { + // Wait until the thread has seen the new pathset + } while (tx_state->epochs[i].epoch != new_epoch); + } + for (u32 i = 0; i < old_pathset->n_paths; i++) { + // If CC was replaced, this contains the pointer to the old CC + // state. Otherwise it contains NULL, and we don't need to free + // anything. + free(replaced_cc[i]); + } + free(replaced_cc); + free(old_pathset); + debug_printf("done with update"); } } @@ -3021,104 +3000,103 @@ struct prints{ u64 ts; }; -static void print_session_stats(struct hercules_server *server, - struct prints *p) { - u64 now = get_nsecs(); - if (now < p->ts + 1e9) { - return; - } - u64 tdiff = now - p->ts; - p->ts = now; - +static void print_session_stats(struct hercules_server *server, u64 now, + struct prints *tx, struct prints *rx) { + fprintf(stderr, "\n"); + double send_rate_total = 0; + double recv_rate_total = 0; for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { struct hercules_session *session_tx = server->sessions_tx[s]; if (session_tx && session_tx->state != SESSION_STATE_DONE) { + struct prints *p = &tx[s]; u32 sent_now = session_tx->tx_npkts; u32 acked_count = session_tx->tx_state->acked_chunks.num_set; u32 total = session_tx->tx_state->acked_chunks.num; + u64 tdiff = now - p->ts; + p->ts = now; double send_rate_pps = (sent_now - p->tx_sent) / ((double)tdiff / 1e9); p->tx_sent = sent_now; - double send_rate = 8 * send_rate_pps * - session_tx->tx_state->chunklen / 1e6; + double send_rate = + 8 * send_rate_pps * session_tx->tx_state->chunklen / 1e6; + double progress_percent = acked_count / (double)total * 100; + send_rate_total += send_rate; fprintf(stderr, - "(TX %2d) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", - s, - acked_count, total, session_tx->rx_npkts, - session_tx->tx_npkts, send_rate); + "(TX %2d) [%4.1f] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, rate " + "%8.2f " + "Mbps\n", + s, progress_percent, acked_count, total, + session_tx->rx_npkts, session_tx->tx_npkts, send_rate); } struct hercules_session *session_rx = server->sessions_rx[s]; if (session_rx && session_rx->state != SESSION_STATE_DONE) { - u32 begin = - bitset__scan_neg(&session_rx->rx_state->received_chunks, 0); + struct prints *p = &rx[s]; u32 rec_count = session_rx->rx_state->received_chunks.num_set; u32 total = session_rx->rx_state->received_chunks.num; u32 rcvd_now = session_rx->rx_npkts; + u64 tdiff = now - p->ts; + p->ts = now; double recv_rate_pps = (rcvd_now - p->rx_received) / ((double)tdiff / 1e9); p->rx_received = rcvd_now; - double recv_rate = 8 * recv_rate_pps * - session_rx->rx_state->chunklen / 1e6; + double recv_rate = + 8 * recv_rate_pps * session_rx->rx_state->chunklen / 1e6; + recv_rate_total += recv_rate; + double progress_percent = rec_count / (double)total * 100; fprintf(stderr, - "(RX %2d) Chunks: %u/%u, rx: %ld, tx:%ld, rate %.2f Mbps\n", - s, - rec_count, total, session_rx->rx_npkts, + "(RX %2d) [%4.1f%%] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, " + "rate %8.2f " + "Mbps\n", + s, progress_percent, rec_count, total, session_rx->rx_npkts, session_rx->tx_npkts, recv_rate); } } + fprintf(stderr, "TX Total Rate: %.2f Mbps\n", send_rate_total); + fprintf(stderr, "RX Total Rate: %.2f Mbps\n", recv_rate_total); } -static void tx_update_monitor(struct hercules_server *server, u64 now) { - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_tx = server->sessions_tx[s]; +static void tx_update_monitor(struct hercules_server *server, int s, u64 now) { + struct hercules_session *session_tx = server->sessions_tx[s]; if (session_tx != NULL && session_state_is_running(session_tx->state)) { - bool ret = monitor_update_job( - server->usock, session_tx->jobid, session_tx->state, 0, - (now - session_tx->tx_state->start_time) / (int)1e9, - session_tx->tx_state->chunklen * - session_tx->tx_state->acked_chunks.num_set); - if (!ret) { - quit_session(session_tx, SESSION_ERROR_CANCELLED); - } + bool ret = monitor_update_job( + server->usock, session_tx->jobid, session_tx->state, 0, + (now - session_tx->tx_state->start_time) / (int)1e9, + session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set); + if (!ret) { + quit_session(session_tx, SESSION_ERROR_CANCELLED); } } } -static void rx_send_cts(struct hercules_server *server, u64 now){ - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_rx = server->sessions_rx[s]; - if (session_rx != NULL && - session_rx->state == SESSION_STATE_INDEX_READY) { - struct receiver_state *rx_state = session_rx->rx_state; - rx_state->mem = rx_mmap(rx_state->index, rx_state->index_size, - rx_state->filesize); - if (rx_state->mem == NULL) { - quit_session(session_rx, SESSION_ERROR_MAP_FAILED); - return; - } - rx_send_cts_ack(server, rx_state); - session_rx->state = SESSION_STATE_RUNNING_DATA; +static void rx_send_cts(struct hercules_server *server, int s, u64 now) { + struct hercules_session *session_rx = server->sessions_rx[s]; + if (session_rx != NULL && session_rx->state == SESSION_STATE_INDEX_READY) { + struct receiver_state *rx_state = session_rx->rx_state; + rx_state->mem = + rx_mmap(rx_state->index, rx_state->index_size, rx_state->filesize); + if (rx_state->mem == NULL) { + quit_session(session_rx, SESSION_ERROR_MAP_FAILED); + return; } + rx_send_cts_ack(server, rx_state); + session_rx->state = SESSION_STATE_RUNNING_DATA; } } -static void stop_finished_sessions(struct hercules_server *server, u64 now) { - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_tx = server->sessions_tx[s]; - if (session_tx != NULL && session_tx->state != SESSION_STATE_DONE && - session_tx->error != SESSION_ERROR_NONE) { - debug_printf("Stopping TX %d", s); - session_tx->state = SESSION_STATE_DONE; - } +static void stop_finished_sessions(struct hercules_server *server, int slot, u64 now) { + struct hercules_session *session_tx = server->sessions_tx[slot]; + if (session_tx != NULL && session_tx->state != SESSION_STATE_DONE && + session_tx->error != SESSION_ERROR_NONE) { + debug_printf("Stopping TX %d", slot); + session_tx->state = SESSION_STATE_DONE; } - for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { - struct hercules_session *session_rx = server->sessions_rx[s]; - if (session_rx != NULL && session_rx->state != SESSION_STATE_DONE && - session_rx->error != SESSION_ERROR_NONE) { - debug_printf("Stopping RX %d", s); - session_rx->state = SESSION_STATE_DONE; - } + struct hercules_session *session_rx = server->sessions_rx[slot]; + if (session_rx != NULL && session_rx->state != SESSION_STATE_DONE && + session_rx->error != SESSION_ERROR_NONE) { + debug_printf("Stopping RX %d", slot); + session_rx->state = SESSION_STATE_DONE; } } @@ -3137,37 +3115,45 @@ static void events_p(void *arg) { const struct udphdr *udphdr; u64 lastpoll = 0; - struct prints prints = {.rx_received=0, .ts=0, .tx_sent=0}; + struct prints tx[HERCULES_CONCURRENT_SESSIONS]; + struct prints rx[HERCULES_CONCURRENT_SESSIONS]; + memset(tx, 0, sizeof(tx)); + memset(rx, 0, sizeof(rx)); + int current_slot = 0; while (1) { // event handler thread loop u64 now = get_nsecs(); + current_slot = (current_slot + 1) % HERCULES_CONCURRENT_SESSIONS; /* if (now > lastpoll + 1e9){ */ // XXX run the following every n seconds or every n socket reads? // FIXME don't loop over all sessions, one at a time new_tx_if_available(server); - mark_timed_out_sessions(server, now); - stop_finished_sessions(server, now); - cleanup_finished_sessions(server, now); - tx_retransmit_initial(server, now); - rx_send_cts(server, now); + mark_timed_out_sessions(server, current_slot, now); + stop_finished_sessions(server, current_slot, now); + cleanup_finished_sessions(server, current_slot, now); + tx_retransmit_initial(server, current_slot, now); + rx_send_cts(server, current_slot, now); #ifdef PRINT_STATS - print_session_stats(server, &prints); + if (now > lastpoll + 1e9) { + print_session_stats(server, now, tx, rx); + lastpoll = now; + } #endif /* if (now > lastpoll + 10e9){ */ /* tx_update_paths(server); */ /* lastpoll = now; */ /* } */ - if (now > lastpoll + 20e9){ - tx_update_monitor(server, now); - tx_update_paths(server); - lastpoll = now; - } + /* if (now > lastpoll + 20e9){ */ + /* tx_update_monitor(server, current_slot, now); */ + /* tx_update_paths(server, current_slot); */ + /* lastpoll = now; */ + /* } */ /* lastpoll = now; */ /* } */ // XXX This is a bit of a hack: We want to handle received packets more // frequently than we poll the monitor or check for expired sessions, so // try to receive 100 times (non-blocking) before doing anything else. - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 1000; i++) { ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&addr, &addr_size); // XXX set timeout @@ -3671,7 +3657,7 @@ int main(int argc, char *argv[]) { "threads, xdp mode 0x%x", queue, rx_threads, tx_threads, xdp_mode); - bool enable_pcc = false; + bool enable_pcc = true; struct hercules_server *server = hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, rx_threads, false, enable_pcc); diff --git a/hercules.h b/hercules.h index 5214b2d..f11ee14 100644 --- a/hercules.h +++ b/hercules.h @@ -95,6 +95,8 @@ struct receiver_state { u64 start_time; u64 end_time; u64 last_pkt_rcvd; // Timeout detection + u32 ack_nr; + u64 next_nack_round_start; u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; From 9cada061d82fc2f1d2649bd27282005a3fa1ed43 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 21 Jun 2024 11:17:22 +0200 Subject: [PATCH 051/112] ensure ports printed correctly --- hercules.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hercules.c b/hercules.c index d658cca..a3d8c34 100644 --- a/hercules.c +++ b/hercules.c @@ -1215,9 +1215,9 @@ static void rx_send_rtt_ack(struct hercules_server *server, }; control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; + stitch_src_port(&path, server->config.port_min + rx_slot + 1, buf); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&control_pkt, sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); - stitch_src_port(&path, server->config.port_min + rx_slot + 1, buf); stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); @@ -1266,9 +1266,9 @@ static void rx_send_cts_ack(struct hercules_server *server, .payload.ack.num_acks = 0, }; + stitch_src_port(&path, rx_state->src_port, buf); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); - stitch_src_port(&path, rx_state->src_port, buf); stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); atomic_fetch_add(&rx_state->session->tx_npkts, 1); @@ -1370,9 +1370,9 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s if (is_index_transfer) { flag |= PKT_FLAG_IS_INDEX; } + stitch_src_port(&path, rx_state->src_port, buf); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, path_idx, flag, 0, (char *)&control_pkt, sizeof(control_pkt.type) + ack__len(&control_pkt.payload.ack), path.payloadlen); - stitch_src_port(&path, rx_state->src_port, buf); stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); @@ -1755,10 +1755,10 @@ static inline void tx_handle_send_queue_unit_for_iface( flags |= PKT_FLAG_IS_INDEX; payload = tx_state->index; } - fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, - payload + chunk_start, len, path->payloadlen); stitch_dst_port(path, dst_port, pkt); stitch_src_port(path, tx_state->src_port, pkt); + fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, + payload + chunk_start, len, path->payloadlen); stitch_checksum_with_dst(path, path->header.checksum, pkt); } xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); From be4afb6ffe8ed346106d1af42625663279816556 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 21 Jun 2024 11:48:32 +0200 Subject: [PATCH 052/112] make sure sessions are freed entirely --- hercules.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/hercules.c b/hercules.c index a3d8c34..be5b130 100644 --- a/hercules.c +++ b/hercules.c @@ -75,6 +75,12 @@ static const u64 session_hs_retransmit_interval = 2e9; // 2 sec static const u64 session_stale_timeout = 30e9; // 30 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path +#define FREE_NULL(p) \ + do { \ + free(p); \ + p = NULL; \ + } while (0); + // Fill packet with n bytes from data and pad with zeros to payloadlen. static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, u8 flags, sequence_number seqnr, const char *data, size_t n, @@ -294,16 +300,22 @@ static void destroy_session_tx(struct hercules_session *session) { int ret = munmap(session->tx_state->mem, session->tx_state->filesize); assert(ret == 0); // No reason this should ever fail + session->tx_state->mem = NULL; bitset__destroy(&session->tx_state->acked_chunks); - // TODO when can we free pathset? - /* free(session->tx_state->paths); */ - /* bitset__destroy(&session->tx_state->cc_states->mi_nacked); */ - /* free(session->tx_state->cc_states); */ - free(session->tx_state); + bitset__destroy(&session->tx_state->acked_chunks_index); + struct path_set *pathset = session->tx_state->pathset; + for (u32 i = 0; i < pathset->n_paths; i++){ + destroy_ccontrol_state(pathset->paths[i].cc_state, 0); + pathset->paths[i].cc_state = NULL; + } + FREE_NULL(session->tx_state->pathset); + FREE_NULL(session->tx_state->index); + + FREE_NULL(session->tx_state); destroy_send_queue(session->send_queue); - free(session->send_queue); + FREE_NULL(session->send_queue); free(session); } @@ -316,12 +328,15 @@ static void destroy_session_rx(struct hercules_session *session) { int ret = munmap(session->rx_state->mem, session->rx_state->filesize); assert(ret == 0); // No reason this should ever fail + session->rx_state->mem = NULL; bitset__destroy(&session->rx_state->received_chunks); - free(session->rx_state); + bitset__destroy(&session->rx_state->received_chunks_index); + FREE_NULL(session->rx_state->index); + FREE_NULL(session->rx_state); destroy_send_queue(session->send_queue); - free(session->send_queue); + FREE_NULL(session->send_queue); free(session); } @@ -2022,13 +2037,6 @@ static void reset_tx_state(struct sender_state *tx_state) { tx_state->prev_chunk_idx = 0; } -static void destroy_tx_state(struct sender_state *tx_state) { - bitset__destroy(&tx_state->acked_chunks); - // TODO freeing pathset - /* free(tx_state->paths); */ - free(tx_state); -} - // (Re)send HS if needed static void tx_retransmit_initial(struct hercules_server *server, int s, u64 now) { struct hercules_session *session_tx = server->sessions_tx[s]; @@ -2800,13 +2808,13 @@ static void new_tx_if_available(struct hercules_server *server) { monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_NO_PATHS, 0, 0); return; } - // TODO free paths debug_printf("received %d paths", n_paths); u16 src_port = server->config.port_min + session_slot + 1; struct sender_state *tx_state = init_tx_state( session, filesize, chunklen, chunks_for_index, index, server->rate_limit, mem, paths, 1, n_paths, server->max_paths, server->n_threads, src_port); + free(paths); strncpy(tx_state->filename, fname, 99); tx_state->index = index; tx_state->index_size = index_size; From 342e26a72754f82f5364d6e2dfc35f7eafe3969e Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 21 Jun 2024 15:10:45 +0200 Subject: [PATCH 053/112] cleanup on shutdown --- hercules.c | 62 ++++++++++++++++++++++++++++++++++++++---------------- xdp.c | 5 +++++ xdp.h | 3 +++ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/hercules.c b/hercules.c index be5b130..2764403 100644 --- a/hercules.c +++ b/hercules.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ static const u64 session_timeout = 10e9; // 10 sec static const u64 session_hs_retransmit_interval = 2e9; // 2 sec static const u64 session_stale_timeout = 30e9; // 30 sec #define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path +_Atomic bool wants_shutdown = false; #define FREE_NULL(p) \ do { \ @@ -97,6 +99,12 @@ static struct hercules_session *make_session(struct hercules_server *server); /// COMMON +// Signal handler +void hercules_stop(int signo) { + (void) signo; + wants_shutdown = true; +} + // Check the SCION UDP address matches the session's peer static inline bool src_matches_address(struct hercules_session *session, const struct scionaddrhdr_ipv4 *scionaddrhdr, @@ -2471,7 +2479,7 @@ static void tx_send_p(void *arg) { struct worker_args *args = arg; struct hercules_server *server = args->server; int cur_session = 0; - while (1) { + while (!wants_shutdown) { cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; struct hercules_session *session_tx = server->sessions_tx[cur_session]; if (session_tx == NULL || @@ -2515,7 +2523,7 @@ static void tx_send_p(void *arg) { static void rx_trickle_acks(void *arg) { struct hercules_server *server = arg; int cur_session = 0; - while (1) { + while (!wants_shutdown) { cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; struct hercules_session *session_rx = server->sessions_rx[cur_session]; if (session_rx != NULL && session_state_is_running(session_rx->state)) { @@ -2551,7 +2559,7 @@ static void rx_trickle_acks(void *arg) { static void rx_trickle_nacks(void *arg) { struct hercules_server *server = arg; int cur_session = 0; - while (1) { + while (!wants_shutdown) { cur_session = (cur_session + 1) % HERCULES_CONCURRENT_SESSIONS; struct hercules_session *session_rx = server->sessions_rx[cur_session]; if (session_rx != NULL && session_state_is_running(session_rx->state)) { @@ -2587,7 +2595,7 @@ static void rx_p(void *arg) { struct hercules_server *server = args->server; int num_ifaces = server->num_ifaces; u32 i = 0; - while (1) { + while (!wants_shutdown) { rx_receive_batch(server, args->xsks[i % num_ifaces]); i++; } @@ -2621,7 +2629,7 @@ static void rx_p(void *arg) { static void *tx_p(void *arg) { struct hercules_server *server = arg; int cur_session = 0; - while (1) { + while (!wants_shutdown) { /* pthread_spin_lock(&server->biglock); */ cur_session = (cur_session +1) % HERCULES_CONCURRENT_SESSIONS; pop_completion_rings(server); @@ -2968,7 +2976,7 @@ static void tx_update_paths(struct hercules_server *server, int s) { } if (replaced_return_path) { // If we changed the return path we re-send the handshake on - // all paths to update RTT + // all paths to update RTT. debug_printf( "Re-sending HS on path %d because return path changed", i); new_pathset->paths[i].next_handshake_at = 0; @@ -3128,7 +3136,7 @@ static void events_p(void *arg) { memset(tx, 0, sizeof(tx)); memset(rx, 0, sizeof(rx)); int current_slot = 0; - while (1) { // event handler thread loop + while (!wants_shutdown) { // event handler thread loop u64 now = get_nsecs(); current_slot = (current_slot + 1) % HERCULES_CONCURRENT_SESSIONS; /* if (now > lastpoll + 1e9){ */ @@ -3158,9 +3166,9 @@ static void events_p(void *arg) { /* lastpoll = now; */ /* } */ - // XXX This is a bit of a hack: We want to handle received packets more - // frequently than we poll the monitor or check for expired sessions, so - // try to receive 100 times (non-blocking) before doing anything else. + // We want to handle received packets more frequently than we poll the + // monitor or check for expired sessions, so try to receive 1000 times + // (non-blocking) before doing anything else. for (int i = 0; i < 1000; i++) { ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&addr, @@ -3525,10 +3533,6 @@ void hercules_main(struct hercules_server *server) { exit(1); } - // Start event receiver thread - debug_printf("Starting event receiver thread"); - pthread_t events = start_thread(NULL, events_p, server); - // Start the NACK sender thread debug_printf("starting NACK trickle thread"); pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); @@ -3556,11 +3560,18 @@ void hercules_main(struct hercules_server *server) { debug_printf("starting thread tx_p"); pthread_t tx_p_thread = start_thread(NULL, tx_p, server); - while (1) { - // XXX STOP HERE - // FIXME make this thread do something - } + events_p(server); + + join_thread(server, trickle_acks); + join_thread(server, trickle_nacks); join_thread(server, tx_p_thread); + for (int i = 0; i < server->n_threads; i++){ + join_thread(server, rx_workers[i]); + join_thread(server, tx_workers[i]); + } + + xdp_teardown(server); + exit(0); } void usage(){ @@ -3670,6 +3681,21 @@ int main(int argc, char *argv[]) { hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, rx_threads, false, enable_pcc); + // Register a handler for SIGINT/SIGTERM for clean shutdown + struct sigaction act = {0}; + act.sa_handler = hercules_stop; + act.sa_flags = SA_RESETHAND; + int ret = sigaction(SIGINT, &act, NULL); + if (ret == -1){ + fprintf(stderr, "Error registering signal handler\n"); + exit(1); + } + ret = sigaction(SIGTERM, &act, NULL); + if (ret == -1){ + fprintf(stderr, "Error registering signal handler\n"); + exit(1); + } + hercules_main(server); } diff --git a/xdp.c b/xdp.c index 73161b7..0839803 100644 --- a/xdp.c +++ b/xdp.c @@ -378,3 +378,8 @@ int xdp_setup(struct hercules_server *server) { debug_printf("XSK stuff complete"); return 0; } + +void xdp_teardown(struct hercules_server *server){ + remove_xdp_program(server); + unconfigure_rx_queues(server); +} diff --git a/xdp.h b/xdp.h index 29aab32..afeb719 100644 --- a/xdp.h +++ b/xdp.h @@ -42,4 +42,7 @@ int load_xsk_redirect_userspace(struct hercules_server *server, int xdp_setup(struct hercules_server *server); +// Remove xdp program from interface and ethtool rules +void xdp_teardown(struct hercules_server *server); + #endif // HERCULES_XDP_H_ From cdfebfcd60e999ce9cd69a615a0f4530602192dd Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 21 Jun 2024 17:36:16 +0200 Subject: [PATCH 054/112] make server read config file --- .gitmodules | 4 + Makefile | 13 +- hercules.c | 345 ++++++++++++++++++++++++---------------- hercules.h | 4 + monitor.c | 40 ++--- monitor.h | 5 +- monitor/config.go | 6 + monitor/sampleconf.toml | 22 ++- tomlc99 | 1 + xdp.c | 13 +- 10 files changed, 280 insertions(+), 173 deletions(-) create mode 160000 tomlc99 diff --git a/.gitmodules b/.gitmodules index ae716eb..7b39c30 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = bpf url = https://github.com/libbpf/libbpf/ ignore = untracked +[submodule "tomlc99"] + path = tomlc99 + url = https://github.com/cktan/tomlc99.git + ignore = untracked diff --git a/Makefile b/Makefile index a49d063..9b202cc 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets # CFLAGS += -DPRINT_STATS # CFLAGS += -DRANDOMIZE_UNDERLAY_SRC # Enabling this breaks SCMP packet parsing -LDFLAGS = -lbpf -Lbpf/src -lm -lelf -pthread -lz -z noexecstack $(ASAN_FLAG) +LDFLAGS = -lbpf -Lbpf/src -Ltomlc99 -lm -lelf -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) DEPFLAGS:=-MP -MD SRCS := $(wildcard *.c) @@ -31,7 +31,7 @@ all: $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) cd monitor && go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" -$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a builder +$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a builder @# update modification dates in assembly, so that the new version gets loaded @sed -i -e "s/\(load bpf_prgm_pass\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/pass.c | head -c 32)/g" bpf_prgms.s @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s @@ -63,6 +63,15 @@ bpf/src/libbpf.a: builder docker exec -w /`basename $(PWD)`/bpf/src hercules-builder $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ fi +tomlc99/libtoml.a: builder + @if [ ! -d tomlc99 ]; then \ + echo "Error: Need libtoml submodule"; \ + echo "May need to run git submodule update --init"; \ + exit 1; \ + else \ + docker exec -w /`basename $(PWD)`/tomlc99 hercules-builder $(MAKE) all; \ + fi + .PHONY: builder builder_image install clean all diff --git a/hercules.c b/hercules.c index 2764403..816942a 100644 --- a/hercules.c +++ b/hercules.c @@ -48,6 +48,7 @@ #include "bpf/src/xsk.h" #include "linux/filter.h" // actually linux/tools/include/linux/filter.h +#include "tomlc99/toml.h" #include "frame_queue.h" #include "bitset.h" #include "libscion_checksum.h" @@ -350,43 +351,39 @@ static void destroy_session_rx(struct hercules_session *session) { // Initialise the Hercules server. If this runs into trouble we just exit as // there's no point in continuing. -struct hercules_server *hercules_init_server( - int *ifindices, int num_ifaces, const struct hercules_app_addr local_addr, - int queue, int xdp_mode, int n_threads, bool configure_queues, - bool enable_pcc) { +struct hercules_server *hercules_init_server( struct hercules_config config, + int *ifindices, int num_ifaces, bool enable_pcc) { struct hercules_server *server; server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); if (server == NULL) { exit_with_error(NULL, ENOMEM); } - server->usock = monitor_bind_daemon_socket(); + server->usock = monitor_bind_daemon_socket(config.server_socket, config.monitor_socket); if (server->usock == 0) { fprintf(stderr, "Error binding daemon socket\n"); exit_with_error(NULL, EINVAL); } - server->ifindices = ifindices; - server->num_ifaces = num_ifaces; - server->config.queue = queue; - server->n_threads = n_threads; - memset(server->sessions_rx, 0, sizeof(server->sessions_rx[0])*HERCULES_CONCURRENT_SESSIONS); - memset(server->sessions_tx, 0, sizeof(server->sessions_tx[0])*HERCULES_CONCURRENT_SESSIONS); - server->worker_args = calloc(server->n_threads, sizeof(struct worker_args *)); - if (server->worker_args == NULL){ - exit_with_error(NULL, ENOMEM); - } - server->config.local_addr = local_addr; - server->config.port_min = ntohs(local_addr.port); + server->config = config; + server->ifindices = ifindices; + server->num_ifaces = num_ifaces; + server->n_threads = config.num_threads; + memset(server->sessions_rx, 0, + sizeof(server->sessions_rx[0]) * HERCULES_CONCURRENT_SESSIONS); + memset(server->sessions_tx, 0, + sizeof(server->sessions_tx[0]) * HERCULES_CONCURRENT_SESSIONS); + server->worker_args = calloc(server->n_threads, sizeof(struct worker_args *)); + if (server->worker_args == NULL) { + exit_with_error(NULL, ENOMEM); + } + server->config.port_min = ntohs(config.local_addr.port); server->config.port_max = server->config.port_min + HERCULES_CONCURRENT_SESSIONS; - server->config.configure_queues = configure_queues; - server->config.xdp_mode = xdp_mode; - /* server->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; */ - // FIXME with flags set, setup may fail and we don't catch it? + server->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; server->enable_pcc = enable_pcc; for (int i = 0; i < num_ifaces; i++) { server->ifaces[i] = (struct hercules_interface){ - .queue = queue, + .queue = config.queue, .ifid = ifindices[i], .ethtool_rule = -1, }; @@ -3575,128 +3572,202 @@ void hercules_main(struct hercules_server *server) { } void usage(){ - fprintf(stderr, "usage: ?? TODO\n"); + fprintf(stderr, "usage: hercules-server [-c config.toml]\n"); exit(1); } // TODO Test multiple interfaces #define HERCULES_MAX_INTERFACES 1 int main(int argc, char *argv[]) { - // Options: - // -i interface - // -l listen address - // -z XDP zerocopy mode - // -q queue - // -t TX worker threads - // -r RX worker threads - unsigned int if_idxs[HERCULES_MAX_INTERFACES]; - int n_interfaces = 0; - struct hercules_app_addr listen_addr = {.ia = 0, .ip = 0, .port = 0}; - int xdp_mode = XDP_COPY; - int queue = 0; - int tx_threads = 1; - int rx_threads = 1; - int opt; - while ((opt = getopt(argc, argv, "i:l:q:t:r:z")) != -1) { - switch (opt) { - case 'i': - debug_printf("Using interface %s", optarg); - if (n_interfaces >= HERCULES_MAX_INTERFACES) { - fprintf(stderr, "Too many interfaces specified\n"); - exit(1); - } - if_idxs[n_interfaces] = if_nametoindex(optarg); - if (if_idxs[n_interfaces] == 0) { - fprintf(stderr, "No such interface: %s\n", optarg); - exit(1); - } - n_interfaces++; - break; - case 'l':; - // Expect something of the form: 17-ffaa:1:fe2,192.168.50.2:123 - u64 ia; - u16 *ia_ptr = (u16 *)&ia; - char ip_str[100]; - u32 ip; - u16 port; - int ret = sscanf(optarg, "%hu-%hx:%hx:%hx,%99[^:]:%hu", ia_ptr + 3, - ia_ptr + 2, ia_ptr + 1, ia_ptr + 0, ip_str, &port); - if (ret != 6) { - fprintf(stderr, "Error parsing listen address\n"); - exit(1); - } - listen_addr.ia = htobe64(ia); - listen_addr.port = htons(port); - ret = inet_pton(AF_INET, ip_str, &listen_addr.ip); - if (ret != 1) { - fprintf(stderr, "Error parsing listen address\n"); - exit(1); - } - break; - case 'q': - queue = strtol(optarg, NULL, 10); - if (errno == EINVAL || errno == ERANGE) { - fprintf(stderr, "Error parsing queue\n"); - exit(1); - } - break; - case 't': - tx_threads = strtol(optarg, NULL, 10); - if (errno == EINVAL || errno == ERANGE) { - fprintf(stderr, "Error parsing number of tx threads\n"); - exit(1); - } - break; - case 'r': - rx_threads = strtol(optarg, NULL, 10); - if (errno == EINVAL || errno == ERANGE) { - fprintf(stderr, "Error parsing number of rx threads\n"); - exit(1); - } - break; - case 'z': - xdp_mode = XDP_ZEROCOPY; - break; - default: - usage(); - } - } - if (n_interfaces == 0 || listen_addr.ip == 0) { - fprintf(stderr, "Missing required argument\n"); - exit(1); - } - if (rx_threads != tx_threads) { - // XXX This is not required, but if they are different we need to take care - // to allocate the right number of sockets, or have XDP sockets that are - // each used only for one of TX/RX - fprintf(stderr, "TX/RX threads must match\n"); - exit(1); - } - debug_printf("Starting Hercules using queue %d, %d rx threads, %d tx " - "threads, xdp mode 0x%x", - queue, rx_threads, tx_threads, xdp_mode); - - bool enable_pcc = true; - struct hercules_server *server = - hercules_init_server(if_idxs, n_interfaces, listen_addr, queue, xdp_mode, - rx_threads, false, enable_pcc); - - // Register a handler for SIGINT/SIGTERM for clean shutdown - struct sigaction act = {0}; - act.sa_handler = hercules_stop; - act.sa_flags = SA_RESETHAND; - int ret = sigaction(SIGINT, &act, NULL); - if (ret == -1){ - fprintf(stderr, "Error registering signal handler\n"); - exit(1); - } - ret = sigaction(SIGTERM, &act, NULL); - if (ret == -1){ - fprintf(stderr, "Error registering signal handler\n"); - exit(1); - } + unsigned int if_idxs[HERCULES_MAX_INTERFACES]; + int n_interfaces = 0; + char *config_path = NULL; + struct hercules_config config; + memset(&config, 0, sizeof(config)); + // Set defaults + config.monitor_socket = HERCULES_DEFAULT_MONITOR_SOCKET; + config.server_socket = HERCULES_DEFAULT_DAEMON_SOCKET; + config.queue = 0; + config.configure_queues = true; + config.num_threads = 1; + config.xdp_mode = XDP_COPY; + + // Parse command line args (there is only one) + int opt; + while ((opt = getopt(argc, argv, "c:")) != -1) { + switch (opt) { + case 'c': + config_path = optarg; + break; + default: + usage(); + } + } + + // Open and parse config file + FILE *config_file; + char errbuf[200]; + if (config_path != NULL) { + config_file = fopen(config_path, "r"); + debug_printf("Using config file %s", config_path); + } else { + config_file = fopen(HERCULES_DEFAULT_CONFIG_PATH, "r"); + debug_printf("Using default config file %s", + HERCULES_DEFAULT_CONFIG_PATH); + } + if (!config_file) { + fprintf(stderr, "Cannot open config file!\n"); + exit(1); + } + + toml_table_t *conf = toml_parse_file(config_file, errbuf, sizeof(errbuf)); + fclose(config_file); + if (!conf) { + fprintf(stderr, "Error parsing config file: %s", errbuf); + exit(1); + } + + // Socket paths + toml_datum_t monitor_socket = toml_string_in(conf, "MonitorSocket"); + if (monitor_socket.ok) { + config.monitor_socket = monitor_socket.u.s; + } else { + if (toml_key_exists(conf, "MonitorSocket")) { + fprintf(stderr, "Error parsing MonitorSocket\n"); + exit(1); + } + } + toml_datum_t server_socket = toml_string_in(conf, "ServerSocket"); + if (server_socket.ok) { + config.server_socket = server_socket.u.s; + } else { + if (toml_key_exists(conf, "ServerSocket")) { + fprintf(stderr, "Error parsing ServerSocket\n"); + exit(1); + } + } + + // Listening address + toml_datum_t listen_addr = toml_string_in(conf, "ListenAddress"); + if (!listen_addr.ok) { + fprintf(stderr, "Missing required ListenAddress in config file?\n"); + exit(1); + } + // Expect something of the form: 17-ffaa:1:fe2,192.168.50.2:123 + u64 ia; + u16 *ia_ptr = (u16 *)&ia; + char ip_str[100]; + u32 ip; + u16 port; + int ret = sscanf(listen_addr.u.s, "%hu-%hx:%hx:%hx,%99[^:]:%hu", ia_ptr + 3, + ia_ptr + 2, ia_ptr + 1, ia_ptr + 0, ip_str, &port); + if (ret != 6) { + fprintf(stderr, "Error parsing listen address\n"); + exit(1); + } + config.local_addr.ia = htobe64(ia); + config.local_addr.port = htons(port); + ret = inet_pton(AF_INET, ip_str, &config.local_addr.ip); + if (ret != 1) { + fprintf(stderr, "Error parsing listen address\n"); + exit(1); + } + + // NIC Queue + toml_datum_t queue = toml_int_in(conf, "Queue"); + if (queue.ok) { + config.queue = queue.u.i; + } else { + if (toml_key_exists(conf, "Queue")) { + fprintf(stderr, "Error parsing Queue\n"); + exit(1); + } + } + // Automatic queue configuration + toml_datum_t configure_queues = toml_bool_in(conf, "ConfigureQueues"); + if (configure_queues.ok) { + config.configure_queues = (configure_queues.u.b); + } else { + if (toml_key_exists(conf, "ConfigureQueues")) { + fprintf(stderr, "Error parsing ConfigureQueues\n"); + exit(1); + } + } + + // XDP Zerocopy + toml_datum_t zerocopy_enabled = toml_bool_in(conf, "XDPZeroCopy"); + if (zerocopy_enabled.ok) { + config.xdp_mode = (zerocopy_enabled.u.b) ? XDP_ZEROCOPY : XDP_COPY; + } else { + if (toml_key_exists(conf, "XDPZeroCopy")) { + fprintf(stderr, "Error parsing XDPZeroCopy\n"); + exit(1); + } + } + + // Worker threads + toml_datum_t nthreads = toml_int_in(conf, "NumThreads"); + if (nthreads.ok) { + config.num_threads = nthreads.u.i; + } else { + if (toml_key_exists(conf, "NumThreads")) { + fprintf(stderr, "Error parsing NumThreads\n"); + exit(1); + } + } + + // Interfaces + toml_array_t *interfaces = toml_array_in(conf, "Interfaces"); + if (!interfaces || toml_array_nelem(interfaces) == 0) { + fprintf(stderr, "Missing required Interfaces in config file?\n"); + exit(1); + } + + for (int i = 0; i < toml_array_nelem(interfaces); i++) { + toml_datum_t this_if = toml_string_at(interfaces, i); + if (!this_if.ok){ + fprintf(stderr, "Error parsing interfaces?\n"); + exit(1); + } + debug_printf("Using interface %s", this_if.u.s); + if (n_interfaces >= HERCULES_MAX_INTERFACES) { + fprintf(stderr, "Too many interfaces specified\n"); + exit(1); + } + if_idxs[n_interfaces] = if_nametoindex(this_if.u.s); + if (if_idxs[n_interfaces] == 0) { + fprintf(stderr, "No such interface: %s\n", this_if.u.s); + exit(1); + } + n_interfaces++; + } + + debug_printf( + "Starting Hercules using queue %d, queue config %d, %d worker threads, " + "xdp mode 0x%x", + config.queue, config.configure_queues, config.num_threads, + config.xdp_mode); + + bool enable_pcc = true; + struct hercules_server *server = + hercules_init_server(config, if_idxs, n_interfaces, enable_pcc); + + // Register a handler for SIGINT/SIGTERM for clean shutdown + struct sigaction act = {0}; + act.sa_handler = hercules_stop; + act.sa_flags = SA_RESETHAND; + ret = sigaction(SIGINT, &act, NULL); + if (ret == -1) { + fprintf(stderr, "Error registering signal handler\n"); + exit(1); + } + ret = sigaction(SIGTERM, &act, NULL); + if (ret == -1) { + fprintf(stderr, "Error registering signal handler\n"); + exit(1); + } - hercules_main(server); + hercules_main(server); } /// Local Variables: diff --git a/hercules.h b/hercules.h index f11ee14..8a1c1ee 100644 --- a/hercules.h +++ b/hercules.h @@ -26,6 +26,7 @@ #include "frame_queue.h" #include "packet.h" +#define HERCULES_DEFAULT_CONFIG_PATH "hercules.conf" #define HERCULES_MAX_HEADERLEN 256 // NOTE: The maximum packet size is limited by the size of a single XDP frame // (page size - metadata overhead). This is around 3500, but the exact value @@ -233,10 +234,13 @@ struct hercules_interface { // Config determined at program start struct hercules_config { + char *monitor_socket; + char *server_socket; u32 xdp_flags; int xdp_mode; int queue; bool configure_queues; + int num_threads; struct hercules_app_addr local_addr; u16 port_min; // Lowest port on which to accept packets (in HOST // endianness) diff --git a/monitor.c b/monitor.c index 41e13ee..ea03f8f 100644 --- a/monitor.c +++ b/monitor.c @@ -13,17 +13,12 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, int etherlen, struct hercules_path *path) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; msg.payload.reply_path.etherlen = etherlen; msg.payload.reply_path.sample_len = rx_sample_len; memcpy(msg.payload.reply_path.sample, rx_sample_buf, rx_sample_len); - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, - sizeof(monitor)); // TODO return val + send(sockfd, &msg, sizeof(msg), 0); // TODO return val struct hercules_sockmsg_A reply; int n = recv(sockfd, &reply, sizeof(reply), 0); @@ -46,14 +41,10 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, // to compute the paths payload and frame lengths. bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, struct hercules_path **paths) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_GET_PATHS; msg.payload.paths.job_id = job_id; - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + send(sockfd, &msg, sizeof(msg), 0); struct hercules_sockmsg_A reply; int n = recv(sockfd, &reply, sizeof(reply), 0); @@ -81,12 +72,8 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, bool monitor_get_new_job(int sockfd, char *name, char *destname, u16 *job_id, u16 *dst_port, u16 *payloadlen) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + send(sockfd, &msg, sizeof(msg), 0); struct hercules_sockmsg_A reply; int n = recv(sockfd, &reply, sizeof(reply), 0); @@ -107,10 +94,6 @@ bool monitor_get_new_job(int sockfd, char *name, char *destname, u16 *job_id, bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum session_error err, u64 seconds_elapsed, u64 bytes_acked) { - struct sockaddr_un monitor; - monitor.sun_family = AF_UNIX; - strcpy(monitor.sun_path, "/var/herculesmon.sock"); - struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_UPDATE_JOB; msg.payload.job_update.job_id = job_id; @@ -118,7 +101,7 @@ bool monitor_update_job(int sockfd, int job_id, enum session_state state, msg.payload.job_update.error = err; msg.payload.job_update.seconds_elapsed = seconds_elapsed; msg.payload.job_update.bytes_acked = bytes_acked; - sendto(sockfd, &msg, sizeof(msg), 0, &monitor, sizeof(monitor)); + send(sockfd, &msg, sizeof(msg), 0); struct hercules_sockmsg_A reply; int n = recv(sockfd, &reply, sizeof(reply), 0); @@ -128,19 +111,26 @@ bool monitor_update_job(int sockfd, int job_id, enum session_state state, return true; } -#define HERCULES_DAEMON_SOCKET_PATH "/var/hercules.sock" -int monitor_bind_daemon_socket() { +int monitor_bind_daemon_socket(char *server, char *monitor) { int usock = socket(AF_UNIX, SOCK_DGRAM, 0); if (usock <= 0) { return 0; } struct sockaddr_un name; name.sun_family = AF_UNIX; - strcpy(name.sun_path, HERCULES_DAEMON_SOCKET_PATH); - unlink(HERCULES_DAEMON_SOCKET_PATH); + strcpy(name.sun_path, server); + unlink(server); int ret = bind(usock, &name, sizeof(name)); if (ret) { return 0; } + + struct sockaddr_un monitor_sock; + monitor_sock.sun_family = AF_UNIX; + strcpy(monitor_sock.sun_path, monitor); + ret = connect(usock, &monitor_sock, sizeof(monitor_sock)); + if (ret){ + return 0; + } return usock; } diff --git a/monitor.h b/monitor.h index 3f19d98..bf7deca 100644 --- a/monitor.h +++ b/monitor.h @@ -30,7 +30,10 @@ bool monitor_update_job(int sockfd, int job_id, enum session_state state, // Bind the socket for the daemon. The file is deleted if already present. // Returns the file descriptor if successful, 0 otherwise. -int monitor_bind_daemon_socket(); +int monitor_bind_daemon_socket(char *server, char *monitor); + +#define HERCULES_DEFAULT_MONITOR_SOCKET "/var/run/herculesmon.sock" +#define HERCULES_DEFAULT_DAEMON_SOCKET "/var/hercules.sock" // Maximum size of variable-length fields in socket messages. Since we pass // entire packets to the monitor to get reply paths, this must be at least as diff --git a/monitor/config.go b/monitor/config.go index 7ba4f51..67351d3 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -50,6 +50,12 @@ type MonitorConfig struct { MonitorSocket string ListenAddress UDPAddr Interfaces []Interface + // The following are not used by the monitor, they are listed here for completeness + ServerSocket string + XDPZeroCopy bool + Queue int + ConfigureQueues bool + NumThreads int } type PathRules struct { diff --git a/monitor/sampleconf.toml b/monitor/sampleconf.toml index 8e8aeff..35c557e 100644 --- a/monitor/sampleconf.toml +++ b/monitor/sampleconf.toml @@ -3,16 +3,36 @@ # By default, up to `DefaultNumPaths` paths will be used. DefaultNumPaths = 1 -# Path to the monitor socket +# Path to the monitor's unix socket MonitorSocket = "var/run/herculesmon.sock" +# +# Path to the server's unix socket +ServerSocket = "var/run/hercules.sock" # SCION address the Hercules server should listen on ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" +# Network interfaces to use for Hercules Interfaces = [ "eth0", ] +# If the NIC/drivers support XDP in zerocopy mode, enabling it +# will improve performance. +XDPZeroCopy = true + +# Specify the NIC RX queue on which to receive packets +Queue = 0 + +# For Hercules to receive traffic, packets must be redirected to the queue specified above. +# Hercules will try to configure this automatically, but this behaviour can be overridden, +# e.g. if you wish to set custom rules or automatic configuration fails. +# If you set this to false, you must manually ensure packets end up in the right queue. +ConfigureQueues = false + +# The number of RX/TX worker threads to use +NumThreads = 2 + # The number and choice of paths can be overridden on a destination-host # or destination-AS basis. In case both an AS and Host rule match, the Host # rule takes precedence. diff --git a/tomlc99 b/tomlc99 new file mode 160000 index 0000000..5221b3d --- /dev/null +++ b/tomlc99 @@ -0,0 +1 @@ +Subproject commit 5221b3d3d66c25a1dc6f0372b4f824f1202fe398 diff --git a/xdp.c b/xdp.c index 0839803..f6e38d9 100644 --- a/xdp.c +++ b/xdp.c @@ -366,15 +366,14 @@ int xdp_setup(struct hercules_server *server) { } load_xsk_redirect_userspace(server, server->worker_args, server->n_threads); - // TODO this is not set anywhere, so it will never run + if (server->config.configure_queues) { - configure_rx_queues(server); + int ret = configure_rx_queues(server); + if (ret != 0) { + return ret; + } } - // TODO when/where is this needed? - // same for rx_state - /* libbpf_smp_rmb(); */ - /* session->tx_state = tx_state; */ - /* libbpf_smp_wmb(); */ + debug_printf("XSK stuff complete"); return 0; } From d38c677ad473c70c4ceb1bc6f130e5c7db63d5cf Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sat, 22 Jun 2024 12:11:07 +0200 Subject: [PATCH 055/112] big cleanup, check return codes in xdp setup --- Makefile | 48 +- bitset.c | 9 +- bitset.h | 2 +- bpf_prgm/redirect_userspace.c | 2 +- congestion_control.c | 13 +- congestion_control.h | 5 +- frame_queue.h | 5 - hercules.c | 1952 ++++++++++++++++++--------------- hercules.h | 116 +- monitor.c | 115 +- monitor.h | 50 +- monitor/config.go | 12 +- monitor/http_api.go | 2 +- monitor/monitor.go | 55 +- monitor/sampleconf.toml | 16 +- packet.h | 4 +- send_queue.c | 1 - utils.h | 2 + xdp.c | 93 +- xdp.h | 8 +- 20 files changed, 1399 insertions(+), 1111 deletions(-) diff --git a/Makefile b/Makefile index 9b202cc..2ca4294 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,27 @@ -# TODO change binary names -TARGET_SERVER := runHercules -TARGET_MONITOR := runMonitor +TARGET_SERVER := hercules-server +TARGET_MONITOR := hercules-monitor CC := gcc -CFLAGS = -O0 -std=gnu11 -D_GNU_SOURCE +CFLAGS = -O3 -flto -std=gnu11 -D_GNU_SOURCE -Itomlc99 # CFLAGS += -Wall -Wextra -# for debugging: -# ASAN_FLAG := -fsanitize=address,leak,undefined,pointer-compare,pointer-subtract -ASAN_FLAG := -fsanitize=address -CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) + +## Options: +# Print rx/tx session stats +CFLAGS += -DPRINT_STATS +# Enforce checking the source SCION/UDP address/port of received packets +CFLAGS += -DCHECK_SRC_ADDRESS +# Randomise the UDP underlay port (no restriction on the range of used ports). +# Enabling this currently breaks SCMP packet parsing +# CFLAGS += -DRANDOMIZE_UNDERLAY_SRC + +## for debugging: +# ASAN_FLAG := -fsanitize=address +# CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets -# CFLAGS += -DPRINT_STATS -# CFLAGS += -DRANDOMIZE_UNDERLAY_SRC # Enabling this breaks SCMP packet parsing -LDFLAGS = -lbpf -Lbpf/src -Ltomlc99 -lm -lelf -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) -DEPFLAGS:=-MP -MD + + +LDFLAGS = -flto -lbpf -Lbpf/src -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) +DEPFLAGS := -MP -MD SRCS := $(wildcard *.c) OBJS := $(SRCS:.c=.o) @@ -23,13 +31,18 @@ MONITORFILES := $(wildcard monitor/*) VERSION := $(shell (ref=$$(git describe --tags --long --dirty 2>/dev/null) && echo $$(git rev-parse --abbrev-ref HEAD)-$$ref) ||\ echo $$(git rev-parse --abbrev-ref HEAD)-untagged-$$(git describe --tags --dirty --always)) -# TODO: Install target -# TODO: Native vs docker builds + all: $(TARGET_MONITOR) $(TARGET_SERVER) +install: all +ifndef DESTDIR + $(error DESTDIR is not set) +endif + cp hercules-server hercules-monitor hercules.conf $(DESTDIR) + # List all headers as dependency because we include a header file via cgo (which in turn may include other headers) $(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) - cd monitor && go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" + docker exec -w /`basename $(PWD)`/monitor hercules-builder go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" $(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a builder @# update modification dates in assembly, so that the new version gets loaded @@ -75,9 +88,8 @@ tomlc99/libtoml.a: builder .PHONY: builder builder_image install clean all -# what to do with this?? -mockules: builder mockules/main.go mockules/network.go - docker exec -w /`basename $(PWD)`/mockules hercules-builder go build +# mockules: builder mockules/main.go mockules/network.go +# docker exec -w /`basename $(PWD)`/mockules hercules-builder go build # docker stuff builder: builder_image diff --git a/bitset.c b/bitset.c index 967e0a6..4eabf0d 100644 --- a/bitset.c +++ b/bitset.c @@ -15,13 +15,18 @@ #include "bitset.h" #include -void bitset__create(struct bitset *s, u32 num) +int bitset__create(struct bitset *s, u32 num) { s->bitmap = calloc((num + HERCULES_BITSET_WORD_BITS - 1) / HERCULES_BITSET_WORD_BITS, HERCULES_BITSET_WORD_BITS / 8); s->num = num; s->num_set = 0; - pthread_spin_init(&s->lock, PTHREAD_PROCESS_PRIVATE); + int ret = pthread_spin_init(&s->lock, PTHREAD_PROCESS_PRIVATE); + if (ret){ + free(s->bitmap); + return 1; + } + return 0; } void bitset__destroy(struct bitset *s) diff --git a/bitset.h b/bitset.h index 3f238c9..232e835 100644 --- a/bitset.h +++ b/bitset.h @@ -34,7 +34,7 @@ struct bitset { #define HERCULES_BITSET_WORD_BITS (8 * sizeof(unsigned int)) -void bitset__create(struct bitset *s, u32 num); +int bitset__create(struct bitset *s, u32 num); void bitset__destroy(struct bitset *s); diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index 182f951..19bda6b 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -128,7 +128,7 @@ int xdp_prog_redirect_userspace(struct xdp_md *ctx) } if (ntohs(l4udph->dest) < ntohs(addr->port) || ntohs(l4udph->dest) > - ntohs(addr->port) + HERCULES_CONCURRENT_SESSIONS) { + ntohs(addr->port) + 2 * HERCULES_CONCURRENT_SESSIONS) { return XDP_PASS; } offset += sizeof(struct udphdr); diff --git a/congestion_control.c b/congestion_control.c index 4edf2aa..2eb6ea6 100644 --- a/congestion_control.c +++ b/congestion_control.c @@ -4,7 +4,6 @@ #include #include -#include "hercules.h" #include "congestion_control.h" #include "utils.h" @@ -13,12 +12,15 @@ #define MSS 1460 -struct ccontrol_state *init_ccontrol_state(u32 max_rate_limit, - u32 total_chunks, u32 num_paths) { +struct ccontrol_state *init_ccontrol_state(u32 max_rate_limit, u32 num_paths) { struct ccontrol_state *cc_state = calloc(1, sizeof(struct ccontrol_state)); cc_state->max_rate_limit = max_rate_limit; cc_state->num_paths = num_paths; - pthread_spin_init(&cc_state->lock, PTHREAD_PROCESS_PRIVATE); + int ret = pthread_spin_init(&cc_state->lock, PTHREAD_PROCESS_PRIVATE); + if (ret){ + free(cc_state); + return NULL; + } continue_ccontrol(cc_state); return cc_state; @@ -107,11 +109,12 @@ u32 ccontrol_can_send_npkts(struct ccontrol_state *cc_state, u64 now) void kick_ccontrol(struct ccontrol_state *cc_state) { + (void)cc_state; // TODO can / should we get rid of this? //cc_state->state = pcc_startup; } -void destroy_ccontrol_state(struct ccontrol_state *cc_states, size_t num_paths) +void destroy_ccontrol_state(struct ccontrol_state *cc_states) { free(cc_states); } diff --git a/congestion_control.h b/congestion_control.h index b6857f4..dd6223f 100644 --- a/congestion_control.h +++ b/congestion_control.h @@ -2,7 +2,6 @@ #define _CCONTROL_H_ #include "bitset.h" -#include "hercules.h" #include "utils.h" #include @@ -76,13 +75,13 @@ struct ccontrol_state { */ // Initialize congestion control state struct ccontrol_state * -init_ccontrol_state(u32 max_rate_limit, u32 total_chunks, u32 num_paths); +init_ccontrol_state(u32 max_rate_limit, u32 num_paths); void terminate_ccontrol(struct ccontrol_state *cc_state); void continue_ccontrol(struct ccontrol_state *cc_state); void ccontrol_update_rtt(struct ccontrol_state *cc_state, u64 rtt); u32 ccontrol_can_send_npkts(struct ccontrol_state *cc_state, u64 now); void kick_ccontrol(struct ccontrol_state *cc_state); -void destroy_ccontrol_state(struct ccontrol_state *cc_states, size_t num_paths); +void destroy_ccontrol_state(struct ccontrol_state *cc_states); void ccontrol_start_monitoring_interval(struct ccontrol_state *cc_state); // Apply PCC control decision, return new rate diff --git a/frame_queue.h b/frame_queue.h index 7d70ca7..1ce6604 100644 --- a/frame_queue.h +++ b/frame_queue.h @@ -45,7 +45,6 @@ static inline int frame_queue__init(struct frame_queue *fq, u16 size) static inline u16 frame_queue__prod_reserve(struct frame_queue *fq, u16 num) { - /* debug_printf("Empty slots in fq %llu", fq->cons - fq->prod); */ return umin16(atomic_load(&fq->cons) - fq->prod, num); } @@ -57,14 +56,10 @@ static inline void frame_queue__prod_fill(struct frame_queue *fq, u16 offset, u6 static inline void frame_queue__push(struct frame_queue *fq, u16 num) { atomic_fetch_add(&fq->prod, num); - u64 now = atomic_load(&fq->prod) - atomic_load(&fq->cons) + fq->size; - /* debug_printf("Frames in fq after push %llu", now); */ } static inline u16 frame_queue__cons_reserve(struct frame_queue *fq, u16 num) { - /* debug_printf("Reserve prod %llu, cons %llu", fq->prod, fq->cons); */ - /* debug_printf("Frames in fq %llu", fq->prod - fq->cons + fq->size); */ return umin16(atomic_load(&fq->prod) - fq->cons + fq->size, num); } diff --git a/hercules.c b/hercules.c index 816942a..211b260 100644 --- a/hercules.c +++ b/hercules.c @@ -48,7 +48,7 @@ #include "bpf/src/xsk.h" #include "linux/filter.h" // actually linux/tools/include/linux/filter.h -#include "tomlc99/toml.h" +#include "toml.h" #include "frame_queue.h" #include "bitset.h" #include "libscion_checksum.h" @@ -71,11 +71,18 @@ #define ACK_RATE_TIME_MS 100 // send ACKS after at most X milliseconds +// After how many NACK tracking errors to resend a path handshake +#define NACK_ERRS_ALLOWED 20 + static const int rbudp_headerlen = sizeof(struct hercules_header); -static const u64 session_timeout = 10e9; // 10 sec -static const u64 session_hs_retransmit_interval = 2e9; // 2 sec -static const u64 session_stale_timeout = 30e9; // 30 sec -#define PCC_NO_PATH UINT8_MAX // tell the receiver not to count the packet on any path +static const u64 session_timeout = 10e9; // 10 sec +static const u64 session_hs_retransmit_interval = 2e9; // 2 sec +static const u64 session_stale_timeout = 30e9; // 30 sec +static const u64 print_stats_interval = 1e9; // 1 sec +static const u64 path_update_interval = 60e9 * 2; // 2 minutes +static const u64 monitor_update_interval = 30e9; // 30 seconds +#define PCC_NO_PATH \ + UINT8_MAX // tell the receiver not to count the packet on any path _Atomic bool wants_shutdown = false; #define FREE_NULL(p) \ @@ -96,7 +103,7 @@ void debug_print_rbudp_pkt(const char *pkt, bool recv); static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, struct rbudp_initial_pkt **parsed_pkt); -static struct hercules_session *make_session(struct hercules_server *server); +static struct hercules_session *make_session(); /// COMMON @@ -108,12 +115,10 @@ void hercules_stop(int signo) { // Check the SCION UDP address matches the session's peer static inline bool src_matches_address(struct hercules_session *session, - const struct scionaddrhdr_ipv4 *scionaddrhdr, - const struct udphdr *udphdr) { - /* struct hercules_app_addr *addr = &session->peer; */ - /* return scionaddrhdr->src_ia == addr->ia && */ - /* scionaddrhdr->src_ip == addr->ip && udphdr->uh_sport == addr->port; */ - return true; + struct hercules_app_addr *pkt_source) { + return pkt_source->ia == session->peer.ia && + pkt_source->ip == session->peer.ip && + pkt_source->port == session->peer.port; } static void __exit_with_error(struct hercules_server *server, int error, const char *file, const char *func, int line) @@ -184,6 +189,19 @@ static inline bool session_state_is_running(enum session_state s) { return false; } +static inline void count_received_pkt(struct hercules_session *session, + u32 path_idx) { + atomic_fetch_add(&session->rx_npkts, 1); + u64 now = get_nsecs(); + u64 old_ts = atomic_load(&session->last_pkt_rcvd); + if (old_ts < now) { + atomic_compare_exchange_strong(&session->last_pkt_rcvd, &old_ts, now); + } + if (path_idx < PCC_NO_PATH && session->rx_state != NULL) { + atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); + } +} + #ifdef DEBUG_PRINT_PKTS // recv indicates whether printed packets should be prefixed with TX or RX void debug_print_rbudp_pkt(const char *pkt, bool recv) { @@ -252,37 +270,35 @@ void debug_print_rbudp_pkt(const char * pkt, bool recv){ } #endif -static struct hercules_session *lookup_session_tx(struct hercules_server *server, u16 port){ - if (port < server->config.port_min || port > server->config.port_max){ - return NULL; - } - if (port == server->config.port_min){ +static struct hercules_session *lookup_session_tx( + struct hercules_server *server, u16 port) { + if (port <= server->config.port_min || + port > server->config.port_min + HERCULES_CONCURRENT_SESSIONS) { return NULL; } u32 off = port - server->config.port_min - 1; return server->sessions_tx[off]; } -static struct hercules_session *lookup_session_rx(struct hercules_server *server, u16 port){ - if (port < server->config.port_min || port > server->config.port_max){ - return NULL; - } - if (port == server->config.port_min){ +static struct hercules_session *lookup_session_rx( + struct hercules_server *server, u16 port) { + if (port <= server->config.port_min + HERCULES_CONCURRENT_SESSIONS || + port > server->config.port_max) { return NULL; } - u32 off = port - server->config.port_min - 1; + u32 off = + port - server->config.port_min - HERCULES_CONCURRENT_SESSIONS - 1; return server->sessions_rx[off]; } // Initialise a new session. Returns null in case of error. -static struct hercules_session *make_session(struct hercules_server *server) { +static struct hercules_session *make_session( + u32 payloadlen, u64 job_id, struct hercules_app_addr *peer_addr) { struct hercules_session *s; s = calloc(1, sizeof(*s)); if (s == NULL) { return NULL; } - s->state = SESSION_STATE_NONE; - s->error = SESSION_ERROR_NONE; int err = posix_memalign((void **)&s->send_queue, CACHELINE_SIZE, sizeof(*s->send_queue)); if (err != 0) { @@ -290,6 +306,12 @@ static struct hercules_session *make_session(struct hercules_server *server) { return NULL; } init_send_queue(s->send_queue, BATCH_SIZE); + + s->state = SESSION_STATE_NONE; + s->error = SESSION_ERROR_NONE; + s->payloadlen = payloadlen; + s->jobid = job_id; + s->peer = *peer_addr; s->last_pkt_sent = 0; u64 now = get_nsecs(); s->last_pkt_rcvd = @@ -297,30 +319,38 @@ static struct hercules_session *make_session(struct hercules_server *server) { // (when no packet was received yet), once packets are // received it will be updated accordingly s->last_new_pkt_rcvd = now; + s->last_monitor_update = now; + s->last_path_update = now; return s; } // Cleanup and free TX session -static void destroy_session_tx(struct hercules_session *session) { +static void destroy_session_tx(struct hercules_server *server, + struct hercules_session *session) { if (session == NULL) { return; } assert(session->state == SESSION_STATE_DONE); int ret = munmap(session->tx_state->mem, session->tx_state->filesize); - assert(ret == 0); // No reason this should ever fail + if (ret == 0) { + // XXX Is there anything we can do if this fails? + fprintf(stderr, "munmap failure!\n"); + exit_with_error(server, errno); + } session->tx_state->mem = NULL; bitset__destroy(&session->tx_state->acked_chunks); bitset__destroy(&session->tx_state->acked_chunks_index); + struct path_set *pathset = session->tx_state->pathset; - for (u32 i = 0; i < pathset->n_paths; i++){ - destroy_ccontrol_state(pathset->paths[i].cc_state, 0); + for (u32 i = 0; i < pathset->n_paths; i++) { + destroy_ccontrol_state(pathset->paths[i].cc_state); pathset->paths[i].cc_state = NULL; } FREE_NULL(session->tx_state->pathset); - FREE_NULL(session->tx_state->index); + FREE_NULL(session->tx_state->index); FREE_NULL(session->tx_state); destroy_send_queue(session->send_queue); @@ -329,14 +359,18 @@ static void destroy_session_tx(struct hercules_session *session) { } // Cleanup and free RX session -static void destroy_session_rx(struct hercules_session *session) { +static void destroy_session_rx(struct hercules_server *server, + struct hercules_session *session) { if (session == NULL) { return; } assert(session->state == SESSION_STATE_DONE); int ret = munmap(session->rx_state->mem, session->rx_state->filesize); - assert(ret == 0); // No reason this should ever fail + if (ret == 0) { + fprintf(stderr, "munmap failure!\n"); + exit_with_error(server, errno); + } session->rx_state->mem = NULL; bitset__destroy(&session->rx_state->received_chunks); @@ -349,37 +383,40 @@ static void destroy_session_rx(struct hercules_session *session) { free(session); } -// Initialise the Hercules server. If this runs into trouble we just exit as -// there's no point in continuing. -struct hercules_server *hercules_init_server( struct hercules_config config, - int *ifindices, int num_ifaces, bool enable_pcc) { +// Initialise the Hercules server. If this runs into trouble we just exit. +struct hercules_server *hercules_init_server(struct hercules_config config, + unsigned int *ifindices, int num_ifaces) { struct hercules_server *server; server = calloc(1, sizeof(*server) + num_ifaces * sizeof(*server->ifaces)); if (server == NULL) { exit_with_error(NULL, ENOMEM); } - server->usock = monitor_bind_daemon_socket(config.server_socket, config.monitor_socket); - if (server->usock == 0) { - fprintf(stderr, "Error binding daemon socket\n"); - exit_with_error(NULL, EINVAL); - } - server->config = config; - server->ifindices = ifindices; - server->num_ifaces = num_ifaces; - server->n_threads = config.num_threads; - memset(server->sessions_rx, 0, - sizeof(server->sessions_rx[0]) * HERCULES_CONCURRENT_SESSIONS); - memset(server->sessions_tx, 0, - sizeof(server->sessions_tx[0]) * HERCULES_CONCURRENT_SESSIONS); - server->worker_args = calloc(server->n_threads, sizeof(struct worker_args *)); - if (server->worker_args == NULL) { - exit_with_error(NULL, ENOMEM); + server->usock = + monitor_bind_daemon_socket(config.server_socket, config.monitor_socket); + if (server->usock == 0) { + fprintf(stderr, "Error binding daemon socket\n"); + exit_with_error(NULL, EINVAL); + } + + server->config = config; + server->ifindices = ifindices; + server->num_ifaces = num_ifaces; + memset(server->sessions_rx, 0, + sizeof(server->sessions_rx[0]) * HERCULES_CONCURRENT_SESSIONS); + memset(server->sessions_tx, 0, + sizeof(server->sessions_tx[0]) * HERCULES_CONCURRENT_SESSIONS); + + server->worker_args = + calloc(server->config.n_threads, sizeof(struct worker_args *)); + if (server->worker_args == NULL) { + exit_with_error(NULL, ENOMEM); } + server->config.port_min = ntohs(config.local_addr.port); - server->config.port_max = server->config.port_min + HERCULES_CONCURRENT_SESSIONS; + server->config.port_max = + server->config.port_min + 2*HERCULES_CONCURRENT_SESSIONS; server->config.xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; - server->enable_pcc = enable_pcc; for (int i = 0; i < num_ifaces; i++) { server->ifaces[i] = (struct hercules_interface){ @@ -394,7 +431,7 @@ struct hercules_server *hercules_init_server( struct hercules_config config, server->control_sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); if (server->control_sockfd == -1) { - exit_with_error(server, 0); + exit_with_error(server, errno); } debug_printf("init complete"); return server; @@ -503,10 +540,11 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec } // The SCMP packet contains a copy of the offending message we sent, parse it to -// figure out which path the SCMP message is referring to. +// figure out which path/session the SCMP message is referring to. // Returns the offending path's id, or PCC_NO_PATH on failure. // XXX Not checking dst or source ia/addr/port in reflected packet -static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, u16 *offending_dst_port) { +static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, + u16 *offending_dst_port) { size_t offset = 0; const char *pkt = NULL; debug_printf("SCMP type %d", scmp->type); @@ -576,9 +614,9 @@ static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, u16 if (next_header != IPPROTO_UDP) { return PCC_NO_PATH; } - const struct scionaddrhdr_ipv4 *scionaddrh = - (const struct scionaddrhdr_ipv4 *)(pkt + offset + - sizeof(struct scionhdr)); + /* const struct scionaddrhdr_ipv4 *scionaddrh = */ + /* (const struct scionaddrhdr_ipv4 *)(pkt + offset + */ + /* sizeof(struct scionhdr)); */ offset = next_offset; // Finally parse the L4-UDP header @@ -738,18 +776,25 @@ static const char *parse_pkt(const struct hercules_server *server, return parse_pkt_fast_path(pkt, length, check, offset); } -static inline void stitch_src_port(const struct hercules_path *path, u16 port, char *pkt){ +// Fill in the source port in the SCION/UDP header. Port argument is in host +// byte order. +static inline void stitch_src_port(const struct hercules_path *path, u16 port, + char *pkt) { char *payload = pkt + path->headerlen; - u16 *udp_src = (u16 *)(payload-8); + u16 *udp_src = (u16 *)(payload - 8); *udp_src = htons(port); } -static inline void stitch_dst_port(const struct hercules_path *path, u16 port, char *pkt){ +// Fill in the destination port in the SCION/UDP header. +// Port argument is in network byte order. +static inline void stitch_dst_port(const struct hercules_path *path, u16 port, + char *pkt) { char *payload = pkt + path->headerlen; - u16 *udp_dst = (u16 *)(payload-6); - *udp_dst = htons(port); + u16 *udp_dst = (u16 *)(payload - 6); + *udp_dst = port; } +// Used when the original header (and, thus, the checksum) does not contain the correct destination port. static void stitch_checksum_with_dst(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) { chk_input chk_input_s; @@ -768,6 +813,7 @@ static void stitch_checksum_with_dst(const struct hercules_path *path, u16 preco mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); } +// Used when the original header (and checksum) already contains the correct destination port. static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) { chk_input chk_input_s; @@ -901,7 +947,7 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p bool prev; if(rx_state->is_pcc_benchmark) { prev = false; // for benchmarking, we did "not receive this packet before" - // this wilrcl trick the sender into sending the file over and over again, + // this will trick the sender into sending the file over and over again, // regardless of which packets have actually been received. This does not // break PCC because that takes NACKs send on a per-path basis as feedback } else { @@ -925,7 +971,12 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p } memcpy(target_ptr + chunk_start, payload, len); // Update last new pkt timestamp - atomic_store(&rx_state->session->last_new_pkt_rcvd, get_nsecs()); + u64 now = get_nsecs(); + u64 old_ts = atomic_load(&rx_state->session->last_new_pkt_rcvd); + if (old_ts < now) { + atomic_compare_exchange_strong( + &rx_state->session->last_new_pkt_rcvd, &old_ts, now); + } } return true; } @@ -991,11 +1042,6 @@ submit_rx_frames(struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames size_t reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); while(reserved != num_frames) { reserved = xsk_ring_prod__reserve(&umem->fq, num_frames, &idx_fq); - // FIXME this - /* if(session == NULL || session->state != SESSION_STATE_RUNNING) { */ - /* pthread_spin_unlock(&umem->fq_lock); */ - /* return; */ - /* } */ } for(size_t i = 0; i < num_frames; i++) { @@ -1009,15 +1055,12 @@ submit_rx_frames(struct xsk_umem_info *umem, const u64 *addrs, size_t num_frames static void rx_receive_batch(struct hercules_server *server, struct xsk_socket_info *xsk) { u32 idx_rx = 0; - int ignored = 0; size_t rcvd = xsk_ring_cons__peek(&xsk->rx, BATCH_SIZE, &idx_rx); if (!rcvd) { return; } - // optimistically update receive timestamp - u64 frame_addrs[BATCH_SIZE]; for (size_t i = 0; i < rcvd; i++) { u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; @@ -1026,19 +1069,43 @@ static void rx_receive_batch(struct hercules_server *server, const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); u16 pkt_dst_port = ntohs(*(u16 *)(rbudp_pkt - 6)); - struct hercules_session *session_rx = lookup_session_rx(server, pkt_dst_port); - if (session_rx == NULL || !session_state_is_running(session_rx->state)){ + + struct hercules_session *session_rx = + lookup_session_rx(server, pkt_dst_port); + if (session_rx == NULL || + !session_state_is_running(session_rx->state)) { continue; } - u64 now = get_nsecs(); - u64 old_last_pkt_rcvd = atomic_load(&session_rx->rx_state->last_pkt_rcvd); - if (old_last_pkt_rcvd < now) { - atomic_compare_exchange_strong(&session_rx->rx_state->last_pkt_rcvd, - &old_last_pkt_rcvd, now); - } - atomic_store(&session_rx->last_pkt_rcvd, now); + if (rbudp_pkt) { debug_print_rbudp_pkt(rbudp_pkt, true); + +#ifdef CHECK_SRC_ADDRESS + struct hercules_app_addr pkt_source = { + // SCION address header offset: 14 (ethernet) + 20 (IP) + 8 + // (underlay + // UDP) + 12 (common header) = 54 + // Source IA offset: + 8 = 62 + // Source host addr offset: + 20 (for ipv4) = 74 + .port = *(u16 *)(rbudp_pkt - 8), + .ia = *(u64 *)&pkt[62], + .ip = *(u32 *)&pkt[74]}; + if (!src_matches_address(session_rx, &pkt_source)) { + debug_printf("Dropping packet with unexpected source"); + debug_printf("have %llx %x %u\n want %llx %x %u", pkt_source.ia, + pkt_source.ip, pkt_source.port, + session_rx->peer.ia, session_rx->peer.ip, + session_rx->peer.port); + continue; + } +#endif + + u64 now = get_nsecs(); + u64 old_last_pkt_rcvd = atomic_load(&session_rx->last_pkt_rcvd); + if (old_last_pkt_rcvd < now) { + atomic_compare_exchange_strong(&session_rx->last_pkt_rcvd, + &old_last_pkt_rcvd, now); + } if (!handle_rbudp_data_pkt(session_rx->rx_state, rbudp_pkt, len - (rbudp_pkt - pkt))) { debug_printf("Non-data packet on XDP socket? Ignoring."); @@ -1047,77 +1114,110 @@ static void rx_receive_batch(struct hercules_server *server, debug_printf("Unparseable packet on XDP socket, ignoring"); } atomic_fetch_add(&session_rx->rx_npkts, 1); - } xsk_ring_cons__release(&xsk->rx, rcvd); submit_rx_frames(xsk->umem, frame_addrs, rcvd); } -// Prepare a file and memory mapping to receive a file -static char *rx_mmap(const char *index, size_t index_size, size_t total_filesize) { - debug_printf("total filesize %ld", total_filesize); - debug_printf("total entry size %ld", index_size); - char *mem = mmap(NULL, total_filesize, PROT_READ, MAP_PRIVATE | MAP_ANON, 0, 0); +// Prepare a file and memory mapping to receive a file. +// This will create the required directory structure and map the files to be +// received into memory. If a directory already exists, this is not an error, +// the files and directories below it will still be created. +static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { + debug_printf("Total filesize %ld", total_filesize); + debug_printf("Total entry size %ld", index_size); + char *mem = + mmap(NULL, total_filesize, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0); if (mem == MAP_FAILED) { return NULL; } char *next_mapping = mem; - struct dir_index_entry *p = (struct dir_index_entry *)index; - while (1) { - debug_printf("Read: %s (%d) %dB", p->path, p->type, p->filesize); + bool encountered_err = false; + for (char *p = index; p < index + index_size;) { + struct dir_index_entry *entry = (struct dir_index_entry *)p; + debug_printf("Read: %s (%d) %dB", entry->path, entry->type, + entry->filesize); + int ret; - if (p->type == INDEX_TYPE_FILE) { - int f = open(p->path, O_RDWR | O_CREAT | O_EXCL, 0664); + if (entry->type == INDEX_TYPE_FILE) { + int f = open((char *)entry->path, O_RDWR | O_CREAT | O_EXCL, 0664); if (f == -1 && errno == EEXIST) { - f = open(p->path, O_RDWR | O_EXCL); + struct stat statbuf; + ret = stat((char *)entry->path, &statbuf); + if (ret) { + encountered_err = true; + break; + } + if (!(S_ISREG(statbuf.st_mode))) { + debug_printf("path exists but is not a regular file?"); + encountered_err = true; + break; + } + debug_printf("Overwriting existing file!"); + f = open((char *)entry->path, O_RDWR); } if (f == -1) { - return NULL; + encountered_err = true; + break; } - ret = - fallocate(f, 0, 0, - p->filesize); // Will fail on old filesystems (ext3) + ret = fallocate( + f, 0, 0, + entry->filesize); // Will fail on old filesystems (ext3) if (ret) { close(f); - return NULL; + encountered_err = true; + break; } - debug_printf("%p: %s", next_mapping, p->path); - char *filemap = mmap(next_mapping, p->filesize, PROT_WRITE, + + char *filemap = mmap(next_mapping, entry->filesize, PROT_WRITE, MAP_SHARED | MAP_FIXED, f, 0); - debug_printf("%p: %d", filemap, filemap == next_mapping); if (filemap == MAP_FAILED) { - debug_printf("filemap err!"); - return NULL; + debug_printf("filemap err! %s", strerror(errno)); + encountered_err = true; + break; } - u32 filesize_up = - ((4096 - 1) & p->filesize) - ? ((p->filesize + 4096) & ~(4096 - 1)) - : p->filesize; + + u32 filesize_up = ROUND_UP_PAGESIZE(entry->filesize); next_mapping += filesize_up; close(f); - } - else if (p->type == INDEX_TYPE_DIR){ - ret = mkdir(p->path, 0664); + } else if (entry->type == INDEX_TYPE_DIR) { + ret = mkdir((char *)entry->path, 0664); if (ret != 0) { - // XXX should an already existing directory be an error? if (errno == EEXIST) { struct stat statbuf; - stat(p->path, &statbuf); - if (!S_ISDIR(statbuf.st_mode)){ + ret = stat((char *)entry->path, &statbuf); + if (ret) { + encountered_err = true; + break; + } + if (!S_ISDIR(statbuf.st_mode)) { debug_printf("path exists but is not a directory?"); - return NULL; + encountered_err = true; + break; } + fprintf(stderr, "Directory already exists: %s\n", + (char *)entry->path); } else { debug_printf("mkdir err"); - return NULL; + encountered_err = true; + break; } } - } - p = ((char *)p) + sizeof(*p) + p->path_len; - if (p >= index + index_size) { + } else { + debug_printf("Illegal entry type: %d", entry->type); + encountered_err = true; break; } + p = p + sizeof(*entry) + entry->path_len; + } + if (encountered_err) { + int ret = munmap(mem, total_filesize); + if (ret) { + fprintf(stderr, "munmap error: %s\n", strerror(errno)); + exit_with_error(NULL, errno); + } + return NULL; } return mem; } @@ -1142,9 +1242,10 @@ static struct receiver_state *make_rx_state(struct hercules_session *session, rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = is_pcc_benchmark; - rx_state->mem = rx_mmap(index, index_size, filesize); rx_state->src_port = src_port; + rx_state->mem = rx_mmap(index, index_size, filesize); if (rx_state->mem == NULL) { + bitset__destroy(&rx_state->received_chunks); free(rx_state); return NULL; } @@ -1183,9 +1284,9 @@ static struct receiver_state *make_rx_state_nomap( // The packet is sent to the monitor, which will return a new header with the // path reversed. static bool rx_update_reply_path( - struct hercules_server *server, struct receiver_state *rx_state, int ifid, - int etherlen, - int rx_sample_len, const char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]) { + struct hercules_server *server, struct receiver_state *rx_state, + int etherlen, int rx_sample_len, + const char rx_sample_buf[XSK_UMEM__DEFAULT_FRAME_SIZE]) { debug_printf("Updating reply path"); if (!rx_state) { debug_printf("ERROR: invalid rx_state"); @@ -1194,34 +1295,35 @@ static bool rx_update_reply_path( assert(rx_sample_len > 0); assert(rx_sample_len <= XSK_UMEM__DEFAULT_FRAME_SIZE); - // TODO writing to reply path needs sync? int ret = monitor_get_reply_path(server->usock, rx_sample_buf, rx_sample_len, etherlen, &rx_state->reply_path); if (!ret) { + debug_printf("Error getting reply path"); return false; } - // XXX Do we always want to reply from the interface the packet was received - // on? - // TODO The monitor also returns an interface id (from route lookup) - rx_state->reply_path.ifid = ifid; + // NOTE: To determine the interface, the monitor does a route lookup (for + // the next hop address). This may not be the interface the packet was + // received on. return true; } // Return a copy of the currently stored reply path. static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) { - memcpy(path, &rx_state->reply_path, sizeof(*path)); + struct hercules_path p = atomic_load(&rx_state->reply_path); + memcpy(path, &p, sizeof(*path)); return true; } // Reflect the received initial packet back to the sender. The sent packet is -// identical to the one received, but has the HS_CONFIRM flag set. +// identical to the one received, but has the HS_CONFIRM flag set and does not +// contain the directory index. static void rx_send_rtt_ack(struct hercules_server *server, - struct receiver_state *rx_state, int rx_slot, + struct receiver_state *rx_state, struct rbudp_initial_pkt *pld) { struct hercules_path path; - if(!rx_get_reply_path(rx_state, &path)) { + if (!rx_get_reply_path(rx_state, &path)) { debug_printf("no return path"); return; } @@ -1230,14 +1332,16 @@ static void rx_send_rtt_ack(struct hercules_server *server, void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); struct hercules_control_packet control_pkt = { - .type = CONTROL_PACKET_TYPE_INITIAL, - .payload.initial = *pld, + .type = CONTROL_PACKET_TYPE_INITIAL, + .payload.initial = *pld, }; control_pkt.payload.initial.flags |= HANDSHAKE_FLAG_HS_CONFIRM; - stitch_src_port(&path, server->config.port_min + rx_slot + 1, buf); - fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&control_pkt, - sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), path.payloadlen); + stitch_src_port(&path, rx_state->src_port, buf); + fill_rbudp_pkt( + rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&control_pkt, + sizeof(control_pkt.type) + sizeof(control_pkt.payload.initial), + path.payloadlen); stitch_checksum(&path, path.header.checksum, buf); send_eth_frame(server, &path, buf); @@ -1248,8 +1352,8 @@ static void rx_send_rtt_ack(struct hercules_server *server, // the session's reply path if the corresponding flag was set static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, - struct rbudp_initial_pkt *initial, int rx_slot, - const char *buf, int ifid, const char *payload, + struct rbudp_initial_pkt *initial, + const char *buf, const char *payload, int framelen) { debug_printf("handling initial"); // Payload points to the rbudp payload (after the rbudp header) @@ -1259,16 +1363,17 @@ static void rx_handle_initial(struct hercules_server *server, debug_printf("initial chunklen: %d", initial->chunklen); // XXX Why use both initial->chunklen (transmitted) and the size of the received packet? // Are they ever not the same? - rx_update_reply_path(server, rx_state, ifid, initial->chunklen + headerlen, framelen, buf); + bool ok = rx_update_reply_path( + server, rx_state, initial->chunklen + headerlen, framelen, buf); + if (!ok) { + quit_session(rx_state->session, SESSION_ERROR_NO_PATHS); + } } - rx_send_rtt_ack(server, rx_state, rx_slot, - initial); // echo back initial pkt to ACK filesize + rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize } // Send an empty ACK, indicating to the sender that it may start sending data // packets. -// This is not strictly necessary. Once the ACK sender thread sees -// the session it will start sending ACKs, which will also be empty. static void rx_send_cts_ack(struct hercules_server *server, struct receiver_state *rx_state) { debug_printf("Send CTS ACK"); @@ -1294,7 +1399,80 @@ static void rx_send_cts_ack(struct hercules_server *server, atomic_fetch_add(&rx_state->session->tx_npkts, 1); } -// TODO some other functions repeat this code +static int find_free_rx_slot(struct hercules_server *server){ + for (int i = 0; i < HERCULES_CONCURRENT_SESSIONS; i++){ + if (server->sessions_rx[i] == NULL){ + return i; + } + } + return -1; +} + +static void rx_accept_new_session(struct hercules_server *server, + struct rbudp_initial_pkt *parsed_pkt, + struct hercules_app_addr *peer, + const char *buf, const char *payload, + ssize_t len, int rx_slot) { + if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + // The very first packet needs to set the return + // path or we won't be able to reply + debug_printf("Accepting new rx session"); + + struct hercules_session *session = make_session(len, 0, peer); + session->state = SESSION_STATE_NEW; + u16 src_port = server->config.port_min + HERCULES_CONCURRENT_SESSIONS + + rx_slot + 1; + + if (!(parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS)) { + // Entire index contained in this packet, + // we can go ahead and proceed with transfer + struct receiver_state *rx_state = make_rx_state( + session, (char *)parsed_pkt->index, parsed_pkt->index_len, + parsed_pkt->filesize, parsed_pkt->chunklen, src_port, false); + if (rx_state == NULL) { + debug_printf("Error creating RX state!"); + destroy_send_queue(session->send_queue); + free(session->send_queue); + free(session); + return; + } + session->rx_state = rx_state; + + rx_handle_initial(server, rx_state, parsed_pkt, buf, + payload, len); + rx_send_cts_ack(server, rx_state); + session->state = SESSION_STATE_RUNNING_DATA; + + } else { + // Index transferred separately + struct receiver_state *rx_state = make_rx_state_nomap( + session, parsed_pkt->index_len, parsed_pkt->filesize, + parsed_pkt->chunklen, src_port, false); + if (rx_state == NULL) { + debug_printf("Error creating RX state!"); + destroy_send_queue(session->send_queue); + free(session->send_queue); + free(session); + return; + } + rx_state->index_size = parsed_pkt->index_len; + rx_state->index = calloc(1, parsed_pkt->index_len); + if (rx_state->index == NULL) { + debug_printf("Error allocating index"); + destroy_send_queue(session->send_queue); + free(session); + return; + } + session->rx_state = rx_state; + + rx_handle_initial(server, rx_state, parsed_pkt, buf, + payload, len); + session->state = SESSION_STATE_RUNNING_IDX; + } + server->sessions_rx[rx_slot] = session; + } +} + // Send the given control packet via the server's control socket. static void send_control_pkt(struct hercules_server *server, struct hercules_session *session, @@ -1413,18 +1591,12 @@ static void rx_send_nacks(struct hercules_server *server, struct receiver_state } /// SENDER -static bool tx_acked_all(const struct sender_state *tx_state) { - if (tx_state->acked_chunks.num_set != tx_state->total_chunks) { - return false; - } - return true; +static inline bool tx_acked_all(const struct sender_state *tx_state) { + return tx_state->acked_chunks.num_set == tx_state->total_chunks; } -static bool tx_acked_all_index(const struct sender_state *tx_state) { - if (tx_state->acked_chunks_index.num_set != tx_state->index_chunks) { - return false; - } - return true; +static inline bool tx_acked_all_index(const struct sender_state *tx_state) { + return tx_state->acked_chunks_index.num_set == tx_state->index_chunks; } // Submitting the frames to the TX ring does not mean they will be sent immediately, @@ -1509,12 +1681,13 @@ static inline void pop_completion_rings(struct hercules_server *server) } } -static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_state *cc_state) +static bool tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_state *cc_state) { pthread_spin_lock(&cc_state->lock); atomic_store(&cc_state->mi_seq_max, umax32(atomic_load(&cc_state->mi_seq_max), nack->max_seq)); cc_state->num_nack_pkts++; u32 counted = 0; + bool range_ok = true; for(uint16_t e = 0; e < nack->num_acks; ++e) { u32 begin = nack->acks[e].begin; u32 end = nack->acks[e].end; @@ -1533,7 +1706,14 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ begin -= cc_state->mi_seq_start; end -= cc_state->mi_seq_start; if(end >= cc_state->mi_nacked.num) { - fprintf(stderr, "Cannot track NACK! Out of range: nack end = %d >= bitset size %d\n", end, cc_state->mi_nacked.num); + // If this is triggered frequently, we probably have a wrong RTT for + // this path and should resend an initial packet to get a new + // measurement + range_ok = false; + fprintf(stderr, + "Cannot track NACK! Out of range: nack end = %d >= bitset " + "size %d\n", + end, cc_state->mi_nacked.num); } end = umin32(end, cc_state->mi_nacked.num); for(u32 i = begin; i < end; ++i) { // XXX: this can *obviously* be optimized @@ -1541,21 +1721,24 @@ static void tx_register_nacks(const struct rbudp_ack_pkt *nack, struct ccontrol_ } } pthread_spin_unlock(&cc_state->lock); + return range_ok; } - -static void -tx_send_initial(struct hercules_server *server, const struct hercules_path *path, void *index, u64 index_size, int tx_slot, u16 dst_port, size_t filesize, u32 chunklen, unsigned long timestamp, u32 path_index, bool set_return_path, bool new_transfer) -{ +static void tx_send_initial(struct hercules_server *server, + struct hercules_session *session, + const struct hercules_path *path, u8 path_index, + unsigned long timestamp, bool set_return_path, + bool new_transfer) { debug_printf("Sending initial"); + struct sender_state *tx_state = session->tx_state; char buf[HERCULES_MAX_PKTSIZE]; void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); u8 flags = 0; - if (set_return_path){ + if (set_return_path) { flags |= HANDSHAKE_FLAG_SET_RETURN_PATH; } - if (new_transfer){ + if (new_transfer) { flags |= HANDSHAKE_FLAG_NEW_TRANSFER; } @@ -1563,12 +1746,12 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path .type = CONTROL_PACKET_TYPE_INITIAL, .payload.initial = { - .filesize = filesize, - .chunklen = chunklen, + .filesize = tx_state->filesize, + .chunklen = tx_state->chunklen, .timestamp = timestamp, .path_index = path_index, .flags = flags, - .index_len = index_size, + .index_len = tx_state->index_size, }, }; // Using sizeof(pld) would give fewer bytes than actually available due @@ -1579,30 +1762,31 @@ tx_send_initial(struct hercules_server *server, const struct hercules_path *path if (new_transfer) { u64 index_bytes_available = path->payloadlen - initial_pl_size; - debug_printf("bytes for index: %lld, size %lld", index_bytes_available, - index_size); - if (index_size > index_bytes_available) { + debug_printf("bytes for index: %lld, size %ld", index_bytes_available, + tx_state->index_size); + if (tx_state->index_size > index_bytes_available) { // Index won't fit, will be transferred separately debug_printf("index too long for HS packet!"); pld.payload.initial.flags |= HANDSHAKE_FLAG_INDEX_FOLLOWS; + tx_state->needs_index_transfer = true; } else { // Index is small enough to fit in the HS packet, include it debug_printf("Index contained in HS packet"); - memcpy(pld.payload.initial.index, index, index_size); - initial_pl_size += index_size; + memcpy(pld.payload.initial.index, tx_state->index, tx_state->index_size); + initial_pl_size += tx_state->index_size; } } - stitch_src_port(path, server->config.port_min + tx_slot + 1, buf); - stitch_dst_port(path, dst_port, buf); + stitch_src_port(path, tx_state->src_port, buf); + stitch_dst_port(path, session->peer.port, buf); fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&pld, initial_pl_size, path->payloadlen); stitch_checksum_with_dst(path, path->header.checksum, buf); send_eth_frame(server, path, buf); - atomic_fetch_add(&server->sessions_tx[tx_slot]->tx_npkts, 1); + atomic_fetch_add(&session->tx_npkts, 1); + session->last_pkt_sent = timestamp; } -// TODO do something instead of spinning until time is up static void rate_limit_tx(struct sender_state *tx_state) { if(tx_state->prev_tx_npkts_queued + RATE_LIMIT_CHECK > tx_state->tx_npkts_queued) @@ -1619,10 +1803,7 @@ static void rate_limit_tx(struct sender_state *tx_state) if(tx_pps > tx_state->rate_limit) { u64 min_dt = (d_npkts * 1.e9 / tx_state->rate_limit); - // Busy wait implementation - while(now < tx_state->prev_rate_check + min_dt) { - now = get_nsecs(); - } + tx_state->rate_limit_wait_until = tx_state->prev_rate_check + min_dt; } tx_state->prev_rate_check = now; @@ -1631,7 +1812,6 @@ static void rate_limit_tx(struct sender_state *tx_state) void send_path_handshakes(struct hercules_server *server, struct sender_state *tx_state, - int tx_slot, struct path_set *pathset) { u64 now = get_nsecs(); for (u32 p = 0; p < pathset->n_paths; p++) { @@ -1643,17 +1823,15 @@ void send_path_handshakes(struct hercules_server *server, &path->next_handshake_at, &handshake_at, now + PATH_HANDSHAKE_TIMEOUT_NS)) { debug_printf("sending hs on path %d", p); - // FIXME file name below? - tx_send_initial(server, path, NULL, 0, tx_slot, tx_state->session->dst_port, tx_state->filesize, - tx_state->chunklen, get_nsecs(), p, false, - false); + tx_send_initial(server, tx_state->session, path, p, now, + false, false); } } } } } -static void claim_tx_frames(struct hercules_server *server, struct hercules_session *session, struct hercules_interface *iface, u64 *addrs, size_t num_frames) +static void claim_tx_frames(struct hercules_server *server, struct hercules_interface *iface, u64 *addrs, size_t num_frames) { pthread_spin_lock(&iface->umem->frames_lock); size_t reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); @@ -1661,12 +1839,6 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_sess // When we're not getting any frames, we might need to... kick_all_tx(server, iface); reserved = frame_queue__cons_reserve(&iface->umem->available_frames, num_frames); - // XXX FIXME - if(!session || !session_state_is_running(atomic_load(&session->state))) { - debug_printf("STOP"); - pthread_spin_unlock(&iface->umem->frames_lock); - return; - } } for(size_t i = 0; i < num_frames; i++) { @@ -1694,39 +1866,49 @@ static short src_port_ctr = 0; static inline void tx_handle_send_queue_unit_for_iface( struct sender_state *tx_state, struct xsk_socket_info *xsk, int ifid, u64 frame_addrs[SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, - u32 thread_id, u16 dst_port, bool is_index_transfer) { + u32 thread_id, bool is_index_transfer) { u32 num_chunks_in_unit = 0; struct path_set *pathset = pathset_read(tx_state, thread_id); - for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { - if(unit->paths[i] == UINT8_MAX) { + for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { + if (unit->paths[i] == UINT8_MAX) { break; } - // TODO path idx may be larger if paths changed in meantime - struct hercules_path *path = &pathset->paths[unit->paths[i]]; - if(path->ifid == ifid) { - num_chunks_in_unit++; + // Path idx may be larger if paths changed in meantime + if (unit->paths[i] < pathset->n_paths) { + struct hercules_path *path = &pathset->paths[unit->paths[i]]; + if (path->ifid == ifid) { + num_chunks_in_unit++; + } } } u32 idx; - if(xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != num_chunks_in_unit) { - // As there are fewer frames in the loop than slots in the TX ring, this should not happen + if (xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != + num_chunks_in_unit) { + // As there are fewer frames in the loop than slots in the TX ring, this + // should not happen exit_with_error(NULL, EINVAL); } int current_frame = 0; - for(u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { - if(unit->paths[i] == UINT8_MAX) { + for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { + if (unit->paths[i] == UINT8_MAX) { break; } - // TODO check idx not too large (see above) + // This can happen if the pathset changed between now and when this unit + // was created + if (unit->paths[i] >= pathset->n_paths) { + // XXX We need to send something (to put the frame back on the tx + // ring), so pick path 0. This could cause us to briefly exceed the + // path's rate limit. + unit->paths[i] = 0; + } const struct hercules_path *path = &pathset->paths[unit->paths[i]]; - if(path->ifid != ifid) { + if (path->ifid != ifid) { continue; } u32 chunk_idx = unit->chunk_idx[i]; - if (!is_index_transfer && - chunk_idx >= tx_state->total_chunks){ + if (!is_index_transfer && chunk_idx >= tx_state->total_chunks) { // Since we use the same send queue for both index and data // transfer, we don't know which one the dequeued chunk idx refers // to. This is only a problem right after the swap from index to @@ -1742,10 +1924,12 @@ static inline void tx_handle_send_queue_unit_for_iface( size_t len = umin64(tx_state->chunklen, tx_state->filesize - chunk_start); if (is_index_transfer) { - len = umin64(tx_state->chunklen, tx_state->index_size - chunk_start); + len = + umin64(tx_state->chunklen, tx_state->index_size - chunk_start); } - void *pkt = prepare_frame(xsk, frame_addrs[current_frame], idx + current_frame, path->framelen); + void *pkt = prepare_frame(xsk, frame_addrs[current_frame], + idx + current_frame, path->framelen); frame_addrs[current_frame] = -1; current_frame++; void *rbudp_pkt = mempcpy(pkt, path->header.header, path->headerlen); @@ -1775,7 +1959,7 @@ static inline void tx_handle_send_queue_unit_for_iface( flags |= PKT_FLAG_IS_INDEX; payload = tx_state->index; } - stitch_dst_port(path, dst_port, pkt); + stitch_dst_port(path, tx_state->session->peer.port, pkt); stitch_src_port(path, tx_state->src_port, pkt); fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, payload + chunk_start, len, path->payloadlen); @@ -1788,16 +1972,17 @@ static inline void tx_handle_send_queue_unit( struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], - struct send_queue_unit *unit, u32 thread_id, u16 dst_port, bool is_index_transfer) { - for(int i = 0; i < server->num_ifaces; i++) { - tx_handle_send_queue_unit_for_iface(tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, thread_id, dst_port, is_index_transfer); + struct send_queue_unit *unit, u32 thread_id, bool is_index_transfer) { + for (int i = 0; i < server->num_ifaces; i++) { + tx_handle_send_queue_unit_for_iface( + tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, + thread_id, is_index_transfer); } } -static void -produce_batch(struct hercules_server *server, struct hercules_session *session, const u8 *path_by_rcvr, const u32 *chunks, - const u8 *rcvr_by_chunk, u32 num_chunks) -{ +static void produce_batch(struct hercules_server *server, + struct hercules_session *session, const u8 path, + const u32 *chunks, u32 num_chunks) { u32 chk; u32 num_chunks_in_unit; struct send_queue_unit *unit = NULL; @@ -1815,8 +2000,7 @@ produce_batch(struct hercules_server *server, struct hercules_session *session, } } - /* unit->rcvr[num_chunks_in_unit] = rcvr_by_chunk[chk]; */ - unit->paths[num_chunks_in_unit] = path_by_rcvr[rcvr_by_chunk[chk]]; + unit->paths[num_chunks_in_unit] = path; unit->chunk_idx[num_chunks_in_unit] = chunks[chk]; num_chunks_in_unit++; @@ -1831,7 +2015,6 @@ produce_batch(struct hercules_server *server, struct hercules_session *session, } static inline void allocate_tx_frames(struct hercules_server *server, - struct hercules_session *session, u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT]) { for(int i = 0; i < server->num_ifaces; i++) { @@ -1841,16 +2024,15 @@ static inline void allocate_tx_frames(struct hercules_server *server, break; } } - claim_tx_frames(server, session, &server->ifaces[i], frame_addrs[i], num_frames); + claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); } } // Compute rate limit for the path currently marked active -static u32 compute_max_chunks_current_path(struct sender_state *tx_state, struct path_set *pathset) { +static u32 compute_max_chunks_current_path(struct path_set *pathset) { u32 allowed_chunks = 0; u64 now = get_nsecs(); - // TODO make sure path_index is reset correctly on pathset change struct hercules_path *path = &pathset->paths[pathset->path_index]; if (!path->enabled) { return 0; // if a receiver does not have any enabled paths, we can @@ -1867,28 +2049,8 @@ static u32 compute_max_chunks_current_path(struct sender_state *tx_state, struct return allowed_chunks; } - -// Send a total max of BATCH_SIZE -static u32 shrink_sending_rates(struct sender_state *tx_state, - u32 *max_chunks_per_rcvr, u32 total_chunks) { - if (total_chunks > BATCH_SIZE) { - u32 new_total_chunks = - 0; // due to rounding errors, we need to aggregate again - max_chunks_per_rcvr[0] = - max_chunks_per_rcvr[0] * BATCH_SIZE / total_chunks; - new_total_chunks += max_chunks_per_rcvr[0]; - return new_total_chunks; - } - return total_chunks; -} - -// TODO remove -static inline void prepare_rcvr_paths(struct sender_state *tx_state, u8 *rcvr_path) { - rcvr_path[0] = tx_state->pathset->path_index; -} - // Mark the next available path as active -static void iterate_paths(struct sender_state *tx_state, struct path_set *pathset) { +static void iterate_paths(struct path_set *pathset) { if (pathset->n_paths == 0) { return; } @@ -1922,10 +2084,10 @@ static void kick_cc(struct sender_state *tx_state, struct path_set *pathset) { // Batch ends if an un-ACKed chunk is encountered for which we should keep // waiting a bit before retransmit. // -// If a chunk can not yet be send, because we need to wait for an ACK, wait_until +// If a chunk can not yet be sent, because we need to wait for an ACK, wait_until // is set to the timestamp by which that ACK should arrive. Otherwise, wait_until // is not modified. -static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 *chunks, u8 *chunk_rcvr, const u64 now, +static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 *chunks, const u64 now, u64 *wait_until, u32 num_chunks, bool is_index_transfer) { u32 num_chunks_prepared = 0; @@ -1966,9 +2128,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 const u64 ack_due = prev_transmit + tx_state->ack_wait_duration; // 0 for first round if(now >= ack_due) { // add the chunk to the current batch *chunks = chunk_idx++; - *chunk_rcvr = rcvr_idx; chunks++; - chunk_rcvr++; } else { // no chunk to send - skip this receiver in the current batch (*wait_until) = ack_due; break; @@ -1981,333 +2141,388 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 // Initialise new sender state. Returns null in case of error. static struct sender_state *init_tx_state(struct hercules_session *session, size_t filesize, int chunklen, - size_t index_chunks, char *index, + char *index, size_t index_size, int max_rate_limit, char *mem, struct hercules_path *paths, - u32 num_dests, const int num_paths, - u32 max_paths_per_dest, u32 num_threads, u16 src_port) { + const int num_paths, u32 num_threads, + u16 src_port) { u64 total_chunks = (filesize + chunklen - 1) / chunklen; if (total_chunks >= UINT_MAX) { fprintf(stderr, "File too big, not enough chunks available (chunks needed: " "%llu, chunks available: %u)\n", total_chunks, UINT_MAX - 1); - // TODO update monitor err + return NULL; + } + + u64 chunks_for_index = (index_size + chunklen - 1) / chunklen; + if (chunks_for_index >= UINT_MAX) { + fprintf(stderr, + "Index too big, not enough chunks available (chunks needed: " + "%llu, chunks available: %u)\n", + chunks_for_index, UINT_MAX - 1); return NULL; } struct sender_state *tx_state = calloc(1, sizeof(*tx_state)); - if (tx_state == NULL){ + if (tx_state == NULL) { return NULL; } tx_state->session = session; tx_state->filesize = filesize; tx_state->chunklen = chunklen; tx_state->total_chunks = total_chunks; - tx_state->index_chunks = index_chunks; + tx_state->index_chunks = chunks_for_index; + tx_state->index_size = index_size; tx_state->mem = mem; tx_state->index = index; tx_state->rate_limit = max_rate_limit; tx_state->start_time = 0; tx_state->end_time = 0; + tx_state->handshake_rtt = 0; tx_state->src_port = src_port; bitset__create(&tx_state->acked_chunks, tx_state->total_chunks); - bitset__create(&tx_state->acked_chunks_index, index_chunks); - tx_state->handshake_rtt = 0; + bitset__create(&tx_state->acked_chunks_index, chunks_for_index); + struct path_set *pathset = calloc(1, sizeof(*tx_state->pathset)); - if (pathset == NULL){ + if (pathset == NULL) { + bitset__destroy(&tx_state->acked_chunks); + bitset__destroy(&tx_state->acked_chunks_index); free(tx_state); return NULL; } pathset->n_paths = num_paths; - memcpy(pathset->paths, paths, sizeof(*paths)*num_paths); + pathset->path_index = 0; + memcpy(pathset->paths, paths, sizeof(*paths) * num_paths); tx_state->pathset = pathset; // tx_p uses index 0, tx_send_p threads start at index 1 int err = posix_memalign((void **)&tx_state->epochs, CACHELINE_SIZE, - sizeof(*tx_state->epochs)*(num_threads+1)); + sizeof(*tx_state->epochs) * (num_threads + 1)); if (err != 0) { + bitset__destroy(&tx_state->acked_chunks); + bitset__destroy(&tx_state->acked_chunks_index); free(pathset); free(tx_state); return NULL; } memset(tx_state->epochs, 0, sizeof(*tx_state->epochs) * (num_threads + 1)); tx_state->next_epoch = 1; + return tx_state; } +// Used when switching from index to data transfer phase. static void reset_tx_state(struct sender_state *tx_state) { tx_state->finished = false; tx_state->prev_chunk_idx = 0; } // (Re)send HS if needed -static void tx_retransmit_initial(struct hercules_server *server, int s, u64 now) { +static void tx_retransmit_initial(struct hercules_server *server, int s, + u64 now) { struct hercules_session *session_tx = server->sessions_tx[s]; if (session_tx && session_tx->state == SESSION_STATE_PENDING) { if (now > session_tx->last_pkt_sent + session_hs_retransmit_interval) { struct sender_state *tx_state = session_tx->tx_state; struct path_set *pathset = tx_state->pathset; // We always use the first path as the return path - tx_send_initial(server, &pathset->paths[0], tx_state->index, - tx_state->index_size, s, session_tx->dst_port, - tx_state->filesize, tx_state->chunklen, now, 0, + tx_send_initial(server, session_tx, &pathset->paths[0], 0, now, true, true); - session_tx->last_pkt_sent = now; } } } static void tx_handle_hs_confirm(struct hercules_server *server, struct rbudp_initial_pkt *parsed_pkt, - u16 dst_port, u16 src_port) { - struct hercules_session *session_tx = lookup_session_tx(server, dst_port); - if (session_tx != NULL && - session_tx->state == SESSION_STATE_PENDING) { - struct sender_state *tx_state = session_tx->tx_state; + u16 pkt_dst_port, u8 pkt_path_idx, + struct hercules_app_addr *pkt_src) { + struct hercules_session *session_tx = + lookup_session_tx(server, pkt_dst_port); + if (session_tx != NULL && session_tx->state == SESSION_STATE_PENDING) { // This is a reply to the very first packet and confirms connection // setup +#ifdef CHECK_SRC_ADDRESS + if (pkt_src->ia != session_tx->peer.ia || + pkt_src->ip != session_tx->peer.ip) { + // Intentionally not checking the port here, this is the packet that + // informs us about the peer's port + debug_printf( + "Incorrect IA or IP in packet: Want %llx %x, have %llx %x", + session_tx->peer.ia, session_tx->peer.ip, pkt_src->ia, + pkt_src->ip); + return; + } +#endif + struct sender_state *tx_state = session_tx->tx_state; if (!(parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { debug_printf("Handshake did not have correct flag set"); return; } - if (server->enable_pcc) { - struct path_set *pathset = tx_state->pathset; - u64 now = get_nsecs(); + struct path_set *pathset = tx_state->pathset; + u64 now = get_nsecs(); + if (server->config.enable_pcc) { tx_state->handshake_rtt = now - parsed_pkt->timestamp; - // TODO where to get rate limit? - // below is ~in Mb/s (but really pps) - /* u32 rate = 20000e3; // 200 Gbps */ - u32 rate = 100; // 1 Mbps - debug_printf("rate limit %u", rate); - for (u32 i = 0; i < pathset->n_paths; i++){ + for (u32 i = 0; i < pathset->n_paths; i++) { pathset->paths[i].cc_state = init_ccontrol_state( - rate, tx_state->total_chunks, - pathset->n_paths); + server->config.rate_limit, pathset->n_paths); } ccontrol_update_rtt(pathset->paths[0].cc_state, tx_state->handshake_rtt); + // Return path is always idx 0 - fprintf(stderr, - "[receiver %d] [path 0] handshake_rtt: " - "%fs, MI: %fs\n", - 0, tx_state->handshake_rtt / 1e9, - pathset->paths[0].cc_state->pcc_mi_duration); - - // TODO setting HS ok can be moved outside the if-pcc block - // make sure we later perform RTT estimation - // on every enabled path - pathset->paths[0].next_handshake_at = - UINT64_MAX; // We just completed the HS for this path - for (u32 p = 1; p < pathset->n_paths; p++) { - pathset->paths[p].next_handshake_at = now; - } + debug_printf( + "[receiver %d] [path 0] handshake_rtt: " + "%fs, MI: %fs\n", + 0, tx_state->handshake_rtt / 1e9, + pathset->paths[0].cc_state->pcc_mi_duration); + } + // make sure we later perform RTT estimation + // on every enabled path + pathset->paths[0].next_handshake_at = + UINT64_MAX; // We just completed the HS for this path + for (u32 p = 1; p < pathset->n_paths; p++) { + pathset->paths[p].next_handshake_at = now; } tx_state->start_time = get_nsecs(); - session_tx->dst_port = src_port; - if (parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS) { + session_tx->peer.port = pkt_src->port; + debug_printf("Updating peer port for this session to %u", + ntohs(pkt_src->port)); + if (tx_state->needs_index_transfer) { // Need to do index transfer first - // XXX relying on data echoed back by receiver instead of local - // state + if (!(parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS)) { + debug_printf("Missing flag in handshake"); + return; + } session_tx->state = SESSION_STATE_RUNNING_IDX; } else { // Index transfer not needed, straight to data transfer session_tx->state = SESSION_STATE_WAIT_CTS; } + count_received_pkt(session_tx, pkt_path_idx); return; } if (session_tx != NULL && session_state_is_running(session_tx->state)) { - struct sender_state *tx_state = session_tx->tx_state; - struct path_set *pathset = tx_state->pathset; // This is a reply to some handshake we sent during an already // established session (e.g. to open a new path) +#ifdef CHECK_SRC_ADDRESS + if (!src_matches_address(session_tx, pkt_src)) { + debug_printf( + "Dropping initail packet with wrong source IA/IP/Port"); + return; + } +#endif + struct sender_state *tx_state = session_tx->tx_state; + struct path_set *pathset = tx_state->pathset; u64 now = get_nsecs(); - if (server->enable_pcc) { + if (server->config.enable_pcc) { ccontrol_update_rtt(pathset->paths[parsed_pkt->path_index].cc_state, now - parsed_pkt->timestamp); } pathset->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; // We have a new return path, redo handshakes on all other paths - if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH){ + if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { tx_state->handshake_rtt = now - parsed_pkt->timestamp; - for (u32 p = 0; p < pathset->n_paths; p++){ - if (p != parsed_pkt->path_index && pathset->paths[p].enabled){ + for (u32 p = 0; p < pathset->n_paths; p++) { + if (p != parsed_pkt->path_index && pathset->paths[p].enabled) { pathset->paths[p].next_handshake_at = now; pathset->paths[p].cc_state->pcc_mi_duration = DBL_MAX; pathset->paths[p].cc_state->rtt = DBL_MAX; } } } + count_received_pkt(session_tx, pkt_path_idx); return; } // In other cases we just drop the packet debug_printf("Dropping HS confirm packet, was not expecting one"); } -static void replace_dir(char *entry, char *src, char *dst) { - int oldlen = strlen(src); - int entry_size = strlen(entry) - oldlen + 1; - -} - -// Map the provided file into memory for reading. Returns pointer to the mapped -// area, or null on error. -static char *tx_mmap(char *fname, char *dstname, size_t *filesize, void **index_o, u64 *index_size_o) { +// Prepare the directory listing starting at fname +static char *prepare_dir_index(char *fname, u64 *index_size_o, + u64 *total_filesize_o, u64 *real_filesize_o) { FTS *fts = NULL; FTSENT *ent = NULL; - debug_printf("opening"); - char* fts_arg[2] = {fname, NULL}; - fts = fts_open(fts_arg, FTS_PHYSICAL, NULL); // Don't follow symlinks + char *fts_arg[2] = {fname, NULL}; + fts = fts_open(fts_arg, FTS_PHYSICAL, NULL); // Don't follow symlinks if (fts == NULL) { + fprintf(stderr, "Error opening %s\n", fname); return NULL; } - debug_printf("fts open"); - int index_cap = 4096; - void *index = malloc(index_cap); - if (index == NULL){ + + const int stepsize = 4096; + u64 index_cap = stepsize; + char *index = malloc(index_cap); + if (index == NULL) { fts_close(fts); return NULL; } - int index_size = 0; - - int total_filesize = 0; - int real_filesize = 0; + u64 index_size = 0; + u64 total_filesize = 0; // Since mappings must start at page boundaries the + // total size will likely be larger than the sum of + // the sizes of the individual files + u64 real_filesize = 0; while ((ent = fts_read(fts)) != NULL) { + int entry_size = sizeof(struct dir_index_entry) + ent->fts_pathlen + 1; + if (index_size + entry_size >= index_cap) { + char *old_index = index; + index = realloc(index, index_cap + stepsize); + if (index == NULL) { + fts_close(fts); + free(old_index); + return NULL; + } + index_cap += stepsize; + } + struct dir_index_entry *newentry = + (struct dir_index_entry *)(index + index_size); switch (ent->fts_info) { - case FTS_F:; // Regular file - int entry_size = - sizeof(struct dir_index_entry) + ent->fts_pathlen + 1; - debug_printf("entry size %d", entry_size); - if (index_size + entry_size >= index_cap) { - debug_printf("need realloc"); - index = realloc(index, index_cap + 4096); - if (index == NULL) { - fts_close(fts); - return NULL; - } - index_cap += 4096; - } - debug_printf("adding to index: %s (%ldB)", ent->fts_path, ent->fts_statp->st_size); - struct dir_index_entry *newentry = - (struct dir_index_entry *)(index + index_size); - + case FTS_F: // Regular file + debug_printf("Adding file to index: %s (%ldB)", ent->fts_path, + ent->fts_statp->st_size); newentry->filesize = ent->fts_statp->st_size; newentry->type = INDEX_TYPE_FILE; newentry->path_len = ent->fts_pathlen + 1; - debug_printf("pathlen %d", ent->fts_pathlen); - strncpy(newentry->path, ent->fts_path, newentry->path_len); - debug_printf("Readback: %s (%d) %dB", newentry->path, - newentry->type, newentry->filesize); + strncpy((char *)newentry->path, ent->fts_path, newentry->path_len); + index_size += entry_size; - u32 filesize_up = - ((4096 - 1) & newentry->filesize) - ? ((newentry->filesize + 4096) & ~(4096 - 1)) - : newentry->filesize; - debug_printf("size was %x, up %x", newentry->filesize, filesize_up); - total_filesize += filesize_up; - real_filesize += newentry->filesize; + total_filesize += ROUND_UP_PAGESIZE(newentry->filesize); + real_filesize += newentry->filesize; break; - case FTS_D:; // Directory - entry_size = - sizeof(struct dir_index_entry) + ent->fts_pathlen + 1; - if (index_size + entry_size >= index_cap) { - debug_printf("need realloc"); - index = realloc(index, index_cap + 4096); - if (index == NULL) { - fts_close(fts); - return NULL; - } - index_cap += 4096; - } - debug_printf("adding to index: %s (%ldB)", ent->fts_path, ent->fts_statp->st_size); - newentry = - (struct dir_index_entry *)(index + index_size); + + case FTS_D: // Directory + debug_printf("Adding directory to index: %s", ent->fts_path); newentry->filesize = 0; newentry->type = INDEX_TYPE_DIR; newentry->path_len = ent->fts_pathlen + 1; - strncpy(newentry->path, ent->fts_path, newentry->path_len); + strncpy((char *)newentry->path, ent->fts_path, newentry->path_len); index_size += entry_size; break; default: + fprintf(stderr, + "!> Skipping %s, not a regular file or directory\n", + ent->fts_path); break; } } - fts_close(fts); - debug_printf("total filesize %d", total_filesize); - debug_printf("real filesize %d", real_filesize); - debug_printf("total entry size %d", index_size); - char *mem = mmap(NULL, total_filesize, PROT_READ, MAP_PRIVATE | MAP_ANON, 0, 0); + *index_size_o = index_size; + *total_filesize_o = total_filesize; + *real_filesize_o = real_filesize; + return index; +} + +// Map the provided file into memory for reading. Returns pointer to the mapped +// area, or null on error. +static char *tx_mmap(char *fname, char *dstname, size_t *filesize, + void **index_o, u64 *index_size_o) { + u64 index_size; + u64 total_filesize; + u64 real_filesize; + char *index = + prepare_dir_index(fname, &index_size, &total_filesize, &real_filesize); + if (index == NULL) { + return NULL; + } + debug_printf("total filesize %lld", total_filesize); + debug_printf("real filesize %lld", real_filesize); + debug_printf("total entry size %lld", index_size); + + char *mem = + mmap(NULL, total_filesize, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0); if (mem == MAP_FAILED) { + free(index); return NULL; } - char *next_mapping = mem; - void *dst_index = malloc(index_cap); - if (index == NULL){ - // TODO + + // Now we go over the directory tree we just generated and + // - Map the files + // - Generate a directory index for the receiver (with the source path + // replaced by the destination path) + const int stepsize = 4096; + u64 dst_index_cap = stepsize; + u64 dst_index_size = 0; + char *dst_index = malloc(dst_index_cap); + if (dst_index == NULL) { + int ret = munmap(mem, total_filesize); + if (ret) { + fprintf(stderr, "munmap failure!\n"); + exit_with_error(NULL, errno); + } + free(index); return NULL; } - int dst_index_cap = 4096; - int dst_index_size = 0; - struct dir_index_entry *p = index; - while (1) { - debug_printf("Read: %s (%d) %dB", p->path, p->type, p->filesize); - int src_path_len = strlen(p->path); + char *next_mapping = mem; + bool encountered_err = false; + for (char *p = index; p < index + index_size;) { + struct dir_index_entry *entry = (struct dir_index_entry *)p; + debug_printf("Read: %s (%d) %dB", entry->path, entry->type, + entry->filesize); + + int src_path_len = strlen((char *)entry->path); int src_root_len = strlen(fname); int dst_root_len = strlen(dstname); int dst_path_len = src_path_len - src_root_len + dst_root_len; - debug_printf("src path %d, root %d. dst path %d, root %d", src_path_len, src_root_len, dst_path_len, dst_root_len); + int entry_size = sizeof(struct dir_index_entry) + dst_path_len + 1; if (dst_index_size + entry_size >= dst_index_cap) { - debug_printf("need realloc"); - dst_index = realloc(dst_index, dst_index_cap + 4096); + char *old_index = dst_index; + dst_index = realloc(dst_index, dst_index_cap + stepsize); if (dst_index == NULL) { - fts_close(fts); - return NULL; + dst_index = old_index; + encountered_err = true; + break; } - dst_index_cap += 4096; + dst_index_cap += stepsize; } - struct dir_index_entry *newentry = (struct dir_index_entry *)(dst_index + dst_index_size); - debug_printf("entry size %d, index size %d", entry_size, dst_index_size); - newentry->filesize = p->filesize; - newentry->type = p->type; + + struct dir_index_entry *newentry = + (struct dir_index_entry *)(dst_index + dst_index_size); + newentry->filesize = entry->filesize; + newentry->type = entry->type; newentry->path_len = dst_path_len + 1; - strncpy(newentry->path, dstname, dst_root_len); - strncpy(&newentry->path[dst_root_len], &p->path[src_root_len], + strncpy((char *)newentry->path, dstname, dst_root_len); + strncpy((char *)&newentry->path[dst_root_len], (char *)&entry->path[src_root_len], dst_path_len - dst_root_len + 1); debug_printf("Set dst path %s", newentry->path); dst_index_size += entry_size; - if (p->type == INDEX_TYPE_FILE) { - int f = open(p->path, O_RDONLY); + if (entry->type == INDEX_TYPE_FILE) { + int f = open((char *)entry->path, O_RDONLY); if (f == -1) { - return NULL; + encountered_err = true; + break; } - char *filemap = mmap(next_mapping, p->filesize, PROT_READ, + char *filemap = mmap(next_mapping, entry->filesize, PROT_READ, MAP_PRIVATE | MAP_FIXED, f, 0); if (filemap == MAP_FAILED) { debug_printf("filemap err! %d", errno); - return NULL; + close(f); + encountered_err = true; + break; } - u32 filesize_up = - ((4096 - 1) & p->filesize) - ? ((p->filesize + 4096) & ~(4096 - 1)) - : p->filesize; - next_mapping += filesize_up; + next_mapping += ROUND_UP_PAGESIZE(entry->filesize); close(f); } - p = ((char *)p) + sizeof(*p) + p->path_len; - if (p >= index + index_size) { - break; - } + p = p + sizeof(*entry) + entry->path_len; } free(index); + if (encountered_err) { + int ret = munmap(mem, total_filesize); + if (ret) { + fprintf(stderr, "munmap error: %s\n", strerror(errno)); + exit_with_error(NULL, errno); + } + free(dst_index); + return NULL; + } + *filesize = total_filesize; *index_o = dst_index; *index_size_o = dst_index_size; @@ -2509,10 +2724,11 @@ static void tx_send_p(void *arg) { frame_addrs[0][i] = 0; } } - allocate_tx_frames(server, session_tx, frame_addrs); + allocate_tx_frames(server, frame_addrs); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, - frame_addrs, &unit, args->id, session_tx->dst_port, is_index_transfer); - atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); // FIXME should this be here? + frame_addrs, &unit, args->id, + is_index_transfer); + atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); } } @@ -2525,18 +2741,14 @@ static void rx_trickle_acks(void *arg) { struct hercules_session *session_rx = server->sessions_rx[cur_session]; if (session_rx != NULL && session_state_is_running(session_rx->state)) { struct receiver_state *rx_state = session_rx->rx_state; - bool is_index_transfer = (session_rx->state == SESSION_STATE_RUNNING_IDX); - // XXX: data races in access to shared rx_state! - atomic_store(&rx_state->last_pkt_rcvd, get_nsecs()); // FIXME what? - if (atomic_load(&rx_state->last_pkt_rcvd) + - umax64(100 * ACK_RATE_TIME_MS * 1e6, - 3 * rx_state->handshake_rtt) < - get_nsecs()) { - // Transmission timed out - quit_session(session_rx, SESSION_ERROR_TIMEOUT); + u64 now = get_nsecs(); + if (now < rx_state->next_ack_round_start){ + continue; } + bool is_index_transfer = (session_rx->state == SESSION_STATE_RUNNING_IDX); rx_send_acks(server, rx_state, is_index_transfer); if (rx_received_all(rx_state, is_index_transfer)) { + // If we're done, send a final ack covering the entire range if (is_index_transfer) { debug_printf("Received entire index"); rx_send_acks(server, rx_state, is_index_transfer); @@ -2547,8 +2759,8 @@ static void rx_trickle_acks(void *arg) { quit_session(session_rx, SESSION_ERROR_OK); } } + rx_state->next_ack_round_start = now + ACK_RATE_TIME_MS * 1e6; } - sleep_nsecs(ACK_RATE_TIME_MS * 1e6); } } @@ -2624,100 +2836,94 @@ static void rx_p(void *arg) { * occur uniformly. */ static void *tx_p(void *arg) { - struct hercules_server *server = arg; - int cur_session = 0; - while (!wants_shutdown) { - /* pthread_spin_lock(&server->biglock); */ - cur_session = (cur_session +1) % HERCULES_CONCURRENT_SESSIONS; - pop_completion_rings(server); - u32 chunks[BATCH_SIZE]; - u8 chunk_rcvr[BATCH_SIZE]; - struct hercules_session *session_tx = server->sessions_tx[cur_session]; - if (session_tx != NULL && - session_state_is_running(atomic_load(&session_tx->state))) { - struct sender_state *tx_state = session_tx->tx_state; - bool is_index_transfer = (session_tx->state == SESSION_STATE_RUNNING_IDX); - struct path_set *pathset = pathset_read(tx_state, 0); - /* debug_printf("Start transmit round"); */ - tx_state->prev_rate_check = get_nsecs(); - - pop_completion_rings(server); - send_path_handshakes(server, tx_state, cur_session, pathset); - u64 next_ack_due = 0; - - // in each iteration, we send packets on a single path to each receiver - // collect the rate limits for each active path - u32 allowed_chunks = compute_max_chunks_current_path(tx_state, pathset); - - if (allowed_chunks == - 0) { // we hit the rate limits on every path; switch paths - iterate_paths(tx_state, pathset); - continue; - } + struct hercules_server *server = arg; + int cur_session = 0; + while (!wants_shutdown) { + cur_session = (cur_session + 1) % HERCULES_CONCURRENT_SESSIONS; + pop_completion_rings(server); + u32 chunks[BATCH_SIZE]; - // TODO re-enable? - // sending rates might add up to more than BATCH_SIZE, shrink - // proportionally, if needed - /* shrink_sending_rates(tx_state, max_chunks_per_rcvr, total_chunks); */ - - const u64 now = get_nsecs(); - u32 num_chunks = 0; - if (!tx_state->finished) { - u64 ack_due = 0; - // for each receiver, we prepare up to max_chunks_per_rcvr[r] chunks - // to send - u32 cur_num_chunks = prepare_rcvr_chunks( - tx_state, 0, &chunks[num_chunks], &chunk_rcvr[num_chunks], now, - &ack_due, allowed_chunks, is_index_transfer); - num_chunks += cur_num_chunks; - if (tx_state->finished && !is_index_transfer) { - terminate_cc(pathset); - kick_cc(tx_state, pathset); - } else { - // only wait for the nearest ack - if (next_ack_due) { - if (next_ack_due > ack_due) { + struct hercules_session *session_tx = server->sessions_tx[cur_session]; + if (session_tx != NULL && + session_state_is_running(atomic_load(&session_tx->state))) { + struct sender_state *tx_state = session_tx->tx_state; + bool is_index_transfer = + (session_tx->state == SESSION_STATE_RUNNING_IDX); + struct path_set *pathset = pathset_read(tx_state, 0); + tx_state->prev_rate_check = get_nsecs(); + + pop_completion_rings(server); + send_path_handshakes(server, tx_state, pathset); + u64 next_ack_due = 0; + const u64 now = get_nsecs(); + if (now < tx_state->rate_limit_wait_until){ + // Hit the global per-transfer rate limit + continue; + } + if (now < tx_state->next_ack_due){ + // No new chunks due for sending + continue; + } + + // in each iteration, we send packets on a single path to each + // receiver collect the rate limits for each active path This + // computes the PCC per-path rate limit + u32 allowed_chunks = compute_max_chunks_current_path(pathset); + + if (allowed_chunks == + 0) { // we hit the rate limit on this path; switch paths + iterate_paths(pathset); + continue; + } + + u32 num_chunks = 0; + if (!tx_state->finished) { + u64 ack_due = 0; + // prepare up to allowed_chunks chunks to send + u32 cur_num_chunks = prepare_rcvr_chunks( + tx_state, 0, &chunks[num_chunks], now, &ack_due, + allowed_chunks, is_index_transfer); + num_chunks += cur_num_chunks; + if (tx_state->finished && !is_index_transfer) { + terminate_cc(pathset); + kick_cc(tx_state, pathset); + } else { + // only wait for the nearest ack + if (next_ack_due) { + if (next_ack_due > ack_due) { + next_ack_due = ack_due; + } + } else { next_ack_due = ack_due; } - } else { - next_ack_due = ack_due; } } - } - if (num_chunks > 0) { - u8 rcvr_path[1]; - prepare_rcvr_paths(tx_state, rcvr_path); - produce_batch(server, session_tx, rcvr_path, chunks, chunk_rcvr, - num_chunks); - tx_state->tx_npkts_queued += num_chunks; - rate_limit_tx(tx_state); - - // update book-keeping - u32 path_idx = pathset->path_index; - struct ccontrol_state *cc_state = pathset->paths[path_idx].cc_state; - if (cc_state != NULL) { - // FIXME allowed_chunks below is not correct (3x) - atomic_fetch_add(&cc_state->mi_tx_npkts, allowed_chunks); - atomic_fetch_add(&cc_state->total_tx_npkts, allowed_chunks); - if (pcc_has_active_mi(cc_state, now)) { - atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, - allowed_chunks); + if (num_chunks > 0) { + u32 path_idx = pathset->path_index; + produce_batch(server, session_tx, path_idx, chunks, num_chunks); + tx_state->tx_npkts_queued += num_chunks; + rate_limit_tx(tx_state); + + // update book-keeping + struct ccontrol_state *cc_state = + pathset->paths[path_idx].cc_state; + if (cc_state != NULL) { + atomic_fetch_add(&cc_state->mi_tx_npkts, num_chunks); + atomic_fetch_add(&cc_state->total_tx_npkts, num_chunks); + if (pcc_has_active_mi(cc_state, now)) { + atomic_fetch_add(&cc_state->mi_tx_npkts_monitored, + num_chunks); + } } } - } - - iterate_paths(tx_state, pathset); - if (now < next_ack_due) { - // XXX if the session vanishes in the meantime, we might wait - // unnecessarily - sleep_until(next_ack_due); + iterate_paths(pathset); + tx_state->next_ack_due = next_ack_due; } } - } - return NULL; + return NULL; } /// Event handler tasks @@ -2731,43 +2937,44 @@ static int find_free_tx_slot(struct hercules_server *server){ return -1; } -static int find_free_rx_slot(struct hercules_server *server){ - for (int i = 0; i < HERCULES_CONCURRENT_SESSIONS; i++){ - if (server->sessions_rx[i] == NULL){ - return i; - } - } - return -1; -} // Check if the monitor has new transfer jobs available and, if so, start one static void new_tx_if_available(struct hercules_server *server) { int session_slot = find_free_tx_slot(server); - if (session_slot == -1){ + if (session_slot == -1) { // no free tx slot return; } // We're the only thread adding/removing sessions, so if we found a free // slot it will still be free when we assign to it later on - char fname[1000]; - memset(fname, 0, 1000); - char destname[1000]; - memset(destname, 0, 1000); - int count; + char *fname; + char *destname; u16 jobid; u16 payloadlen; - u16 dst_port; - - int ret = monitor_get_new_job(server->usock, fname, destname, &jobid, &dst_port, &payloadlen); + struct hercules_app_addr dest; + int ret = monitor_get_new_job(server->usock, &fname, &destname, &jobid, + &dest, &payloadlen); if (!ret) { return; } debug_printf("new job: %s -> %s", fname, destname); debug_printf("using tx slot %d", session_slot); - - if (sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > (size_t)payloadlen) { + debug_printf("destination address %x-%x:%x:%x %u.%u.%u.%u %u", + ntohs(*((u16 *)&dest.ia + 3)), ntohs(*((u16 *)&dest.ia + 2)), + ntohs(*((u16 *)&dest.ia + 1)), ntohs(*((u16 *)&dest.ia + 0)), + *((u8 *)&dest.ip + 3), *((u8 *)&dest.ip + 2), + *((u8 *)&dest.ip + 1), *((u8 *)&dest.ip + 0), ntohs(dest.port)); + + // It's ok to ignore the return value of monitor_update_job here: Since + // we're informing the monitor about an error we don't care if the + // monitor wants us to stop the transfer. + if (sizeof(struct rbudp_initial_pkt) + rbudp_headerlen > + (size_t)payloadlen) { debug_printf("supplied payloadlen too small"); - monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_BAD_MTU, 0, 0); + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, + SESSION_ERROR_BAD_MTU, 0, 0); + free(fname); + free(destname); return; } @@ -2775,54 +2982,66 @@ static void new_tx_if_available(struct hercules_server *server) { void *index; u64 index_size; char *mem = tx_mmap(fname, destname, &filesize, &index, &index_size); - if (mem == NULL){ + FREE_NULL(fname); + FREE_NULL(destname); + if (mem == NULL) { debug_printf("mmap failed"); - monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_MAP_FAILED, 0, 0); + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, + SESSION_ERROR_MAP_FAILED, 0, 0); return; } - debug_printf("Index totals %llu bytes, data size %llu bytes", index_size, filesize); - debug_printf("Transfer index in %f packets", index_size/(double)payloadlen); + debug_printf("Index totals %llu bytes, data size %lu bytes", index_size, + filesize); u64 chunklen = payloadlen - rbudp_headerlen; - u64 chunks_for_index = (index_size + chunklen - 1) / chunklen; - if (chunks_for_index >= UINT_MAX) { - fprintf(stderr, - "Index too big, not enough chunks available (chunks needed: " - "%llu, chunks available: %u)\n", - chunks_for_index, UINT_MAX - 1); - monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_TOO_LARGE, 0, 0); - return; - } - struct hercules_session *session = make_session(server); - if (session == NULL){ - monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_INIT, 0, 0); - munmap(mem, filesize); // FIXME when to unmap? + + struct hercules_session *session = make_session(payloadlen, jobid, &dest); + if (session == NULL) { + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, + SESSION_ERROR_INIT, 0, 0); + int ret = munmap(mem, filesize); + if (ret) { + fprintf(stderr, "munmap error: %s\n", strerror(errno)); + exit_with_error(NULL, errno); + } + debug_printf("Error creating session!"); return; } session->state = SESSION_STATE_PENDING; - session->payloadlen = payloadlen; - session->jobid = jobid; - session->dst_port = dst_port; int n_paths; struct hercules_path *paths; ret = monitor_get_paths(server->usock, jobid, payloadlen, &n_paths, &paths); - if (!ret || n_paths == 0){ + if (!ret || n_paths == 0) { debug_printf("error getting paths"); - munmap(mem, filesize); - /* destroy_session(session); */ // FIXME - monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, SESSION_ERROR_NO_PATHS, 0, 0); + int ret = munmap(mem, filesize); + if (ret) { + fprintf(stderr, "munmap error: %s\n", strerror(errno)); + exit_with_error(NULL, errno); + } + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, + SESSION_ERROR_NO_PATHS, 0, 0); return; } debug_printf("received %d paths", n_paths); u16 src_port = server->config.port_min + session_slot + 1; - struct sender_state *tx_state = init_tx_state( - session, filesize, chunklen, chunks_for_index, index, server->rate_limit, mem, paths, 1, n_paths, - server->max_paths, server->n_threads, src_port); + struct sender_state *tx_state = + init_tx_state(session, filesize, chunklen, index, index_size, + server->config.rate_limit, mem, paths, n_paths, + server->config.n_threads, src_port); free(paths); - strncpy(tx_state->filename, fname, 99); - tx_state->index = index; - tx_state->index_size = index_size; + if (tx_state == NULL) { + debug_printf("Error setting up tx_state"); + int ret = munmap(mem, filesize); + if (ret) { + fprintf(stderr, "munmap error: %s\n", strerror(errno)); + exit_with_error(NULL, errno); + } + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, + SESSION_ERROR_INIT, 0, 0); + return; + } + session->tx_state = tx_state; atomic_store(&server->sessions_tx[session_slot], session); } @@ -2833,14 +3052,14 @@ static void cleanup_finished_sessions(struct hercules_server *server, int s, u64 // session (and thus before accepting new sessions). This ensures the // other party has also quit or timed out its session and won't send // packets that would then be mixed into future sessions. - // XXX This depends on both endpoints sharing the same timeout value, - // which is not negotiated but defined at the top of this file. struct hercules_session *session_tx = atomic_load(&server->sessions_tx[s]); if (session_tx && session_tx->state == SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout * 2) { u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; u64 bytes_acked = session_tx->tx_state->chunklen * session_tx->tx_state->acked_chunks.num_set; + // OK to ignore return value: We're done with the transfer and don't + // care if the monitor wants us to stop it monitor_update_job(server->usock, session_tx->jobid, session_tx->state, session_tx->error, sec_elapsed, bytes_acked); @@ -2853,7 +3072,7 @@ static void cleanup_finished_sessions(struct hercules_server *server, int s, u64 // freeing it until after the next session has completed. At // that point, no references to the deferred session should be // around, so we then free it. - destroy_session_tx(server->deferreds_tx[s]); + destroy_session_tx(server, server->deferreds_tx[s]); server->deferreds_tx[s] = current; } } @@ -2864,14 +3083,15 @@ static void cleanup_finished_sessions(struct hercules_server *server, int s, u64 atomic_store(&server->sessions_rx[s], NULL); fprintf(stderr, "Cleaning up RX session %d...\n", s); // See the note above on deferred freeing - destroy_session_rx(server->deferreds_rx[s]); + destroy_session_rx(server, server->deferreds_rx[s]); server->deferreds_rx[s] = current; } } } // Time out if no packets received for a while -static void mark_timed_out_sessions(struct hercules_server *server, int s, u64 now) { +static void mark_timed_out_sessions(struct hercules_server *server, int s, + u64 now) { struct hercules_session *session_tx = server->sessions_tx[s]; if (session_tx && session_tx->state != SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout) { @@ -2892,12 +3112,18 @@ static void mark_timed_out_sessions(struct hercules_server *server, int s, u64 n } } -static void tx_update_paths(struct hercules_server *server, int s) { +// Ask the monitor for new paths for the session and swap them in. +// The paths may or may not be identical to the old ones; for those that have +// changed the congestion control state is also reset, for those that remain +// unchanged it carries over. +static void tx_update_paths(struct hercules_server *server, int s, u64 now) { struct hercules_session *session_tx = server->sessions_tx[s]; - if (session_tx && session_state_is_running(session_tx->state)) { + if (session_tx && session_state_is_running(session_tx->state) && + now > session_tx->last_path_update + path_update_interval) { debug_printf("Updating paths for TX %d", s); struct sender_state *tx_state = session_tx->tx_state; struct path_set *old_pathset = tx_state->pathset; + int n_paths; struct hercules_path *paths; bool ret = monitor_get_paths(server->usock, session_tx->jobid, @@ -2908,19 +3134,21 @@ static void tx_update_paths(struct hercules_server *server, int s) { } debug_printf("received %d paths", n_paths); if (n_paths == 0) { - free(paths); quit_session(session_tx, SESSION_ERROR_NO_PATHS); return; } + struct path_set *new_pathset = calloc(1, sizeof(*new_pathset)); if (new_pathset == NULL) { - // FIXME leak? + free(paths); return; } + u32 new_epoch = tx_state->next_epoch; new_pathset->epoch = new_epoch; tx_state->next_epoch++; new_pathset->n_paths = n_paths; + new_pathset->path_index = 0; memcpy(new_pathset->paths, paths, sizeof(*paths) * n_paths); u32 path_lim = (old_pathset->n_paths > (u32)n_paths) ? (u32)n_paths @@ -2929,9 +3157,10 @@ static void tx_update_paths(struct hercules_server *server, int s) { struct ccontrol_state **replaced_cc = calloc(old_pathset->n_paths, sizeof(*replaced_cc)); if (replaced_cc == NULL) { - // FIXME leak? + free(paths); return; } + for (u32 i = 0; i < old_pathset->n_paths; i++) { replaced_cc[i] = old_pathset->paths[i].cc_state; } @@ -2958,15 +3187,10 @@ static void tx_update_paths(struct hercules_server *server, int s) { // Return path is always idx 0 replaced_return_path = true; } - // TODO whether to use pcc should be decided on a per-path - // basis by the monitor - if (server->enable_pcc) { - // TODO assert chunk length fits onto path + if (server->config.enable_pcc) { // The new path is different, restart CC - // TODO where to get rate - u32 rate = 100; new_pathset->paths[i].cc_state = init_ccontrol_state( - rate, tx_state->total_chunks, new_pathset->n_paths); + server->config.rate_limit, new_pathset->n_paths); // Re-send a handshake to update path rtt new_pathset->paths[i].next_handshake_at = 0; } @@ -2982,7 +3206,8 @@ static void tx_update_paths(struct hercules_server *server, int s) { // Finally, swap in the new pathset tx_state->pathset = new_pathset; free(paths); // These were *copied* into the new pathset - for (int i = 0; i < server->n_threads + 1; i++) { + for (int i = 0; i < server->config.n_threads + 1; i++) { + // We have n_threads worker threads (tx_send_p) + 1 tx_p thread do { // Wait until the thread has seen the new pathset } while (tx_state->epochs[i].epoch != new_epoch); @@ -2990,38 +3215,32 @@ static void tx_update_paths(struct hercules_server *server, int s) { for (u32 i = 0; i < old_pathset->n_paths; i++) { // If CC was replaced, this contains the pointer to the old CC // state. Otherwise it contains NULL, and we don't need to free - // anything. + // anything (but it's ok to pass NULL to free). free(replaced_cc[i]); } free(replaced_cc); free(old_pathset); + session_tx->last_path_update = now; debug_printf("done with update"); } } -static inline void count_received_pkt(struct hercules_session *session, - u32 path_idx) { - atomic_fetch_add(&session->rx_npkts, 1); - if (path_idx < PCC_NO_PATH && session->rx_state != NULL) { - atomic_fetch_add(&session->rx_state->path_state[path_idx].rx_npkts, 1); - } -} - -struct prints{ +struct print_info { u32 rx_received; u32 tx_sent; u64 ts; }; static void print_session_stats(struct hercules_server *server, u64 now, - struct prints *tx, struct prints *rx) { - fprintf(stderr, "\n"); + struct print_info *tx, struct print_info *rx) { double send_rate_total = 0; double recv_rate_total = 0; + bool active_session = false; for (int s = 0; s < HERCULES_CONCURRENT_SESSIONS; s++) { struct hercules_session *session_tx = server->sessions_tx[s]; if (session_tx && session_tx->state != SESSION_STATE_DONE) { - struct prints *p = &tx[s]; + active_session = true; + struct print_info *p = &tx[s]; u32 sent_now = session_tx->tx_npkts; u32 acked_count = session_tx->tx_state->acked_chunks.num_set; u32 total = session_tx->tx_state->acked_chunks.num; @@ -3034,17 +3253,19 @@ static void print_session_stats(struct hercules_server *server, u64 now, 8 * send_rate_pps * session_tx->tx_state->chunklen / 1e6; double progress_percent = acked_count / (double)total * 100; send_rate_total += send_rate; - fprintf(stderr, - "(TX %2d) [%4.1f] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, rate " - "%8.2f " - "Mbps\n", - s, progress_percent, acked_count, total, - session_tx->rx_npkts, session_tx->tx_npkts, send_rate); + fprintf( + stderr, + "(TX %2d) [%4.1f%%] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, rate " + "%8.2f " + "Mbps\n", + s, progress_percent, acked_count, total, session_tx->rx_npkts, + session_tx->tx_npkts, send_rate); } struct hercules_session *session_rx = server->sessions_rx[s]; if (session_rx && session_rx->state != SESSION_STATE_DONE) { - struct prints *p = &rx[s]; + active_session = true; + struct print_info *p = &rx[s]; u32 rec_count = session_rx->rx_state->received_chunks.num_set; u32 total = session_rx->rx_state->received_chunks.num; u32 rcvd_now = session_rx->rx_npkts; @@ -3065,13 +3286,20 @@ static void print_session_stats(struct hercules_server *server, u64 now, session_rx->tx_npkts, recv_rate); } } - fprintf(stderr, "TX Total Rate: %.2f Mbps\n", send_rate_total); - fprintf(stderr, "RX Total Rate: %.2f Mbps\n", recv_rate_total); + if (active_session) { + fprintf(stderr, "TX Total Rate: %.2f Mbps\n", send_rate_total); + fprintf(stderr, "RX Total Rate: %.2f Mbps\n", recv_rate_total); + fprintf(stderr, "\n"); + } } +// Inform the monitor about the sessions current status (time elapsed and bytes +// transferred). If the monitor wants to cancel the ongoing transfer, stop it. static void tx_update_monitor(struct hercules_server *server, int s, u64 now) { struct hercules_session *session_tx = server->sessions_tx[s]; - if (session_tx != NULL && session_state_is_running(session_tx->state)) { + if (session_tx != NULL && session_state_is_running(session_tx->state) && + now > session_tx->last_monitor_update + monitor_update_interval) { + session_tx->last_monitor_update = now; bool ret = monitor_update_job( server->usock, session_tx->jobid, session_tx->state, 0, (now - session_tx->tx_state->start_time) / (int)1e9, @@ -3083,7 +3311,8 @@ static void tx_update_monitor(struct hercules_server *server, int s, u64 now) { } } -static void rx_send_cts(struct hercules_server *server, int s, u64 now) { +// Send a CTS ACK (empty ACK), if necessary. +static void rx_send_cts(struct hercules_server *server, int s) { struct hercules_session *session_rx = server->sessions_rx[s]; if (session_rx != NULL && session_rx->state == SESSION_STATE_INDEX_READY) { struct receiver_state *rx_state = session_rx->rx_state; @@ -3098,7 +3327,11 @@ static void rx_send_cts(struct hercules_server *server, int s, u64 now) { } } -static void stop_finished_sessions(struct hercules_server *server, int slot, u64 now) { +// To stop a session, any thread may set its error to something other than +// ERROR_NONE (generally via quit_session()). If the error is set, this changes +// the sessions state to DONE. The state update needs to happen in the events_p +// thread, otherwise there's a chance of getting stuck (e.g. in update_paths). +static void stop_finished_sessions(struct hercules_server *server, int slot) { struct hercules_session *session_tx = server->sessions_tx[slot]; if (session_tx != NULL && session_tx->state != SESSION_STATE_DONE && session_tx->error != SESSION_ERROR_NONE) { @@ -3113,8 +3346,6 @@ static void stop_finished_sessions(struct hercules_server *server, int slot, u64 } } -#define PRINT_STATS - // Read control packets from the control socket and process them; also handles // interaction with the monitor static void events_p(void *arg) { @@ -3127,49 +3358,39 @@ static void events_p(void *arg) { const struct scionaddrhdr_ipv4 *scionaddrhdr; const struct udphdr *udphdr; - u64 lastpoll = 0; - struct prints tx[HERCULES_CONCURRENT_SESSIONS]; - struct prints rx[HERCULES_CONCURRENT_SESSIONS]; - memset(tx, 0, sizeof(tx)); - memset(rx, 0, sizeof(rx)); + u64 lastprint = 0; + struct print_info tx_stats[HERCULES_CONCURRENT_SESSIONS]; + struct print_info rx_stats[HERCULES_CONCURRENT_SESSIONS]; + memset(tx_stats, 0, sizeof(tx_stats)); + memset(rx_stats, 0, sizeof(rx_stats)); int current_slot = 0; - while (!wants_shutdown) { // event handler thread loop + while (!wants_shutdown) { u64 now = get_nsecs(); current_slot = (current_slot + 1) % HERCULES_CONCURRENT_SESSIONS; - /* if (now > lastpoll + 1e9){ */ - // XXX run the following every n seconds or every n socket reads? - // FIXME don't loop over all sessions, one at a time - new_tx_if_available(server); + mark_timed_out_sessions(server, current_slot, now); - stop_finished_sessions(server, current_slot, now); + stop_finished_sessions(server, current_slot); + tx_update_monitor(server, current_slot, now); + tx_update_paths(server, current_slot, now); cleanup_finished_sessions(server, current_slot, now); + new_tx_if_available(server); tx_retransmit_initial(server, current_slot, now); - rx_send_cts(server, current_slot, now); + rx_send_cts(server, current_slot); #ifdef PRINT_STATS - if (now > lastpoll + 1e9) { - print_session_stats(server, now, tx, rx); - lastpoll = now; + if (now > lastprint + print_stats_interval) { + print_session_stats(server, now, tx_stats, rx_stats); + lastprint = now; } #endif - /* if (now > lastpoll + 10e9){ */ - /* tx_update_paths(server); */ - /* lastpoll = now; */ - /* } */ - /* if (now > lastpoll + 20e9){ */ - /* tx_update_monitor(server, current_slot, now); */ - /* tx_update_paths(server, current_slot); */ - /* lastpoll = now; */ - /* } */ - /* lastpoll = now; */ - /* } */ // We want to handle received packets more frequently than we poll the // monitor or check for expired sessions, so try to receive 1000 times // (non-blocking) before doing anything else. + // XXX 1000 is an arbitrary value for (int i = 0; i < 1000; i++) { - ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), - MSG_DONTWAIT, (struct sockaddr *)&addr, - &addr_size); // XXX set timeout + ssize_t len = + recvfrom(server->control_sockfd, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr *)&addr, &addr_size); if (len == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { continue; @@ -3191,8 +3412,10 @@ static void events_p(void *arg) { &scmp_bad_path, &scmp_bad_port); if (rbudp_pkt == NULL) { if (scmp_bad_path != PCC_NO_PATH) { - debug_printf("Received SCMP error on path %d, dst port %u, disabling", - scmp_bad_path, scmp_bad_port); + debug_printf( + "Received SCMP error on path %d, dst port %u, " + "disabling", + scmp_bad_path, scmp_bad_port); // XXX We disable the path that received an SCMP error. The // next time we fetch new paths from the monitor it will be // re-enabled, if it's still present. It may be desirable to @@ -3200,7 +3423,8 @@ static void events_p(void *arg) { // update paths and on the exact SCMP error. Also, should // "destination unreachable" be treated as a permanent // failure and the session abandoned immediately? - struct hercules_session *session_tx = lookup_session_tx(server, scmp_bad_port); + struct hercules_session *session_tx = + lookup_session_tx(server, scmp_bad_port); if (session_tx != NULL && session_state_is_running(session_tx->state)) { struct path_set *pathset = @@ -3212,8 +3436,11 @@ static void events_p(void *arg) { } continue; } - u16 pkt_dst_port = ntohs(*(u16 *)( rbudp_pkt - 6 )); - u16 pkt_src_port = ntohs(*(u16 *)( rbudp_pkt - 8 )); + + u16 pkt_dst_port = ntohs(*(u16 *)(rbudp_pkt - 6)); + struct hercules_app_addr pkt_source = {.port = udphdr->uh_sport, + .ip = scionaddrhdr->src_ip, + .ia = scionaddrhdr->src_ia}; const size_t rbudp_len = len - (rbudp_pkt - buf); if (rbudp_len < sizeof(u32)) { @@ -3224,197 +3451,158 @@ static void events_p(void *arg) { u32 chunk_idx; memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); if (chunk_idx != UINT_MAX) { - debug_printf("Ignoring, chunk_idx != UINT_MAX"); + // Only data packets can have another value and we don't handle + // them here + debug_printf( + "Ignoring, chunk_idx != UINT_MAX. Data packet on control " + "socket?"); continue; } debug_print_rbudp_pkt(rbudp_pkt, true); - // TODO Count received pkt, call count_received_pkt everywhere - // TODO check received packet has expected source address struct hercules_header *h = (struct hercules_header *)rbudp_pkt; - if (h->chunk_idx == UINT_MAX) { // This is a control packet - const char *pl = rbudp_pkt + rbudp_headerlen; - struct hercules_control_packet *cp = - (struct hercules_control_packet *)pl; - u32 control_pkt_payloadlen = rbudp_len - rbudp_headerlen; - - switch (cp->type) { - case CONTROL_PACKET_TYPE_INITIAL:; - struct rbudp_initial_pkt *parsed_pkt = NULL; - rbudp_check_initial(cp, - rbudp_len - rbudp_headerlen, - &parsed_pkt); - if (parsed_pkt->flags & HANDSHAKE_FLAG_HS_CONFIRM) { - // This is a confirmation for a handshake packet - // we sent out earlier - debug_printf("HS confirm packet"); - tx_handle_hs_confirm(server, parsed_pkt, pkt_dst_port, pkt_src_port); - break; // Make sure we don't process this further - } - // Otherwise, we process and reflect the packet - struct hercules_session *session_rx = lookup_session_rx(server, pkt_dst_port); - if (session_rx != NULL && - session_state_is_running(session_rx->state)) { - if (!(parsed_pkt->flags & - HANDSHAKE_FLAG_NEW_TRANSFER)) { - // This is a handshake that tries to open a new - // path for the running transfer - // Source port does not matter, so we pass 0 - rx_handle_initial( - server, session_rx->rx_state, - parsed_pkt, 0, buf, addr.sll_ifindex, - rbudp_pkt + rbudp_headerlen, len); - } - } - int rx_slot = find_free_rx_slot(server); - if (rx_slot != -1 && - (parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { - // We don't have a running session and this is an - // attempt to start a new one, go ahead and start a - // new rx session - debug_printf("Accepting new rx session"); - if (parsed_pkt->flags & - HANDSHAKE_FLAG_SET_RETURN_PATH) { - // The very first packet needs to set the return - // path or we won't be able to reply - struct hercules_session *session = - make_session(server); - server->sessions_rx[rx_slot] = session; - session->state = SESSION_STATE_NEW; - u16 src_port = - server->config.port_min + rx_slot + 1; - debug_printf("src port is %d", src_port); - if (!(parsed_pkt->flags & - HANDSHAKE_FLAG_INDEX_FOLLOWS)) { - // Entire index contained in this packet, - // we can go ahead and proceed with transfer - struct receiver_state *rx_state = - make_rx_state( - session, parsed_pkt->index, - parsed_pkt->index_len, - parsed_pkt->filesize, - parsed_pkt->chunklen, src_port, false); - if (rx_state == NULL) { - debug_printf( - "Error creating RX state!"); - break; - } - session->rx_state = rx_state; - rx_handle_initial( - server, rx_state, parsed_pkt, rx_slot, buf, - addr.sll_ifindex, - rbudp_pkt + rbudp_headerlen, len); - rx_send_cts_ack(server, rx_state); - session->state = - SESSION_STATE_RUNNING_DATA; - } - else { - // Index transferred separately - struct receiver_state *rx_state = - make_rx_state_nomap( - session, parsed_pkt->index_len, - parsed_pkt->filesize, - parsed_pkt->chunklen, src_port, false); - if (rx_state == NULL) { - debug_printf( - "Error creating RX state!"); - break; - } - rx_state->index_size = parsed_pkt->index_len; - rx_state->index = calloc(1, parsed_pkt->index_len); - if (rx_state->index == NULL){ - debug_printf("Error allocating index"); - break; - } - session->rx_state = rx_state; - rx_handle_initial( - server, rx_state, parsed_pkt, rx_slot, buf, - addr.sll_ifindex, - rbudp_pkt + rbudp_headerlen, len); - session->state = - SESSION_STATE_RUNNING_IDX; - - } - } + const char *pl = rbudp_pkt + rbudp_headerlen; + struct hercules_control_packet *cp = + (struct hercules_control_packet *)pl; + u32 control_pkt_payloadlen = rbudp_len - rbudp_headerlen; + + switch (cp->type) { + case CONTROL_PACKET_TYPE_INITIAL:; + struct rbudp_initial_pkt *parsed_pkt = NULL; + rbudp_check_initial(cp, rbudp_len - rbudp_headerlen, + &parsed_pkt); + if (parsed_pkt->flags & HANDSHAKE_FLAG_HS_CONFIRM) { + // This is a confirmation for a handshake packet + // we sent out earlier + debug_printf("HS confirm packet"); + tx_handle_hs_confirm(server, parsed_pkt, pkt_dst_port, + h->path, &pkt_source); + break; // Make sure we don't process this further + } + + // Otherwise, we process and reflect the packet + struct hercules_session *session_rx = + lookup_session_rx(server, pkt_dst_port); + if (session_rx != NULL && + session_state_is_running(session_rx->state)) { + if (!(parsed_pkt->flags & + HANDSHAKE_FLAG_NEW_TRANSFER)) { + // This is a handshake that tries to open a new + // path for the running transfer + debug_printf( + "Handling initial packet for existing session"); + count_received_pkt(session_rx, h->path); + rx_handle_initial(server, session_rx->rx_state, + parsed_pkt, buf, + rbudp_pkt + rbudp_headerlen, len); + } else { + debug_printf( + "Not allowed: Initial packet with NEW_TRANSFER " + "flag set in existing session"); } break; + } + int rx_slot = find_free_rx_slot(server); + if (rx_slot != -1 && + (parsed_pkt->flags & HANDSHAKE_FLAG_NEW_TRANSFER)) { + // We don't have a running session and this is an + // attempt to start a new one, go ahead and start a + // new rx session + rx_accept_new_session(server, parsed_pkt, &pkt_source, + buf, pl, len, rx_slot); + } + break; - case CONTROL_PACKET_TYPE_ACK: - if (control_pkt_payloadlen < ack__len(&cp->payload.ack)){ - debug_printf("ACK packet too short"); - break; - } - struct hercules_session *session_tx = - lookup_session_tx(server, pkt_dst_port); - if (session_tx != NULL && - session_tx->state == - SESSION_STATE_WAIT_CTS) { - if (cp->payload.ack.num_acks == 0) { - debug_printf("CTS received"); - atomic_store(&session_tx->state, - SESSION_STATE_RUNNING_DATA); - } - } - if (session_tx != NULL && - session_tx->state == SESSION_STATE_RUNNING_DATA) { - tx_register_acks(&cp->payload.ack, - session_tx->tx_state); + case CONTROL_PACKET_TYPE_ACK: + if (control_pkt_payloadlen < ack__len(&cp->payload.ack)) { + debug_printf("ACK packet too short"); + break; + } + struct hercules_session *session_tx = + lookup_session_tx(server, pkt_dst_port); +#ifdef CHECK_SRC_ADDRESS + if (session_tx != NULL && + !src_matches_address(session_tx, &pkt_source)) { + debug_printf("Dropping packet with unexpected source"); + break; + } +#endif + if (session_tx != NULL && + session_tx->state == SESSION_STATE_WAIT_CTS) { + if (cp->payload.ack.num_acks == 0) { + debug_printf("CTS received"); count_received_pkt(session_tx, h->path); - atomic_store(&session_tx->last_pkt_rcvd, get_nsecs()); - if (tx_acked_all(session_tx->tx_state)) { - debug_printf("TX done, received all acks (%d)", pkt_dst_port-server->config.port_min); - quit_session(session_tx, - SESSION_ERROR_OK); - } + atomic_store(&session_tx->state, + SESSION_STATE_RUNNING_DATA); } - if (session_tx != NULL && - session_tx->state == SESSION_STATE_RUNNING_IDX) { - tx_register_acks_index(&cp->payload.ack, - session_tx->tx_state); - count_received_pkt(session_tx, h->path); - atomic_store(&session_tx->last_pkt_rcvd, get_nsecs()); - if (tx_acked_all_index(session_tx->tx_state)) { - debug_printf("Index transfer done, received all acks"); - reset_tx_state(session_tx->tx_state); - session_tx->state = SESSION_STATE_WAIT_CTS; - } + } + if (session_tx != NULL && + session_tx->state == SESSION_STATE_RUNNING_DATA) { + tx_register_acks(&cp->payload.ack, + session_tx->tx_state); + count_received_pkt(session_tx, h->path); + if (tx_acked_all(session_tx->tx_state)) { + debug_printf( + "TX done, received all acks (%d)", + pkt_dst_port - server->config.port_min - 1); + quit_session(session_tx, SESSION_ERROR_OK); } - break; + } + if (session_tx != NULL && + session_tx->state == SESSION_STATE_RUNNING_IDX) { + tx_register_acks_index(&cp->payload.ack, + session_tx->tx_state); + count_received_pkt(session_tx, h->path); + if (tx_acked_all_index(session_tx->tx_state)) { + debug_printf( + "Index transfer done, received all acks"); + reset_tx_state(session_tx->tx_state); + session_tx->state = SESSION_STATE_WAIT_CTS; + } + } + break; - case CONTROL_PACKET_TYPE_NACK: - if (control_pkt_payloadlen < - ack__len(&cp->payload.ack)) { - debug_printf("NACK packet too short"); + case CONTROL_PACKET_TYPE_NACK: + if (control_pkt_payloadlen < ack__len(&cp->payload.ack)) { + debug_printf("NACK packet too short"); + break; + } + session_tx = lookup_session_tx(server, pkt_dst_port); +#ifdef CHECK_SRC_ADDRESS + if (session_tx != NULL && + !src_matches_address(session_tx, &pkt_source)) { + debug_printf("Dropping packet with unexpected source"); + break; + } +#endif + if (session_tx != NULL && + session_state_is_running(session_tx->state)) { + count_received_pkt(session_tx, h->path); + nack_trace_push(cp->payload.ack.timestamp, + cp->payload.ack.ack_nr); + struct path_set *pathset = + session_tx->tx_state->pathset; + if (h->path > pathset->n_paths) { + // The pathset was updated in the meantime and + // there are now fewer paths, so ignore this break; } - session_tx = lookup_session_tx(server, pkt_dst_port); - if (session_tx != NULL && - session_state_is_running( - session_tx->state)) { - count_received_pkt(session_tx, h->path); - nack_trace_push(cp->payload.ack.timestamp, - cp->payload.ack.ack_nr); - struct path_set *pathset = - session_tx->tx_state->pathset; - if (h->path > pathset->n_paths) { - // The pathset was updated in the meantime and - // there are now fewer paths, so ignore this - break; + bool nack_track_ok = tx_register_nacks( + &cp->payload.ack, pathset->paths[h->path].cc_state); + if (!nack_track_ok) { + pathset->paths[h->path].nack_errs++; + if (pathset->paths[h->path].nack_errs > + NACK_ERRS_ALLOWED) { + pathset->paths[h->path].next_handshake_at = now; + pathset->paths[h->path].nack_errs = 0; } - tx_register_nacks( - &cp->payload.ack, - pathset->paths[h->path].cc_state); } - break; - default: - debug_printf("Received control packet of unknown type"); - break; - } - } else { - // This should never happen beacuse the xdp program redirects - // all data packets - debug_printf("Non-control packet received on control socket"); + } + break; + default: + debug_printf("Received control packet of unknown type"); + break; } struct hercules_session *session_tx = lookup_session_tx(server, pkt_dst_port); @@ -3441,85 +3629,8 @@ static void join_thread(struct hercules_server *server, pthread_t pt) exit_with_error(server, ret); } } -/// (TODO)stats -struct path_stats *make_path_stats_buffer(int num_paths) { - struct path_stats *path_stats = calloc(1, sizeof(*path_stats) + num_paths * sizeof(path_stats->paths[0])); - path_stats->num_paths = num_paths; - return path_stats; -} - -/* static struct hercules_stats tx_stats(struct sender_state *tx_state, struct path_stats* path_stats) */ -/* { */ -/* if(path_stats != NULL && tx_state->receiver[0].cc_states != NULL) { */ -/* if(path_stats->num_paths < tx_state->num_receivers * tx_state->max_paths_per_rcvr) { */ -/* fprintf(stderr,"stats buffer not large enough: %d given, %d required\n", path_stats->num_paths, */ -/* tx_state->num_receivers * tx_state->max_paths_per_rcvr); */ -/* exit_with_error(tx_state->session, EINVAL); */ -/* } */ -/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ -/* const struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; */ -/* for(u32 p = 0; p < receiver->num_paths; p++) { */ -/* path_stats->paths[r * tx_state->max_paths_per_rcvr + p].pps_target = receiver->cc_states[p].curr_rate; */ -/* path_stats->paths[r * tx_state->max_paths_per_rcvr + p].total_packets = receiver->cc_states[p].total_tx_npkts; */ -/* } */ -/* memset(&path_stats->paths[r * tx_state->max_paths_per_rcvr + receiver->num_paths], 0, */ -/* sizeof(path_stats->paths[0]) * (tx_state->max_paths_per_rcvr - receiver->num_paths)); */ -/* } */ -/* } */ -/* u32 completed_chunks = 0; */ -/* u64 rate_limit = 0; */ -/* for(u32 r = 0; r < tx_state->num_receivers; r++) { */ -/* const struct sender_state_per_receiver *receiver = &tx_state->receiver[r]; */ -/* completed_chunks += tx_state->receiver[r].acked_chunks.num_set; */ -/* for(u8 p = 0; p < receiver->num_paths; p++) { */ -/* if(receiver->cc_states == NULL) { // no path-specific rate-limit */ -/* rate_limit += tx_state->rate_limit; */ -/* } else { // PCC provided limit */ -/* rate_limit += receiver->cc_states[p].curr_rate; */ -/* } */ -/* } */ -/* } */ -/* return (struct hercules_stats){ */ -/* .start_time = tx_state->start_time, */ -/* .end_time = tx_state->end_time, */ -/* .now = get_nsecs(), */ -/* .tx_npkts = tx_state->session->tx_npkts, */ -/* .rx_npkts = tx_state->session->rx_npkts, */ -/* .filesize = tx_state->filesize, */ -/* .framelen = tx_state->session->config.ether_size, */ -/* .chunklen = tx_state->chunklen, */ -/* .total_chunks = tx_state->total_chunks * tx_state->num_receivers, */ -/* .completed_chunks = completed_chunks, */ -/* .rate_limit = umin64(tx_state->rate_limit, rate_limit), */ -/* }; */ -/* } */ - -/* static struct hercules_stats rx_stats(struct receiver_state *rx_state, struct path_stats* path_stats) */ -/* { */ -/* if(path_stats != NULL) { */ -/* if(path_stats->num_paths < rx_state->num_tracked_paths) { */ -/* fprintf(stderr,"stats buffer not large enough: %d given, %d required\n", path_stats->num_paths, */ -/* rx_state->num_tracked_paths); */ -/* exit_with_error(rx_state->session, EINVAL); */ -/* } */ -/* for(u32 p = 0; p < rx_state->num_tracked_paths; p++) { */ -/* path_stats->paths[p].total_packets = rx_state->path_state[p].rx_npkts; */ -/* } */ -/* } */ -/* return (struct hercules_stats){ */ -/* .start_time = rx_state->start_time, */ -/* .end_time = rx_state->end_time, */ -/* .now = get_nsecs(), */ -/* .tx_npkts = rx_state->session->tx_npkts, */ -/* .rx_npkts = rx_state->session->rx_npkts, */ -/* .filesize = rx_state->filesize, */ -/* .framelen = rx_state->session->config.ether_size, */ -/* .chunklen = rx_state->chunklen, */ -/* .total_chunks = rx_state->total_chunks, */ -/* .completed_chunks = rx_state->received_chunks.num_set, */ -/* .rate_limit = 0 */ -/* }; */ -/* } */ + + /// Hercules main void hercules_main(struct hercules_server *server) { debug_printf("Hercules main"); @@ -3540,15 +3651,15 @@ void hercules_main(struct hercules_server *server) { // Start the RX worker threads - pthread_t rx_workers[server->n_threads]; - for (int i = 0; i < server->n_threads; i++) { + pthread_t rx_workers[server->config.n_threads]; + for (int i = 0; i < server->config.n_threads; i++) { debug_printf("starting thread rx_p %d", i); rx_workers[i] = start_thread(NULL, rx_p, server->worker_args[i]); } // Start the TX worker threads - pthread_t tx_workers[server->n_threads]; - for (int i = 0; i < server->n_threads; i++) { + pthread_t tx_workers[server->config.n_threads]; + for (int i = 0; i < server->config.n_threads; i++) { debug_printf("starting thread tx_send_p %d", i); tx_workers[i] = start_thread(NULL, tx_send_p, server->worker_args[i]); } @@ -3562,7 +3673,7 @@ void hercules_main(struct hercules_server *server) { join_thread(server, trickle_acks); join_thread(server, trickle_nacks); join_thread(server, tx_p_thread); - for (int i = 0; i < server->n_threads; i++){ + for (int i = 0; i < server->config.n_threads; i++){ join_thread(server, rx_workers[i]); join_thread(server, tx_workers[i]); } @@ -3576,8 +3687,7 @@ void usage(){ exit(1); } -// TODO Test multiple interfaces -#define HERCULES_MAX_INTERFACES 1 +#define HERCULES_MAX_INTERFACES 3 int main(int argc, char *argv[]) { unsigned int if_idxs[HERCULES_MAX_INTERFACES]; int n_interfaces = 0; @@ -3589,7 +3699,9 @@ int main(int argc, char *argv[]) { config.server_socket = HERCULES_DEFAULT_DAEMON_SOCKET; config.queue = 0; config.configure_queues = true; - config.num_threads = 1; + config.enable_pcc = true; + config.rate_limit = 3333333; + config.n_threads = 1; config.xdp_mode = XDP_COPY; // Parse command line args (there is only one) @@ -3657,7 +3769,6 @@ int main(int argc, char *argv[]) { u64 ia; u16 *ia_ptr = (u16 *)&ia; char ip_str[100]; - u32 ip; u16 port; int ret = sscanf(listen_addr.u.s, "%hu-%hx:%hx:%hx,%99[^:]:%hu", ia_ptr + 3, ia_ptr + 2, ia_ptr + 1, ia_ptr + 0, ip_str, &port); @@ -3683,6 +3794,7 @@ int main(int argc, char *argv[]) { exit(1); } } + // Automatic queue configuration toml_datum_t configure_queues = toml_bool_in(conf, "ConfigureQueues"); if (configure_queues.ok) { @@ -3694,6 +3806,17 @@ int main(int argc, char *argv[]) { } } + // PCC enable + toml_datum_t enable_pcc = toml_bool_in(conf, "EnablePCC"); + if (enable_pcc.ok) { + config.enable_pcc = (enable_pcc.u.b); + } else { + if (toml_key_exists(conf, "EnablePCC")) { + fprintf(stderr, "Error parsing EnablePCC\n"); + exit(1); + } + } + // XDP Zerocopy toml_datum_t zerocopy_enabled = toml_bool_in(conf, "XDPZeroCopy"); if (zerocopy_enabled.ok) { @@ -3708,7 +3831,7 @@ int main(int argc, char *argv[]) { // Worker threads toml_datum_t nthreads = toml_int_in(conf, "NumThreads"); if (nthreads.ok) { - config.num_threads = nthreads.u.i; + config.n_threads = nthreads.u.i; } else { if (toml_key_exists(conf, "NumThreads")) { fprintf(stderr, "Error parsing NumThreads\n"); @@ -3716,6 +3839,17 @@ int main(int argc, char *argv[]) { } } + // Worker threads + toml_datum_t rate_limit = toml_int_in(conf, "RateLimit"); + if (rate_limit.ok) { + config.rate_limit = rate_limit.u.i; + } else { + if (toml_key_exists(conf, "RateLimit")) { + fprintf(stderr, "Error parsing RateLimit\n"); + exit(1); + } + } + // Interfaces toml_array_t *interfaces = toml_array_in(conf, "Interfaces"); if (!interfaces || toml_array_nelem(interfaces) == 0) { @@ -3742,15 +3876,29 @@ int main(int argc, char *argv[]) { n_interfaces++; } + struct sockaddr_un dummy; + if (strlen(config.monitor_socket) >= sizeof(dummy.sun_path)) { + fprintf(stderr, "Monitor socket path too long (max %ld)", + sizeof(dummy.sun_path) - 1); + } + if (strlen(config.server_socket) >= sizeof(dummy.sun_path)) { + fprintf(stderr, "Server socket path too long (max %ld)", + sizeof(dummy.sun_path) - 1); + } + + // The strings allocated by the toml parser (socket paths) are + // intentionally not freed, as pointers to them still exist in the + // server config. + debug_printf( - "Starting Hercules using queue %d, queue config %d, %d worker threads, " + "Starting Hercules using queue %d, queue config %d, %d worker " + "threads, " "xdp mode 0x%x", - config.queue, config.configure_queues, config.num_threads, + config.queue, config.configure_queues, config.n_threads, config.xdp_mode); - bool enable_pcc = true; struct hercules_server *server = - hercules_init_server(config, if_idxs, n_interfaces, enable_pcc); + hercules_init_server(config, if_idxs, n_interfaces); // Register a handler for SIGINT/SIGTERM for clean shutdown struct sigaction act = {0}; diff --git a/hercules.h b/hercules.h index 8a1c1ee..41ee79d 100644 --- a/hercules.h +++ b/hercules.h @@ -47,7 +47,8 @@ struct hercules_path_header { // Path are specified as ETH/IP/UDP/SCION/UDP headers. struct hercules_path { - __u64 next_handshake_at; + _Atomic __u64 next_handshake_at; + int nack_errs; int headerlen; int payloadlen; int framelen; //!< length of ethernet frame; headerlen + payloadlen @@ -73,7 +74,7 @@ struct receiver_state { atomic_uint_least64_t handshake_rtt; /** Filesize in bytes */ size_t filesize; - size_t index_size; + size_t index_size; // Size of the directory index in bytes. /** Size of file data (in byte) per packet */ u32 chunklen; /** Number of packets that will make up the entire file. Equal to @@ -84,36 +85,43 @@ struct receiver_state { char *mem; char *index; - struct bitset received_chunks; - struct bitset received_chunks_index; + struct bitset received_chunks; // Bitset for marking received DATA chunks + struct bitset received_chunks_index; // Bitset for received IDX chunks // The reply path to use for contacting the sender. This is the reversed // path of the last initial packet with the SET_RETURN_PATH flag set. - // TODO needs atomic? -> perf? - struct hercules_path reply_path; + // XXX (Performance) Some form of synchronisation is required for + // reading/writing the reply path. Even though it's marked atomic, atomicity + // of updates is ensured using locks behind the scenes (the type is too + // large). Could be optimised by making it a pointer. + _Atomic struct hercules_path reply_path; - // Start/end time of the current transfer - u64 start_time; - u64 end_time; - u64 last_pkt_rcvd; // Timeout detection u32 ack_nr; u64 next_nack_round_start; + u64 next_ack_round_start; u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; - u16 src_port; + u16 src_port; // The UDP/SCION port to use when sending packets (LE) + u64 start_time; // Start/end time of the current transfer + u64 end_time; }; /// SENDER // Used to atomically swap in new paths struct path_set { - u64 epoch; + u64 epoch; // Epoch value of this path set. Set by the updating thread. u32 n_paths; u8 path_index; // Path to use for sending next batch (used by tx_p) struct hercules_path paths[256]; }; +// When a thread reads the current path set it published the epoch value of the +// set it read to let the updating thread know when it has moved on to the new +// pathset and it's thus safe to free the previous one. +// These should occupy exactly one cache line to stop multiple threads from +// frequently writing to the same cache line. struct thread_epoch { _Atomic u64 epoch; u64 _[7]; @@ -127,6 +135,8 @@ struct sender_state { // State for transmit rate control size_t tx_npkts_queued; u64 prev_rate_check; + u64 rate_limit_wait_until; + u64 next_ack_due; size_t prev_tx_npkts_queued; _Atomic u32 rate_limit; u64 prev_round_start; @@ -135,6 +145,7 @@ struct sender_state { u64 ack_wait_duration; u32 prev_chunk_idx; bool finished; + struct bitset acked_chunks; //< Chunks we've received an ack for struct bitset acked_chunks_index; //< Chunks we've received an ack for atomic_uint_least64_t handshake_rtt; // Handshake RTT in ns @@ -146,7 +157,6 @@ struct sender_state { /** Filesize in bytes */ size_t filesize; - char filename[100]; /** Size of file data (in byte) per packet */ u32 chunklen; /** Number of packets that will make up the entire file. Equal to @@ -158,10 +168,13 @@ struct sender_state { u64 start_time; u64 end_time; - u32 index_chunks; + u32 index_chunks; // Chunks that make up the directory index char *index; - size_t index_size; - u16 src_port; + size_t index_size; // Size of the directory index in bytes + bool needs_index_transfer; // Index does not fit in initial packet and + // needs to be transferred separately + + u16 src_port; // UDP/SCION port to use when sending packets }; /// SESSION @@ -200,24 +213,25 @@ struct hercules_session { struct receiver_state *rx_state; //< Valid if this is the receiving side struct sender_state *tx_state; //< Valid if this is the sending side _Atomic enum session_state state; - _Atomic enum session_error error; //< Valid if the session's state is DONE + _Atomic enum session_error error; struct send_queue *send_queue; - u64 last_pkt_sent; + u64 last_pkt_sent; //< Used for HS retransmit interval u64 last_pkt_rcvd; //< Used for timeout detection u64 last_new_pkt_rcvd; //< If we only receive packets containing // already-seen chunks for a while something is - // probably wrong - u32 jobid; //< The monitor's ID for this job + // probably wrong. (Only used by receiver) + u64 last_path_update; + u64 last_monitor_update; + + size_t rx_npkts; // Number of sent/received packets (for stats) + size_t tx_npkts; + + struct hercules_app_addr peer; //< UDP/SCION address of peer (big endian) + u32 jobid; //< The monitor's ID for this job u32 payloadlen; //< The payload length used for this transfer. Note that // the payload length includes the rbudp header while the // chunk length does not. - - u16 dst_port; - - // Number of sent/received packets (for stats) - size_t rx_npkts; - size_t tx_npkts; }; /// SERVER @@ -232,7 +246,7 @@ struct hercules_interface { struct xsk_socket_info **xsks; }; -// Config determined at program start +// Values obtained from config file (or defaults) struct hercules_config { char *monitor_socket; char *server_socket; @@ -240,7 +254,9 @@ struct hercules_config { int xdp_mode; int queue; bool configure_queues; - int num_threads; + bool enable_pcc; + int rate_limit; // Sending rate limit, only used when PCC is enabled + int n_threads; // Number of RX/TX worker threads struct hercules_app_addr local_addr; u16 port_min; // Lowest port on which to accept packets (in HOST // endianness) @@ -251,9 +267,6 @@ struct hercules_server { struct hercules_config config; int control_sockfd; // AF_PACKET socket used for control traffic int usock; // Unix socket used for communication with the monitor - int max_paths; - int rate_limit; - int n_threads; // Number of RX/TX worker threads struct worker_args **worker_args; // Args passed to RX/TX workers struct hercules_session *_Atomic @@ -264,10 +277,11 @@ struct hercules_server { // waiting to be freed struct hercules_session *_Atomic sessions_rx[HERCULES_CONCURRENT_SESSIONS]; // Current RX sessions - struct hercules_session *deferreds_rx[HERCULES_CONCURRENT_SESSIONS]; + struct hercules_session + *deferreds_rx[HERCULES_CONCURRENT_SESSIONS]; // Previous RX sessions, + // waiting to be freed - bool enable_pcc; // TODO make per path or session or something - int *ifindices; + unsigned int *ifindices; int num_ifaces; struct hercules_interface ifaces[]; }; @@ -277,7 +291,8 @@ struct xsk_umem_info { struct xsk_ring_prod fq; struct xsk_ring_cons cq; struct frame_queue available_frames; - // TODO ok to have locks closeby? + // XXX (Performance) Do we need to ensure spinlocks are in different + // cachelines? pthread_spinlock_t fq_lock; // Lock for the fill queue (fq) pthread_spinlock_t frames_lock; // Lock for the frame queue (available_frames) @@ -302,37 +317,6 @@ struct worker_args { struct xsk_socket_info *xsks[]; }; -/// STATS TODO -struct path_stats_path { - __u64 total_packets; - __u64 pps_target; -}; - -struct path_stats { - __u32 num_paths; - struct path_stats_path paths[1]; // XXX this is actually used as a dynamic - // struct member; the 1 is needed for CGO -}; -struct path_stats *make_path_stats_buffer(int num_paths); - -struct hercules_stats { - __u64 start_time; - __u64 end_time; - __u64 now; - - __u64 tx_npkts; - __u64 rx_npkts; - - __u64 filesize; - __u32 framelen; - __u32 chunklen; - __u32 total_chunks; - __u32 completed_chunks; //!< either number of acked (for sender) or - //!< received (for receiver) chunks - - __u32 rate_limit; -}; - #endif // __HERCULES_H__ /// Local Variables: diff --git a/monitor.c b/monitor.c index ea03f8f..1a57133 100644 --- a/monitor.c +++ b/monitor.c @@ -1,6 +1,7 @@ #include "monitor.h" #include +#include #include #include #include @@ -10,30 +11,51 @@ #include "hercules.h" #include "utils.h" +static bool monitor_send_recv(int sockfd, struct hercules_sockmsg_Q *in, + struct hercules_sockmsg_A *out) { + int ret = send(sockfd, in, sizeof(*in), 0); + if (ret != sizeof(*in)) { + debug_printf("Error sending to monitor?"); + return false; + } + ret = recv(sockfd, out, sizeof(*out), 0); + if (ret <= 0) { + debug_printf("Error reading from monitor?"); + return false; + } + return true; +} + bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, int etherlen, - struct hercules_path *path) { + _Atomic struct hercules_path *path) { struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_GET_REPLY_PATH; msg.payload.reply_path.etherlen = etherlen; msg.payload.reply_path.sample_len = rx_sample_len; memcpy(msg.payload.reply_path.sample, rx_sample_buf, rx_sample_len); - send(sockfd, &msg, sizeof(msg), 0); // TODO return val struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); - debug_printf("Read %d bytes", n); - if (n <= 0) { + int ret = monitor_send_recv(sockfd, &msg, &reply); + if (!ret) { return false; } - memcpy(&path->header, reply.payload.reply_path.path.header, + if (!reply.payload.reply_path.reply_path_ok){ + return false; + } + + struct hercules_path new_reply_path = { + .headerlen = reply.payload.reply_path.path.headerlen, + .header.checksum = reply.payload.reply_path.path.chksum, + .enabled = true, + .payloadlen = etherlen - reply.payload.reply_path.path.headerlen, + .framelen = etherlen, + .ifid = reply.payload.reply_path.path.ifid, + }; + memcpy(&new_reply_path.header, reply.payload.reply_path.path.header, reply.payload.reply_path.path.headerlen); - path->headerlen = reply.payload.reply_path.path.headerlen; - path->header.checksum = reply.payload.reply_path.path.chksum; - path->enabled = true; - path->payloadlen = etherlen - path->headerlen; - path->framelen = etherlen; - path->ifid = reply.payload.reply_path.path.ifid; + + atomic_store(path, new_reply_path); return true; } @@ -44,15 +66,20 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_GET_PATHS; msg.payload.paths.job_id = job_id; - send(sockfd, &msg, sizeof(msg), 0); struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); - debug_printf("receive %d bytes", n); + int ret = monitor_send_recv(sockfd, &msg, &reply); + if (!ret) { + return false; + } int received_paths = reply.payload.paths.n_paths; + assert(received_paths <= SOCKMSG_MAX_PATHS); struct hercules_path *p = calloc(received_paths, sizeof(struct hercules_path)); + if (p == NULL) { + return false; + } for (int i = 0; i < received_paths; i++) { p[i].headerlen = reply.payload.paths.paths[i].headerlen; @@ -70,24 +97,45 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, return true; } -bool monitor_get_new_job(int sockfd, char *name, char *destname, u16 *job_id, - u16 *dst_port, u16 *payloadlen) { +bool monitor_get_new_job(int sockfd, char **name, char **destname, u16 *job_id, + struct hercules_app_addr *dest, u16 *payloadlen) { struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; - send(sockfd, &msg, sizeof(msg), 0); struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); + int ret = monitor_send_recv(sockfd, &msg, &reply); + if (!ret) { + return false; + } + if (!reply.payload.newjob.has_job) { return false; } - // XXX name needs to be allocated large enough by caller - strncpy(name, reply.payload.newjob.names, + assert(reply.payload.newjob.filename_len + + reply.payload.newjob.destname_len <= + SOCKMSG_MAX_PAYLOAD); + + *name = calloc(1, reply.payload.newjob.filename_len + 1); + if (*name == NULL) { + return false; + } + *destname = calloc(1, reply.payload.newjob.destname_len + 1); + if (*destname == NULL) { + free(*name); + return false; + } + + strncpy(*name, (char *)reply.payload.newjob.names, reply.payload.newjob.filename_len); - strncpy(destname, reply.payload.newjob.names + reply.payload.newjob.filename_len, reply.payload.newjob.destname_len); - *job_id = reply.payload.newjob.job_id; + strncpy( + *destname, + (char *)reply.payload.newjob.names + reply.payload.newjob.filename_len, + reply.payload.newjob.destname_len); debug_printf("received job id %d", reply.payload.newjob.job_id); + *job_id = reply.payload.newjob.job_id; *payloadlen = reply.payload.newjob.payloadlen; - *dst_port = reply.payload.newjob.dest_port; + dest->ia = reply.payload.newjob.dest_ia; + dest->ip = reply.payload.newjob.dest_ip; + dest->port = reply.payload.newjob.dest_port; return true; } @@ -101,10 +149,13 @@ bool monitor_update_job(int sockfd, int job_id, enum session_state state, msg.payload.job_update.error = err; msg.payload.job_update.seconds_elapsed = seconds_elapsed; msg.payload.job_update.bytes_acked = bytes_acked; - send(sockfd, &msg, sizeof(msg), 0); struct hercules_sockmsg_A reply; - int n = recv(sockfd, &reply, sizeof(reply), 0); + int ret = monitor_send_recv(sockfd, &msg, &reply); + if (!ret) { + return false; + } + if (!reply.payload.job_update.ok) { return false; } @@ -118,18 +169,20 @@ int monitor_bind_daemon_socket(char *server, char *monitor) { } struct sockaddr_un name; name.sun_family = AF_UNIX; - strcpy(name.sun_path, server); + // Unix socket paths limited to 107 chars + strncpy(name.sun_path, server, sizeof(name.sun_path)-1); unlink(server); - int ret = bind(usock, &name, sizeof(name)); + int ret = bind(usock, (struct sockaddr *)&name, sizeof(name)); if (ret) { return 0; } struct sockaddr_un monitor_sock; monitor_sock.sun_family = AF_UNIX; - strcpy(monitor_sock.sun_path, monitor); - ret = connect(usock, &monitor_sock, sizeof(monitor_sock)); - if (ret){ + strncpy(monitor_sock.sun_path, monitor, 107); + ret = + connect(usock, (struct sockaddr *)&monitor_sock, sizeof(monitor_sock)); + if (ret) { return 0; } return usock; diff --git a/monitor.h b/monitor.h index bf7deca..24ddb93 100644 --- a/monitor.h +++ b/monitor.h @@ -6,34 +6,41 @@ #include "hercules.h" #include "utils.h" -// Get a reply path from the monitor. The reversed path will be written to -// *path. Returns false in case of error. +// Get a reply path from the monitor. Supply a received packet, the monitor will +// parse it and reverse the SCION path. The header with the reversed path will +// be written to *path. Returns false in case of error. bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, int rx_sample_len, int etherlen, - struct hercules_path *path); + _Atomic struct hercules_path *path); -// Get SCION paths from the monitor. The caller is responsible for freeing -// **paths. +// Get SCION paths from the monitor for a given job ID. The caller is +// responsible for freeing **paths. +// Returns false on error. bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, struct hercules_path **paths); // Check if the monitor has a new job available. // If so the function returns true and the job's details are filled into the // arguments. -bool monitor_get_new_job(int sockfd, char *name, char *destname, u16 *job_id, - u16 *dst_port, u16 *payloadlen); - -// Inform the monitor about a transfer's (new) status. +// Returns false if no new job available OR on error. +// The caller is responsible for freeing **name and **destname if the return +// value was true. +bool monitor_get_new_job(int sockfd, char **name, char **destname, u16 *job_id, + struct hercules_app_addr *dest, u16 *payloadlen); + +// Inform the monitor about a transfer's status. +// Returns false if the job was cancelled by the monitor or on error. bool monitor_update_job(int sockfd, int job_id, enum session_state state, enum session_error err, u64 seconds_elapsed, u64 bytes_acked); -// Bind the socket for the daemon. The file is deleted if already present. -// Returns the file descriptor if successful, 0 otherwise. +// Bind and connect the socket for communication with the monitor. The file is +// deleted if already present. Returns the file descriptor if successful, 0 +// otherwise. int monitor_bind_daemon_socket(char *server, char *monitor); #define HERCULES_DEFAULT_MONITOR_SOCKET "/var/run/herculesmon.sock" -#define HERCULES_DEFAULT_DAEMON_SOCKET "/var/hercules.sock" +#define HERCULES_DEFAULT_DAEMON_SOCKET "/var/run/hercules.sock" // Maximum size of variable-length fields in socket messages. Since we pass // entire packets to the monitor to get reply paths, this must be at least as @@ -41,12 +48,13 @@ int monitor_bind_daemon_socket(char *server, char *monitor); #define SOCKMSG_MAX_PAYLOAD 5000 _Static_assert(SOCKMSG_MAX_PAYLOAD >= HERCULES_MAX_PKTSIZE, "Socket messages too small"); + // Maximum number of paths transferred #define SOCKMSG_MAX_PATHS 10 -// Messages used for communication between the Hercules daemon and monitor -// via unix socket. Queries are sent by the daemon, Replies by the monitor. -// Structs suffixed _Q are queries, ones suffixed _A are answers. +// The following messages are used for communication between the Hercules daemon +// and monitor via unix socket. Queries are sent by the daemon, Replies by the +// monitor. Structs suffixed _Q are queries, ones suffixed _A are answers. #pragma pack(push) #pragma pack(1) @@ -67,21 +75,25 @@ struct sockmsg_serialized_path { }; struct sockmsg_reply_path_A { + uint8_t reply_path_ok; struct sockmsg_serialized_path path; }; -// Ask the monitor for new transfer jobs. +// Ask the monitor for a new transfer job. // The answer contains at most one new job, if one was queued at the monitor. #define SOCKMSG_TYPE_GET_NEW_JOB (2) struct sockmsg_new_job_Q {}; struct sockmsg_new_job_A { uint8_t has_job; // The other fields are only valid if this is set to 1 uint16_t job_id; + uint64_t dest_ia; //< Destination address in network byte order + uint32_t dest_ip; uint16_t dest_port; uint16_t payloadlen; - uint16_t filename_len; - uint16_t destname_len; - uint8_t names[SOCKMSG_MAX_PAYLOAD]; // Filenames *without* terminating 0-byte + uint16_t filename_len; // String length, excluding terminating 0-byte + uint16_t destname_len; // Same + uint8_t names[SOCKMSG_MAX_PAYLOAD]; // Concatenated filenames *without* + // terminating 0-byte }; // Get paths to use for a given job ID diff --git a/monitor/config.go b/monitor/config.go index 67351d3..52a0898 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -51,11 +51,13 @@ type MonitorConfig struct { ListenAddress UDPAddr Interfaces []Interface // The following are not used by the monitor, they are listed here for completeness - ServerSocket string - XDPZeroCopy bool - Queue int - ConfigureQueues bool - NumThreads int + ServerSocket string + XDPZeroCopy bool + Queue int + ConfigureQueues bool + EnablePCC bool + RateLimit int + NumThreads int } type PathRules struct { diff --git a/monitor/http_api.go b/monitor/http_api.go index 77ede2c..e9561bd 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -66,7 +66,7 @@ func http_status(w http.ResponseWriter, r *http.Request) { if !ok { return } - io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.chunks_acked)) + io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.bytes_acked)) } // Handle cancelling a transfer diff --git a/monitor/monitor.go b/monitor/monitor.go index 69162da..53132a5 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "sync" + "time" "github.com/scionproto/scion/pkg/addr" "github.com/scionproto/scion/pkg/snet" @@ -70,18 +71,19 @@ const ( // since the server has no concept of pending jobs. type HerculesTransfer struct { - id int // ID identifying this transfer - status TransferStatus // Status as seen by the monitor - file string // Name of the file to transfer on the source host - destFile string // Name of the file to transfer at destination host - dest snet.UDPAddr // Destination - pm *PathManager + id int // ID identifying this transfer + status TransferStatus // Status as seen by the monitor + file string // Name of the file to transfer on the source host + destFile string // Name of the file to transfer at destination host + dest snet.UDPAddr // Destination + pm *PathManager + timeFinished time.Time // The following two fields are meaningless if the job's status is 'Queued' // They are updated when the server sends messages of type 'update_job' state C.enum_session_state // The state returned by the server err C.enum_session_error // The error returned by the server time_elapsed int // Seconds the transfer has been running - chunks_acked int // Number of successfully transferred chunks + bytes_acked int // Number of successfully transferred chunks } var transfersLock sync.Mutex // To protect the map below @@ -103,7 +105,7 @@ func main() { listenAddress = config.ListenAddress.addr - GlobalQuerier = newPathQuerier() // TODO can the connection time out or break? + GlobalQuerier = newPathQuerier() // XXX Can the connection time out or break? monitorSocket, err := net.ResolveUnixAddr("unixgram", config.MonitorSocket) if err != nil { @@ -139,7 +141,6 @@ func main() { http.HandleFunc("/stat", http_stat) go http.ListenAndServe(":8000", nil) - // TODO remove finished sessions after a while // Communication is always initiated by the server, // the monitor's job is to respond to queries from the server for { @@ -160,17 +161,33 @@ func main() { buf = buf[2:] etherlen := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] - fmt.Println("sampel len, etherlen", sample_len, etherlen) replyPath, nextHop, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) - fmt.Println(err) // TODO signal error to server? - iface, _ := pm.interfaceForRoute(nextHop) - b := SerializePathHeader(replyPath, iface.Index, C.HERCULES_MAX_HEADERLEN) + var b []byte + if err != nil { + fmt.Println("Error in reply path lookup:", err) + b = append(b, 0) + usock.WriteToUnix(b, a) + continue + } + iface, err := pm.interfaceForRoute(nextHop) + if err != nil { + fmt.Println("Error in reply interface lookup:", err) + b = append(b, 0) + usock.WriteToUnix(b, a) + continue + } + b = append(b, 1) + b = append(b, SerializePathHeader(replyPath, iface.Index, C.HERCULES_MAX_HEADERLEN)...) usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_NEW_JOB: transfersLock.Lock() var selectedJob *HerculesTransfer = nil - for _, job := range transfers { + for k, job := range transfers { + if job.status == Done && time.Since(job.timeFinished) > time.Hour { + // Clean up old jobs while we're at it + delete(transfers, k) + } if job.status == Queued { selectedJob = job job.status = Submitted @@ -186,7 +203,12 @@ func main() { strlen_dst := len(selectedJob.destFile) b = append(b, 1) b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) - b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.dest.Host.Port)) + + // Address components in network byte order + b = binary.BigEndian.AppendUint64(b, uint64(selectedJob.dest.IA)) + b = append(b, selectedJob.dest.Host.IP[len(selectedJob.dest.Host.IP)-4:]...) + b = binary.BigEndian.AppendUint16(b, uint16(selectedJob.dest.Host.Port)) + b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.pm.payloadLen)) b = binary.LittleEndian.AppendUint16(b, uint16(strlen_src)) b = binary.LittleEndian.AppendUint16(b, uint16(strlen_dst)) @@ -227,8 +249,9 @@ func main() { job.err = errorcode if job.state == C.SESSION_STATE_DONE { job.status = Done + job.timeFinished = time.Now() } - job.chunks_acked = int(bytes_acked) // FIXME + job.bytes_acked = int(bytes_acked) job.time_elapsed = int(seconds) isCancelled := job.status == Cancelled transfersLock.Unlock() diff --git a/monitor/sampleconf.toml b/monitor/sampleconf.toml index 35c557e..b42d874 100644 --- a/monitor/sampleconf.toml +++ b/monitor/sampleconf.toml @@ -24,12 +24,20 @@ XDPZeroCopy = true # Specify the NIC RX queue on which to receive packets Queue = 0 -# For Hercules to receive traffic, packets must be redirected to the queue specified above. -# Hercules will try to configure this automatically, but this behaviour can be overridden, -# e.g. if you wish to set custom rules or automatic configuration fails. -# If you set this to false, you must manually ensure packets end up in the right queue. +# For Hercules to receive traffic, packets must be redirected to the queue +# specified above. Hercules will try to configure this automatically, but this +# behaviour can be overridden, e.g. if you wish to set custom rules or automatic +# configuration fails. If you set this to false, you must manually ensure +# packets end up in the right queue. ConfigureQueues = false +# Disabling congestion control is possible, but probably a bad idea +EnablePCC = false + +# This sets a sending rate limit (in pps) +# that applies to all transfers individually +RateLimit = 100000 + # The number of RX/TX worker threads to use NumThreads = 2 diff --git a/packet.h b/packet.h index ad4ee66..7a3a85e 100644 --- a/packet.h +++ b/packet.h @@ -118,8 +118,8 @@ struct hercules_header { #define INDEX_TYPE_DIR 1 struct dir_index_entry { __u32 filesize; - __u8 type; __u32 path_len; + __u8 type; __u8 path[]; }; @@ -131,7 +131,7 @@ struct rbudp_initial_pkt { __u64 timestamp; __u8 path_index; __u8 flags; - __u32 index_len; + __u64 index_len; __u8 index[]; }; diff --git a/send_queue.c b/send_queue.c index 59ec7f9..a008557 100644 --- a/send_queue.c +++ b/send_queue.c @@ -14,7 +14,6 @@ #include "send_queue.h" #include "hercules.h" -#include #include #include diff --git a/utils.h b/utils.h index 9e6aac3..c2f8e14 100644 --- a/utils.h +++ b/utils.h @@ -35,6 +35,8 @@ typedef __u8 u8; # define likely(x) __builtin_expect(!!(x), 1) #endif +#define ROUND_UP_PAGESIZE(x) (((4096 - 1) & x) ? ((x + 4096) & ~(4096 - 1)) : x) + static inline u16 umin16(u16 a, u16 b) { return (a < b) ? a : b; diff --git a/xdp.c b/xdp.c index f6e38d9..9f87394 100644 --- a/xdp.c +++ b/xdp.c @@ -58,19 +58,25 @@ struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, if (ret) { return NULL; } - pthread_spin_init(&umem->fq_lock, 0); - pthread_spin_init(&umem->frames_lock, 0); + ret = pthread_spin_init(&umem->fq_lock, 0); + if (ret) { + return NULL; + } + ret = pthread_spin_init(&umem->frames_lock, 0); + if (ret) { + return NULL; + } return umem; } void destroy_umem(struct xsk_umem_info *umem) { xsk_umem__delete(umem->umem); free(umem->buffer); + free(umem->available_frames.addrs); free(umem); } -int submit_initial_rx_frames(struct hercules_server *server, - struct xsk_umem_info *umem) { +int submit_initial_rx_frames(struct xsk_umem_info *umem) { int initial_kernel_rx_frame_count = XSK_RING_PROD__DEFAULT_NUM_DESCS - BATCH_SIZE; u32 idx; @@ -87,8 +93,7 @@ int submit_initial_rx_frames(struct hercules_server *server, return 0; } -int submit_initial_tx_frames(struct hercules_server *server, - struct xsk_umem_info *umem) { +int submit_initial_tx_frames(struct xsk_umem_info *umem) { // This number needs to be smaller than the number of slots in the // umem->available_frames queue (initialized in xsk_configure_umem(); // assumption in pop_completion_ring() and handle_send_queue_unit()) @@ -235,13 +240,16 @@ int set_bpf_prgm_active(struct hercules_server *server, return 0; } -int xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, +int xsk_map__add_xsk(xskmap map, int index, struct xsk_socket_info *xsk) { int xsk_fd = xsk_socket__fd(xsk->xsk); if (xsk_fd < 0) { return 1; } - bpf_map_update_elem(map, &index, &xsk_fd, 0); + int ret = bpf_map_update_elem(map, &index, &xsk_fd, 0); + if (ret == -1) { + return 1; + } return 0; } @@ -265,7 +273,11 @@ int load_xsk_redirect_userspace(struct hercules_server *server, return 1; } for (int s = 0; s < num_threads; s++) { - xsk_map__add_xsk(server, xsks_map_fd, s, args[s]->xsks[i]); + int ret = + xsk_map__add_xsk(xsks_map_fd, s, args[s]->xsks[i]); + if (ret) { + return 1; + } } // push XSKs meta @@ -274,17 +286,26 @@ int load_xsk_redirect_userspace(struct hercules_server *server, if (num_xsks_fd < 0) { return 1; } - bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); + int ret = bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); + if (ret == -1) { + return 1; + } // push local address int local_addr_fd = bpf_object__find_map_fd_by_name(obj, "local_addr"); if (local_addr_fd < 0) { return 1; } - bpf_map_update_elem(local_addr_fd, &zero, &server->config.local_addr, - 0); + ret = bpf_map_update_elem(local_addr_fd, &zero, + &server->config.local_addr, 0); + if (ret == -1) { + return 1; + } - set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); + ret = set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); + if (ret) { + return 1; + } } return 0; } @@ -303,13 +324,26 @@ int xdp_setup(struct hercules_server *server) { struct xsk_umem_info *umem = xsk_configure_umem_server( server, i, umem_buf, NUM_FRAMES * XSK_UMEM__DEFAULT_FRAME_SIZE); + if (umem == NULL) { + debug_printf("Error in umem setup"); + return -1; + } debug_printf("Configured umem"); server->ifaces[i].xsks = - calloc(server->n_threads, sizeof(*server->ifaces[i].xsks)); + calloc(server->config.n_threads, sizeof(*server->ifaces[i].xsks)); + if (server->ifaces[i].xsks == NULL) { + return ENOMEM; + } server->ifaces[i].umem = umem; - submit_initial_tx_frames(server, umem); - submit_initial_rx_frames(server, umem); + ret = submit_initial_tx_frames(umem); + if (ret) { + return -ret; + } + ret = submit_initial_rx_frames(umem); + if (ret) { + return -ret; + } debug_printf("umem interface %d %s, queue %d", umem->iface->ifid, umem->iface->ifname, umem->iface->queue); if (server->ifaces[i].ifid != umem->iface->ifid) { @@ -321,7 +355,7 @@ int xdp_setup(struct hercules_server *server) { } // Create XSK sockets - for (int t = 0; t < server->n_threads; t++) { + for (int t = 0; t < server->config.n_threads; t++) { struct xsk_socket_info *xsk; xsk = calloc(1, sizeof(*xsk)); if (!xsk) { @@ -349,23 +383,28 @@ int xdp_setup(struct hercules_server *server) { } server->ifaces[i].xsks[t] = xsk; } - server->ifaces[i].num_sockets = server->n_threads; + server->ifaces[i].num_sockets = server->config.n_threads; } - for (int t = 0; t < server->n_threads; t++) { - server->worker_args[t] = - malloc(sizeof(**server->worker_args) + + for (int t = 0; t < server->config.n_threads; t++) { + server->worker_args[t] = calloc( + 1, sizeof(**server->worker_args) + server->num_ifaces * sizeof(*server->worker_args[t]->xsks)); if (server->worker_args[t] == NULL) { return ENOMEM; } server->worker_args[t]->server = server; - server->worker_args[t]->id = t+1; + server->worker_args[t]->id = t + 1; for (int i = 0; i < server->num_ifaces; i++) { server->worker_args[t]->xsks[i] = server->ifaces[i].xsks[t]; } } - load_xsk_redirect_userspace(server, server->worker_args, server->n_threads); + int ret = load_xsk_redirect_userspace(server, server->worker_args, + server->config.n_threads); + if (ret) { + debug_printf("Error loading XDP redirect"); + return ret; + } if (server->config.configure_queues) { int ret = configure_rx_queues(server); @@ -378,7 +417,13 @@ int xdp_setup(struct hercules_server *server) { return 0; } -void xdp_teardown(struct hercules_server *server){ +void xdp_teardown(struct hercules_server *server) { + for (int i = 0; i < server->num_ifaces; i++) { + for (int j = 0; j < server->config.n_threads; j++) { + close_xsk(server->ifaces[i].xsks[j]); + } + destroy_umem(server->ifaces[i].umem); + } remove_xdp_program(server); unconfigure_rx_queues(server); } diff --git a/xdp.h b/xdp.h index afeb719..d2cf728 100644 --- a/xdp.h +++ b/xdp.h @@ -17,11 +17,9 @@ struct xsk_umem_info *xsk_configure_umem_server(struct hercules_server *server, void destroy_umem(struct xsk_umem_info *umem); -int submit_initial_rx_frames(struct hercules_server *server, - struct xsk_umem_info *umem); +int submit_initial_rx_frames(struct xsk_umem_info *umem); -int submit_initial_tx_frames(struct hercules_server *server, - struct xsk_umem_info *umem); +int submit_initial_tx_frames(struct xsk_umem_info *umem); // Configure the NIC(s) to send incoming packets to the queue Hercules is using. int configure_rx_queues(struct hercules_server *server); @@ -34,7 +32,7 @@ int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj); int set_bpf_prgm_active(struct hercules_server *server, struct hercules_interface *iface, int prog_fd); -int xsk_map__add_xsk(struct hercules_server *server, xskmap map, int index, +int xsk_map__add_xsk(xskmap map, int index, struct xsk_socket_info *xsk); int load_xsk_redirect_userspace(struct hercules_server *server, From 4ca77ea1d14379cb02513232bbed514bfac2c671 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Sun, 23 Jun 2024 19:03:17 +0200 Subject: [PATCH 056/112] Allow clients to override automatic MTU selection --- monitor/http_api.go | 11 ++++++++++- monitor/pathmanager.go | 4 ++-- monitor/pathstodestination.go | 8 ++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/monitor/http_api.go b/monitor/http_api.go index e9561bd..7b069a4 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -28,8 +28,17 @@ func http_submit(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "parse err") return } + payloadlen := 0 // 0 means automatic selection + if r.URL.Query().Has("payloadlen") { + payloadlen, err = strconv.Atoi(r.URL.Query().Get("payloadlen")) + if err != nil { + io.WriteString(w, "parse err") + return + } + } + destination := findPathRule(&pathRules, destParsed) - pm, _ := initNewPathManager(activeInterfaces, &destination, listenAddress) + pm, _ := initNewPathManager(activeInterfaces, &destination, listenAddress, payloadlen) transfersLock.Lock() jobid := nextID diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 3bd5ef5..5da6d05 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -40,7 +40,7 @@ type PathWithInterface struct { iface *net.Interface } -func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr) (*PathManager, error) { +func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr, payloadLen int) (*PathManager, error) { ifMap := make(map[int]*net.Interface) for _, iface := range interfaces { ifMap[iface.Index] = iface @@ -50,7 +50,7 @@ func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet interfaces: ifMap, src: src, dst: &PathsToDestination{}, - payloadLen: 0, // Will be set later, after the first path lookup + payloadLen: payloadLen, } if src.IA == dst.hostAddr.IA { diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 17dc3e0..d5d15f9 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -108,9 +108,9 @@ func (ptd *PathsToDestination) choosePaths() bool { pathMTU := int(path.path.Metadata().MTU) underlayHeaderLen, scionHeaderLen := getPathHeaderlen(path.path) if pathMTU == 0 { - // Empty path has length 0, let's just use the interface's MTU - // XXX If the path's MTU is smaller than the interface's, the packet will end up too large - // TODO allow specifying MTU at submission time + // Empty path has MTU 0, so let's just use the interface's MTU + // If the real MTU is smaller than the interface's, + // a payloadlength can be supplied when submitting the transfer. pathMTU = path.iface.MTU - scionHeaderLen - underlayHeaderLen } pathPayloadlen := pathMTU - scionHeaderLen @@ -118,7 +118,7 @@ func (ptd *PathsToDestination) choosePaths() bool { // Cap to Hercules' max pkt size maxPayloadlen = min(maxPayloadlen, HerculesMaxPktsize-scionHeaderLen-underlayHeaderLen) // Check the interface's MTU is large enough - if maxPayloadlen + scionHeaderLen + underlayHeaderLen - 14 > path.iface.MTU { + if maxPayloadlen+scionHeaderLen+underlayHeaderLen-14 > path.iface.MTU { // Packet exceeds the interface MTU // 14 is the size of the ethernet header, which is not included in the interface's MTU fmt.Printf("Interface (%v) MTU too low, decreasing payload length", path.iface.Name) From 78031a187624e8318271083788bc3023650e9619 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 24 Jun 2024 08:50:33 +0200 Subject: [PATCH 057/112] monitor cleanup update readme fix makefile dependency move kick_tx around merge two make_rx_state functions --- .gitignore | 5 +- Makefile | 7 +- README.md | 62 ++++++++ hercules.c | 117 ++++++-------- hercules.conf | 9 ++ monitor/config.go | 8 +- monitor/http_api.go | 41 +++-- monitor/monitor.go | 24 ++- monitor/pathmanager.go | 1 + monitor/pathstodestination.go | 2 +- monitor/scionheader.go | 4 +- packet.h | 2 +- monitor/sampleconf.toml => sampleconf.toml | 5 +- test.sh | 174 +++++++++++++++++++++ 14 files changed, 362 insertions(+), 99 deletions(-) create mode 100644 hercules.conf rename monitor/sampleconf.toml => sampleconf.toml (94%) create mode 100755 test.sh diff --git a/.gitignore b/.gitignore index 5b1801d..1aab344 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ bpf_prgm/*.ll bpf_prgm/*.o -hercules +hercules-server +hercules-monitor +*.o +monitor/monitor mockules/mockules diff --git a/Makefile b/Makefile index 2ca4294..873cb5a 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,11 @@ CFLAGS += -DCHECK_SRC_ADDRESS ## for debugging: # ASAN_FLAG := -fsanitize=address # CFLAGS += -g3 -DDEBUG $(ASAN_FLAG) -# CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets +# +# CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets (lots of noise!) -LDFLAGS = -flto -lbpf -Lbpf/src -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) +LDFLAGS = -flto -l:libbpf.a -Lbpf/src -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) DEPFLAGS := -MP -MD SRCS := $(wildcard *.c) @@ -41,7 +42,7 @@ endif cp hercules-server hercules-monitor hercules.conf $(DESTDIR) # List all headers as dependency because we include a header file via cgo (which in turn may include other headers) -$(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) +$(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) builder docker exec -w /`basename $(PWD)`/monitor hercules-builder go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" $(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a builder diff --git a/README.md b/README.md index 692dc0f..516ac28 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,65 @@ +# Hercules-server +## Overview +This version of Hercules consists of two components: + +- The monitor (Go) is responsible for handling SCION paths and exposes a HTTP API which clients can use to submit new transfers, check the status of ongoing transfers, or cancel them. +- The server (C) carries out the actual file transfers. + +Unlike previously, these are two separate processes that communicate via a unix socket. +They share a configuration file, the simplest version of which is in `hercules.conf`; see `sampleconf.toml` for all options. + +## Changes from regular Hercules +The following should be the most important changes: + +- Split C and Go parts into dedicated processes, with a unix socket between them. +- This is now a server, i.e. intended to run forever, not just a single-transfer application. +- No command line arguments (except for, optionally, the config file path), all options set in config file. +- The sender includes the destination file path for the receiver to use in the setup handshake. +CAVEAT: No checks on that path at the moment, so any file on the destination can be overwritten. +- Multiple concurrent transfers (both rx and tx) are supported (up to `HERCULES_CONCURRENT_SESSIONS`). +- Only 1 destination per transfer, the idea is to submit the transfer once per destination if multiple are needed. +- Transfer of entire directories is supported: If the source path is a directory, Hercules will try to recursively transfer the entire directory. +- In order to inform the receiver of the directory structure, a directory index is transferred at the beginning. +If this index is larger than the space available in the initial packet, a first phase of RBUDP is performed to transfer the index. +- Automatic MTU selection: At the start of a transfer, Hercules will pick the MTU (and, consequently, chunk length) that is as large as possible while still fitting on its selected paths. +This relies on the MTU in the path metadata, for the empty path the sending interface's MTU is used. +This behaviour can be overridden by manually supplying the desired payload length. +- The server will periodically update a transfer's paths by querying the monitor. The monitor will reply with the set of paths to use, which do not need to overlap with the previous set. +There is a restriction on path selection: All paths must be large enough to fit the payload length fixed at the beginning of the transfer. +- Implemented SCMP error handling: Upon receiving an error, the offending path will be disabled. It will be re-enabled if it is returned by the monitor in the next path update. +- Check the SCION/UDP source address/port of received packets match the expected values (i.e. the host that started the transfer). + +## Getting started + +### Building +Running `make` should build both the monitor and server. This uses the provided dockerfile. + +### Running +On both the sender and the receiver: + +- Fill in the provided `hercules.conf` with the host's SCION address and NIC +- First, start the monitor: `./hercules-monitor`. +- If there is already an XDP program loaded on the interface, you first have to remove it (e.g. `ip l set dev eth0 xdp off`) +Then, in a second shell, start the server: `./hercules-server`. + +### Submitting a transfer +- To transfer `infile` from the sender to `outfile` at the receiver with address `64-2:0:c,148.187.128.136:8000`, run the following on the sender: `curl "localhost:8000/submit?file=infile&destfile=outfile&dest=64-2:0:c,148.187.128.136:8000"`. +- This should yield `OK 1`, where `1` is the submitted transfer's job id. +- You should see the transfer progress in the server's output at both the sender and receiver. +- It is also possible to query the transfer status via `curl "localhost:8000/status?id=1"`, though the output is not intended to be read by humans. + +## Testing/Debugging + +- It may be useful to uncomment some of the lines marked "for debugging" at the top of the Makefile. +- If you want to run under valgrind, passing `--fair-sched=yes` is helpful. +- The script `test.sh` will try 3 sets of transfers: a file, a directory, and 2 files concurrently. +In order to use it, adjust the definitions at the top of the file (hostnames, addresses and interfaces). +The script further relies on you having ssh keys set up for those two hosts. +Depending on the network cards you may need to comment in the two lines with `ConfigureQueues = false`. + + +# Readme not updated below this line. + # Hercules High speed bulk data transfer application. diff --git a/hercules.c b/hercules.c index 211b260..0da4dea 100644 --- a/hercules.c +++ b/hercules.c @@ -333,7 +333,7 @@ static void destroy_session_tx(struct hercules_server *server, assert(session->state == SESSION_STATE_DONE); int ret = munmap(session->tx_state->mem, session->tx_state->filesize); - if (ret == 0) { + if (ret) { // XXX Is there anything we can do if this fails? fprintf(stderr, "munmap failure!\n"); exit_with_error(server, errno); @@ -367,7 +367,7 @@ static void destroy_session_rx(struct hercules_server *server, assert(session->state == SESSION_STATE_DONE); int ret = munmap(session->rx_state->mem, session->rx_state->filesize); - if (ret == 0) { + if (ret) { fprintf(stderr, "munmap failure!\n"); exit_with_error(server, errno); } @@ -1136,7 +1136,7 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { bool encountered_err = false; for (char *p = index; p < index + index_size;) { struct dir_index_entry *entry = (struct dir_index_entry *)p; - debug_printf("Read: %s (%d) %dB", entry->path, entry->type, + debug_printf("Read: %s (%d) %lluB", entry->path, entry->type, entry->filesize); int ret; @@ -1182,7 +1182,7 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { next_mapping += filesize_up; close(f); } else if (entry->type == INDEX_TYPE_DIR) { - ret = mkdir((char *)entry->path, 0664); + ret = mkdir((char *)entry->path, 0775); if (ret != 0) { if (errno == EEXIST) { struct stat statbuf; @@ -1223,60 +1223,50 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { } // Create new receiver state. Returns null in case of error. -static struct receiver_state *make_rx_state(struct hercules_session *session, - char *index, size_t index_size, - size_t filesize, int chunklen, - u16 src_port, - bool is_pcc_benchmark) { +static struct receiver_state *make_rx_state( + struct hercules_session *session, struct rbudp_initial_pkt *parsed_pkt, + u16 src_port, bool can_map) { struct receiver_state *rx_state; rx_state = calloc(1, sizeof(*rx_state)); - if (rx_state == NULL){ + if (rx_state == NULL) { return NULL; } rx_state->session = session; - rx_state->filesize = filesize; - rx_state->chunklen = chunklen; - rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; - bitset__create(&rx_state->received_chunks, rx_state->total_chunks); - rx_state->start_time = 0; - rx_state->end_time = 0; - rx_state->handshake_rtt = 0; - rx_state->is_pcc_benchmark = is_pcc_benchmark; - rx_state->src_port = src_port; - rx_state->mem = rx_mmap(index, index_size, filesize); - if (rx_state->mem == NULL) { - bitset__destroy(&rx_state->received_chunks); + rx_state->filesize = parsed_pkt->filesize; + rx_state->index_size = parsed_pkt->index_len; + rx_state->chunklen = parsed_pkt->chunklen; + rx_state->index = calloc(1, parsed_pkt->index_len); + if (rx_state->index == NULL) { + debug_printf("Error allocating index"); free(rx_state); return NULL; } - return rx_state; -} - -// For index transfer: Create new receiver state without mapping a file. Returns -// null in case of error. -static struct receiver_state *make_rx_state_nomap( - struct hercules_session *session, size_t index_size, - size_t filesize, int chunklen, u16 src_port, bool is_pcc_benchmark) { - struct receiver_state *rx_state; - rx_state = calloc(1, sizeof(*rx_state)); - if (rx_state == NULL) { - return NULL; - } - rx_state->session = session; - rx_state->filesize = filesize; - rx_state->chunklen = chunklen; - rx_state->total_chunks = (filesize + chunklen - 1) / chunklen; - rx_state->index_chunks = (index_size + chunklen - 1) / chunklen; + rx_state->total_chunks = + (rx_state->filesize + rx_state->chunklen - 1) / rx_state->chunklen; + rx_state->index_chunks = + (rx_state->index_size + rx_state->chunklen - 1) / rx_state->chunklen; bitset__create(&rx_state->received_chunks, rx_state->total_chunks); bitset__create(&rx_state->received_chunks_index, rx_state->index_chunks); rx_state->start_time = 0; rx_state->end_time = 0; rx_state->handshake_rtt = 0; + rx_state->is_pcc_benchmark = false; +#ifdef PCC_BENCH + rx_state->is_pcc_benchmark = true; +#endif rx_state->src_port = src_port; - rx_state->is_pcc_benchmark = is_pcc_benchmark; - // XXX We cannot map the file(s) yet since we don't have the index, - // but we could already reserve the required range (to check if there's even - // enough memory available) + if (can_map) { + rx_state->mem = rx_mmap((char *)parsed_pkt->index, + parsed_pkt->index_len, rx_state->filesize); + if (rx_state->mem == NULL) { + bitset__destroy(&rx_state->received_chunks); + free(rx_state); + return NULL; + } + } + // XXX In case of a separate index transfer, we cannot map the file(s) yet + // since we don't have the index, but we could already reserve the required + // range (to check if there's even enough memory available) return rx_state; } @@ -1427,8 +1417,7 @@ static void rx_accept_new_session(struct hercules_server *server, // Entire index contained in this packet, // we can go ahead and proceed with transfer struct receiver_state *rx_state = make_rx_state( - session, (char *)parsed_pkt->index, parsed_pkt->index_len, - parsed_pkt->filesize, parsed_pkt->chunklen, src_port, false); + session, parsed_pkt, src_port, true); if (rx_state == NULL) { debug_printf("Error creating RX state!"); destroy_send_queue(session->send_queue); @@ -1445,9 +1434,8 @@ static void rx_accept_new_session(struct hercules_server *server, } else { // Index transferred separately - struct receiver_state *rx_state = make_rx_state_nomap( - session, parsed_pkt->index_len, parsed_pkt->filesize, - parsed_pkt->chunklen, src_port, false); + struct receiver_state *rx_state = make_rx_state( + session, parsed_pkt, src_port, false); if (rx_state == NULL) { debug_printf("Error creating RX state!"); destroy_send_queue(session->send_queue); @@ -1455,14 +1443,6 @@ static void rx_accept_new_session(struct hercules_server *server, free(session); return; } - rx_state->index_size = parsed_pkt->index_len; - rx_state->index = calloc(1, parsed_pkt->index_len); - if (rx_state->index == NULL) { - debug_printf("Error allocating index"); - destroy_send_queue(session->send_queue); - free(session); - return; - } session->rx_state = rx_state; rx_handle_initial(server, rx_state, parsed_pkt, buf, @@ -2105,13 +2085,6 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 total_chunks = tx_state->total_chunks; } if (chunk_idx == total_chunks) { - /* debug_printf("prev %d", rcvr->prev_chunk_idx); */ - if(tx_state->prev_chunk_idx == 0) { // this receiver has finished - debug_printf("receiver has finished"); - tx_state->finished = true; - break; - } - // switch round for this receiver: debug_printf("Receiver %d switches to next round", rcvr_idx); @@ -2428,9 +2401,9 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, if (index == NULL) { return NULL; } - debug_printf("total filesize %lld", total_filesize); - debug_printf("real filesize %lld", real_filesize); - debug_printf("total entry size %lld", index_size); + debug_printf("total filesize %llu", total_filesize); + debug_printf("real filesize %llu", real_filesize); + debug_printf("total entry size %llu", index_size); char *mem = mmap(NULL, total_filesize, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0); @@ -2438,6 +2411,7 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, free(index); return NULL; } + debug_printf("Mapped at %p, %llu", mem, total_filesize); // Now we go over the directory tree we just generated and // - Map the files @@ -2461,7 +2435,7 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, bool encountered_err = false; for (char *p = index; p < index + index_size;) { struct dir_index_entry *entry = (struct dir_index_entry *)p; - debug_printf("Read: %s (%d) %dB", entry->path, entry->type, + debug_printf("Read: %s (%d) %lluB", entry->path, entry->type, entry->filesize); int src_path_len = strlen((char *)entry->path); @@ -2696,7 +2670,6 @@ static void tx_send_p(void *arg) { struct hercules_session *session_tx = server->sessions_tx[cur_session]; if (session_tx == NULL || !session_state_is_running(session_tx->state)) { - kick_tx_server(server); // flush any pending packets continue; } bool is_index_transfer = (session_tx->state == SESSION_STATE_RUNNING_IDX); @@ -2729,6 +2702,7 @@ static void tx_send_p(void *arg) { frame_addrs, &unit, args->id, is_index_transfer); atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); + kick_tx_server(server); } } @@ -3893,9 +3867,10 @@ int main(int argc, char *argv[]) { debug_printf( "Starting Hercules using queue %d, queue config %d, %d worker " "threads, " - "xdp mode 0x%x", + "xdp mode 0x%x, " + "Rate limit %d, PCC %d", config.queue, config.configure_queues, config.n_threads, - config.xdp_mode); + config.xdp_mode, config.rate_limit, config.enable_pcc); struct hercules_server *server = hercules_init_server(config, if_idxs, n_interfaces); diff --git a/hercules.conf b/hercules.conf new file mode 100644 index 0000000..4e2c8fd --- /dev/null +++ b/hercules.conf @@ -0,0 +1,9 @@ +# This is the default config file + +# SCION address the Hercules server should listen on +# ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" + +# Network interfaces to use for Hercules +Interfaces = [ +# "eth0", +] diff --git a/monitor/config.go b/monitor/config.go index 52a0898..3b3903b 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -49,6 +49,7 @@ type MonitorConfig struct { DefaultNumPaths int MonitorSocket string ListenAddress UDPAddr + MonitorHTTP string Interfaces []Interface // The following are not used by the monitor, they are listed here for completeness ServerSocket string @@ -94,8 +95,7 @@ func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { } } -const defaultConfigPath = "herculesmon.conf" -const defaultMonitorSocket = "var/run/herculesmon.sock" +const defaultMonitorHTTP = ":8000" // Decode the config file and fill in any unspecified values with defaults. // Will exit if an error occours or a required value is not specified. @@ -120,6 +120,10 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { config.MonitorSocket = defaultMonitorSocket } + if config.MonitorHTTP == "" { + config.MonitorHTTP = defaultMonitorHTTP + } + // This is required if config.ListenAddress.addr == nil { fmt.Println("Error: Listening address not specified") diff --git a/monitor/http_api.go b/monitor/http_api.go index 7b069a4..2e9b8c3 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -12,34 +12,44 @@ import ( // Handle submission of a new transfer // GET params: -// file (File to transfer) +// file (Path to file to transfer) +// destfile (Path at destination) // dest (Destination IA+Host) +// payloadlen (optional, override automatic MTU selection) func http_submit(w http.ResponseWriter, r *http.Request) { if !r.URL.Query().Has("file") || !r.URL.Query().Has("destfile") || !r.URL.Query().Has("dest") { - io.WriteString(w, "missing parameter") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "Missing parameter\n") return } file := r.URL.Query().Get("file") destfile := r.URL.Query().Get("destfile") dest := r.URL.Query().Get("dest") - fmt.Println(file, destfile, dest) destParsed, err := snet.ParseUDPAddr(dest) if err != nil { - io.WriteString(w, "parse err") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "parse err\n") return } + payloadlen := 0 // 0 means automatic selection if r.URL.Query().Has("payloadlen") { payloadlen, err = strconv.Atoi(r.URL.Query().Get("payloadlen")) if err != nil { - io.WriteString(w, "parse err") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "parse err\n") return } } destination := findPathRule(&pathRules, destParsed) - pm, _ := initNewPathManager(activeInterfaces, &destination, listenAddress, payloadlen) + pm, err := initNewPathManager(activeInterfaces, &destination, listenAddress, payloadlen) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + fmt.Printf("Received submission: %v -> %v %v\n", file, dest, destfile) transfersLock.Lock() jobid := nextID transfers[nextID] = &HerculesTransfer{ @@ -62,19 +72,24 @@ func http_submit(w http.ResponseWriter, r *http.Request) { // Returns OK status state err seconds_elapsed chucks_acked func http_status(w http.ResponseWriter, r *http.Request) { if !r.URL.Query().Has("id") { - io.WriteString(w, "missing parameter") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing parameter\n") return } id, err := strconv.Atoi(r.URL.Query().Get("id")) if err != nil { + w.WriteHeader(http.StatusBadRequest) return } + transfersLock.Lock() info, ok := transfers[id] transfersLock.Unlock() if !ok { + w.WriteHeader(http.StatusBadRequest) return } + io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.bytes_acked)) } @@ -84,16 +99,19 @@ func http_status(w http.ResponseWriter, r *http.Request) { // Returns OK func http_cancel(w http.ResponseWriter, r *http.Request) { if !r.URL.Query().Has("id") { - io.WriteString(w, "missing parameter") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing parameter\n") return } id, err := strconv.Atoi(r.URL.Query().Get("id")) if err != nil { + w.WriteHeader(http.StatusBadRequest) return } transfersLock.Lock() info, ok := transfers[id] if !ok { + w.WriteHeader(http.StatusBadRequest) transfersLock.Unlock() return } @@ -109,7 +127,8 @@ func http_cancel(w http.ResponseWriter, r *http.Request) { // Returns OK exists? size func http_stat(w http.ResponseWriter, r *http.Request) { if !r.URL.Query().Has("file") { - io.WriteString(w, "missing parameter") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing parameter\n") return } file := r.URL.Query().Get("file") @@ -118,10 +137,12 @@ func http_stat(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "OK 0 0\n") return } else if err != nil { + w.WriteHeader(http.StatusBadRequest) io.WriteString(w, "err\n") return } - if !info.Mode().IsRegular() { + if !info.Mode().IsRegular() && !info.Mode().IsDir() { + w.WriteHeader(http.StatusBadRequest) io.WriteString(w, "err\n") return } diff --git a/monitor/monitor.go b/monitor/monitor.go index 53132a5..6ca2a2b 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -18,6 +18,8 @@ import ( import "C" const HerculesMaxPktsize = C.HERCULES_MAX_PKTSIZE +const defaultConfigPath = C.HERCULES_DEFAULT_CONFIG_PATH +const defaultMonitorSocket = C.HERCULES_DEFAULT_MONITOR_SOCKET // Select paths and serialize headers for a given transfer func headersToDestination(transfer HerculesTransfer) (int, []byte) { @@ -29,7 +31,11 @@ func headersToDestination(transfer HerculesTransfer) (int, []byte) { IA: transfer.dest.IA, Host: addr.MustParseHost(transfer.dest.Host.IP.String()), } - transfer.pm.choosePaths() + ok := transfer.pm.choosePaths() + if !ok { + fmt.Println("Error choosing paths!") + return 0, nil + } paths := transfer.pm.dst.paths enabledPaths := []PathMeta{} for _, p := range paths { @@ -128,7 +134,7 @@ func main() { // used for looking up reply path interface pm, err := initNewPathManager(activeInterfaces, &Destination{ hostAddr: config.ListenAddress.addr, - }, config.ListenAddress.addr) + }, config.ListenAddress.addr, 0) if err != nil { fmt.Printf("Error initialising path manager: %v\n", err) os.Exit(1) @@ -139,13 +145,12 @@ func main() { http.HandleFunc("/status", http_status) http.HandleFunc("/cancel", http_cancel) http.HandleFunc("/stat", http_stat) - go http.ListenAndServe(":8000", nil) + go http.ListenAndServe(config.MonitorHTTP, nil) // Communication is always initiated by the server, // the monitor's job is to respond to queries from the server for { buf := make([]byte, C.SOCKMSG_SIZE) - fmt.Println("read...", C.SOCKMSG_SIZE) n, a, err := usock.ReadFromUnix(buf) if err != nil { fmt.Println("Error reading from socket!", err) @@ -197,7 +202,7 @@ func main() { transfersLock.Unlock() var b []byte if selectedJob != nil { - fmt.Println("sending file to daemon:", selectedJob.file, selectedJob.destFile, selectedJob.id) + fmt.Println("Sending transfer to daemon:", selectedJob.file, selectedJob.destFile, selectedJob.id) _, _ = headersToDestination(*selectedJob) // look up paths to fix mtu strlen_src := len(selectedJob.file) strlen_dst := len(selectedJob.destFile) @@ -243,8 +248,14 @@ func main() { bytes_acked := binary.LittleEndian.Uint64(buf[:8]) buf = buf[8:] fmt.Println("updating job", job_id, status, errorcode) + var b []byte transfersLock.Lock() - job, _ := transfers[int(job_id)] + job, ok := transfers[int(job_id)] + if !ok { + b = binary.LittleEndian.AppendUint16(b, uint16(0)) + usock.WriteToUnix(b,a) + continue + } job.state = status job.err = errorcode if job.state == C.SESSION_STATE_DONE { @@ -255,7 +266,6 @@ func main() { job.time_elapsed = int(seconds) isCancelled := job.status == Cancelled transfersLock.Unlock() - var b []byte if isCancelled { b = binary.LittleEndian.AppendUint16(b, uint16(0)) } else { diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 5da6d05..159b6f9 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -40,6 +40,7 @@ type PathWithInterface struct { iface *net.Interface } +// Setting payloadlen to 0 means automatic selection func initNewPathManager(interfaces []*net.Interface, dst *Destination, src *snet.UDPAddr, payloadLen int) (*PathManager, error) { ifMap := make(map[int]*net.Interface) for _, iface := range interfaces { diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index d5d15f9..99e75eb 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -73,7 +73,7 @@ func (ptd *PathsToDestination) choosePaths() bool { return false } - if allPaths == nil { + if allPaths == nil || len(allPaths) == 0 { return false } diff --git a/monitor/scionheader.go b/monitor/scionheader.go index d130914..2e553d6 100644 --- a/monitor/scionheader.go +++ b/monitor/scionheader.go @@ -361,7 +361,7 @@ func prepareHeader(path PathMeta, payloadLen int, srcUDP, dstUDP net.UDPAddr, sr // Return the path's underlay and scion header length by creating a bogus packet. func getPathHeaderlen(path snet.Path) (int, int) { nilMAC := []byte{0, 0, 0, 0, 0, 0} - nilIP := []byte{0,0,0,0} + nilIP := []byte{0, 0, 0, 0} underlayHeader, err := prepareUnderlayPacketHeader(nilMAC, nilMAC, nilIP, path.UnderlayNextHop().IP, uint16(path.UnderlayNextHop().Port), 9000) if err != nil { return 0, 0 @@ -388,7 +388,7 @@ func getPathHeaderlen(path snet.Path) (int, int) { if err = destPkt.Serialize(); err != nil { fmt.Println("serializer err", err) - return 0,0 + return 0, 0 } fmt.Println("Header length: ", len(destPkt.Bytes)) fmt.Println("Underlay Header length: ", len(underlayHeader)) diff --git a/packet.h b/packet.h index 7a3a85e..b8d5152 100644 --- a/packet.h +++ b/packet.h @@ -117,7 +117,7 @@ struct hercules_header { #define INDEX_TYPE_FILE 0 #define INDEX_TYPE_DIR 1 struct dir_index_entry { - __u32 filesize; + __u64 filesize; __u32 path_len; __u8 type; __u8 path[]; diff --git a/monitor/sampleconf.toml b/sampleconf.toml similarity index 94% rename from monitor/sampleconf.toml rename to sampleconf.toml index b42d874..78553d2 100644 --- a/monitor/sampleconf.toml +++ b/sampleconf.toml @@ -12,6 +12,9 @@ ServerSocket = "var/run/hercules.sock" # SCION address the Hercules server should listen on ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" +# Listenting address for the monitor (HTTP) +MonitorHTTP = ":8000" + # Network interfaces to use for Hercules Interfaces = [ "eth0", @@ -35,7 +38,7 @@ ConfigureQueues = false EnablePCC = false # This sets a sending rate limit (in pps) -# that applies to all transfers individually +# that applies to transfers individually RateLimit = 100000 # The number of RX/TX worker threads to use diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..421e69a --- /dev/null +++ b/test.sh @@ -0,0 +1,174 @@ +#!/bin/bash +set -euo pipefail +# set -x + +HOST_A_CMD="ssh scionclient1" +HOST_B_CMD="ssh scionclient2" + +A_API="192.168.10.92:8000" +A_SRV="17-ffaa:1:fe2,192.168.10.121:8000" +A_IFACE="ens5f0" +B_SRV="17-ffaa:1:113c,192.168.10.141:8000" +B_IFACE="ens5f0" + +testfile="testfile" +testfile2="testfile2" +testdir="testdir" +destfile="destfile" +destfile2="destfile2" +destdir="destdir" + +tmux kill-session -t hercules-test || true +tmux new-session -d -s hercules-test + +$HOST_A_CMD dd if=/dev/urandom bs=1K count=50 of="$testfile" +$HOST_A_CMD dd if=/dev/urandom bs=1K count=50 of="$testfile2" +$HOST_A_CMD sudo "mkdir -p ${testdir}; for i in {1..10}; do echo \$i > $testdir/file\$i; done;" +$HOST_B_CMD sudo rm -rf "$destfile" +$HOST_B_CMD sudo rm -rf "$destfile2" +$HOST_B_CMD sudo rm -rf "$destdir" +testfile_sum=$($HOST_A_CMD md5sum $testfile | cut -d ' ' -f1) +testfile2_sum=$($HOST_A_CMD md5sum $testfile2 | cut -d ' ' -f1) + +$HOST_A_CMD "echo 'ListenAddress = \"$A_SRV\"' > test_a.toml" +$HOST_A_CMD "echo 'Interfaces = [\"$A_IFACE\"]' >> test_a.toml" +# $HOST_A_CMD "echo 'ConfigureQueues = false' >> test_a.toml" + +$HOST_B_CMD "echo 'ListenAddress = \"$B_SRV\"' > test_b.toml" +$HOST_B_CMD "echo 'Interfaces = [\"$B_IFACE\"]' >> test_b.toml" +# $HOST_B_CMD "echo 'ConfigureQueues = false' >> test_b.toml" + +# # Start the monitor +tmux new-window -n mon_a -t hercules-test: "$HOST_A_CMD" +tmux send-keys -t hercules-test:mon_a sudo\ ./hercules-monitor\ -c\ test_a.toml ENTER +sleep 0.5 + +# # Start the server +tmux new-window -n srv_a -t hercules-test: "$HOST_A_CMD" +tmux send-keys -t hercules-test:srv_a sudo\ ./hercules-server\ -c\ test_a.toml ENTER + +# # Start the monitor +tmux new-window -n mon_b -t hercules-test: "$HOST_B_CMD" +tmux send-keys -t hercules-test:mon_b sudo\ ./hercules-monitor\ -c\ test_b.toml ENTER +sleep 0.5 + +# # Start the server +tmux new-window -n srv_b -t hercules-test: "$HOST_B_CMD" +tmux send-keys -t hercules-test:srv_b sudo\ ./hercules-server\ -c\ test_b.toml ENTER + +quit () { + set +e + $HOST_A_CMD sudo pkill hercules-server + $HOST_B_CMD sudo pkill hercules-server + exit 1 +} + +# # Transfer a single file +echo "Submitting single file" +id=$(curl -s "$A_API/submit?file=$testfile&dest=$B_SRV&destfile=$destfile" | cut -d ' ' -f 2) +echo "Job has id $id" +sleep 1 + +while true; do +echo -n "." +response=$(curl -s "$A_API/status?id=$id") +status=$(echo "$response" | cut -d ' ' -f 2) +err=$(echo "$response" | cut -d ' ' -f 4) +if [[ "$status" == "3" ]] +then + break +fi +sleep 1 +done + +echo "" +if [[ "$err" == 1 ]] +then +echo "File transfer done" +else + echo "File transfer error: $err" + quit +fi +destfile_sum=$($HOST_B_CMD md5sum $destfile | cut -d ' ' -f1) +if [[ $destfile_sum != $testfile_sum ]] +then + echo "Checksum mismatch!" + quit +fi + +# Transfer a directory +echo "Submitting directory" +id=$(curl -s "$A_API/submit?file=$testdir&dest=$B_SRV&destfile=$destdir" | cut -d ' ' -f 2) +echo "Job has id $id" +sleep 1 + +while true; do +echo -n "." +response=$(curl -s "$A_API/status?id=$id") +status=$(echo "$response" | cut -d ' ' -f 2) +err=$(echo "$response" | cut -d ' ' -f 4) +if [[ "$status" == "3" ]] +then + break +fi +sleep 1 +done + +echo "" +if [[ "$err" == 1 ]] +then +echo "Directory transfer complete" +else + echo "Directory transfer error" + quit +fi +for i in {1..10}; +do + if [[ $($HOST_B_CMD "sudo cat $destdir/file$i") != $i ]] + then + echo "File content incorrect" + quit + fi +done + +# Transfer multiple files concurrently +echo "Submitting 2 files" +id=$(curl -s "$A_API/submit?file=$testfile&dest=$B_SRV&destfile=$destfile" | cut -d ' ' -f 2) +echo "Job has id $id" +id2=$(curl -s "$A_API/submit?file=$testfile2&dest=$B_SRV&destfile=$destfile2" | cut -d ' ' -f 2) +echo "Job 2 has id $id2" +sleep 1 + +while true; do +echo -n "." +response=$(curl -s "$A_API/status?id=$id") +response2=$(curl -s "$A_API/status?id=$id2") +status=$(echo "$response" | cut -d ' ' -f 2) +status2=$(echo "$response2" | cut -d ' ' -f 2) +err=$(echo "$response" | cut -d ' ' -f 4) +err2=$(echo "$response2" | cut -d ' ' -f 4) +if [[ "$status" == "3" && "$status2" == 3 ]] +then + break +fi +sleep 1 +done + +echo "" +if [[ "$err" == 1 && "$err2" == 1 ]] +then +echo "Multiple file transfer complete" +else + echo "Multiple file transfer error" + quit +fi + +destfile_sum=$($HOST_B_CMD md5sum $destfile | cut -d ' ' -f1) +destfile2_sum=$($HOST_B_CMD md5sum $destfile2 | cut -d ' ' -f1) +if [[ $destfile_sum != $testfile_sum || $destfile2_sum != $testfile2_sum ]] +then + echo "Checksum mismatch!" + quit +fi + +quit From ac75f795ee501e8af769ccd849581e6b3bb8b130 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 10:48:55 +0200 Subject: [PATCH 058/112] Ensure RTT is updated at receiver --- hercules.c | 57 +++++++++++++++++++++++++++++++++++++++++++++--------- hercules.h | 1 + packet.h | 1 + 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/hercules.c b/hercules.c index 0da4dea..e7c3705 100644 --- a/hercules.c +++ b/hercules.c @@ -254,6 +254,9 @@ void debug_print_rbudp_pkt(const char *pkt, bool recv) { } printf("\n"); break; + case CONTROL_PACKET_TYPE_RTT: + printf("%s RTT\n", prefix); + break; default: printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); break; @@ -1358,6 +1361,7 @@ static void rx_handle_initial(struct hercules_server *server, if (!ok) { quit_session(rx_state->session, SESSION_ERROR_NO_PATHS); } + rx_state->sent_initial_at = get_nsecs(); } rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize } @@ -1767,6 +1771,29 @@ static void tx_send_initial(struct hercules_server *server, session->last_pkt_sent = timestamp; } +static void tx_send_rtt(struct hercules_server *server, + struct hercules_session *session, + const struct hercules_path *path, u64 timestamp) { + debug_printf("Sending RTT packet"); + struct sender_state *tx_state = session->tx_state; + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + + struct hercules_control_packet pld = { + .type = CONTROL_PACKET_TYPE_RTT, + }; + + stitch_src_port(path, tx_state->src_port, buf); + stitch_dst_port(path, session->peer.port, buf); + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&pld, + sizeof(pld.type), path->payloadlen); + stitch_checksum_with_dst(path, path->header.checksum, buf); + + send_eth_frame(server, path, buf); + atomic_fetch_add(&session->tx_npkts, 1); + session->last_pkt_sent = timestamp; +} + static void rate_limit_tx(struct sender_state *tx_state) { if(tx_state->prev_tx_npkts_queued + RATE_LIMIT_CHECK > tx_state->tx_npkts_queued) @@ -2243,7 +2270,6 @@ static void tx_handle_hs_confirm(struct hercules_server *server, } ccontrol_update_rtt(pathset->paths[0].cc_state, tx_state->handshake_rtt); - // Return path is always idx 0 debug_printf( "[receiver %d] [path 0] handshake_rtt: " @@ -2262,6 +2288,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, session_tx->peer.port = pkt_src->port; debug_printf("Updating peer port for this session to %u", ntohs(pkt_src->port)); + tx_send_rtt(server, session_tx, &pathset->paths[0], now); if (tx_state->needs_index_transfer) { // Need to do index transfer first if (!(parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS)) { @@ -2283,7 +2310,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, #ifdef CHECK_SRC_ADDRESS if (!src_matches_address(session_tx, pkt_src)) { debug_printf( - "Dropping initail packet with wrong source IA/IP/Port"); + "Dropping initial packet with wrong source IA/IP/Port"); return; } #endif @@ -2293,6 +2320,8 @@ static void tx_handle_hs_confirm(struct hercules_server *server, if (server->config.enable_pcc) { ccontrol_update_rtt(pathset->paths[parsed_pkt->path_index].cc_state, now - parsed_pkt->timestamp); + debug_printf("[Path %d] New RTT %fs", parsed_pkt->path_index, + (now - parsed_pkt->timestamp) / 1e9); } pathset->paths[parsed_pkt->path_index].next_handshake_at = UINT64_MAX; @@ -2306,6 +2335,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, pathset->paths[p].cc_state->rtt = DBL_MAX; } } + tx_send_rtt(server, session_tx, &pathset->paths[0], now); } count_received_pkt(session_tx, pkt_path_idx); return; @@ -3440,6 +3470,10 @@ static void events_p(void *arg) { struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; u32 control_pkt_payloadlen = rbudp_len - rbudp_headerlen; + struct hercules_session *session_rx = + lookup_session_rx(server, pkt_dst_port); + struct hercules_session *session_tx = + lookup_session_tx(server, pkt_dst_port); switch (cp->type) { case CONTROL_PACKET_TYPE_INITIAL:; @@ -3456,8 +3490,6 @@ static void events_p(void *arg) { } // Otherwise, we process and reflect the packet - struct hercules_session *session_rx = - lookup_session_rx(server, pkt_dst_port); if (session_rx != NULL && session_state_is_running(session_rx->state)) { if (!(parsed_pkt->flags & @@ -3493,8 +3525,6 @@ static void events_p(void *arg) { debug_printf("ACK packet too short"); break; } - struct hercules_session *session_tx = - lookup_session_tx(server, pkt_dst_port); #ifdef CHECK_SRC_ADDRESS if (session_tx != NULL && !src_matches_address(session_tx, &pkt_source)) { @@ -3542,7 +3572,6 @@ static void events_p(void *arg) { debug_printf("NACK packet too short"); break; } - session_tx = lookup_session_tx(server, pkt_dst_port); #ifdef CHECK_SRC_ADDRESS if (session_tx != NULL && !src_matches_address(session_tx, &pkt_source)) { @@ -3574,12 +3603,22 @@ static void events_p(void *arg) { } } break; + + case CONTROL_PACKET_TYPE_RTT:; + if (session_rx && + src_matches_address(session_rx, &pkt_source)) { + struct receiver_state *rx_state = session_rx->rx_state; + rx_state->handshake_rtt = + get_nsecs() - rx_state->sent_initial_at; + debug_printf("Updating RTT to %fs", + rx_state->handshake_rtt / 1e9); + } + break; + default: debug_printf("Received control packet of unknown type"); break; } - struct hercules_session *session_tx = - lookup_session_tx(server, pkt_dst_port); if (session_tx) { pcc_monitor(session_tx->tx_state); } diff --git a/hercules.h b/hercules.h index 41ee79d..1983791 100644 --- a/hercules.h +++ b/hercules.h @@ -105,6 +105,7 @@ struct receiver_state { u16 src_port; // The UDP/SCION port to use when sending packets (LE) u64 start_time; // Start/end time of the current transfer u64 end_time; + u64 sent_initial_at; }; /// SENDER diff --git a/packet.h b/packet.h index b8d5152..82bef95 100644 --- a/packet.h +++ b/packet.h @@ -161,6 +161,7 @@ struct rbudp_ack_pkt { #define CONTROL_PACKET_TYPE_INITIAL 0 #define CONTROL_PACKET_TYPE_ACK 1 #define CONTROL_PACKET_TYPE_NACK 2 +#define CONTROL_PACKET_TYPE_RTT 3 struct hercules_control_packet { __u8 type; From 269c6351b9e430b476951d1381ac998da9877ab4 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 10:50:01 +0200 Subject: [PATCH 059/112] Inform monitor earlier about finished transfers --- hercules.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/hercules.c b/hercules.c index e7c3705..dd59353 100644 --- a/hercules.c +++ b/hercules.c @@ -3335,18 +3335,32 @@ static void rx_send_cts(struct hercules_server *server, int s) { // ERROR_NONE (generally via quit_session()). If the error is set, this changes // the sessions state to DONE. The state update needs to happen in the events_p // thread, otherwise there's a chance of getting stuck (e.g. in update_paths). -static void stop_finished_sessions(struct hercules_server *server, int slot) { +static void stop_finished_sessions(struct hercules_server *server, int slot, + u64 now) { struct hercules_session *session_tx = server->sessions_tx[slot]; if (session_tx != NULL && session_tx->state != SESSION_STATE_DONE && session_tx->error != SESSION_ERROR_NONE) { debug_printf("Stopping TX %d", slot); session_tx->state = SESSION_STATE_DONE; + u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; + u64 bytes_acked = session_tx->tx_state->chunklen * + session_tx->tx_state->acked_chunks.num_set; + // OK to ignore return value: We're done with the transfer and don't + // care if the monitor wants us to stop it + monitor_update_job(server->usock, session_tx->jobid, session_tx->state, + session_tx->error, sec_elapsed, bytes_acked); } struct hercules_session *session_rx = server->sessions_rx[slot]; if (session_rx != NULL && session_rx->state != SESSION_STATE_DONE && session_rx->error != SESSION_ERROR_NONE) { debug_printf("Stopping RX %d", slot); session_rx->state = SESSION_STATE_DONE; + int ret = + msync(session_rx->rx_state->mem, session_rx->rx_state->filesize, + MS_ASYNC); // XXX do we need SYNC here? + if (ret) { + debug_printf("msync err? %s", strerror(errno)); + } } } @@ -3373,7 +3387,7 @@ static void events_p(void *arg) { current_slot = (current_slot + 1) % HERCULES_CONCURRENT_SESSIONS; mark_timed_out_sessions(server, current_slot, now); - stop_finished_sessions(server, current_slot); + stop_finished_sessions(server, current_slot, now); tx_update_monitor(server, current_slot, now); tx_update_paths(server, current_slot, now); cleanup_finished_sessions(server, current_slot, now); From 03241462f75140cdc0856a48d0ceaaae7385dc00 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 11:32:38 +0200 Subject: [PATCH 060/112] fix some memory leaks (cc state) --- congestion_control.c | 5 +++-- hercules.c | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/congestion_control.c b/congestion_control.c index 2eb6ea6..b28addc 100644 --- a/congestion_control.c +++ b/congestion_control.c @@ -114,9 +114,10 @@ void kick_ccontrol(struct ccontrol_state *cc_state) //cc_state->state = pcc_startup; } -void destroy_ccontrol_state(struct ccontrol_state *cc_states) +void destroy_ccontrol_state(struct ccontrol_state *cc_state) { - free(cc_states); + bitset__destroy(&cc_state->mi_nacked); + free(cc_state); } // XXX: explicitly use symbols from old libc version to allow building on diff --git a/hercules.c b/hercules.c index dd59353..43e9fbb 100644 --- a/hercules.c +++ b/hercules.c @@ -353,6 +353,7 @@ static void destroy_session_tx(struct hercules_server *server, } FREE_NULL(session->tx_state->pathset); + FREE_NULL(session->tx_state->epochs); FREE_NULL(session->tx_state->index); FREE_NULL(session->tx_state); @@ -378,6 +379,9 @@ static void destroy_session_rx(struct hercules_server *server, bitset__destroy(&session->rx_state->received_chunks); bitset__destroy(&session->rx_state->received_chunks_index); + for (int i = 0; i < 256; i++){ + bitset__destroy(&session->rx_state->path_state[i].seq_rcvd); + } FREE_NULL(session->rx_state->index); FREE_NULL(session->rx_state); From 2ec64bae27d0e165411d5be05dfa8702b2dfe3ed Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 11:34:23 +0200 Subject: [PATCH 061/112] exit instead of waiting if sciond unreachable --- monitor/network.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monitor/network.go b/monitor/network.go index 4e5fa0b..58cb46c 100644 --- a/monitor/network.go +++ b/monitor/network.go @@ -17,9 +17,11 @@ package main import ( "context" "fmt" + "os" + "time" + "github.com/scionproto/scion/pkg/daemon" "github.com/scionproto/scion/pkg/snet" - "os" ) func exit(err error) { @@ -40,7 +42,8 @@ func newDaemonConn(ctx context.Context) (daemon.Connector, error) { } func newPathQuerier() snet.PathQuerier { - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), time.Second * 10) + defer cancel() daemonConn, err := newDaemonConn(ctx) if err != nil { exit(err) From accd3797d0c97d6a07c92ca34a863d41db15cf10 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 11:35:13 +0200 Subject: [PATCH 062/112] print version at startup --- Makefile | 1 + hercules.c | 5 +++++ monitor/monitor.go | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Makefile b/Makefile index 873cb5a..eecef5d 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ MONITORFILES := $(wildcard monitor/*) VERSION := $(shell (ref=$$(git describe --tags --long --dirty 2>/dev/null) && echo $$(git rev-parse --abbrev-ref HEAD)-$$ref) ||\ echo $$(git rev-parse --abbrev-ref HEAD)-untagged-$$(git describe --tags --dirty --always)) +CFLAGS += -DHERCULES_VERSION="\"$(VERSION)\"" all: $(TARGET_MONITOR) $(TARGET_SERVER) diff --git a/hercules.c b/hercules.c index 43e9fbb..d569a25 100644 --- a/hercules.c +++ b/hercules.c @@ -3718,8 +3718,13 @@ void usage(){ exit(1); } +#ifndef HERCULES_VERSION +#define HERCULES_VERSION "Version ??" +#endif + #define HERCULES_MAX_INTERFACES 3 int main(int argc, char *argv[]) { + printf("Starting Hercules server [%s]\n", HERCULES_VERSION); unsigned int if_idxs[HERCULES_MAX_INTERFACES]; int n_interfaces = 0; char *config_path = NULL; diff --git a/monitor/monitor.go b/monitor/monitor.go index 6ca2a2b..9132ec2 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -101,7 +101,10 @@ var listenAddress *snet.UDPAddr var activeInterfaces []*net.Interface var pathRules PathRules +var startupVersion string + func main() { + fmt.Printf("Starting Hercules monitor [%v]\n", startupVersion) var configFile string flag.StringVar(&configFile, "c", defaultConfigPath, "Path to the monitor configuration file") flag.Parse() From fa56c7b3bb1ead806eb142ab0e799f3ef262e1cd Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 12:28:54 +0200 Subject: [PATCH 063/112] Improve printed error messages make job_id same size everywhere --- congestion_control.c | 2 - hercules.c | 85 +++++++++++++++++++++-------------- hercules.h | 2 +- monitor.c | 12 ++--- monitor.h | 12 ++--- monitor/monitor.go | 10 ++--- monitor/pathmanager.go | 2 +- monitor/pathstodestination.go | 1 - monitor/scionheader.go | 3 -- xdp.c | 2 +- 10 files changed, 72 insertions(+), 59 deletions(-) diff --git a/congestion_control.c b/congestion_control.c index b28addc..229836e 100644 --- a/congestion_control.c +++ b/congestion_control.c @@ -42,7 +42,6 @@ void ccontrol_start_monitoring_interval(struct ccontrol_state *cc_state) void ccontrol_update_rtt(struct ccontrol_state *cc_state, u64 rtt) { - debug_printf("update rtt"); cc_state->rtt = rtt / 1e9; float m = (rand() % 6) / 10.f + 1.7; // m in [1.7, 2.2] @@ -89,7 +88,6 @@ void continue_ccontrol(struct ccontrol_state *cc_state) u32 ccontrol_can_send_npkts(struct ccontrol_state *cc_state, u64 now) { if(cc_state->state == pcc_uninitialized) { - debug_printf("uninit"); cc_state->state = pcc_startup; cc_state->mi_start = get_nsecs(); cc_state->mi_end = cc_state->mi_start + cc_state->pcc_mi_duration * 1e9; diff --git a/hercules.c b/hercules.c index d569a25..e021dce 100644 --- a/hercules.c +++ b/hercules.c @@ -402,7 +402,8 @@ struct hercules_server *hercules_init_server(struct hercules_config config, server->usock = monitor_bind_daemon_socket(config.server_socket, config.monitor_socket); if (server->usock == 0) { - fprintf(stderr, "Error binding daemon socket\n"); + fprintf(stderr, + "Error binding daemon socket. Is the monitor running?\n"); exit_with_error(NULL, EINVAL); } @@ -1153,18 +1154,24 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { struct stat statbuf; ret = stat((char *)entry->path, &statbuf); if (ret) { + fprintf(stderr, "Error reading %s\n", (char *)entry->path); encountered_err = true; break; } if (!(S_ISREG(statbuf.st_mode))) { - debug_printf("path exists but is not a regular file?"); + fprintf( + stderr, + "Error: Path %s exists but is not a regular file?\n", + (char *)entry->path); encountered_err = true; break; } - debug_printf("Overwriting existing file!"); + fprintf(stderr, "!> Overwriting existing file: %s\n", + (char *)entry->path); f = open((char *)entry->path, O_RDWR); } if (f == -1) { + fprintf(stderr, "Error opening %s\n", (char *)entry->path); encountered_err = true; break; } @@ -1199,11 +1206,14 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { break; } if (!S_ISDIR(statbuf.st_mode)) { - debug_printf("path exists but is not a directory?"); + fprintf( + stderr, + "Error: Path %s exists but is not a directory?\n", + (char *)entry->path); encountered_err = true; break; } - fprintf(stderr, "Directory already exists: %s\n", + fprintf(stderr, "!> Directory already exists: %s\n", (char *)entry->path); } else { debug_printf("mkdir err"); @@ -2103,9 +2113,7 @@ static u32 prepare_rcvr_chunks(struct sender_state *tx_state, u32 rcvr_idx, u32 { u32 num_chunks_prepared = 0; u32 chunk_idx = tx_state->prev_chunk_idx; - /* debug_printf("n chunks %d", num_chunks); */ for(; num_chunks_prepared < num_chunks; num_chunks_prepared++) { - /* debug_printf("prepared %d/%d chunks", num_chunks_prepared, num_chunks); */ u32 total_chunks; if (is_index_transfer) { chunk_idx = @@ -2445,7 +2453,6 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, free(index); return NULL; } - debug_printf("Mapped at %p, %llu", mem, total_filesize); // Now we go over the directory tree we just generated and // - Map the files @@ -2503,6 +2510,7 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, if (entry->type == INDEX_TYPE_FILE) { int f = open((char *)entry->path, O_RDONLY); if (f == -1) { + fprintf(stderr, "Error opening %s\n", (char *)entry->path); encountered_err = true; break; } @@ -2571,13 +2579,13 @@ static struct { double target_duration, actual_duration; } pcc_trace[PCC_TRACE_SIZE]; -static void pcc_trace_push(u64 time, sequence_number range_start, sequence_number range_end, sequence_number mi_min, +static bool pcc_trace_push(u64 time, sequence_number range_start, sequence_number range_end, sequence_number mi_min, sequence_number mi_max, u32 excess, float loss, u32 delta_left, u32 delta_right, u32 nnacks, u32 nack_pkts, enum pcc_state state, u32 target_rate, u32 actual_rate, double target_duration, double actual_duration) { u32 idx = atomic_fetch_add(&pcc_trace_count, 1); if(idx >= PCC_TRACE_SIZE) { fprintf(stderr, "oops: pcc trace too small, trying to push #%d\n", idx); - exit(133); + return false; } pcc_trace[idx].time = time; pcc_trace[idx].range_start = range_start; @@ -2595,6 +2603,7 @@ static void pcc_trace_push(u64 time, sequence_number range_start, sequence_numbe pcc_trace[idx].actual_rate = actual_rate; pcc_trace[idx].target_duration = target_duration; pcc_trace[idx].actual_duration = actual_duration; + return true; } static bool pcc_mi_elapsed(struct ccontrol_state *cc_state) @@ -2634,6 +2643,8 @@ static void pcc_monitor(struct sender_state *tx_state) fprintf(stderr, "Assumption violated.\n"); quit_session(tx_state->session, SESSION_ERROR_PCC); cc_state->mi_end = now; + pthread_spin_unlock(&cc_state->lock); + return; } u32 throughput = cc_state->mi_seq_end - cc_state->mi_seq_start; // pkts sent in MI @@ -2659,8 +2670,16 @@ static void pcc_monitor(struct sender_state *tx_state) enum pcc_state state = cc_state->state; double actual_duration = (double)(cc_state->mi_end - cc_state->mi_start) / 1e9; - pcc_trace_push(now, start, end, mi_min, mi_max, excess, loss, delta_left, delta_right, nnacks, nack_pkts, state, - cc_state->curr_rate * cc_state->pcc_mi_duration, throughput, cc_state->pcc_mi_duration, actual_duration); + bool ok = pcc_trace_push( + now, start, end, mi_min, mi_max, excess, loss, delta_left, + delta_right, nnacks, nack_pkts, state, + cc_state->curr_rate * cc_state->pcc_mi_duration, throughput, + cc_state->pcc_mi_duration, actual_duration); + if (!ok) { + pthread_spin_unlock(&cc_state->lock); + quit_session(tx_state->session, SESSION_ERROR_PCC); + return; + } if(cc_state->num_nack_pkts != 0) { // skip PCC control if no NACKs received if(cc_state->ignored_first_mi) { // first MI after booting will only contain partial feedback, skip it as well @@ -2957,7 +2976,7 @@ static void new_tx_if_available(struct hercules_server *server) { // slot it will still be free when we assign to it later on char *fname; char *destname; - u16 jobid; + u64 jobid; u16 payloadlen; struct hercules_app_addr dest; int ret = monitor_get_new_job(server->usock, &fname, &destname, &jobid, @@ -2965,13 +2984,13 @@ static void new_tx_if_available(struct hercules_server *server) { if (!ret) { return; } - debug_printf("new job: %s -> %s", fname, destname); - debug_printf("using tx slot %d", session_slot); - debug_printf("destination address %x-%x:%x:%x %u.%u.%u.%u %u", - ntohs(*((u16 *)&dest.ia + 3)), ntohs(*((u16 *)&dest.ia + 2)), - ntohs(*((u16 *)&dest.ia + 1)), ntohs(*((u16 *)&dest.ia + 0)), - *((u8 *)&dest.ip + 3), *((u8 *)&dest.ip + 2), - *((u8 *)&dest.ip + 1), *((u8 *)&dest.ip + 0), ntohs(dest.port)); + fprintf(stderr, "Starting new transfer (%2d): %s -> %s\n", session_slot, + fname, destname); + fprintf(stderr, "Destination address: %u-%x:%x:%x %u.%u.%u.%u %u\n", + ntohs(*((u16 *)&dest.ia + 0)), ntohs(*((u16 *)&dest.ia + 1)), + ntohs(*((u16 *)&dest.ia + 2)), ntohs(*((u16 *)&dest.ia + 3)), + *((u8 *)&dest.ip + 0), *((u8 *)&dest.ip + 1), *((u8 *)&dest.ip + 2), + *((u8 *)&dest.ip + 3), ntohs(dest.port)); // It's ok to ignore the return value of monitor_update_job here: Since // we're informing the monitor about an error we don't care if the @@ -3073,7 +3092,7 @@ static void cleanup_finished_sessions(struct hercules_server *server, int s, u64 sec_elapsed, bytes_acked); struct hercules_session *current = session_tx; atomic_store(&server->sessions_tx[s], NULL); - fprintf(stderr, "Cleaning up TX session %d...\n", s); + debug_printf("Cleaning up TX session %d", s); // At this point we don't know if some other thread still has a // pointer to the session that it might dereference, so we // cannot safely free it. So, we record the pointer and defer @@ -3089,7 +3108,7 @@ static void cleanup_finished_sessions(struct hercules_server *server, int s, u64 if (now > session_rx->last_pkt_rcvd + session_timeout * 2) { struct hercules_session *current = session_rx; atomic_store(&server->sessions_rx[s], NULL); - fprintf(stderr, "Cleaning up RX session %d...\n", s); + debug_printf("Cleaning up RX session %d", s); // See the note above on deferred freeing destroy_session_rx(server, server->deferreds_rx[s]); server->deferreds_rx[s] = current; @@ -3104,18 +3123,18 @@ static void mark_timed_out_sessions(struct hercules_server *server, int s, if (session_tx && session_tx->state != SESSION_STATE_DONE) { if (now > session_tx->last_pkt_rcvd + session_timeout) { quit_session(session_tx, SESSION_ERROR_TIMEOUT); - debug_printf("Session (TX %2d) timed out!", s); + fprintf(stderr, "Session (TX %2d) timed out!\n", s); } } struct hercules_session *session_rx = server->sessions_rx[s]; if (session_rx && session_rx->state != SESSION_STATE_DONE) { if (now > session_rx->last_pkt_rcvd + session_timeout) { quit_session(session_rx, SESSION_ERROR_TIMEOUT); - debug_printf("Session (RX %2d) timed out!", s); + fprintf(stderr, "Session (RX %2d) timed out!\n", s); } else if (now > session_rx->last_new_pkt_rcvd + session_stale_timeout) { quit_session(session_rx, SESSION_ERROR_STALE); - debug_printf("Session (RX %2d) stale!", s); + fprintf(stderr, "Session (RX %2d) stale!\n", s); } } } @@ -3262,7 +3281,7 @@ static void print_session_stats(struct hercules_server *server, u64 now, double progress_percent = acked_count / (double)total * 100; send_rate_total += send_rate; fprintf( - stderr, + stdout, "(TX %2d) [%4.1f%%] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, rate " "%8.2f " "Mbps\n", @@ -3286,7 +3305,7 @@ static void print_session_stats(struct hercules_server *server, u64 now, 8 * recv_rate_pps * session_rx->rx_state->chunklen / 1e6; recv_rate_total += recv_rate; double progress_percent = rec_count / (double)total * 100; - fprintf(stderr, + fprintf(stdout, "(RX %2d) [%4.1f%%] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, " "rate %8.2f " "Mbps\n", @@ -3295,9 +3314,9 @@ static void print_session_stats(struct hercules_server *server, u64 now, } } if (active_session) { - fprintf(stderr, "TX Total Rate: %.2f Mbps\n", send_rate_total); - fprintf(stderr, "RX Total Rate: %.2f Mbps\n", recv_rate_total); - fprintf(stderr, "\n"); + fprintf(stdout, "TX Total Rate: %.2f Mbps\n", send_rate_total); + fprintf(stdout, "RX Total Rate: %.2f Mbps\n", recv_rate_total); + fprintf(stdout, "\n"); } } @@ -3344,7 +3363,7 @@ static void stop_finished_sessions(struct hercules_server *server, int slot, struct hercules_session *session_tx = server->sessions_tx[slot]; if (session_tx != NULL && session_tx->state != SESSION_STATE_DONE && session_tx->error != SESSION_ERROR_NONE) { - debug_printf("Stopping TX %d", slot); + fprintf(stderr, "Stopping TX %d\n", slot); session_tx->state = SESSION_STATE_DONE; u64 sec_elapsed = (now - session_tx->last_pkt_rcvd) / (int)1e9; u64 bytes_acked = session_tx->tx_state->chunklen * @@ -3357,13 +3376,13 @@ static void stop_finished_sessions(struct hercules_server *server, int slot, struct hercules_session *session_rx = server->sessions_rx[slot]; if (session_rx != NULL && session_rx->state != SESSION_STATE_DONE && session_rx->error != SESSION_ERROR_NONE) { - debug_printf("Stopping RX %d", slot); + fprintf(stderr, "Stopping RX %d\n", slot); session_rx->state = SESSION_STATE_DONE; int ret = msync(session_rx->rx_state->mem, session_rx->rx_state->filesize, MS_ASYNC); // XXX do we need SYNC here? if (ret) { - debug_printf("msync err? %s", strerror(errno)); + fprintf(stderr, "msync err? %s\n", strerror(errno)); } } } diff --git a/hercules.h b/hercules.h index 1983791..b21b6dd 100644 --- a/hercules.h +++ b/hercules.h @@ -229,7 +229,7 @@ struct hercules_session { size_t tx_npkts; struct hercules_app_addr peer; //< UDP/SCION address of peer (big endian) - u32 jobid; //< The monitor's ID for this job + u64 jobid; //< The monitor's ID for this job u32 payloadlen; //< The payload length used for this transfer. Note that // the payload length includes the rbudp header while the // chunk length does not. diff --git a/monitor.c b/monitor.c index 1a57133..c9731ee 100644 --- a/monitor.c +++ b/monitor.c @@ -15,12 +15,12 @@ static bool monitor_send_recv(int sockfd, struct hercules_sockmsg_Q *in, struct hercules_sockmsg_A *out) { int ret = send(sockfd, in, sizeof(*in), 0); if (ret != sizeof(*in)) { - debug_printf("Error sending to monitor?"); + fprintf(stderr, "Error sending to monitor?\n"); return false; } ret = recv(sockfd, out, sizeof(*out), 0); if (ret <= 0) { - debug_printf("Error reading from monitor?"); + fprintf(stderr, "Error reading from monitor?\n"); return false; } return true; @@ -61,7 +61,7 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, // The payload length is fixed when first fetching the job, we pass it in here // to compute the paths payload and frame lengths. -bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, +bool monitor_get_paths(int sockfd, u64 job_id, int payloadlen, int *n_paths, struct hercules_path **paths) { struct hercules_sockmsg_Q msg; msg.msgtype = SOCKMSG_TYPE_GET_PATHS; @@ -97,7 +97,7 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, return true; } -bool monitor_get_new_job(int sockfd, char **name, char **destname, u16 *job_id, +bool monitor_get_new_job(int sockfd, char **name, char **destname, u64 *job_id, struct hercules_app_addr *dest, u16 *payloadlen) { struct hercules_sockmsg_Q msg = {.msgtype = SOCKMSG_TYPE_GET_NEW_JOB}; @@ -130,7 +130,7 @@ bool monitor_get_new_job(int sockfd, char **name, char **destname, u16 *job_id, *destname, (char *)reply.payload.newjob.names + reply.payload.newjob.filename_len, reply.payload.newjob.destname_len); - debug_printf("received job id %d", reply.payload.newjob.job_id); + debug_printf("received job id %lu", reply.payload.newjob.job_id); *job_id = reply.payload.newjob.job_id; *payloadlen = reply.payload.newjob.payloadlen; dest->ia = reply.payload.newjob.dest_ia; @@ -139,7 +139,7 @@ bool monitor_get_new_job(int sockfd, char **name, char **destname, u16 *job_id, return true; } -bool monitor_update_job(int sockfd, int job_id, enum session_state state, +bool monitor_update_job(int sockfd, u64 job_id, enum session_state state, enum session_error err, u64 seconds_elapsed, u64 bytes_acked) { struct hercules_sockmsg_Q msg; diff --git a/monitor.h b/monitor.h index 24ddb93..e92e07e 100644 --- a/monitor.h +++ b/monitor.h @@ -16,7 +16,7 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, // Get SCION paths from the monitor for a given job ID. The caller is // responsible for freeing **paths. // Returns false on error. -bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, +bool monitor_get_paths(int sockfd, u64 job_id, int payloadlen, int *n_paths, struct hercules_path **paths); // Check if the monitor has a new job available. @@ -25,12 +25,12 @@ bool monitor_get_paths(int sockfd, int job_id, int payloadlen, int *n_paths, // Returns false if no new job available OR on error. // The caller is responsible for freeing **name and **destname if the return // value was true. -bool monitor_get_new_job(int sockfd, char **name, char **destname, u16 *job_id, +bool monitor_get_new_job(int sockfd, char **name, char **destname, u64 *job_id, struct hercules_app_addr *dest, u16 *payloadlen); // Inform the monitor about a transfer's status. // Returns false if the job was cancelled by the monitor or on error. -bool monitor_update_job(int sockfd, int job_id, enum session_state state, +bool monitor_update_job(int sockfd, u64 job_id, enum session_state state, enum session_error err, u64 seconds_elapsed, u64 bytes_acked); @@ -85,7 +85,7 @@ struct sockmsg_reply_path_A { struct sockmsg_new_job_Q {}; struct sockmsg_new_job_A { uint8_t has_job; // The other fields are only valid if this is set to 1 - uint16_t job_id; + uint64_t job_id; uint64_t dest_ia; //< Destination address in network byte order uint32_t dest_ip; uint16_t dest_port; @@ -99,7 +99,7 @@ struct sockmsg_new_job_A { // Get paths to use for a given job ID #define SOCKMSG_TYPE_GET_PATHS (3) struct sockmsg_paths_Q { - uint16_t job_id; + uint64_t job_id; }; struct sockmsg_paths_A { uint16_t n_paths; @@ -109,7 +109,7 @@ struct sockmsg_paths_A { // Inform the monitor about a job's status #define SOCKMSG_TYPE_UPDATE_JOB (4) struct sockmsg_update_job_Q { - uint16_t job_id; + uint64_t job_id; uint32_t status; // One of enum session_state uint32_t error; // One of enum session_error uint64_t seconds_elapsed; diff --git a/monitor/monitor.go b/monitor/monitor.go index 9132ec2..a0487f7 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -210,7 +210,7 @@ func main() { strlen_src := len(selectedJob.file) strlen_dst := len(selectedJob.destFile) b = append(b, 1) - b = binary.LittleEndian.AppendUint16(b, uint16(selectedJob.id)) + b = binary.LittleEndian.AppendUint64(b, uint64(selectedJob.id)) // Address components in network byte order b = binary.BigEndian.AppendUint64(b, uint64(selectedJob.dest.IA)) @@ -229,8 +229,8 @@ func main() { usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_GET_PATHS: - job_id := binary.LittleEndian.Uint16(buf[:2]) - buf = buf[2:] + job_id := binary.LittleEndian.Uint64(buf[:8]) + buf = buf[8:] transfersLock.Lock() job, _ := transfers[int(job_id)] n_headers, headers := headersToDestination(*job) @@ -240,8 +240,8 @@ func main() { usock.WriteToUnix(b, a) case C.SOCKMSG_TYPE_UPDATE_JOB: - job_id := binary.LittleEndian.Uint16(buf[:2]) - buf = buf[2:] + job_id := binary.LittleEndian.Uint64(buf[:8]) + buf = buf[8:] status := binary.LittleEndian.Uint32(buf[:4]) buf = buf[4:] errorcode := binary.LittleEndian.Uint32(buf[:4]) diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 159b6f9..6c0a757 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -115,7 +115,7 @@ func (pm *PathManager) interfaceForRoute(ip net.IP) (*net.Interface, error) { for _, route := range routes { if iface, ok := pm.interfaces[route.LinkIndex]; ok { - fmt.Printf("sending via #%d (%s) to %s\n", route.LinkIndex, pm.interfaces[route.LinkIndex].Name, ip) + fmt.Printf("route to %s via #%d (%s)\n", ip, route.LinkIndex, pm.interfaces[route.LinkIndex].Name) return iface, nil } } diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 99e75eb..a7f8d4e 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -137,7 +137,6 @@ func (ptd *PathsToDestination) chooseNewPaths(availablePaths []PathWithInterface // pick paths picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) - fmt.Println(availablePaths) var pathSet []PathWithInterface disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) maxRuleIdx := 0 // the highest index of a PathSpec that is used (to be minimized) diff --git a/monitor/scionheader.go b/monitor/scionheader.go index 2e553d6..77410ce 100644 --- a/monitor/scionheader.go +++ b/monitor/scionheader.go @@ -324,7 +324,6 @@ func prepareHeader(path PathMeta, payloadLen int, srcUDP, dstUDP net.UDPAddr, sr etherLen := underlayHeaderLen + scionHdrLen + payloadLen underlayHeader, err := prepareUnderlayPacketHeader(srcMAC, dstMAC, srcUDP.IP, path.path.UnderlayNextHop().IP, uint16(path.path.UnderlayNextHop().Port), etherLen) - fmt.Println(underlayHeader, err) payload := snet.UDPPayload{ SrcPort: 0, // Will be filled by server, left empty for correct checksum computation @@ -390,7 +389,5 @@ func getPathHeaderlen(path snet.Path) (int, int) { fmt.Println("serializer err", err) return 0, 0 } - fmt.Println("Header length: ", len(destPkt.Bytes)) - fmt.Println("Underlay Header length: ", len(underlayHeader)) return len(underlayHeader), len(destPkt.Bytes) } diff --git a/xdp.c b/xdp.c index 9f87394..dae995e 100644 --- a/xdp.c +++ b/xdp.c @@ -402,7 +402,7 @@ int xdp_setup(struct hercules_server *server) { int ret = load_xsk_redirect_userspace(server, server->worker_args, server->config.n_threads); if (ret) { - debug_printf("Error loading XDP redirect"); + fprintf(stderr, "Error loading XDP redirect, is another program loaded?\n"); return ret; } From 98872a96ddf5c8f72584f86d729178c1ddddde42 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 26 Jun 2024 16:14:42 +0200 Subject: [PATCH 064/112] merge tls-certs --- monitor/config.go | 54 ++++++++++++++++++++++++++- monitor/http_api.go | 90 ++++++++++++++++++++++++++++++++++++++++++++- monitor/monitor.go | 31 ++++++++++++++-- sampleconf.toml | 19 ++++++++++ 4 files changed, 188 insertions(+), 6 deletions(-) diff --git a/monitor/config.go b/monitor/config.go index 3b3903b..b80578e 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -4,6 +4,8 @@ import ( "fmt" "net" "os" + "os/user" + "strconv" "github.com/BurntSushi/toml" "github.com/scionproto/scion/pkg/addr" @@ -43,14 +45,26 @@ func (i *Interface) UnmarshalText(text []byte) error { return err } +type UserGroup struct { + User string // Username supplied in the config file + uidLookup int // User ID, looked up and filled when parsing config + Group string + gidLookup int +} + type MonitorConfig struct { DestinationHosts []HostConfig DestinationASes []ASConfig DefaultNumPaths int MonitorSocket string ListenAddress UDPAddr - MonitorHTTP string + MonitorHTTP string + MonitorHTTPS string + TLSCert string + TLSKey string Interfaces []Interface + UserMap map[string]*UserGroup + ClientCACerts []string // The following are not used by the monitor, they are listed here for completeness ServerSocket string XDPZeroCopy bool @@ -96,6 +110,8 @@ func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { } const defaultMonitorHTTP = ":8000" +const defaultMonitorHTTPS = "disabled" +// Disabled by default because further config (certs) is needed // Decode the config file and fill in any unspecified values with defaults. // Will exit if an error occours or a required value is not specified. @@ -124,6 +140,20 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { config.MonitorHTTP = defaultMonitorHTTP } + if config.MonitorHTTPS == "" { + config.MonitorHTTPS = defaultMonitorHTTPS + } + if config.MonitorHTTPS != "disabled" { + if config.TLSCert == "" || config.TLSKey == "" { + fmt.Println("HTTPS enabled and no certificate or key specified") + os.Exit(1) + } + if len(config.ClientCACerts) == 0 { + fmt.Println("HTTPS enabled and no certificates for client authentication specified") + os.Exit(1) + } + } + // This is required if config.ListenAddress.addr == nil { fmt.Println("Error: Listening address not specified") @@ -140,6 +170,28 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { os.Exit(1) } + for _, u := range config.UserMap { + userLookup, err := user.Lookup(u.User) + if err != nil { + fmt.Printf("User lookup error: %v\n", u.User) + os.Exit(1) + } + u.uidLookup, err = strconv.Atoi(userLookup.Uid) + if err != nil { + os.Exit(1) + } + + groupLookup, err := user.LookupGroup(u.Group) + if err != nil { + fmt.Printf("Group lookup error: %v\n", u.Group) + os.Exit(1) + } + u.gidLookup, err = strconv.Atoi(groupLookup.Gid) + if err != nil { + os.Exit(1) + } + } + pathRules := PathRules{} // It would be nice not to have to do this dance and specify the maps directly in the config file, // but the toml package crashes if the keys are addr.Addr diff --git a/monitor/http_api.go b/monitor/http_api.go index 2e9b8c3..332b7cf 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -3,13 +3,44 @@ package main import ( "fmt" "io" + "io/fs" "net/http" "os" "strconv" + "syscall" "github.com/scionproto/scion/pkg/snet" ) +// Check if the user can open the file +func checkReadPerm(user, file string) bool { + ug, ok := config.UserMap[user] + if !ok { + return false + } + + err := syscall.Setegid(ug.gidLookup) + if err != nil { + return false + } + defer syscall.Setegid(0) + err = syscall.Seteuid(ug.uidLookup) + if err != nil { + return false + } + defer syscall.Seteuid(0) + + f, err := os.Open(file) + if err != nil { + return false + } + err = f.Close() + if err != nil { + return false + } + return true +} + // Handle submission of a new transfer // GET params: // file (Path to file to transfer) @@ -31,6 +62,18 @@ func http_submit(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "parse err\n") return } + owner := "" + if r.TLS != nil { + // There must be at least 1 cert because we require client certs in the TLS config + certDN := r.TLS.PeerCertificates[0].Subject.String() + fmt.Println("Read user from cert:", certDN) + if !checkReadPerm(certDN, file) { + w.WriteHeader(http.StatusUnauthorized) + io.WriteString(w, "Source file does not exist or insufficient permissions\n") + return + } + owner = certDN + } payloadlen := 0 // 0 means automatic selection if r.URL.Query().Has("payloadlen") { @@ -58,6 +101,7 @@ func http_submit(w http.ResponseWriter, r *http.Request) { file: file, destFile: destfile, dest: *destParsed, + owner: owner, pm: pm, } nextID += 1 @@ -89,6 +133,12 @@ func http_status(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) return } + if r.TLS != nil { + if info.owner != r.TLS.PeerCertificates[0].Subject.String() { + w.WriteHeader(http.StatusUnauthorized) + return + } + } io.WriteString(w, fmt.Sprintf("OK %d %d %d %d %d\n", info.status, info.state, info.err, info.time_elapsed, info.bytes_acked)) } @@ -115,12 +165,39 @@ func http_cancel(w http.ResponseWriter, r *http.Request) { transfersLock.Unlock() return } + if r.TLS != nil { + if info.owner != r.TLS.PeerCertificates[0].Subject.String() { + w.WriteHeader(http.StatusUnauthorized) + transfersLock.Unlock() + return + } + } info.status = Cancelled transfersLock.Unlock() io.WriteString(w, "OK\n") } +func statAsUser(user, file string) (fs.FileInfo, error) { + ug, ok := config.UserMap[user] + if !ok { + return nil, fmt.Errorf("No user?") + } + + err := syscall.Seteuid(ug.uidLookup) + if err != nil { + return nil, err + } + defer syscall.Seteuid(0) + err = syscall.Setegid(ug.gidLookup) + if err != nil { + return nil, err + } + defer syscall.Setegid(0) + + return os.Stat(file) +} + // Handle gfal's stat command // GET Params: // file: a file path @@ -132,7 +209,16 @@ func http_stat(w http.ResponseWriter, r *http.Request) { return } file := r.URL.Query().Get("file") - info, err := os.Stat(file) + var info fs.FileInfo + var err error + if r.TLS != nil { + // There must be at least 1 cert because we require client certs in the TLS config + certDN := r.TLS.PeerCertificates[0].Subject.String() + fmt.Println("Read user from cert:", certDN) + info, err = statAsUser(certDN, file) + } else { + info, err = os.Stat(file) + } if os.IsNotExist(err) { io.WriteString(w, "OK 0 0\n") return @@ -143,7 +229,7 @@ func http_stat(w http.ResponseWriter, r *http.Request) { } if !info.Mode().IsRegular() && !info.Mode().IsDir() { w.WriteHeader(http.StatusBadRequest) - io.WriteString(w, "err\n") + io.WriteString(w, "File is not a regular file or directory\n") return } diff --git a/monitor/monitor.go b/monitor/monitor.go index a0487f7..84d7263 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -1,6 +1,8 @@ package main import ( + "crypto/tls" + "crypto/x509" "encoding/binary" "flag" "fmt" @@ -83,6 +85,7 @@ type HerculesTransfer struct { destFile string // Name of the file to transfer at destination host dest snet.UDPAddr // Destination pm *PathManager + owner string // The name in the certificate of whoever submitted the transfer timeFinished time.Time // The following two fields are meaningless if the job's status is 'Queued' // They are updated when the server sends messages of type 'update_job' @@ -100,6 +103,7 @@ var nextID int = 1 // ID to use for the next transfer var listenAddress *snet.UDPAddr var activeInterfaces []*net.Interface var pathRules PathRules +var config MonitorConfig var startupVersion string @@ -109,7 +113,6 @@ func main() { flag.StringVar(&configFile, "c", defaultConfigPath, "Path to the monitor configuration file") flag.Parse() - var config MonitorConfig config, pathRules = readConfig(configFile) listenAddress = config.ListenAddress.addr @@ -148,7 +151,29 @@ func main() { http.HandleFunc("/status", http_status) http.HandleFunc("/cancel", http_cancel) http.HandleFunc("/stat", http_stat) - go http.ListenAndServe(config.MonitorHTTP, nil) + if config.MonitorHTTP != "disabled" { + go http.ListenAndServe(config.MonitorHTTP, nil) + } + + if config.MonitorHTTPS != "disabled" { + clientCAs := x509.NewCertPool() + for _, c := range config.ClientCACerts { + cert, err := os.ReadFile(c) + if err != nil { + fmt.Printf("Error reading file: %v\n", err) + os.Exit(1) + } + clientCAs.AppendCertsFromPEM(cert) + } + httpsServer := &http.Server{ + Addr: config.MonitorHTTPS, + TLSConfig: &tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCAs, + }, + } + go httpsServer.ListenAndServeTLS(config.TLSCert, config.TLSKey) + } // Communication is always initiated by the server, // the monitor's job is to respond to queries from the server @@ -256,7 +281,7 @@ func main() { job, ok := transfers[int(job_id)] if !ok { b = binary.LittleEndian.AppendUint16(b, uint16(0)) - usock.WriteToUnix(b,a) + usock.WriteToUnix(b, a) continue } job.state = status diff --git a/sampleconf.toml b/sampleconf.toml index 78553d2..ce6525a 100644 --- a/sampleconf.toml +++ b/sampleconf.toml @@ -13,8 +13,17 @@ ServerSocket = "var/run/hercules.sock" ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" # Listenting address for the monitor (HTTP) +# Set to "disabled" to disable MonitorHTTP = ":8000" +# Listening address for the monitor (HTTPS) +# Set to "disabled" to disable +MonitorHTTPS = "disabled" + +# Path to the server's certificate and key for TLS +TLSCert = "cert.pem" +TLSKey = "key.pem" + # Network interfaces to use for Hercules Interfaces = [ "eth0", @@ -72,3 +81,13 @@ NumPaths = 2 [[DestinationASes]] IA = "17-a:b:c" NumPaths = 2 + +# Specify mapping of certificates to system users and groups to use for +# file permission checks. +[UserMap] +"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" = {User = "marco", Group = "marco"} + +# Paths to certificates used for validation of TLS client certificates +ClientCACerts = [ +"cert.pem", +] From 8a30fd92a92ab3cb46988f352e81f05950b08e5a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 27 Jun 2024 15:50:15 +0200 Subject: [PATCH 065/112] commit gfal plugin --- errors.h | 69 ++++ gfal2-hercules/Makefile | 46 +++ gfal2-hercules/README_PLUGIN_HERCULES | 16 + gfal2-hercules/gfal_hercules_plugin.c | 133 +++++++ gfal2-hercules/gfal_hercules_plugin.h | 20 + gfal2-hercules/gfal_hercules_transfer.c | 474 ++++++++++++++++++++++++ gfal2-hercules/gfal_hercules_transfer.h | 15 + gfal2-hercules/hercules_plugin.conf | 1 + hercules.h | 30 +- monitor/http_api.go | 13 +- monitor/monitor.go | 3 + sampleconf.toml | 10 +- 12 files changed, 792 insertions(+), 38 deletions(-) create mode 100644 errors.h create mode 100644 gfal2-hercules/Makefile create mode 100644 gfal2-hercules/README_PLUGIN_HERCULES create mode 100644 gfal2-hercules/gfal_hercules_plugin.c create mode 100644 gfal2-hercules/gfal_hercules_plugin.h create mode 100644 gfal2-hercules/gfal_hercules_transfer.c create mode 100644 gfal2-hercules/gfal_hercules_transfer.h create mode 100644 gfal2-hercules/hercules_plugin.conf diff --git a/errors.h b/errors.h new file mode 100644 index 0000000..fb4cd3f --- /dev/null +++ b/errors.h @@ -0,0 +1,69 @@ +#ifndef HERCULES_ERRORS_H +#define HERCULES_ERRORS_H + +// Some states are used only by the TX/RX side and are marked accordingly +enum session_state { + SESSION_STATE_NONE, + SESSION_STATE_PENDING, //< (TX) Need to send HS and repeat until TO, + // waiting for a reflected HS packet + SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and + // CTS + SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS + SESSION_STATE_INDEX_READY, //< (RX) Index transfer complete, map files and + // send CTS + SESSION_STATE_RUNNING_DATA, //< Data transfer in progress + SESSION_STATE_RUNNING_IDX, //< Directory index transfer in progress + SESSION_STATE_DONE, //< Transfer done (or cancelled with error) +}; + +enum session_error { + SESSION_ERROR_NONE, // Error not set yet + SESSION_ERROR_OK, //< No error, transfer completed successfully + SESSION_ERROR_TIMEOUT, //< Session timed out + SESSION_ERROR_STALE, //< Packets are being received, but none are new + SESSION_ERROR_PCC, //< Something wrong with PCC + SESSION_ERROR_SEQNO_OVERFLOW, + SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination + SESSION_ERROR_CANCELLED, //< Transfer cancelled by monitor + SESSION_ERROR_BAD_MTU, //< Invalid MTU supplied by the monitor + SESSION_ERROR_MAP_FAILED, //< Could not mmap file + SESSION_ERROR_TOO_LARGE, //< File or index size too large + SESSION_ERROR_INIT, //< Could not initialise session +}; + +static inline int hercules_err_is_ok(enum session_error err) { + return err == SESSION_ERROR_OK; +} + +static inline const char *hercules_strerror(enum session_error err) { + switch (err) { + case SESSION_ERROR_NONE: + return "Error not set"; + case SESSION_ERROR_OK: + return "Transfer successful"; + case SESSION_ERROR_TIMEOUT: + return "Session timed out"; + case SESSION_ERROR_STALE: + return "Session stalled"; + case SESSION_ERROR_PCC: + return "PCC error"; + case SESSION_ERROR_SEQNO_OVERFLOW: + return "PCC sequence number overflow"; + case SESSION_ERROR_NO_PATHS: + return "No paths to destination"; + case SESSION_ERROR_CANCELLED: + return "Transfer cancelled"; + case SESSION_ERROR_BAD_MTU: + return "Bad MTU"; + case SESSION_ERROR_MAP_FAILED: + return "Mapping file failed"; + case SESSION_ERROR_TOO_LARGE: + return "File or directory listing too large"; + case SESSION_ERROR_INIT: + return "Could not initialise session"; + default: + return "Unknown error"; + } +} + +#endif // HERCULES_ERRORS_H diff --git a/gfal2-hercules/Makefile b/gfal2-hercules/Makefile new file mode 100644 index 0000000..bb3e389 --- /dev/null +++ b/gfal2-hercules/Makefile @@ -0,0 +1,46 @@ +TARGET := gfal_plugin_hercules.so + +CC := cc +CFLAGS = -O2 -std=c99 -fPIC +CFLAGS += -Wall -Wextra +CFLAGS += -I/usr/local/include/gfal2 -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include + +## for debugging: +CFLAGS += -g3 + +LDFLAGS := -shared -lgfal2 -lgfal_transfer -lglib-2.0 -lcurl +DEPFLAGS := -MP -MD + +SRCS := $(wildcard *.c) +OBJS := $(SRCS:.c=.o) +DEPS := $(OBJS:.o=.d) + +.PHONY: all +all: $(TARGET) + +ifndef PREFIX +PREFIX := /usr/local +endif +ifndef SYSCONFDIR +SYSCONFDIR := $(PREFIX)/etc +endif + +.PHONY: install +install: $(TARGET) +# Fail if target directories don't exist (gfal is probably installed elsewhere) + install $(TARGET) $(PREFIX)/lib64/gfal2-plugins/ + install -m 444 README_PLUGIN_HERCULES $(PREFIX)/share/doc/gfal2/ + install -m 644 hercules_plugin.conf $(SYSCONFDIR)/gfal2.d/ + + +$(TARGET): $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) + +%.o: %.c + $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ + +.PHONY: clean +clean: + rm -rf $(TARGET) $(OBJS) $(DEPS) + +-include $(DEPS) diff --git a/gfal2-hercules/README_PLUGIN_HERCULES b/gfal2-hercules/README_PLUGIN_HERCULES new file mode 100644 index 0000000..1a8c61c --- /dev/null +++ b/gfal2-hercules/README_PLUGIN_HERCULES @@ -0,0 +1,16 @@ +Hercules plugin for gfal2 +========================= +This plugin adds support for the Hercules file transfer tool to gfal2 (and FTS). +The plugin will handle transfers with URLs of the following form: +hercules://hercules_monitor/path/to/file +where `hercules_monitor` is the address and port of the Hercules monitor. + + +To build this plugin, you will need the following dependencies: +- gfal2 +- libcurl + +To build, type `make`. +To install the plugin, type `make install`. By default, this will install the +plugin, documentation and configuration to `/usr/local`. Depending on your FTS +installation, you may need to use `PREFIX=/usr SYSCONFDIR=/etc make install`. diff --git a/gfal2-hercules/gfal_hercules_plugin.c b/gfal2-hercules/gfal_hercules_plugin.c new file mode 100644 index 0000000..0553062 --- /dev/null +++ b/gfal2-hercules/gfal_hercules_plugin.c @@ -0,0 +1,133 @@ +#include "gfal_hercules_plugin.h" +#include "gfal_hercules_transfer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// The documentation for how to write gfal plugins is somewhat sparse, +// this has been pieced together by looking at the code of existing plugins; +// Note also the dropbox gfal plugin (not included in the gfal2 repository, but +// at github.com/cern-fts/gfal2-dropbox), which also uses libcurl to talk to a +// HTTP API. + +GQuark hercules_domain() { return g_quark_from_static_string("hercules"); } + +// Return the plugin name and version +// (I believe newer versions will take precedence, if multiple are installed) +const char *gfal_hercules_plugin_get_name() { + return GFAL2_PLUGIN_VERSIONED("hercules", "0.1"); +} + +static gboolean is_hercules_url(const char *url) { + return strncmp(url, "hercules:", 9) == 0; +} + +// This is called by gfal to check whether our plugin supports a given operation +// on a URL. +static gboolean gfal_hercules_check_url(plugin_handle h, const char *url, + plugin_mode mode, GError **err) { + (void)h; + (void)err; + switch (mode) { + // We don't support any file operations. + // STAT is required as it's called before any transfer + case GFAL_PLUGIN_STAT: + return is_hercules_url(url); + default: + return FALSE; + } +} + +// Delete plugin data, called by gfal for cleanup +static void gfal_plugin_hercules_delete(plugin_handle plugin_data) { + struct gfal_hercules_context *data = + (struct gfal_hercules_context *)plugin_data; + curl_easy_cleanup(data->curl); + free(data->user_key); + free(data->user_cert); + free(data); +} + +// This will be called to determine whether the hercules plugin can handle +// a transfer of src to dst. +int gfal_plugin_hercules_check_url_transfer(plugin_handle h, + gfal2_context_t ctxt, + const char *src, const char *dst, + gfal_url2_check check) { + (void)h; + (void)ctxt; + // XXX I'm not sure what the `check` arg is used for + return is_hercules_url(src) && is_hercules_url(dst); +} + +// This is used by gfal to register the plugin +gfal_plugin_interface gfal_plugin_init(gfal2_context_t context, GError **err) { + // Interface struct, create and zero out (to set all function pointers to + // NULL) + gfal_plugin_interface hercules_plugin; + memset(&hercules_plugin, 0, sizeof(gfal_plugin_interface)); + + GError *tmp_err = NULL; + + // Context we can fill with whatever we want, + // will be passed to function calls + struct gfal_hercules_context *data = calloc(1, sizeof(*data)); + if (data == NULL) { + gfal2_set_error(&tmp_err, hercules_domain(), ENOMEM, __func__, + "calloc failed"); + // Error is returned at the end + } + data->gfal2_context = context; + data->curl = curl_easy_init(); + if (data->curl == NULL) { + gfal2_set_error(&tmp_err, hercules_domain(), EINVAL, __func__, + "CURL init error"); + // Error is returned at the end + } + + // Now fill in the struct + // MANDATORY fields + hercules_plugin.plugin_data = data; + hercules_plugin.priority = GFAL_PLUGIN_PRIORITY_DATA; + hercules_plugin.getName = gfal_hercules_plugin_get_name; + hercules_plugin.plugin_delete = gfal_plugin_hercules_delete; + hercules_plugin.check_plugin_url = gfal_hercules_check_url; + + // FILE API + // Not supported, but stat is required + hercules_plugin.statG = gfal_plugin_hercules_statG; + + // TRANSFER API + // return whether we support third-party transfer from src to dst + hercules_plugin.check_plugin_url_transfer = + gfal_plugin_hercules_check_url_transfer; + // perform file copy + hercules_plugin.copy_file = gfal_plugin_hercules_copy_file; + // Not clear to me what bulk copy is. I think it refers to the ability to + // submit a batch of transfers to FTS in a single submission. I'm not + // sure why we'd need to handle that differently, it may just be an + // optimisation? + hercules_plugin.copy_bulk = NULL; + // hook executed before a copy, may be useful? + hercules_plugin.copy_enter_hook = NULL; + + // QoS API + // Not supported + + // ARCHIVE API + // Not supported + + // TOKEN API + // Not supported + + // Returning an error here seems to leave the transfer hanging around as + // "active" in the FTS dashboard. It will only be marked as failed after 900 + // seconds, which means that no error is reported for that time. The gridftp + // plugin does the same, though. + G_RETURN_ERR(hercules_plugin, tmp_err, err); +} diff --git a/gfal2-hercules/gfal_hercules_plugin.h b/gfal2-hercules/gfal_hercules_plugin.h new file mode 100644 index 0000000..21aea99 --- /dev/null +++ b/gfal2-hercules/gfal_hercules_plugin.h @@ -0,0 +1,20 @@ +#ifndef GFAL_HERCULES_PLUGIN_H +#define GFAL_HERCULES_PLUGIN_H + +#include +#include +#include +#include + +// This can be used to keep state between function calls, gfal passes it into +// every call. (1 per transfer) +struct gfal_hercules_context { + CURL *curl; + gfal2_context_t gfal2_context; + gchar *user_cert; + gchar *user_key; +}; + +GQuark hercules_domain(); + +#endif // GFAL_HERCULES_PLUGIN_H diff --git a/gfal2-hercules/gfal_hercules_transfer.c b/gfal2-hercules/gfal_hercules_transfer.c new file mode 100644 index 0000000..dc14527 --- /dev/null +++ b/gfal2-hercules/gfal_hercules_transfer.c @@ -0,0 +1,474 @@ +#include "gfal_hercules_transfer.h" +#include "../errors.h" +#include "common/gfal_common.h" +#include "gfal_hercules_plugin.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// In order to get the HTTP response from libcurl, we need to supply it with a +// receive callback (a function and an argument where that function will store +// the received data). This is a very simple implementation since we expect the +// responses to be very short. +#define MAX_RESPONSE_DATA 100 +struct recvdata { + char response[MAX_RESPONSE_DATA]; + size_t size; // Actual response size +}; + +// Callback function to receive HTTP responses. +// Copied from libcurl docs. +static size_t recvfunc(char *data, size_t size, size_t nmemb, + struct recvdata *s) { + size_t realsize = size * nmemb; + if (s->size + realsize > MAX_RESPONSE_DATA) { + return 0; + } + memcpy(&(s->response[s->size]), data, realsize); + s->size += realsize; + s->response[s->size] = 0; + return realsize; +} + +static int curl_get_and_check(CURL *curl, GError **err) { + CURLcode res; + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, "CURL error: %s", + curl_easy_strerror(res)); + return -1; + } + + long response_code; + res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (res != CURLE_OK) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, "CURL error: %s", + curl_easy_strerror(res)); + return -1; + } + if (response_code != 200) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error (HTTP status %ld)", response_code); + return -1; + } + return 0; +} + +// Ask the destination monitor for its server's address +static int hercules_get_server(CURL *curl, struct recvdata *rec, + char *dst_host_monitor, + char dst_host_server[500], GError **err) { + char server_request_url[800]; + int ret = + snprintf(server_request_url, 800, "https://%s/server", dst_host_monitor); + if (ret >= 800) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Server query URL too long"); + return -1; + } + gfal2_log(G_LOG_LEVEL_DEBUG, "Hercules: Using URL %s", server_request_url); + + curl_easy_setopt(curl, CURLOPT_URL, server_request_url); + rec->size = 0; + ret = curl_get_and_check(curl, err); + if (ret) { + return -1; + } + + printf("read %s\n", rec->response); + ret = sscanf(rec->response, "OK %499s", dst_host_server); + if (ret != 1) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing HTTP response?"); + return -1; + } + return 0; +} + +static int hercules_submit_transfer(CURL *curl, struct recvdata *rec, + char *src_host, char *dst_server, + char *src_path, char *dst_path, + uint64_t *jobid, GError **err) { + char request_url[3000]; + int ret = snprintf(request_url, 3000, + "https://%s/submit?file=%s&dest=%s&destfile=%s", src_host, + src_path, dst_server, dst_path); + if (ret >= 3000) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Submission URL too long"); + return -1; + } + gfal2_log(G_LOG_LEVEL_DEBUG, "Hercules: Using URL %s", request_url); + + curl_easy_setopt(curl, CURLOPT_URL, request_url); + rec->size = 0; + ret = curl_get_and_check(curl, err); + if (ret) { + return -1; + } + + // Parse response + ret = sscanf(rec->response, "OK %lu", jobid); + if (ret != 1) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing HTTP response?"); + return -1; + } + return 0; +} + +struct hercules_status_info { + int status; + enum session_state state; + enum session_error job_err; + int seconds_elapsed; + int bytes_acked; +}; + +// Query current transfer status +static int hercules_get_status(CURL *curl, struct recvdata *rec, char *src_host, + uint64_t jobid, + struct hercules_status_info *status, + GError **err) { + char status_url[1000]; + int ret = + snprintf(status_url, 1000, "https://%s/status?id=%lu", src_host, jobid); + if (ret >= 1000) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Status URL too long"); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, status_url); + rec->size = 0; + ret = curl_get_and_check(curl, err); + if (ret) { + return -1; + } + + // Format of the response: OK status state err seconds_elapsed bytes_acked + ret = sscanf(rec->response, "OK %d %d %d %d %d", &status->status, + (int *)&status->state, (int *)&status->job_err, + &status->seconds_elapsed, &status->bytes_acked); + if (ret != 5) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing HTTP response?"); + return -1; + } + return 0; +} + +static int hercules_cancel_transfer(CURL *curl, struct recvdata *rec, + char *src_host, uint64_t jobid, + GError **err) { + char cancel_url[1000]; + int ret = + snprintf(cancel_url, 1000, "https://%s/cancel?id=%lu", src_host, jobid); + if (ret >= 1000) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Cancel URL too long"); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, cancel_url); + rec->size = 0; + ret = curl_get_and_check(curl, err); + if (ret) { + return -1; + } + + // Format of the response: OK + if (strncmp(rec->response, "OK", 2)) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error cancelling transfer?"); + return -1; + } + return 0; +} + +// Will be registered as callback below +void gfal_plugin_hercules_cancel_transfer(gfal2_context_t ctxt, void *data) { + (void)ctxt; + int *cancel_received = (int *)data; + *cancel_received = 1; +} + +// This function will be called to perform the actual transfer. +// We submit the transfer to the hercules server at src, +// then periodically poll the transfer's status and update FTS accordingly. +// +// We expect URLs of the form: +// hercules://10.0.0.1:8000/path/to/file +// NOTE: The source and destination URLs both refer to the respective Hercules +// monitor's HTTP API. We first need to ask the receiving-side monitor for its +// SCION address (the reason is that stat is called on the destination URL +// first, so it has to refer to the monitor, not the server). +// +// To submit the transfer, send a HTTP GET request to the source host of the +// form +// http://src:api_port/?file=testfile&dest=17-ffaa:1:fe2,127.0.0.1:123&destfile=out +int gfal_plugin_hercules_copy_file(plugin_handle h, gfal2_context_t ctxt, + gfalt_params_t params, const char *src, + const char *dst, GError **err) { + gfal2_log(G_LOG_LEVEL_INFO, "Hercules executing transfer: %s -> %s", src, + dst); + struct gfal_hercules_context *data = (struct gfal_hercules_context *)h; + + // Get client certificate to use + // This gets us the path to the key/cert files + GError *e = NULL; + gchar *user_cert = gfal2_cred_get(ctxt, GFAL_CRED_X509_CERT, src, NULL, &e); + if (e || !user_cert) { + g_propagate_error(err, e); + return -1; + } + if (data->user_cert) { + free(data->user_cert); + data->user_cert = NULL; + } + data->user_cert = user_cert; + gfal2_log(G_LOG_LEVEL_MESSAGE, "cert: %s", user_cert); + g_clear_error(&e); + gchar *user_key = gfal2_cred_get(ctxt, GFAL_CRED_X509_KEY, src, NULL, &e); + if (e || !user_key) { + g_propagate_error(err, e); + return -1; + } + if (data->user_key) { + free(data->user_key); + data->user_key = NULL; + } + data->user_key = user_key; + g_clear_error(&e); + gfal2_log(G_LOG_LEVEL_MESSAGE, "key: %s", user_key); + + // Parse the source URL + char src_host[500]; + char src_path[1000]; + int ret = sscanf(src, "hercules://%499[^/]/%999s", src_host, src_path); + if (ret != 2) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing source URL"); + return -1; + } + + // Parse the destination URL + char dst_host_monitor[500]; + char dst_path[1000]; + ret = sscanf(dst, "hercules://%499[^/]/%999s", dst_host_monitor, dst_path); + if (ret != 2) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing destination URL"); + return -1; + } + + // Set up curl + CURL *curl = data->curl; + struct recvdata rec = {.size = 0}; + + /* curl_easy_setopt(curl, CURLOPT_URL, url_goes_here); */ + curl_easy_setopt(curl, CURLOPT_SSLCERT, user_cert); + curl_easy_setopt(curl, CURLOPT_SSLKEY, user_key); + /* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); */ + /* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recvfunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rec); + + // Get destination server address + char dst_host_server[500]; + ret = hercules_get_server(curl, &rec, dst_host_monitor, dst_host_server, err); + if (ret) { + return -1; + } + + // Submit the transfer + uint64_t jobid; + ret = hercules_submit_transfer(curl, &rec, src_host, dst_host_server, + src_path, dst_path, &jobid, err); + if (ret) { + return -1; + } + gfal2_log(G_LOG_LEVEL_INFO, "Hercules: Job ID: %lu", jobid); + plugin_trigger_event(params, hercules_domain(), GFAL_EVENT_NONE, + GFAL_EVENT_TRANSFER_ENTER, "Hercules starting transfer"); + + // Register cancel callback + int cancel_received = 0; + gfal_cancel_token_t cancel_token = gfal2_register_cancel_callback( + ctxt, gfal_plugin_hercules_cancel_transfer, &cancel_received); + + // Poll the transfer's status until it's done + // In seconds +#define HERCULES_POLL_INTERVAL 60 + int transfer_finished = 0; + + // We fill this struct with the transfer's current stats and then pass it to + // the monitor. + // NOTE: In this case, "monitor" refers to gfal/fts' monitor, + // NOT the Hercules monitor. + struct _gfalt_transfer_status stat; + stat.status = + 0; // XXX Not clear what this does, the other plugins set it to 0 + stat.average_baudrate = 0; // This seems to be in bytes per second + stat.instant_baudrate = 0; // Idem + stat.bytes_transfered = 0; + stat.transfer_time = 0; + + while (!transfer_finished) { + sleep(HERCULES_POLL_INTERVAL); + + if (cancel_received) { + ret = hercules_cancel_transfer(curl, &rec, src_host, jobid, err); + if (ret) { + return -1; + } + gfal2_set_error(err, hercules_domain(), ECANCELED, __func__, + "Transfer cancelled"); + return -1; + } + + struct hercules_status_info status; + ret = hercules_get_status(curl, &rec, src_host, jobid, &status, err); + if (ret) { + return -1; + } + + int tdiff = status.seconds_elapsed - stat.transfer_time; + int bdiff = status.bytes_acked - stat.bytes_transfered; + stat.average_baudrate = (status.seconds_elapsed != 0) + ? status.bytes_acked / status.seconds_elapsed + : 0; + stat.instant_baudrate = (tdiff != 0) ? bdiff / tdiff : 0; + stat.bytes_transfered = status.bytes_acked; + stat.transfer_time = status.seconds_elapsed; + // Inform FTS about current status + plugin_trigger_monitor(params, &stat, src, dst); + + if (status.state == SESSION_STATE_DONE) { + transfer_finished = 1; + if (!hercules_err_is_ok(status.job_err)) { + gfal2_set_error(err, hercules_domain(), status.job_err, __func__, + "Hercules session error: %s", + hercules_strerror(status.job_err)); + return -1; + } + } + } + gfal2_remove_cancel_callback(ctxt, cancel_token); + plugin_trigger_event(params, hercules_domain(), GFAL_EVENT_NONE, + GFAL_EVENT_TRANSFER_EXIT, "Hercules finished transfer"); + return 0; +} + +// Implementing stat seems to be required, it's called before transfers. +// The Hercules monitor has a /stat API endpoint for this. +int gfal_plugin_hercules_statG(plugin_handle h, const char *name, + struct stat *buf, GError **err) { + struct gfal_hercules_context *data = (struct gfal_hercules_context *)h; + + GError *e = NULL; + gfal2_context_t ctxt = gfal2_context_new( + &e); // XXX No context argument to this function. I hope this is the right + // way to get the certificate/key? + if (e) { + g_propagate_error(err, e); + return -1; + } + gchar *user_cert = gfal2_cred_get(ctxt, GFAL_CRED_X509_CERT, name, NULL, &e); + if (e || !user_cert) { + gfal2_context_free(ctxt); + g_propagate_error(err, e); + return -1; + } + gfal2_log(G_LOG_LEVEL_MESSAGE, "cert: %s", user_cert); + g_clear_error(&e); + if (data->user_cert) { + free(data->user_cert); + data->user_cert = NULL; + } + data->user_cert = user_cert; + gchar *user_key = gfal2_cred_get(ctxt, GFAL_CRED_X509_KEY, name, NULL, &e); + if (e || !user_key) { + gfal2_context_free(ctxt); + g_propagate_error(err, e); + return -1; + } + gfal2_context_free(ctxt); + g_clear_error(&e); + if (data->user_key) { + free(data->user_key); + data->user_key = NULL; + } + data->user_key = user_key; + gfal2_log(G_LOG_LEVEL_MESSAGE, "key: %s", user_key); + + // Parse the URL + char url_host[500]; + char url_path[1000]; + int ret = sscanf(name, "hercules://%499[^/]/%999s", url_host, url_path); + if (ret != 2) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing source URL"); + return -1; + } + gfal2_log(G_LOG_LEVEL_DEBUG, "Hercules: Checking %s %s", url_host, url_path); + + char request_url[2000]; + ret = snprintf(request_url, 2000, "https://%s/stat?file=%s", url_host, + url_path); + if (ret >= 2000) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Submission URL too long"); + return -1; + } + gfal2_log(G_LOG_LEVEL_DEBUG, "Hercules: Stat URL %s", request_url); + + CURL *curl = curl_easy_init(); + if (curl == NULL) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, "CURL error"); + return -1; + } + + struct recvdata rec = {.size = 0}; + curl_easy_setopt(curl, CURLOPT_URL, request_url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, recvfunc); + curl_easy_setopt(curl, CURLOPT_SSLCERT, user_cert); + curl_easy_setopt(curl, CURLOPT_SSLKEY, user_key); + /* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); */ + /* curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); */ + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rec); + ret = curl_get_and_check(curl, err); + if (ret) { + curl_easy_cleanup(curl); + return -1; + } + gfal2_log(G_LOG_LEVEL_DEBUG, "Hercules: Stat Response %s", rec.response); + + unsigned long size; + int ok; + ret = sscanf(rec.response, "OK %d %ld", &ok, &size); + if (ret != 2) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "Error parsing HTTP response?"); + curl_easy_cleanup(curl); + return -1; + } + if (!ok) { + gfal2_set_error(err, hercules_domain(), EINVAL, __func__, + "File does not exist or insufficient permissions"); + curl_easy_cleanup(curl); + return -1; + } + gfal2_log(G_LOG_LEVEL_INFO, "Hercules: File size: %d", size); + buf->st_size = size; + curl_easy_cleanup(curl); + return 0; +} diff --git a/gfal2-hercules/gfal_hercules_transfer.h b/gfal2-hercules/gfal_hercules_transfer.h new file mode 100644 index 0000000..eb64162 --- /dev/null +++ b/gfal2-hercules/gfal_hercules_transfer.h @@ -0,0 +1,15 @@ +#ifndef GFAL_HERCULES_TRANSFER_H_ +#define GFAL_HERCULES_TRANSFER_H_ + +#include +#include +#include + +int gfal_plugin_hercules_copy_file(plugin_handle h, gfal2_context_t ctxt, + gfalt_params_t params, const char *src, + const char *dst, GError **err); + +int gfal_plugin_hercules_statG(plugin_handle h, const char *name, + struct stat *buf, GError **err); + +#endif // GFAL_HERCULES_TRANSFER_H_ diff --git a/gfal2-hercules/hercules_plugin.conf b/gfal2-hercules/hercules_plugin.conf new file mode 100644 index 0000000..4b100e5 --- /dev/null +++ b/gfal2-hercules/hercules_plugin.conf @@ -0,0 +1 @@ +# The Hercules plugin does not currently support any configuration options diff --git a/hercules.h b/hercules.h index b21b6dd..4dbd36b 100644 --- a/hercules.h +++ b/hercules.h @@ -25,6 +25,7 @@ #include "congestion_control.h" #include "frame_queue.h" #include "packet.h" +#include "errors.h" #define HERCULES_DEFAULT_CONFIG_PATH "hercules.conf" #define HERCULES_MAX_HEADERLEN 256 @@ -179,35 +180,6 @@ struct sender_state { }; /// SESSION -// Some states are used only by the TX/RX side and are marked accordingly -enum session_state { - SESSION_STATE_NONE, - SESSION_STATE_PENDING, //< (TX) Need to send HS and repeat until TO, - // waiting for a reflected HS packet - SESSION_STATE_NEW, //< (RX) Received a HS packet, need to send HS reply and - // CTS - SESSION_STATE_WAIT_CTS, //< (TX) Waiting for CTS - SESSION_STATE_INDEX_READY, //< (RX) Index transfer complete, map files and - // send CTS - SESSION_STATE_RUNNING_DATA, //< Data transfer in progress - SESSION_STATE_RUNNING_IDX, //< Directory index transfer in progress - SESSION_STATE_DONE, //< Transfer done (or cancelled with error) -}; - -enum session_error { - SESSION_ERROR_NONE, // Error not set yet - SESSION_ERROR_OK, //< No error, transfer completed successfully - SESSION_ERROR_TIMEOUT, //< Session timed out - SESSION_ERROR_STALE, //< Packets are being received, but none are new - SESSION_ERROR_PCC, //< Something wrong with PCC - SESSION_ERROR_SEQNO_OVERFLOW, - SESSION_ERROR_NO_PATHS, //< Monitor returned no paths to destination - SESSION_ERROR_CANCELLED, //< Transfer cancelled by monitor - SESSION_ERROR_BAD_MTU, //< Invalid MTU supplied by the monitor - SESSION_ERROR_MAP_FAILED, //< Could not mmap file - SESSION_ERROR_TOO_LARGE, //< File or index size too large - SESSION_ERROR_INIT, //< Could not initialise session -}; // A session is a transfer between one sender and one receiver struct hercules_session { diff --git a/monitor/http_api.go b/monitor/http_api.go index 332b7cf..abc2c2f 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -184,16 +184,16 @@ func statAsUser(user, file string) (fs.FileInfo, error) { return nil, fmt.Errorf("No user?") } - err := syscall.Seteuid(ug.uidLookup) + err := syscall.Setegid(ug.gidLookup) if err != nil { return nil, err } - defer syscall.Seteuid(0) - err = syscall.Setegid(ug.gidLookup) + defer syscall.Setegid(0) + err = syscall.Seteuid(ug.uidLookup) if err != nil { return nil, err } - defer syscall.Setegid(0) + defer syscall.Seteuid(0) return os.Stat(file) } @@ -235,3 +235,8 @@ func http_stat(w http.ResponseWriter, r *http.Request) { io.WriteString(w, fmt.Sprintf("OK 1 %d\n", info.Size())) } + +// Return the server's SCION address (needed for gfal) +func http_server(w http.ResponseWriter, _ *http.Request) { + io.WriteString(w, fmt.Sprintf("OK %s", config.ListenAddress.addr.String())) +} diff --git a/monitor/monitor.go b/monitor/monitor.go index 84d7263..6ef6994 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -150,6 +150,7 @@ func main() { http.HandleFunc("/submit", http_submit) http.HandleFunc("/status", http_status) http.HandleFunc("/cancel", http_cancel) + http.HandleFunc("/server", http_server) http.HandleFunc("/stat", http_stat) if config.MonitorHTTP != "disabled" { go http.ListenAndServe(config.MonitorHTTP, nil) @@ -282,6 +283,8 @@ func main() { if !ok { b = binary.LittleEndian.AppendUint16(b, uint16(0)) usock.WriteToUnix(b, a) + fmt.Printf("Received job id %v does not exist?\n", job) + transfersLock.Unlock() continue } job.state = status diff --git a/sampleconf.toml b/sampleconf.toml index ce6525a..da55590 100644 --- a/sampleconf.toml +++ b/sampleconf.toml @@ -24,6 +24,11 @@ MonitorHTTPS = "disabled" TLSCert = "cert.pem" TLSKey = "key.pem" +# Paths to certificates used for validation of TLS client certificates +ClientCACerts = [ +"cert.pem", +] + # Network interfaces to use for Hercules Interfaces = [ "eth0", @@ -86,8 +91,3 @@ NumPaths = 2 # file permission checks. [UserMap] "O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" = {User = "marco", Group = "marco"} - -# Paths to certificates used for validation of TLS client certificates -ClientCACerts = [ -"cert.pem", -] From dd1009812659d248b815fa38b27bcba6bf983ca2 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 27 Jun 2024 16:54:04 +0200 Subject: [PATCH 066/112] retry monitor read if it times out --- monitor.c | 38 +++++++++++++++++++++++++------------- monitor.h | 2 ++ monitor/monitor.go | 17 +++++++++++++---- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/monitor.c b/monitor.c index c9731ee..5f264c5 100644 --- a/monitor.c +++ b/monitor.c @@ -11,19 +11,26 @@ #include "hercules.h" #include "utils.h" +static int msgno = 0; + static bool monitor_send_recv(int sockfd, struct hercules_sockmsg_Q *in, struct hercules_sockmsg_A *out) { - int ret = send(sockfd, in, sizeof(*in), 0); - if (ret != sizeof(*in)) { - fprintf(stderr, "Error sending to monitor?\n"); - return false; - } - ret = recv(sockfd, out, sizeof(*out), 0); - if (ret <= 0) { - fprintf(stderr, "Error reading from monitor?\n"); - return false; - } - return true; + for (int i = 0; i < 3; i++) { // 3 Retries, see comment in monitor.go + in->msgno = msgno++; + int ret = send(sockfd, in, sizeof(*in), 0); + if (ret != sizeof(*in)) { + fprintf(stderr, "Error sending to monitor?\n"); + return false; + } + ret = recv(sockfd, out, sizeof(*out), 0); + if (ret <= 0) { + fprintf(stderr, "Error reading from monitor?\n"); + continue; + } + assert(out->msgno == in->msgno && "Monitor replied with wrong msgno?!"); + return true; + } + return false; } bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, @@ -40,7 +47,7 @@ bool monitor_get_reply_path(int sockfd, const char *rx_sample_buf, if (!ret) { return false; } - if (!reply.payload.reply_path.reply_path_ok){ + if (!reply.payload.reply_path.reply_path_ok) { return false; } @@ -170,7 +177,7 @@ int monitor_bind_daemon_socket(char *server, char *monitor) { struct sockaddr_un name; name.sun_family = AF_UNIX; // Unix socket paths limited to 107 chars - strncpy(name.sun_path, server, sizeof(name.sun_path)-1); + strncpy(name.sun_path, server, sizeof(name.sun_path) - 1); unlink(server); int ret = bind(usock, (struct sockaddr *)&name, sizeof(name)); if (ret) { @@ -185,5 +192,10 @@ int monitor_bind_daemon_socket(char *server, char *monitor) { if (ret) { return 0; } + struct timeval to = {.tv_sec = 1, .tv_usec = 0}; + ret = setsockopt(usock, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); + if (ret) { + return 0; + } return usock; } diff --git a/monitor.h b/monitor.h index e92e07e..ba3c363 100644 --- a/monitor.h +++ b/monitor.h @@ -121,6 +121,7 @@ struct sockmsg_update_job_A { struct hercules_sockmsg_Q { uint16_t msgtype; + uint16_t msgno; union { struct sockmsg_reply_path_Q reply_path; struct sockmsg_paths_Q paths; @@ -132,6 +133,7 @@ struct hercules_sockmsg_Q { #define SOCKMSG_SIZE sizeof(struct hercules_sockmsg_Q) struct hercules_sockmsg_A { + uint16_t msgno; union { struct sockmsg_reply_path_A reply_path; struct sockmsg_paths_A paths; diff --git a/monitor/monitor.go b/monitor/monitor.go index 6ef6994..80762b5 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -180,6 +180,14 @@ func main() { // the monitor's job is to respond to queries from the server for { buf := make([]byte, C.SOCKMSG_SIZE) + // XXX FIXME Sometimes this read will miss a message that was sent by the server. + // The culprit seem to be the seteuid/gid calls in http_api.go, with those + // removed the issue does not seem to appear, it also only happens + // together with calls to the endpoints using seteuid. Adding a lock around + // read/seteuid does not help. The server will retry the request if it + // does not receive a response for a while, so this is not *terrible*, + // still, it's strange that this happens and I don't know how to fix it. + // n, a, err := usock.ReadFromUnix(buf) if err != nil { fmt.Println("Error reading from socket!", err) @@ -188,6 +196,10 @@ func main() { if n > 0 { msgtype := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] + msgno := binary.LittleEndian.Uint16(buf[:2]) + buf = buf[2:] + var b []byte + b = binary.LittleEndian.AppendUint16(b, uint16(msgno)) switch msgtype { case C.SOCKMSG_TYPE_GET_REPLY_PATH: @@ -196,7 +208,6 @@ func main() { etherlen := binary.LittleEndian.Uint16(buf[:2]) buf = buf[2:] replyPath, nextHop, err := getReplyPathHeader(buf[:sample_len], int(etherlen)) - var b []byte if err != nil { fmt.Println("Error in reply path lookup:", err) b = append(b, 0) @@ -229,7 +240,6 @@ func main() { } } transfersLock.Unlock() - var b []byte if selectedJob != nil { fmt.Println("Sending transfer to daemon:", selectedJob.file, selectedJob.destFile, selectedJob.id) _, _ = headersToDestination(*selectedJob) // look up paths to fix mtu @@ -261,7 +271,7 @@ func main() { job, _ := transfers[int(job_id)] n_headers, headers := headersToDestination(*job) transfersLock.Unlock() - b := binary.LittleEndian.AppendUint16(nil, uint16(n_headers)) + b = binary.LittleEndian.AppendUint16(b, uint16(n_headers)) b = append(b, headers...) usock.WriteToUnix(b, a) @@ -277,7 +287,6 @@ func main() { bytes_acked := binary.LittleEndian.Uint64(buf[:8]) buf = buf[8:] fmt.Println("updating job", job_id, status, errorcode) - var b []byte transfersLock.Lock() job, ok := transfers[int(job_id)] if !ok { From 92be6a1634409af57b9afe768fe03e2e3bd53478 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 27 Jun 2024 17:02:12 +0200 Subject: [PATCH 067/112] check reply path enabled --- hercules.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/hercules.c b/hercules.c index e021dce..0efe103 100644 --- a/hercules.c +++ b/hercules.c @@ -2,12 +2,6 @@ // Copyright(c) 2017 - 2018 Intel Corporation. // Copyright(c) 2019 ETH Zurich. -// Enable extra warnings; cannot be enabled in CFLAGS because cgo generates a -// ton of warnings that can apparantly not be suppressed. -#pragma GCC diagnostic warning "-Wextra" -/* #pragma GCC diagnostic warning "-Wunused" */ -/* #pragma GCC diagnostic warning "-Wpedantic" */ - #include "hercules.h" #include "packet.h" #include @@ -58,6 +52,7 @@ #include "bpf_prgms.h" #include "monitor.h" #include "xdp.h" +#include "errors.h" #define MAX_MIDDLEBOX_PROTO_EXTENSION_SIZE 128 // E.g., SCION SPAO header added by LightningFilter @@ -1319,6 +1314,9 @@ static bool rx_update_reply_path( static bool rx_get_reply_path(struct receiver_state *rx_state, struct hercules_path *path) { struct hercules_path p = atomic_load(&rx_state->reply_path); + if (!p.enabled) { + return false; + } memcpy(path, &p, sizeof(*path)); return true; } From 7d6b26df5d1760aeb46a8d4dbd242da33b0cdc15 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 28 Jun 2024 10:29:41 +0200 Subject: [PATCH 068/112] fix nack timings and rtt computation --- hercules.c | 59 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/hercules.c b/hercules.c index 0efe103..45dce27 100644 --- a/hercules.c +++ b/hercules.c @@ -60,6 +60,8 @@ #define RANDOMIZE_FLOWID +/* #define PCC_BENCH */ + #define RATE_LIMIT_CHECK 1000 // check rate limit every X packets // Maximum burst above target pps allowed #define PATH_HANDSHAKE_TIMEOUT_NS 100e6 // send a path handshake every X=100 ms until the first response arrives @@ -67,7 +69,7 @@ #define ACK_RATE_TIME_MS 100 // send ACKS after at most X milliseconds // After how many NACK tracking errors to resend a path handshake -#define NACK_ERRS_ALLOWED 20 +#define NACK_ERRS_ALLOWED 10 static const int rbudp_headerlen = sizeof(struct hercules_header); static const u64 session_timeout = 10e9; // 10 sec @@ -1358,6 +1360,7 @@ static void rx_send_rtt_ack(struct hercules_server *server, static void rx_handle_initial(struct hercules_server *server, struct receiver_state *rx_state, struct rbudp_initial_pkt *initial, + u64 pkt_received_at, const char *buf, const char *payload, int framelen) { debug_printf("handling initial"); @@ -1373,9 +1376,14 @@ static void rx_handle_initial(struct hercules_server *server, if (!ok) { quit_session(rx_state->session, SESSION_ERROR_NO_PATHS); } - rx_state->sent_initial_at = get_nsecs(); } + u64 proctime = get_nsecs() - pkt_received_at; + debug_printf("Adjusting timestamp by %fs", proctime / 1e9); + initial->timestamp += proctime; rx_send_rtt_ack(server, rx_state, initial); // echo back initial pkt to ACK filesize + if (initial->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + rx_state->sent_initial_at = get_nsecs(); + } } // Send an empty ACK, indicating to the sender that it may start sending data @@ -1417,6 +1425,7 @@ static int find_free_rx_slot(struct hercules_server *server){ static void rx_accept_new_session(struct hercules_server *server, struct rbudp_initial_pkt *parsed_pkt, struct hercules_app_addr *peer, + u64 pkt_received_at, const char *buf, const char *payload, ssize_t len, int rx_slot) { if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { @@ -1432,8 +1441,8 @@ static void rx_accept_new_session(struct hercules_server *server, if (!(parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS)) { // Entire index contained in this packet, // we can go ahead and proceed with transfer - struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt, src_port, true); + struct receiver_state *rx_state = + make_rx_state(session, parsed_pkt, src_port, true); if (rx_state == NULL) { debug_printf("Error creating RX state!"); destroy_send_queue(session->send_queue); @@ -1443,15 +1452,15 @@ static void rx_accept_new_session(struct hercules_server *server, } session->rx_state = rx_state; - rx_handle_initial(server, rx_state, parsed_pkt, buf, - payload, len); + rx_handle_initial(server, rx_state, parsed_pkt, pkt_received_at, + buf, payload, len); rx_send_cts_ack(server, rx_state); session->state = SESSION_STATE_RUNNING_DATA; } else { // Index transferred separately - struct receiver_state *rx_state = make_rx_state( - session, parsed_pkt, src_port, false); + struct receiver_state *rx_state = + make_rx_state(session, parsed_pkt, src_port, false); if (rx_state == NULL) { debug_printf("Error creating RX state!"); destroy_send_queue(session->send_queue); @@ -1461,8 +1470,8 @@ static void rx_accept_new_session(struct hercules_server *server, } session->rx_state = rx_state; - rx_handle_initial(server, rx_state, parsed_pkt, buf, - payload, len); + rx_handle_initial(server, rx_state, parsed_pkt, pkt_received_at, + buf, payload, len); session->state = SESSION_STATE_RUNNING_IDX; } server->sessions_rx[rx_slot] = session; @@ -2272,6 +2281,11 @@ static void tx_handle_hs_confirm(struct hercules_server *server, } struct path_set *pathset = tx_state->pathset; u64 now = get_nsecs(); + tx_state->start_time = now; + session_tx->peer.port = pkt_src->port; + tx_send_rtt(server, session_tx, &pathset->paths[0], now); + debug_printf("Updating peer port for this session to %u", + ntohs(pkt_src->port)); if (server->config.enable_pcc) { tx_state->handshake_rtt = now - parsed_pkt->timestamp; for (u32 i = 0; i < pathset->n_paths; i++) { @@ -2294,11 +2308,6 @@ static void tx_handle_hs_confirm(struct hercules_server *server, for (u32 p = 1; p < pathset->n_paths; p++) { pathset->paths[p].next_handshake_at = now; } - tx_state->start_time = get_nsecs(); - session_tx->peer.port = pkt_src->port; - debug_printf("Updating peer port for this session to %u", - ntohs(pkt_src->port)); - tx_send_rtt(server, session_tx, &pathset->paths[0], now); if (tx_state->needs_index_transfer) { // Need to do index transfer first if (!(parsed_pkt->flags & HANDSHAKE_FLAG_INDEX_FOLLOWS)) { @@ -2337,6 +2346,7 @@ static void tx_handle_hs_confirm(struct hercules_server *server, // We have a new return path, redo handshakes on all other paths if (parsed_pkt->flags & HANDSHAKE_FLAG_SET_RETURN_PATH) { + tx_send_rtt(server, session_tx, &pathset->paths[0], now); tx_state->handshake_rtt = now - parsed_pkt->timestamp; for (u32 p = 0; p < pathset->n_paths; p++) { if (p != parsed_pkt->path_index && pathset->paths[p].enabled) { @@ -2345,7 +2355,6 @@ static void tx_handle_hs_confirm(struct hercules_server *server, pathset->paths[p].cc_state->rtt = DBL_MAX; } } - tx_send_rtt(server, session_tx, &pathset->paths[0], now); } count_received_pkt(session_tx, pkt_path_idx); return; @@ -2810,13 +2819,13 @@ static void rx_trickle_nacks(void *arg) { is_index_transfer); u64 ack_round_end = get_nsecs(); if (ack_round_end > - ack_round_start + rx_state->handshake_rtt * 1000 / 4) { + ack_round_start + rx_state->handshake_rtt / 4) { /* fprintf(stderr, "NACK send too slow (took %lld of %ld)\n", */ /* ack_round_end - ack_round_start, */ - /* rx_state->handshake_rtt * 1000 / 4); */ + /* rx_state->handshake_rtt / 4); */ } else { rx_state->next_nack_round_start = - ack_round_start + rx_state->handshake_rtt * 1000 / 4; + ack_round_start + rx_state->handshake_rtt / 4; } rx_state->ack_nr++; } @@ -3480,6 +3489,7 @@ static void events_p(void *arg) { struct hercules_app_addr pkt_source = {.port = udphdr->uh_sport, .ip = scionaddrhdr->src_ip, .ia = scionaddrhdr->src_ia}; + u64 pkt_received_at = get_nsecs(); const size_t rbudp_len = len - (rbudp_pkt - buf); if (rbudp_len < sizeof(u32)) { @@ -3535,7 +3545,7 @@ static void events_p(void *arg) { "Handling initial packet for existing session"); count_received_pkt(session_rx, h->path); rx_handle_initial(server, session_rx->rx_state, - parsed_pkt, buf, + parsed_pkt, pkt_received_at, buf, rbudp_pkt + rbudp_headerlen, len); } else { debug_printf( @@ -3551,7 +3561,8 @@ static void events_p(void *arg) { // attempt to start a new one, go ahead and start a // new rx session rx_accept_new_session(server, parsed_pkt, &pkt_source, - buf, pl, len, rx_slot); + pkt_received_at, buf, pl, len, + rx_slot); } break; @@ -3632,6 +3643,7 @@ static void events_p(void *arg) { pathset->paths[h->path].nack_errs++; if (pathset->paths[h->path].nack_errs > NACK_ERRS_ALLOWED) { + debug_printf("Nack track errs exceeded, resending handshake"); pathset->paths[h->path].next_handshake_at = now; pathset->paths[h->path].nack_errs = 0; } @@ -3640,11 +3652,14 @@ static void events_p(void *arg) { break; case CONTROL_PACKET_TYPE_RTT:; + debug_printf("RTT received"); if (session_rx && src_matches_address(session_rx, &pkt_source)) { struct receiver_state *rx_state = session_rx->rx_state; rx_state->handshake_rtt = - get_nsecs() - rx_state->sent_initial_at; + pkt_received_at - rx_state->sent_initial_at; + // XXX Could simply include the RTT value for the + // receiver in this packet, instead of computing it debug_printf("Updating RTT to %fs", rx_state->handshake_rtt / 1e9); } From efa27a0a5f86d51f7d0491bd7d5b3e94d415ced6 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 9 Aug 2024 15:42:33 +0200 Subject: [PATCH 069/112] Changes for benchmarking mode, performance fix --- Makefile | 7 ++- hercules.c | 105 +++++++++++++++++++++++++--------- monitor/pathstodestination.go | 5 +- 3 files changed, 85 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index eecef5d..128a6bc 100644 --- a/Makefile +++ b/Makefile @@ -2,14 +2,15 @@ TARGET_SERVER := hercules-server TARGET_MONITOR := hercules-monitor CC := gcc -CFLAGS = -O3 -flto -std=gnu11 -D_GNU_SOURCE -Itomlc99 +CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 +# CFLAGS += -DNDEBUG # CFLAGS += -Wall -Wextra ## Options: # Print rx/tx session stats CFLAGS += -DPRINT_STATS # Enforce checking the source SCION/UDP address/port of received packets -CFLAGS += -DCHECK_SRC_ADDRESS +# CFLAGS += -DCHECK_SRC_ADDRESS # Randomise the UDP underlay port (no restriction on the range of used ports). # Enabling this currently breaks SCMP packet parsing # CFLAGS += -DRANDOMIZE_UNDERLAY_SRC @@ -21,7 +22,7 @@ CFLAGS += -DCHECK_SRC_ADDRESS # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets (lots of noise!) -LDFLAGS = -flto -l:libbpf.a -Lbpf/src -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) +LDFLAGS = -g3 -l:libbpf.a -Lbpf/src -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) DEPFLAGS := -MP -MD SRCS := $(wildcard *.c) diff --git a/hercules.c b/hercules.c index 45dce27..16c4b1c 100644 --- a/hercules.c +++ b/hercules.c @@ -61,6 +61,7 @@ #define RANDOMIZE_FLOWID /* #define PCC_BENCH */ +#define PCC_BENCH_SEC 30 #define RATE_LIMIT_CHECK 1000 // check rate limit every X packets // Maximum burst above target pps allowed @@ -74,9 +75,9 @@ static const int rbudp_headerlen = sizeof(struct hercules_header); static const u64 session_timeout = 10e9; // 10 sec static const u64 session_hs_retransmit_interval = 2e9; // 2 sec -static const u64 session_stale_timeout = 30e9; // 30 sec +static const u64 session_stale_timeout = 50e9; // 30 sec static const u64 print_stats_interval = 1e9; // 1 sec -static const u64 path_update_interval = 60e9 * 2; // 2 minutes +static const u64 path_update_interval = 60e9 * 5; // 5 minutes static const u64 monitor_update_interval = 30e9; // 30 seconds #define PCC_NO_PATH \ UINT8_MAX // tell the receiver not to count the packet on any path @@ -180,10 +181,7 @@ static void send_eth_frame(struct hercules_server *server, } static inline bool session_state_is_running(enum session_state s) { - if (s == SESSION_STATE_RUNNING_IDX || s == SESSION_STATE_RUNNING_DATA) { - return true; - } - return false; + return (s == SESSION_STATE_RUNNING_IDX || s == SESSION_STATE_RUNNING_DATA); } static inline void count_received_pkt(struct hercules_session *session, @@ -310,6 +308,7 @@ static struct hercules_session *make_session( s->state = SESSION_STATE_NONE; s->error = SESSION_ERROR_NONE; s->payloadlen = payloadlen; + debug_printf("Using payloadlen %u", payloadlen); s->jobid = job_id; s->peer = *peer_addr; s->last_pkt_sent = 0; @@ -396,12 +395,19 @@ struct hercules_server *hercules_init_server(struct hercules_config config, exit_with_error(NULL, ENOMEM); } - server->usock = - monitor_bind_daemon_socket(config.server_socket, config.monitor_socket); - if (server->usock == 0) { - fprintf(stderr, - "Error binding daemon socket. Is the monitor running?\n"); - exit_with_error(NULL, EINVAL); + for (int i = 0; i < 5; i++) { + server->usock = monitor_bind_daemon_socket(config.server_socket, + config.monitor_socket); + if (server->usock == 0) { + fprintf(stderr, + "Error binding daemon socket. Is the monitor running?\n"); + if (i == 4) { + exit_with_error(NULL, EINVAL); + } + sleep(1); + } else { + break; + } } server->config = config; @@ -1073,6 +1079,10 @@ static void rx_receive_batch(struct hercules_server *server, u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); + if (!rbudp_pkt) { + debug_printf("Unparseable packet on XDP socket, ignoring"); + continue; + } u16 pkt_dst_port = ntohs(*(u16 *)(rbudp_pkt - 6)); struct hercules_session *session_rx = @@ -1115,8 +1125,6 @@ static void rx_receive_batch(struct hercules_server *server, len - (rbudp_pkt - pkt))) { debug_printf("Non-data packet on XDP socket? Ignoring."); } - } else { - debug_printf("Unparseable packet on XDP socket, ignoring"); } atomic_fetch_add(&session_rx->rx_npkts, 1); } @@ -1261,7 +1269,7 @@ static struct receiver_state *make_rx_state( (rx_state->index_size + rx_state->chunklen - 1) / rx_state->chunklen; bitset__create(&rx_state->received_chunks, rx_state->total_chunks); bitset__create(&rx_state->received_chunks_index, rx_state->index_chunks); - rx_state->start_time = 0; + rx_state->start_time = get_nsecs(); rx_state->end_time = 0; rx_state->handshake_rtt = 0; rx_state->is_pcc_benchmark = false; @@ -2454,8 +2462,13 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, debug_printf("real filesize %llu", real_filesize); debug_printf("total entry size %llu", index_size); - char *mem = - mmap(NULL, total_filesize, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0); + char *mem = mmap(NULL, total_filesize, PROT_NONE, + MAP_PRIVATE | MAP_ANON + #ifdef PCC_BENCH + | MAP_POPULATE + #endif + , + 0, 0); if (mem == MAP_FAILED) { free(index); return NULL; @@ -2725,9 +2738,11 @@ static void tx_send_p(void *arg) { struct worker_args *args = arg; struct hercules_server *server = args->server; int cur_session = 0; + int batch = 0; while (!wants_shutdown) { cur_session = ( cur_session + 1 ) % HERCULES_CONCURRENT_SESSIONS; struct hercules_session *session_tx = server->sessions_tx[cur_session]; + // XXX may need another kick_tx here? if (session_tx == NULL || !session_state_is_running(session_tx->state)) { continue; @@ -2736,7 +2751,9 @@ static void tx_send_p(void *arg) { struct send_queue_unit unit; int ret = send_queue_pop(session_tx->send_queue, &unit); if (!ret) { - kick_tx_server(server); + for (int i = 0; i < server->num_ifaces; i++){ + kick_tx(server, args->xsks[i]); + } continue; } // The unit may contain fewer than the max number of chunks. We only @@ -2762,7 +2779,12 @@ static void tx_send_p(void *arg) { frame_addrs, &unit, args->id, is_index_transfer); atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); - kick_tx_server(server); + if (++batch > 5){ + for (int i = 0; i < server->num_ifaces; i++){ + kick_tx(server, args->xsks[i]); + } + batch = 0; + } } } @@ -2789,6 +2811,7 @@ static void rx_trickle_acks(void *arg) { session_rx->state = SESSION_STATE_INDEX_READY; } else { debug_printf("Received all, done."); + debug_printf("Time elapsed %.2f sec", (get_nsecs() - rx_state->start_time) / 1.e9); rx_send_acks(server, rx_state, is_index_transfer); quit_session(session_rx, SESSION_ERROR_OK); } @@ -3132,6 +3155,12 @@ static void mark_timed_out_sessions(struct hercules_server *server, int s, quit_session(session_tx, SESSION_ERROR_TIMEOUT); fprintf(stderr, "Session (TX %2d) timed out!\n", s); } +#ifdef PCC_BENCH + if (session_tx->tx_state->start_time != 0 && + now > session_tx->tx_state->start_time + PCC_BENCH_SEC * 1e9) { + quit_session(session_tx, SESSION_ERROR_OK); + } +#endif } struct hercules_session *session_rx = server->sessions_rx[s]; if (session_rx && session_rx->state != SESSION_STATE_DONE) { @@ -3143,6 +3172,12 @@ static void mark_timed_out_sessions(struct hercules_server *server, int s, quit_session(session_rx, SESSION_ERROR_STALE); fprintf(stderr, "Session (RX %2d) stale!\n", s); } +#ifdef PCC_BENCH + if (session_rx->rx_state->start_time != 0 && + now > session_rx->rx_state->start_time + PCC_BENCH_SEC * 1e9) { + quit_session(session_rx, SESSION_ERROR_OK); + } +#endif } } @@ -3260,9 +3295,10 @@ static void tx_update_paths(struct hercules_server *server, int s, u64 now) { } struct print_info { + u64 ts; u32 rx_received; + u32 rx_chunks; u32 tx_sent; - u64 ts; }; static void print_session_stats(struct hercules_server *server, u64 now, @@ -3279,6 +3315,7 @@ static void print_session_stats(struct hercules_server *server, u64 now, u32 acked_count = session_tx->tx_state->acked_chunks.num_set; u32 total = session_tx->tx_state->acked_chunks.num; u64 tdiff = now - p->ts; + u64 elapsed = (now - session_tx->tx_state->start_time) / 1e9; p->ts = now; double send_rate_pps = (sent_now - p->tx_sent) / ((double)tdiff / 1e9); @@ -3289,10 +3326,10 @@ static void print_session_stats(struct hercules_server *server, u64 now, send_rate_total += send_rate; fprintf( stdout, - "(TX %2d) [%4.1f%%] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, rate " + "(TX %2d) [%4.1f%%] %5llus Chunks: %9u/%9u, rx: %9ld, tx:%9ld, rate " "%8.2f " "Mbps\n", - s, progress_percent, acked_count, total, session_tx->rx_npkts, + s, progress_percent, elapsed, acked_count, total, session_tx->rx_npkts, session_tx->tx_npkts, send_rate); } @@ -3304,26 +3341,38 @@ static void print_session_stats(struct hercules_server *server, u64 now, u32 total = session_rx->rx_state->received_chunks.num; u32 rcvd_now = session_rx->rx_npkts; u64 tdiff = now - p->ts; + u64 elapsed = (now - session_rx->rx_state->start_time) / 1e9; p->ts = now; double recv_rate_pps = (rcvd_now - p->rx_received) / ((double)tdiff / 1e9); + double goodput_pps = (rec_count - p->rx_chunks) / ((double) tdiff / 1e9); p->rx_received = rcvd_now; + p->rx_chunks = rec_count; double recv_rate = 8 * recv_rate_pps * session_rx->rx_state->chunklen / 1e6; + double goodput_rate = + 8 * goodput_pps * session_rx->rx_state->chunklen / 1e6; recv_rate_total += recv_rate; double progress_percent = rec_count / (double)total * 100; fprintf(stdout, - "(RX %2d) [%4.1f%%] Chunks: %9u/%9u, rx: %9ld, tx:%9ld, " - "rate %8.2f " + "(RX %2d) [%4.1f%%] %5llus Chunks: %9u/%9u, rx: %9ld, tx:%9ld, " + "rate %8.2f (%8.2f)" "Mbps\n", - s, progress_percent, rec_count, total, session_rx->rx_npkts, - session_rx->tx_npkts, recv_rate); + s, progress_percent, elapsed, rec_count, total, session_rx->rx_npkts, + session_rx->tx_npkts, recv_rate, goodput_rate); + fprintf(stdout, "(RX %2d) 1: %u | 2: %u | 3: %u | 4: %u\n", + s, + session_rx->rx_state->path_state[0].rx_npkts, + session_rx->rx_state->path_state[1].rx_npkts, + session_rx->rx_state->path_state[2].rx_npkts, + session_rx->rx_state->path_state[3].rx_npkts); } } if (active_session) { fprintf(stdout, "TX Total Rate: %.2f Mbps\n", send_rate_total); fprintf(stdout, "RX Total Rate: %.2f Mbps\n", recv_rate_total); fprintf(stdout, "\n"); + fflush(stdout); } } @@ -3383,7 +3432,7 @@ static void stop_finished_sessions(struct hercules_server *server, int slot, struct hercules_session *session_rx = server->sessions_rx[slot]; if (session_rx != NULL && session_rx->state != SESSION_STATE_DONE && session_rx->error != SESSION_ERROR_NONE) { - fprintf(stderr, "Stopping RX %d\n", slot); + fprintf(stderr, "Stopping RX %d. Time elapsed: %.2fs\n", slot, (now - session_rx->rx_state->start_time)/1e9); session_rx->state = SESSION_STATE_DONE; int ret = msync(session_rx->rx_state->mem, session_rx->rx_state->filesize, @@ -3439,6 +3488,7 @@ static void events_p(void *arg) { ssize_t len = recvfrom(server->control_sockfd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&addr, &addr_size); + u64 pkt_received_at = get_nsecs(); if (len == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { continue; @@ -3489,7 +3539,6 @@ static void events_p(void *arg) { struct hercules_app_addr pkt_source = {.port = udphdr->uh_sport, .ip = scionaddrhdr->src_ip, .ia = scionaddrhdr->src_ia}; - u64 pkt_received_at = get_nsecs(); const size_t rbudp_len = len - (rbudp_pkt - buf); if (rbudp_len < sizeof(u32)) { diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index a7f8d4e..9ba7371 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -105,6 +105,9 @@ func (ptd *PathsToDestination) choosePaths() bool { // No payloadlen set yet, we set it to the maximum that all selected paths and interfaces support maxPayloadlen := HerculesMaxPktsize for _, path := range ptd.paths { + if !path.enabled { + continue; + } pathMTU := int(path.path.Metadata().MTU) underlayHeaderLen, scionHeaderLen := getPathHeaderlen(path.path) if pathMTU == 0 { @@ -121,7 +124,7 @@ func (ptd *PathsToDestination) choosePaths() bool { if maxPayloadlen+scionHeaderLen+underlayHeaderLen-14 > path.iface.MTU { // Packet exceeds the interface MTU // 14 is the size of the ethernet header, which is not included in the interface's MTU - fmt.Printf("Interface (%v) MTU too low, decreasing payload length", path.iface.Name) + fmt.Printf("Interface (%v) MTU too low, decreasing payload length\n", path.iface.Name) maxPayloadlen = path.iface.MTU - underlayHeaderLen - scionHeaderLen } } From ef8fb2e633444a423e9cf97449cd58c12c96d0de Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 5 Sep 2024 17:34:00 +0200 Subject: [PATCH 070/112] add hcp: a cli tool to interact with Hercules --- hcp/README | 16 +++ hcp/go.mod | 27 +++++ hcp/go.sum | 94 ++++++++++++++++++ hcp/hcp.go | 240 +++++++++++++++++++++++++++++++++++++++++++++ hercules.c | 6 +- monitor/monitor.go | 2 +- 6 files changed, 381 insertions(+), 4 deletions(-) create mode 100644 hcp/README create mode 100644 hcp/go.mod create mode 100644 hcp/go.sum create mode 100644 hcp/hcp.go diff --git a/hcp/README b/hcp/README new file mode 100644 index 0000000..3262201 --- /dev/null +++ b/hcp/README @@ -0,0 +1,16 @@ +hcp: CLI Client for the Hercules Server +================================================= + +This tool is intended to simplify interaction with the Hercules server via its API. + +Usage: `hcp [OPTIONS]... SRC-API SRC-PATH DST-ADDR DST-PATH` + +For example, if you're running this tool on the same machine as the source Hercules server +and want to transfer the file `hercules.in` to `hercules.out`, with the destination +Hercules server listening on `64-2:0:9,192.168.4.2:10000`, run the following command: + +`hcp localhost:8000 hercules.in 64-2:0:9,192.168.4.2:10000 hercules.out` + +Building +========== +`go build` diff --git a/hcp/go.mod b/hcp/go.mod new file mode 100644 index 0000000..39b04a0 --- /dev/null +++ b/hcp/go.mod @@ -0,0 +1,27 @@ +module hcp + +go 1.22.5 + +require ( + github.com/schollz/progressbar/v3 v3.14.6 + github.com/scionproto/scion v0.11.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect +) diff --git a/hcp/go.sum b/hcp/go.sum new file mode 100644 index 0000000..b79bcc1 --- /dev/null +++ b/hcp/go.sum @@ -0,0 +1,94 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/cmac v1.0.0 h1:Vaorm9FVpO2P+YmRdH0RVCUB1XF3Ge1yg9scPvJphyk= +github.com/dchest/cmac v1.0.0/go.mod h1:0zViPqHm8iZwwMl1cuK3HqK7Tu4Q7DV4EuMIOUwBVQ0= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= +github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= +github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= +github.com/scionproto/scion v0.11.0 h1:bvty7qEBRm1PJ0/XorF/tZ/Jq89yTc9IfTMRduLafAw= +github.com/scionproto/scion v0.11.0/go.mod h1:paxrF6VreownCN7E7Rdri6ifXMkiq3leFGoP6n/BFC4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hcp/hcp.go b/hcp/hcp.go new file mode 100644 index 0000000..7a26dfb --- /dev/null +++ b/hcp/hcp.go @@ -0,0 +1,240 @@ +package main + +// #include "../monitor.h" +import "C" +import ( + "errors" + "flag" + "fmt" + "io" + "net/http" + "os" + "os/signal" + "time" + + "github.com/schollz/progressbar/v3" + "github.com/scionproto/scion/pkg/snet" +) + +type apiStatus struct { + status int + state int + error int + sec_elapsed int + bytes_acked int +} + +func main() { + flag.Usage = func() { + fmt.Printf("Usage: %s [OPTION]... SOURCE-API SOURCE-PATH DEST-ADDR DEST-PATH\n", os.Args[0]) + flag.PrintDefaults() + } + poll_interval := flag.Duration("i", time.Second*1, "Poll frequency") + no_stat_file := flag.Bool("n", false, "Don't stat source file") + + flag.Parse() + + if flag.NArg() != 4 { + flag.Usage() + os.Exit(1) + } + src_api := flag.Arg(0) + src_path := flag.Arg(1) + dst_addr := flag.Arg(2) + dst_path := flag.Arg(3) + + // Try to parse to catch errors + _, err := snet.ParseUDPAddr(dst_addr) + if err != nil { + fmt.Println("Invalid destination address.", err) + os.Exit(2) + } + + filesize := -1 + if !*no_stat_file { + filesize, err = stat(src_api, src_path) + if err != nil { + fmt.Println(err) + os.Exit(3) + } + } + + cancelChan := make(chan os.Signal, 1) + signal.Notify(cancelChan, os.Kill) + signal.Notify(cancelChan, os.Interrupt) + + job_id, err := submit(src_api, src_path, dst_addr, dst_path) + if err != nil { + fmt.Println(err) + os.Exit(2) + } + + finished := false + old_status := apiStatus{} + + bar := progressbar.NewOptions(filesize, + progressbar.OptionFullWidth(), + progressbar.OptionShowBytes(true), + progressbar.OptionShowCount(), + progressbar.OptionSetDescription("Transfer submitted"), + progressbar.OptionSetPredictTime(true), + progressbar.OptionSetRenderBlankState(true), + progressbar.OptionShowElapsedTimeOnFinish(), + ) + + for !finished { + time.Sleep(*poll_interval) + + select { + case <-cancelChan: + fmt.Println("Cancelling transfer, C-c again to quit without waiting") + cancel(src_api, job_id) + signal.Reset() + default: + } + + info, err := poll(src_api, job_id) + if err != nil { + fmt.Println(err) + os.Exit(2) + } + + tdiff := info.sec_elapsed - old_status.sec_elapsed + bar.Add64(0) + byte_diff := info.bytes_acked - old_status.bytes_acked + if tdiff > 0 { + // current_rate := float64(info.bytes_acked-old_status.bytes_acked) * 8 / float64(tdiff) / 1000000 + // avg_rate := 0.0 + // if info.sec_elapsed > 0 { + // avg_rate = float64(info.bytes_acked) * 8 / float64(info.sec_elapsed) / 1000000 + // } + // fmt.Printf("%.2f Mb/s, %.2f Mbps avg, %v MB transferred, %v seconds elapsed\n", current_rate, avg_rate, info.bytes_acked/1000000, info.sec_elapsed) + bar.Describe("Transfer in progress") + bar.Add(byte_diff) + old_status = info + } + + if info.state == C.SESSION_STATE_DONE { + finished = true + bar.Exit() + fmt.Println() + if info.error != C.SESSION_ERROR_OK { + fmt.Println("Transfer terminated with error:", hercules_strerror(info.error)) + os.Exit(10) + } + } + } + +} + +func submit(src_api, src_path, dst_addr, dst_path string) (int, error) { + submit_url := fmt.Sprintf("http://%s/submit?file=%s&dest=%s&destfile=%s", src_api, src_path, dst_addr, dst_path) + submit_response, err := http.Get(submit_url) + if err != nil { + return 0, err + } + if submit_response.StatusCode != http.StatusOK { + return 0, errors.New(fmt.Sprintln("HTTP status:", submit_response.StatusCode)) + } + response_bytes, err := io.ReadAll(submit_response.Body) + if err != nil { + return 0, err + } + var job_id int + n, err := fmt.Sscanf(string(response_bytes), "OK %d", &job_id) + if err != nil || n != 1 { + return 0, errors.New(fmt.Sprintln("Error parsing response", err)) + } + return job_id, nil +} + +func poll(src_api string, job_id int) (apiStatus, error) { + var info apiStatus + poll_url := fmt.Sprintf("http://%s/status?id=%d", src_api, job_id) + poll_response, err := http.Get(poll_url) + if err != nil { + return info, err + } + if poll_response.StatusCode != http.StatusOK { + return info, errors.New(fmt.Sprintln("HTTP status:", poll_response.StatusCode)) + } + response_bytes, err := io.ReadAll(poll_response.Body) + if err != nil { + return info, err + } + // Format of the response: OK status state err seconds_elapsed bytes_acked + n, err := fmt.Sscanf(string(response_bytes), "OK %d %d %d %d %d", &info.status, &info.state, &info.error, &info.sec_elapsed, &info.bytes_acked) + if err != nil || n != 5 { + return info, errors.New(fmt.Sprintln("Error parsing response", err)) + } + return info, nil +} + +func cancel(src_api string, job_id int) error { + cancel_url := fmt.Sprintf("http://%s/cancel?id=%d", src_api, job_id) + cancel_response, err := http.Get(cancel_url) + if err != nil { + return err + } + if cancel_response.StatusCode != http.StatusOK { + return errors.New(fmt.Sprintf("HTTP status:", cancel_response.StatusCode)) + } + return nil +} + +func stat(src_api, src_path string) (int, error) { + stat_url := fmt.Sprintf("http://%s/stat?file=%s", src_api, src_path) + stat_response, err := http.Get(stat_url) + if err != nil { + return 0, err + } + if stat_response.StatusCode != http.StatusOK { + return 0, errors.New(fmt.Sprintf("HTTP status:", stat_response.StatusCode)) + } + response_bytes, err := io.ReadAll(stat_response.Body) + if err != nil { + return 0, err + } + // Response format: OK file_exists? size + var exists int + var size int + n, err := fmt.Sscanf(string(response_bytes), "OK %d %d", &exists, &size) + if err != nil || n != 2 { + return 0, errors.New(fmt.Sprintln("Error parsing response", err)) + } + if exists != 1 { + return 0, errors.New("Source file does not exist?") + } + return size, nil +} + +func hercules_strerror(errno int) string { + switch errno { + case C.SESSION_ERROR_NONE: + return "Error not set" + case C.SESSION_ERROR_OK: + return "Transfer successful" + case C.SESSION_ERROR_TIMEOUT: + return "Session timed out" + case C.SESSION_ERROR_STALE: + return "Session stalled" + case C.SESSION_ERROR_PCC: + return "PCC error" + case C.SESSION_ERROR_SEQNO_OVERFLOW: + return "PCC sequence number overflow" + case C.SESSION_ERROR_NO_PATHS: + return "No paths to destination" + case C.SESSION_ERROR_CANCELLED: + return "Transfer cancelled" + case C.SESSION_ERROR_BAD_MTU: + return "Bad MTU" + case C.SESSION_ERROR_MAP_FAILED: + return "Mapping file failed" + case C.SESSION_ERROR_TOO_LARGE: + return "File or directory listing too large" + case C.SESSION_ERROR_INIT: + return "Could not initialise session" + default: + return "Unknown error" + } +} diff --git a/hercules.c b/hercules.c index 16c4b1c..ffe9205 100644 --- a/hercules.c +++ b/hercules.c @@ -78,7 +78,7 @@ static const u64 session_hs_retransmit_interval = 2e9; // 2 sec static const u64 session_stale_timeout = 50e9; // 30 sec static const u64 print_stats_interval = 1e9; // 1 sec static const u64 path_update_interval = 60e9 * 5; // 5 minutes -static const u64 monitor_update_interval = 30e9; // 30 seconds +static const u64 monitor_update_interval = 5e9; // 5 seconds #define PCC_NO_PATH \ UINT8_MAX // tell the receiver not to count the packet on any path _Atomic bool wants_shutdown = false; @@ -3386,8 +3386,8 @@ static void tx_update_monitor(struct hercules_server *server, int s, u64 now) { bool ret = monitor_update_job( server->usock, session_tx->jobid, session_tx->state, 0, (now - session_tx->tx_state->start_time) / (int)1e9, - session_tx->tx_state->chunklen * - session_tx->tx_state->acked_chunks.num_set); + (u64) session_tx->tx_state->chunklen * + (u64) session_tx->tx_state->acked_chunks.num_set); if (!ret) { quit_session(session_tx, SESSION_ERROR_CANCELLED); } diff --git a/monitor/monitor.go b/monitor/monitor.go index 80762b5..ed01dd8 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -286,7 +286,7 @@ func main() { buf = buf[8:] bytes_acked := binary.LittleEndian.Uint64(buf[:8]) buf = buf[8:] - fmt.Println("updating job", job_id, status, errorcode) + fmt.Println("updating job", job_id, status, errorcode, bytes_acked) transfersLock.Lock() job, ok := transfers[int(job_id)] if !ok { From 361febf1021b6de0f3ed49fbacf3df9ef3feb7bb Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 20 Sep 2024 12:03:18 +0200 Subject: [PATCH 071/112] fix compilation --- Makefile | 3 +-- README.md | 1 + bpf_prgm/pass.c | 14 -------------- bpf_prgms.h | 4 ---- bpf_prgms.s | 11 ----------- 5 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 bpf_prgm/pass.c diff --git a/Makefile b/Makefile index 128a6bc..c546495 100644 --- a/Makefile +++ b/Makefile @@ -49,14 +49,13 @@ $(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) builder $(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a builder @# update modification dates in assembly, so that the new version gets loaded - @sed -i -e "s/\(load bpf_prgm_pass\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/pass.c | head -c 32)/g" bpf_prgms.s @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s docker exec hercules-builder $(CC) -o $@ $(OBJS) bpf_prgms.s $(LDFLAGS) %.o: %.c builder docker exec hercules-builder $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ -hercules: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf_prgm/pass.o bpf/src/libbpf.a +hercules: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf/src/libbpf.a docker exec hercules-builder go build -ldflags "-X main.startupVersion=$${startupVersion}" bpf_prgm/%.ll: bpf_prgm/%.c diff --git a/README.md b/README.md index 516ac28..a19be3f 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ There is a restriction on path selection: All paths must be large enough to fit ## Getting started ### Building +First, run `git submodule update --init`. Running `make` should build both the monitor and server. This uses the provided dockerfile. ### Running diff --git a/bpf_prgm/pass.c b/bpf_prgm/pass.c deleted file mode 100644 index 057143c..0000000 --- a/bpf_prgm/pass.c +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright(c) 2017 - 2018 Intel Corporation. -// Copyright(c) 2019 ETH Zurich. - -#include -#include - -SEC("xdp") -int xdp_prog_pass(struct xdp_md *ctx) -{ - return XDP_PASS; -} - -char _license[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/bpf_prgms.h b/bpf_prgms.h index 7883872..620ff26 100644 --- a/bpf_prgms.h +++ b/bpf_prgms.h @@ -3,10 +3,6 @@ // these programs get loaded in bpf_prgms.s -/* Dummy BPF passing all packets to the traditional network stack */ -extern const char bpf_prgm_pass[]; -extern u32 bpf_prgm_pass_size; - /* The BPF program to parse packets and redirect Hercules packets to user space */ extern const char bpf_prgm_redirect_userspace[]; extern u32 bpf_prgm_redirect_userspace_size; diff --git a/bpf_prgms.s b/bpf_prgms.s index fe825fd..7ce3e1a 100644 --- a/bpf_prgms.s +++ b/bpf_prgms.s @@ -1,16 +1,5 @@ .section ".rodata" -# load bpf_prgm_pass 016472e56208515534444147d4642b7e - .globl bpf_prgm_pass - .type bpf_prgm_pass, STT_OBJECT - .globl bpf_prgm_pass_size - .type bpf_prgm_pass_size, STT_OBJECT -bpf_prgm_pass: - .incbin "bpf_prgm/pass.o" - .byte 0 - .size bpf_prgm_pass, .-bpf_prgm_pass -bpf_prgm_pass_size: - .int (.-bpf_prgm_pass-1) # load bpf_prgm_redirect_userspace 88fc5453564d43b556649eee52e3239a .globl bpf_prgm_redirect_userspace From 3c8e910eaf6f04deaad629dcda6be4d9c920d63c Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 24 Sep 2024 15:59:44 +0200 Subject: [PATCH 072/112] monitor: correctly determine directory size --- monitor/http_api.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/monitor/http_api.go b/monitor/http_api.go index abc2c2f..fd70d07 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -6,6 +6,7 @@ import ( "io/fs" "net/http" "os" + "path/filepath" "strconv" "syscall" @@ -233,7 +234,27 @@ func http_stat(w http.ResponseWriter, r *http.Request) { return } - io.WriteString(w, fmt.Sprintf("OK 1 %d\n", info.Size())) + totalSize := info.Size() + if info.Mode().IsDir() { + dirSize := 0 + walker := func(_ string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsRegular() { + dirSize += int(info.Size()) + } + return nil + } + err := filepath.Walk(file, walker) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + totalSize = int64(dirSize) + } + + io.WriteString(w, fmt.Sprintf("OK 1 %d\n", totalSize)) } // Return the server's SCION address (needed for gfal) From 5095387f4e8321965515d585c2d3d9c029d36102 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 24 Sep 2024 16:43:00 +0200 Subject: [PATCH 073/112] Add option to drop privileges after startup --- hercules.c | 30 ++++++++++++++++++++++++++++++ hercules.h | 3 +++ monitor/config.go | 1 + sampleconf.toml | 3 +++ 4 files changed, 37 insertions(+) diff --git a/hercules.c b/hercules.c index ffe9205..adfae0b 100644 --- a/hercules.c +++ b/hercules.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -3753,6 +3754,17 @@ void hercules_main(struct hercules_server *server) { exit(1); } + // Drop privileges + ret = setgid(server->config.drop_gid); + if (ret != 0) { + fprintf(stderr, "Error in setgid\n"); + exit(1); + } + ret = setuid(server->config.drop_uid); + if (ret != 0) { + fprintf(stderr, "Error in setuid\n"); + exit(1); + } // Start the NACK sender thread debug_printf("starting NACK trickle thread"); pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); @@ -3814,6 +3826,8 @@ int main(int argc, char *argv[]) { // Set defaults config.monitor_socket = HERCULES_DEFAULT_MONITOR_SOCKET; config.server_socket = HERCULES_DEFAULT_DAEMON_SOCKET; + config.drop_uid = 0; + config.drop_gid = 0; config.queue = 0; config.configure_queues = true; config.enable_pcc = true; @@ -3876,6 +3890,22 @@ int main(int argc, char *argv[]) { } } + // User/group to drop privileges + toml_datum_t drop_user = toml_string_in(conf, "DropUser"); + if (drop_user.ok) { + struct passwd *user = getpwnam(drop_user.u.s); + if (!user){ + fprintf(stderr, "Error looking up user\n"); + exit(1); + } + config.drop_uid = user->pw_uid; + config.drop_gid = user->pw_gid; + } else { + if (toml_key_exists(conf, "DropUser")) { + fprintf(stderr, "Error parsing DropUser\n"); + exit(1); + } + } // Listening address toml_datum_t listen_addr = toml_string_in(conf, "ListenAddress"); if (!listen_addr.ok) { diff --git a/hercules.h b/hercules.h index 4dbd36b..7cd1e20 100644 --- a/hercules.h +++ b/hercules.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "bpf/src/xsk.h" #include "congestion_control.h" @@ -223,6 +224,8 @@ struct hercules_interface { struct hercules_config { char *monitor_socket; char *server_socket; + uid_t drop_uid; + gid_t drop_gid; u32 xdp_flags; int xdp_mode; int queue; diff --git a/monitor/config.go b/monitor/config.go index b80578e..a802eed 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -67,6 +67,7 @@ type MonitorConfig struct { ClientCACerts []string // The following are not used by the monitor, they are listed here for completeness ServerSocket string + DropUser string XDPZeroCopy bool Queue int ConfigureQueues bool diff --git a/sampleconf.toml b/sampleconf.toml index da55590..f23c5c8 100644 --- a/sampleconf.toml +++ b/sampleconf.toml @@ -20,6 +20,9 @@ MonitorHTTP = ":8000" # Set to "disabled" to disable MonitorHTTPS = "disabled" +# Drop privileges to the specified user after startup +DropUser = "_hercules" + # Path to the server's certificate and key for TLS TLSCert = "cert.pem" TLSKey = "key.pem" From 64c12de5c8c20f5e99e404fbc286e56b019cd28e Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 24 Sep 2024 16:46:49 +0200 Subject: [PATCH 074/112] remove timeout from monitor socket --- monitor.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/monitor.c b/monitor.c index 5f264c5..8578b51 100644 --- a/monitor.c +++ b/monitor.c @@ -15,7 +15,7 @@ static int msgno = 0; static bool monitor_send_recv(int sockfd, struct hercules_sockmsg_Q *in, struct hercules_sockmsg_A *out) { - for (int i = 0; i < 3; i++) { // 3 Retries, see comment in monitor.go + for (int i = 0; i < 1; i++) { // 3 Retries, see comment in monitor.go in->msgno = msgno++; int ret = send(sockfd, in, sizeof(*in), 0); if (ret != sizeof(*in)) { @@ -25,6 +25,7 @@ static bool monitor_send_recv(int sockfd, struct hercules_sockmsg_Q *in, ret = recv(sockfd, out, sizeof(*out), 0); if (ret <= 0) { fprintf(stderr, "Error reading from monitor?\n"); + fprintf(stderr, "Error was %s", strerror(errno)); continue; } assert(out->msgno == in->msgno && "Monitor replied with wrong msgno?!"); @@ -192,10 +193,10 @@ int monitor_bind_daemon_socket(char *server, char *monitor) { if (ret) { return 0; } - struct timeval to = {.tv_sec = 1, .tv_usec = 0}; - ret = setsockopt(usock, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); - if (ret) { - return 0; - } + /* struct timeval to = {.tv_sec = 1, .tv_usec = 0}; */ + /* ret = setsockopt(usock, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); */ + /* if (ret) { */ + /* return 0; */ + /* } */ return usock; } From 4901b6579488f27a3c2722135727e9c299f62073 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 10 Oct 2024 14:48:06 +0200 Subject: [PATCH 075/112] readme: explain api --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ hercules.c | 12 ++++++------ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a19be3f..e30cabc 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ On both the sender and the receiver: - Fill in the provided `hercules.conf` with the host's SCION address and NIC - First, start the monitor: `./hercules-monitor`. - If there is already an XDP program loaded on the interface, you first have to remove it (e.g. `ip l set dev eth0 xdp off`) +- Depending on your network card/setup, you may want to specify `ConfigureQueues = false` in the config file. If you get an error related to XDP upon starting the server, try this. Then, in a second shell, start the server: `./hercules-server`. ### Submitting a transfer @@ -58,6 +59,52 @@ In order to use it, adjust the definitions at the top of the file (hostnames, ad The script further relies on you having ssh keys set up for those two hosts. Depending on the network cards you may need to comment in the two lines with `ConfigureQueues = false`. +## API +The Monitor's API supports the following operations via HTTP GET requests. + +### `/submit`: Submitting a new transfer +Parameters: +- `file`: Path to source file, from the server's point of view +- `dest`: SCION address of the destination Hercules server +- `destfile`: Destination file path +- `payloadlen`: (Optional) override automatic MTU selection and use the specified payload length instead. + +Example: `localhost:8000/submit?file=infile&destfile=outfile&dest=64-2:0:c,148.187.128.136:8000` + +Returns: `OK id`, where `id` is an integer identifying the submitted job on success, an HTTP error code otherwise. + +### `/status`: Check a transfer's status +Parameters: +- `id`: An id previously returned by `/submit`. + +Returns: `OK status state error time_elapsed bytes_acked` on success, an HTTP error code otherwise. +- `status` is the monitor's internal transfer status and one of `TransferStatus`, as defined in the go code. +- `state` is an integer corresponding to the transfers current status (one of `session_state`, as defined in `errors.h`) +- `error` is an integer corresponding to the transfers error state (one of `session_error`, as defined in `errors.h`) +- `time_elapsed` is an integer representing the number of seconds elapsed since the server started this transfer. +- `bytes_acked` is the number of bytes acknowledged by the receiver. + +### `/cancel`: Cancel a transfer +Parameters: +- `id`: An id previously returned by `/submit`. + +Returns: `OK`on success, an HTTP error code otherwise. + +### `/server`: Returns the server's SCION address +This functionality is provided for integration with FTS. + +Parameters: None + +Returns: `OK addr`, where `addr` is the server's SCION address. + +### `/stat`: Retrieve stat information on a file +This is provided for compatibility with FTS, but also (optionally) used by hcp. + +Parameters: +- `file`: Path to file + +Returns: `OK exists size`, where `exists` is 1 if the file exists, 0 otherwise; `size` is the file's size in bytes. + # Readme not updated below this line. diff --git a/hercules.c b/hercules.c index adfae0b..8475134 100644 --- a/hercules.c +++ b/hercules.c @@ -3361,12 +3361,12 @@ static void print_session_stats(struct hercules_server *server, u64 now, "Mbps\n", s, progress_percent, elapsed, rec_count, total, session_rx->rx_npkts, session_rx->tx_npkts, recv_rate, goodput_rate); - fprintf(stdout, "(RX %2d) 1: %u | 2: %u | 3: %u | 4: %u\n", - s, - session_rx->rx_state->path_state[0].rx_npkts, - session_rx->rx_state->path_state[1].rx_npkts, - session_rx->rx_state->path_state[2].rx_npkts, - session_rx->rx_state->path_state[3].rx_npkts); + /* fprintf(stdout, "(RX %2d) 1: %u | 2: %u | 3: %u | 4: %u\n", */ + /* s, */ + /* session_rx->rx_state->path_state[0].rx_npkts, */ + /* session_rx->rx_state->path_state[1].rx_npkts, */ + /* session_rx->rx_state->path_state[2].rx_npkts, */ + /* session_rx->rx_state->path_state[3].rx_npkts); */ } } if (active_session) { From 78f20a8ce715435455f301535a52068608dc2bd0 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 10 Oct 2024 14:48:30 +0200 Subject: [PATCH 076/112] config: allow specifying payload length per-dest --- monitor/config.go | 45 ++++++++++++++++++++++++------------------ monitor/http_api.go | 6 ++++++ monitor/pathmanager.go | 7 ++++--- sampleconf.toml | 5 ++++- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/monitor/config.go b/monitor/config.go index a802eed..3f52a62 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -14,14 +14,16 @@ import ( // These specify how to read the config file type HostConfig struct { - HostAddr addr.Addr - NumPaths int - PathSpec []PathSpec + HostAddr addr.Addr + NumPaths int + PathSpec []PathSpec + Payloadlen int } type ASConfig struct { - IA addr.IA - NumPaths int - PathSpec []PathSpec + IA addr.IA + NumPaths int + PathSpec []PathSpec + Payloadlen int } // This wraps snet.UDPAddr to make the config parsing work @@ -67,7 +69,7 @@ type MonitorConfig struct { ClientCACerts []string // The following are not used by the monitor, they are listed here for completeness ServerSocket string - DropUser string + DropUser string XDPZeroCopy bool Queue int ConfigureQueues bool @@ -90,17 +92,19 @@ func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { confHost, ok := p.Hosts[a] if ok { return Destination{ - hostAddr: dest, - pathSpec: &confHost.PathSpec, - numPaths: confHost.NumPaths, + hostAddr: dest, + pathSpec: &confHost.PathSpec, + numPaths: confHost.NumPaths, + payloadlen: confHost.Payloadlen, } } conf, ok := p.ASes[dest.IA] if ok { return Destination{ - hostAddr: dest, - pathSpec: &conf.PathSpec, - numPaths: conf.NumPaths, + hostAddr: dest, + pathSpec: &conf.PathSpec, + numPaths: conf.NumPaths, + payloadlen: conf.Payloadlen, } } return Destination{ @@ -112,6 +116,7 @@ func findPathRule(p *PathRules, dest *snet.UDPAddr) Destination { const defaultMonitorHTTP = ":8000" const defaultMonitorHTTPS = "disabled" + // Disabled by default because further config (certs) is needed // Decode the config file and fill in any unspecified values with defaults. @@ -208,9 +213,10 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { pathspec = host.PathSpec } pathRules.Hosts[host.HostAddr] = HostConfig{ - HostAddr: host.HostAddr, - NumPaths: numpaths, - PathSpec: pathspec, + HostAddr: host.HostAddr, + NumPaths: numpaths, + PathSpec: pathspec, + Payloadlen: host.Payloadlen, } } @@ -225,9 +231,10 @@ func readConfig(configFile string) (MonitorConfig, PathRules) { pathspec = as.PathSpec } pathRules.ASes[as.IA] = ASConfig{ - IA: as.IA, - NumPaths: numpaths, - PathSpec: pathspec, + IA: as.IA, + NumPaths: numpaths, + PathSpec: pathspec, + Payloadlen: as.Payloadlen, } } diff --git a/monitor/http_api.go b/monitor/http_api.go index fd70d07..6bb2585 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -87,6 +87,12 @@ func http_submit(w http.ResponseWriter, r *http.Request) { } destination := findPathRule(&pathRules, destParsed) + + // If the config specifies a payload length, use that value + if destination.payloadlen != 0 { + payloadlen = destination.payloadlen + } + pm, err := initNewPathManager(activeInterfaces, &destination, listenAddress, payloadlen) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/monitor/pathmanager.go b/monitor/pathmanager.go index 6c0a757..9ab280e 100644 --- a/monitor/pathmanager.go +++ b/monitor/pathmanager.go @@ -23,9 +23,10 @@ import ( ) type Destination struct { - hostAddr *snet.UDPAddr - pathSpec *[]PathSpec - numPaths int + hostAddr *snet.UDPAddr + pathSpec *[]PathSpec + numPaths int + payloadlen int } type PathManager struct { diff --git a/sampleconf.toml b/sampleconf.toml index f23c5c8..9c34a36 100644 --- a/sampleconf.toml +++ b/sampleconf.toml @@ -79,10 +79,13 @@ PathSpec = [ ] # In this case the number of paths is set to 2, but the exact set of paths -# is left unspecified +# is left unspecified. +# We additionally override automatic MTU selection based on SCION path metadata +# and use a payload length of 1000B for all transfers to this host. [[DestinationHosts]] HostAddr = "18-a:b:c,2.2.2.2" NumPaths = 2 +Payloadlen = 1000 # Similarly, but for an entire destination AS instead of a specific host From 74748500dd027b907192679fca4ff3f96e5e3976 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 10 Oct 2024 14:51:27 +0200 Subject: [PATCH 077/112] add systemd service files --- dist/Readme | 3 +++ dist/hercules-monitor.service | 10 ++++++++++ dist/hercules-server.service | 11 +++++++++++ 3 files changed, 24 insertions(+) create mode 100644 dist/Readme create mode 100644 dist/hercules-monitor.service create mode 100644 dist/hercules-server.service diff --git a/dist/Readme b/dist/Readme new file mode 100644 index 0000000..a2dfbb0 --- /dev/null +++ b/dist/Readme @@ -0,0 +1,3 @@ +You may use the service files provided here to run hercules via systemd. +Make sure to replace the executable path and working directory according to your installation and copy the files to the appropriate location on your system. +The files should take care of starting and stopping the hercules server and monitor together. diff --git a/dist/hercules-monitor.service b/dist/hercules-monitor.service new file mode 100644 index 0000000..79f9e19 --- /dev/null +++ b/dist/hercules-monitor.service @@ -0,0 +1,10 @@ +[Unit] +Description=Hercules Monitor +BindsTo=hercules-server.service + +[Service] +ExecStart=/home/marco/hercules-monitor +StandardOutput=syslog +StandardError=syslog +WorkingDirectory=/home/marco +TimeoutSec=5 diff --git a/dist/hercules-server.service b/dist/hercules-server.service new file mode 100644 index 0000000..4e1546f --- /dev/null +++ b/dist/hercules-server.service @@ -0,0 +1,11 @@ +[Unit] +Description=Hercules Server +BindsTo=hercules-monitor.service +After=hercules-monitor.service + +[Service] +ExecStart=/home/marco/hercules-server +StandardOutput=syslog +StandardError=syslog +WorkingDirectory=/home/marco +TimeoutSec=5 From 33e434d526cb503225e88a90287175856ee9094b Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 10 Oct 2024 16:53:10 +0200 Subject: [PATCH 078/112] fix scmp parsing --- .gitignore | 1 + Makefile | 2 ++ hercules.c | 46 +++++++++++++++++++++++++++------------------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 1aab344..2700932 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ bpf_prgm/*.o hercules-server hercules-monitor *.o +*.d monitor/monitor mockules/mockules diff --git a/Makefile b/Makefile index c546495..3c5dc60 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,8 @@ CFLAGS += -DPRINT_STATS # Randomise the UDP underlay port (no restriction on the range of used ports). # Enabling this currently breaks SCMP packet parsing # CFLAGS += -DRANDOMIZE_UNDERLAY_SRC +# Ignore SCMP error messages, just keep sending +# CFLAGS += -DIGNORE_SCMP ## for debugging: # ASAN_FLAG := -fsanitize=address diff --git a/hercules.c b/hercules.c index 8475134..1a047f7 100644 --- a/hercules.c +++ b/hercules.c @@ -556,7 +556,7 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec // Returns the offending path's id, or PCC_NO_PATH on failure. // XXX Not checking dst or source ia/addr/port in reflected packet static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, - u16 *offending_dst_port) { + u16 *offending_src_port) { size_t offset = 0; const char *pkt = NULL; debug_printf("SCMP type %d", scmp->type); @@ -637,13 +637,13 @@ static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, return PCC_NO_PATH; } - const struct udphdr *l4udph = (const struct udphdr *)(pkt + offset); + const struct udphdr *l4udph = (const struct udphdr *)((char *)scmp + offset); offset += sizeof(struct udphdr); const struct hercules_header *rbudp_hdr = - (const struct hercules_header *)(pkt + offset); - if (offending_dst_port) { - *offending_dst_port = ntohs(l4udph->uh_dport); + (const struct hercules_header *)((char *)scmp + offset); + if (offending_src_port) { + *offending_src_port = ntohs(l4udph->uh_sport); } return rbudp_hdr->path; } @@ -735,20 +735,24 @@ static const char *parse_pkt(const struct hercules_server *server, next_header = *((__u8 *)pkt + next_offset); next_offset += (*((__u8 *)pkt + next_offset + 1) + 1) * SCION_HEADER_LINELEN; } - if(next_header != IPPROTO_UDP) { - if (next_header == L4_SCMP) { - if (next_offset + sizeof(struct scmp_message) > length) { - debug_printf("SCMP, too short?"); - return NULL; - } - const struct scmp_message *scmp_msg = - (const struct scmp_message *)(pkt + next_offset); - *scmp_offending_path_o = - parse_scmp_packet(scmp_msg, length - next_offset, scmp_offending_dst_port_o); - } else { - debug_printf("unknown SCION L4: %u", next_header); - } - return NULL; + if (next_header != IPPROTO_UDP) { + if (next_header == L4_SCMP) { + if (next_offset + sizeof(struct scmp_message) > length) { + debug_printf("SCMP, too short?"); + return NULL; + } +#ifndef IGNORE_SCMP + const struct scmp_message *scmp_msg = + (const struct scmp_message *)(pkt + next_offset); + *scmp_offending_path_o = parse_scmp_packet(scmp_msg, length - next_offset, + scmp_offending_dst_port_o); +#else + debug_printf("Received SCMP error, ignoring"); +#endif + } else { + debug_printf("unknown SCION L4: %u", next_header); + } + return NULL; } const struct scionaddrhdr_ipv4 *scionaddrh = (const struct scionaddrhdr_ipv4 *)(pkt + offset + sizeof(struct scionhdr)); @@ -3510,6 +3514,7 @@ static void events_p(void *arg) { parse_pkt(server, buf, len, true, &scionaddrhdr, &udphdr, &scmp_bad_path, &scmp_bad_port); if (rbudp_pkt == NULL) { + // SCMP messages are ignored for sessions running without PCC if (scmp_bad_path != PCC_NO_PATH) { debug_printf( "Received SCMP error on path %d, dst port %u, " @@ -3522,6 +3527,9 @@ static void events_p(void *arg) { // update paths and on the exact SCMP error. Also, should // "destination unreachable" be treated as a permanent // failure and the session abandoned immediately? + // XXX Nothing happens if we receive an SCMP error for a + // session where we're the receiver, i.e the SCMP error is + // a response to an ACK/NACK we sent struct hercules_session *session_tx = lookup_session_tx(server, scmp_bad_port); if (session_tx != NULL && From ce9b226f52c6eeef29f81b309c8d45c8d476484a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 28 Oct 2024 15:01:48 +0100 Subject: [PATCH 079/112] Rework documentation, add manual pages --- Makefile | 35 +++- README.md | 359 +++++++++++++------------------------- doc/api.md | 46 +++++ doc/developers.md | 30 ++++ doc/hercules-monitor.1 | 70 ++++++++ doc/hercules-monitor.1.md | 81 +++++++++ doc/hercules-server.1 | 64 +++++++ doc/hercules-server.1.md | 73 ++++++++ doc/hercules.7 | 104 +++++++++++ doc/hercules.7.md | 112 ++++++++++++ doc/hercules.conf.5 | 263 ++++++++++++++++++++++++++++ doc/hercules.conf.5.md | 298 +++++++++++++++++++++++++++++++ doc/protocol.md | 99 +++++++++++ hcp/.gitignore | 6 + hcp/README | 2 + hcp/hcp.1 | 95 ++++++++++ hcp/hcp.1.md | 103 +++++++++++ hercules.conf | 12 +- hercules.conf.sample | 99 +++++++++++ 19 files changed, 1703 insertions(+), 248 deletions(-) create mode 100644 doc/api.md create mode 100644 doc/developers.md create mode 100644 doc/hercules-monitor.1 create mode 100644 doc/hercules-monitor.1.md create mode 100644 doc/hercules-server.1 create mode 100644 doc/hercules-server.1.md create mode 100644 doc/hercules.7 create mode 100644 doc/hercules.7.md create mode 100644 doc/hercules.conf.5 create mode 100644 doc/hercules.conf.5.md create mode 100644 doc/protocol.md create mode 100644 hcp/.gitignore create mode 100644 hcp/hcp.1 create mode 100644 hcp/hcp.1.md create mode 100644 hercules.conf.sample diff --git a/Makefile b/Makefile index 3c5dc60..67d4e7f 100644 --- a/Makefile +++ b/Makefile @@ -36,14 +36,27 @@ VERSION := $(shell (ref=$$(git describe --tags --long --dirty 2>/dev/null) && ec echo $$(git rev-parse --abbrev-ref HEAD)-untagged-$$(git describe --tags --dirty --always)) CFLAGS += -DHERCULES_VERSION="\"$(VERSION)\"" +PREFIX ?= /usr/local + +.PHONY: all install all: $(TARGET_MONITOR) $(TARGET_SERVER) -install: all -ifndef DESTDIR - $(error DESTDIR is not set) -endif - cp hercules-server hercules-monitor hercules.conf $(DESTDIR) +install: + install -d $(DESTDIR)$(PREFIX)/bin/ + install $(TARGET_MONITOR) $(DESTDIR)$(PREFIX)/bin/ + install $(TARGET_SERVER) $(DESTDIR)$(PREFIX)/bin/ + + install -d $(DESTDIR)$(PREFIX)/etc/ + install hercules.conf $(DESTDIR)$(PREFIX)/etc/ + + install -d $(DESTDIR)$(PREFIX)/share/doc/hercules/ + install hercules.conf.sample $(DESTDIR)$(PREFIX)/share/doc/hercules/ + + install -d $(DESTDIR)$(PREFIX)/share/man/man1/ + install doc/hercules-server.1 $(DESTDIR)$(PREFIX)/share/man/man1/ + install doc/hercules-monitor.1 $(DESTDIR)$(PREFIX)/share/man/man1/ + install hcp/hcp.1 $(DESTDIR)$(PREFIX)/share/man/man1/ # List all headers as dependency because we include a header file via cgo (which in turn may include other headers) $(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) builder @@ -90,7 +103,7 @@ tomlc99/libtoml.a: builder fi -.PHONY: builder builder_image install clean all +.PHONY: builder builder_image clean # mockules: builder mockules/main.go mockules/network.go # docker exec -w /`basename $(PWD)`/mockules hercules-builder go build @@ -112,4 +125,14 @@ clean: docker container rm -f hercules-builder || true docker rmi hercules-builder || true +MANFILES := $(wildcard doc/*.[157]) hcp/hcp.1 +MDFILES := $(addsuffix .md,$(MANFILES)) + +%.md: $(basename %) +# Show linter output for all warning levels, but continue if it's not severe + mandoc -T lint -Wall $< || true + mandoc -T markdown -W warning,stop $< > $@ + +docs: $(MDFILES) + -include $(DEPS) diff --git a/README.md b/README.md index e30cabc..e92b364 100644 --- a/README.md +++ b/README.md @@ -1,285 +1,166 @@ -# Hercules-server -## Overview -This version of Hercules consists of two components: - -- The monitor (Go) is responsible for handling SCION paths and exposes a HTTP API which clients can use to submit new transfers, check the status of ongoing transfers, or cancel them. -- The server (C) carries out the actual file transfers. - -Unlike previously, these are two separate processes that communicate via a unix socket. -They share a configuration file, the simplest version of which is in `hercules.conf`; see `sampleconf.toml` for all options. - -## Changes from regular Hercules -The following should be the most important changes: - -- Split C and Go parts into dedicated processes, with a unix socket between them. -- This is now a server, i.e. intended to run forever, not just a single-transfer application. -- No command line arguments (except for, optionally, the config file path), all options set in config file. -- The sender includes the destination file path for the receiver to use in the setup handshake. -CAVEAT: No checks on that path at the moment, so any file on the destination can be overwritten. -- Multiple concurrent transfers (both rx and tx) are supported (up to `HERCULES_CONCURRENT_SESSIONS`). -- Only 1 destination per transfer, the idea is to submit the transfer once per destination if multiple are needed. -- Transfer of entire directories is supported: If the source path is a directory, Hercules will try to recursively transfer the entire directory. -- In order to inform the receiver of the directory structure, a directory index is transferred at the beginning. -If this index is larger than the space available in the initial packet, a first phase of RBUDP is performed to transfer the index. -- Automatic MTU selection: At the start of a transfer, Hercules will pick the MTU (and, consequently, chunk length) that is as large as possible while still fitting on its selected paths. -This relies on the MTU in the path metadata, for the empty path the sending interface's MTU is used. -This behaviour can be overridden by manually supplying the desired payload length. -- The server will periodically update a transfer's paths by querying the monitor. The monitor will reply with the set of paths to use, which do not need to overlap with the previous set. -There is a restriction on path selection: All paths must be large enough to fit the payload length fixed at the beginning of the transfer. -- Implemented SCMP error handling: Upon receiving an error, the offending path will be disabled. It will be re-enabled if it is returned by the monitor in the next path update. -- Check the SCION/UDP source address/port of received packets match the expected values (i.e. the host that started the transfer). - -## Getting started - -### Building -First, run `git submodule update --init`. -Running `make` should build both the monitor and server. This uses the provided dockerfile. - -### Running -On both the sender and the receiver: - -- Fill in the provided `hercules.conf` with the host's SCION address and NIC -- First, start the monitor: `./hercules-monitor`. -- If there is already an XDP program loaded on the interface, you first have to remove it (e.g. `ip l set dev eth0 xdp off`) -- Depending on your network card/setup, you may want to specify `ConfigureQueues = false` in the config file. If you get an error related to XDP upon starting the server, try this. -Then, in a second shell, start the server: `./hercules-server`. - -### Submitting a transfer -- To transfer `infile` from the sender to `outfile` at the receiver with address `64-2:0:c,148.187.128.136:8000`, run the following on the sender: `curl "localhost:8000/submit?file=infile&destfile=outfile&dest=64-2:0:c,148.187.128.136:8000"`. -- This should yield `OK 1`, where `1` is the submitted transfer's job id. -- You should see the transfer progress in the server's output at both the sender and receiver. -- It is also possible to query the transfer status via `curl "localhost:8000/status?id=1"`, though the output is not intended to be read by humans. - -## Testing/Debugging - -- It may be useful to uncomment some of the lines marked "for debugging" at the top of the Makefile. -- If you want to run under valgrind, passing `--fair-sched=yes` is helpful. -- The script `test.sh` will try 3 sets of transfers: a file, a directory, and 2 files concurrently. -In order to use it, adjust the definitions at the top of the file (hostnames, addresses and interfaces). -The script further relies on you having ssh keys set up for those two hosts. -Depending on the network cards you may need to comment in the two lines with `ConfigureQueues = false`. - -## API -The Monitor's API supports the following operations via HTTP GET requests. - -### `/submit`: Submitting a new transfer -Parameters: -- `file`: Path to source file, from the server's point of view -- `dest`: SCION address of the destination Hercules server -- `destfile`: Destination file path -- `payloadlen`: (Optional) override automatic MTU selection and use the specified payload length instead. - -Example: `localhost:8000/submit?file=infile&destfile=outfile&dest=64-2:0:c,148.187.128.136:8000` - -Returns: `OK id`, where `id` is an integer identifying the submitted job on success, an HTTP error code otherwise. - -### `/status`: Check a transfer's status -Parameters: -- `id`: An id previously returned by `/submit`. - -Returns: `OK status state error time_elapsed bytes_acked` on success, an HTTP error code otherwise. -- `status` is the monitor's internal transfer status and one of `TransferStatus`, as defined in the go code. -- `state` is an integer corresponding to the transfers current status (one of `session_state`, as defined in `errors.h`) -- `error` is an integer corresponding to the transfers error state (one of `session_error`, as defined in `errors.h`) -- `time_elapsed` is an integer representing the number of seconds elapsed since the server started this transfer. -- `bytes_acked` is the number of bytes acknowledged by the receiver. - -### `/cancel`: Cancel a transfer -Parameters: -- `id`: An id previously returned by `/submit`. - -Returns: `OK`on success, an HTTP error code otherwise. - -### `/server`: Returns the server's SCION address -This functionality is provided for integration with FTS. - -Parameters: None - -Returns: `OK addr`, where `addr` is the server's SCION address. - -### `/stat`: Retrieve stat information on a file -This is provided for compatibility with FTS, but also (optionally) used by hcp. - -Parameters: -- `file`: Path to file - -Returns: `OK exists size`, where `exists` is 1 if the file exists, 0 otherwise; `size` is the file's size in bytes. - - -# Readme not updated below this line. - # Hercules -High speed bulk data transfer application. - -This is a proof of concept implementation of file transfer using SCION/UDP (over ethernet/IPv4/UDP). -To achieve high transmit and receive rates, the `hercules` tool is implemented using `AF_XDP`. -On suitable hardware, a single instance can achieve >98Gbps transfer rate, and multiple instances can run in parallel on different network interfaces. - -`hercules` is not a daemon, it performs for only a single file transmission and then stops. -There are at least two hosts involved; exactly one of which behaves as a _sender_, the remaining hosts behave as receiver. -The sender transmits the data to all receivers. -Each receiver waits for the sender to start the transmission. -There is no authorization, access control etc. The idea is that this will be integrated in a more generic framework that does all of that (e.g. make this run as an FTP extension). - -## Building - -Option -1. Build in Docker, using the `Dockerfile` and `Makefile` provided in the repo; just run `make`. - -1. Build using `go build` - - Requires: - - gcc/clang - - linux kernel headers >= 5.0 - - go 1.21 - - -## Running +Hercules is a high-speed [SCION](scion-architecture.net)-native bulk data transfer application. -> **WARNING**: network drivers seem to crash occasionally. +Hercules achieves high transfer rates by combining the Linux kernel `AF_XDP` express data path and PCC congestion control with a custom data transfer protocol. +Hercules can take advantage of SCION's native multipath capabilities to transfer data using multiple network paths simultaneously. -> **WARNING**: due to the most recent changes on the branch `multicore`, the rate-limit `computation` is a bit off. - When setting the rate-limit with `-p`, keep this in mind and set a lower rate than you aim at. +The Hercules server is intended to run on dedicated machines. +Clients submit transfer jobs to Hercules via a HTTP API. The server may handle multiple concurrent transfers in both the sending and receiving direction. +Hercules supports transferring entire directories in one go. -> **NOTE**: if hercules is aborted forcefully (e.g. while debugging), it can leave an XDP program loaded which will prevent starting again. - Run `ip link set dev xdp off`. +## Prerequisites -> **NOTE**: many things can go wrong, expect to diagnose things before getting it to work. +To run Hercules, the machine on which you plan to install it must have a working SCION endhost stack. +See [HERE](https://docs.scion.org/projects/scion-applications/en/latest/applications/access.html) for how to set that up. -> **NOTE**: Some devices use separate queues for copy and zero-copy mode (e.g. Mellanox). - Make sure to use queues that support the selected mode. - Additionally, you may need to postpone step 2 until the handshake has succeeded. +Hercules relies on `AF_XDP` and loads an XDP program on the interface it uses. Make sure you have no other programs that want to attach XDP programs to the same interface. -1. Make sure that SCION endhost services (sciond, dispatcher) are configured and running on both sender and receiver machines. - For the most recent versions of Hercules, use a SCION version compatible to `https://github.com/scionproto/scion/releases/tag/v0.10.0`. - -1. Configure queue network interfaces to particular queue (if supported by device); in this example queue 0 is used. +## Overview - ```shell - sudo ethtool -N rx-flow-hash udp4 fn - sudo ethtool -N flow-type udp4 dst-port 30041 action 0 - ``` +A Hercules server installation consists of two components, i.e., +two separate processes that communicate via a Unix socket. -1. Start hercules on receiver side +- The monitor (`hercules-monitor`) is responsible for handling SCION paths and + exposes a HTTP API which clients can use to submit new transfers, check the + status of ongoing transfers, or cancel them. +- The server (`hercules-server`) carries out the actual file transfers. - ```shell - sudo numactl -l --cpunodebind=netdev: -- \ - ./hercules -i -q 0 -l -o path/to/output/file.bin - ``` +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. -1. Start hercules on sender side +Clients interact with Hercules via its HTTP API. +You may use this API directly (see [the API docs](doc/api.md)), but the easiest way for users to transfer files is using the provided `hcp` command line tool. +Integration with FTS/gfal2 is also possible via a plugin, see the [`gfal2-hercules`](./gfal2-hercules/) directory for more information. - ```shell - sudo numactl -l --cpunodebind=netdev: -- \ - ./hercules -i -q 0 -l -d -t path/to/file.bin - ``` +## Getting started -* Both `` and `` are SCION/IPv4 addresses with UDP port, e.g. `17-ffaa:0:1102,[172.16.0.1]:10000`. -* To send data to multiple receivers, just provide `-d` multiple times. -* The `numactl` is optional but has a huge effect on performance on systems with multiple numa nodes. -* The command above will use PCC for congestion control. For benchmarking, you might want to use `-pcc=false` and provide a maximum sending rate using `-p`. -* For transfer rates >30Gbps, you might need to use multiple networking queues. At the receiver this is currently only possible in combination with multiple IP addresses. -* See source code (or `-h`) for additional options. -* You should be able to omit `-l`. -* For more sophisticated run configurations (e.g. using multiple paths), it is recommended to use a configuration file. -* When using 4 or more paths per destination, you might need to specify path preferences to make the path selection more efficient. +The following should help you get started with Hercules. +### Installing -## Protocol +To install Hercules, you may build it yourself from source +(see ["Building from Source"](#building-from-source)) or use the provided packages. +TODO packages. -The transmitter splits the file into chunks of the same size. All the chunks are transmitted (in order). -The receiver acknowledges the chunks at regular intervals. -Once the sender has transmitted all chunks once, it will start to retransmit all chunks that have not been acknowledge in time. -This is repeated until all chunks are acked. +### Configuration +> For more information, see the Hercules monitor's manual +> ([hercules-monitor(1)](doc/hercules-monitor.1.md)), the Hercules server's manual +> ([hercules-server(1)](doc/hercules-server.1.md)), and the Hercules configuration +> manual ([hercules.conf(5)](doc/hercules.conf.5.md)). +> The manuals are installed alongside Hercules, if you used the packages +> or `make install`. ---- +Hercules is configured using a configuration file. +The default configuration file is `/usr/local/etc/hercules.conf`. +To get started, filling in the following information is required: -All packets have the following basic layout: +- Change `ListenAddress` to the SCION/UDP address and port your Hercules instance should listen on. +- Replace the entry in `Interfaces` with the network interface Hercules should use. - | index | path | seqnr | payload ... | - | u32 | u8 | u32 | ... | +In both cases you should replace the entire string, including the `replaceme//` markers. +While the above two settings are sufficient to run Hercules, we **strongly recommend** additionally setting the option `DropUser`. +Hercules will then drop its privileges to the specified user after startup and thus use the provided user's permissions for filesystem access. +Hence, you should ensure the specified user has the appropriate read/write permissions on the paths you intend to send from/receive to. +If you omit this option, Hercules will run as root. +See [the configuration documentation](doc/hercules.conf.5.md#CAVEATS) for a discussion of the security implications. -> **NOTE**: Integers are transmitted little endian (host endianness). +See [hercules(5)](doc/hercules.conf.5.md) or the sample configuration file, [`hercules.conf.sample`](hercules.conf.sample) for an example illustrating all +available configuration options. -For control packets (handshake and acknowledgements, either sender to receiver or receiver to sender), index is `UINT_MAX`. -For all control packets, the first byte of the payload contains the control packet type. -The following control packet types exist: +### Starting Hercules - 0: Handshake packet - 1: ACK packet - 2: NACK packet +To start the Hercules server, you may use `systemctl start hercules-server`, if you installed Hercules as described above. +This will start both the server and monitor processes. +You can check their status and log output via `systemctl status hercules-server` or `systemctl status hercules-monitor`, respectively. +If the `hercules-server` process fails to start with `Error in XDP setup!`, the cause is likely either that your setup requires specifying `ConfigureQueues = false` in the config file, or that an XDP program is already loaded on the specified network interface. See the section "[Troubleshooting](#troubleshooting)" below for more information. -For data packets (sender to receiver), the index field is the index of the chunk being transmitted. -This is **not** a packet sequence number, as chunks may be retransmitted; hence the separate field `seqnr` contains the per-path sequence number. -A NACK packet is always associated with a path. +### Submitting a Transfer -If path is not `UINT8_MAX`, it is used to account the packet to a specific path. -This is used to provide quick feedback to the PCC algorithm, if enabled. +Transfers can be submitted to Hercules via its HTTP API. +A user submits his transfer to the sending-side (source) Hercules server. The user does not interact with the receiving-side (destination) Hercules server. +The easiest way to transfer files is using the provided `hcp` utility. +For example, assume we have two hosts with Hercules set up, `hercules1` and `hercules2` and wish to copy the file `/tmp/data/myfile` from `hercules1` to `/mnt/storage/myfile` on `hercules2`. +To do so, we need to know the IP address and port the source Hercules server's API is exposed on, as well as the SCION/UDP address and port the destination Hercules server on `hercules2` is listening on. +The HTTP API is exposed on port 8000 by default. +If you followed this guide, you should have set the destinations listening address in `hercules2`'s configuration file. +Let's assume, for this example, that the server on `hercules2` is listening on `64-2:0:c,10.0.0.12:10000`. +Then, running the following from `hercules1` will transfer the file, giving a progress report while the transfer is running: +``` shell +$ hcp localhost:8000 /tmp/data/myfile 64-2:0:c,10.0.0.12:10000 /mnt/storage/myfile +``` +Note that in the above example we specified `localhost:8000` as the first argument since we submitted the transfer from the very host the source Hercules server is running on. +In practice, `hcp` may be run from a different host, such as a user's machine, too. +In that case, the first argument should be substituted with the listening address of the source server's HTTP API, e.g., `10.10.10.10:8000`. +Note however, that the paths are still relative to the source and destination servers, respectively. +This also implies that the file to be transferred must first be made available to the source Hercules server somehow. This could be done in several ways, e.g., by plugging in a physical disk or via a network share. -#### Handshake +See [the hcp manual](hcp/hcp.1.md) for more information about the `hcp` tool. +If you wish to use the API directly, see [the API docs](doc/api.md) for its description. -1. Sender sends initial packet: +## Building from Source - | num entries | filesize | chunksize | timestamp | path index | flags | - | u8 | u64 | u32 | u64 | u32 | u8 | - - Where `num entries` is `UINT8_MAX` to distinguish handshake replies from ACKs. - - Flags: - - 0-th bit: `SET_RETURN_PATH` The receiver should use this path for sending - ACKs from now on. +Clone this git repository and change to the directory you cloned it to. +Before building Hercules, you must run `git submodule update --init` to download some required dependencies. +You can then build Hercules, either using Docker or natively. -1. Receiver replies immediately with the same packet. +### Building with Docker - This first packet is used to determine an approximate round trip time. - - The receiver proceeds to prepare the file mapping etc. +Hercules can be built from source using Docker and the provided `Dockerfile` which prepares the required build environment. -1. Receiver replies with an empty ACK signaling "Clear to send" +To build Hercules using Docker, simply run `make`. This will build the server and monitor executables. +With `sudo make install`, you can then install Hercules to your machine. +By default, this will install Hercules to `/usr/local/`. -##### Path handshakes +### TODO Native Build -Every time the sender starts using a new path or the receiver starts using a new -return path, the sender will update the RTT estimate used by PCC. -In order to achieve this, it sends a handshake (identical to the above) on the -affected path(s). -The receiver replies immediately with the same packet (using the current return path). +## Debugging and Development -#### Data transmit +See the [developer guide](doc/developers.md). -* The sender sends (un-acknowledged) chunks in data packets at chosen send rate -* The receiver sends ACK packets for the entire file at 100ms intervals. - - ACK packets consist of a list of `begin`,`end` pairs declaring that chunks - with index `i` in `begin <= i < end` have been received. - Lists longer than the packet payload size are transmitted as multiple - independent packets with identical structure. +## Troubleshooting +- If Hercules is aborted forcefully (e.g. while debugging) or crashes, it can leave an XDP program loaded which will prevent the server from starting again, yielding the following error message: + ```text + libbpf: Kernel error message: XDP program already attached + Error loading XDP redirect, is another program loaded? + Error in XDP setup! + ``` + To remove the XDP program from the interface, run `ip link set dev xdp off`. + + +- Some network cards support multiple receive queues. + In such a case, it must be ensured that all incoming Hercules packets are sent to the same queue. + Hercules will, by default, attempt to configure the queues accordingly. + However this fails when using network cards that do not support multiple queues, yielding the following error message: + ```text + rxclass: Cannot get RX class rule count: Operation not supported + Cannot insert classification rule + could not configure queue 0 on interface ens5f0, abort + Error in XDP setup! + ``` + To resolve this, specify `ConfigureQueues = false` in the configuration file. + +- The sending-side Hercules attempts to start a transfer, but the receiver does not show any indication of a received packet and the transfer times out. - | begin, end | begin, end | begin, end | ... - | u32 u32 | u32 u32 | u32 u32 | ... + Hercules attempts to automatically pick the right packet size based on the MTU in the SCION path metadata and the sending interface. + In some cases, however, this information is not accurate and the really supported MTU is smaller. + To work around this, you can manually specify the payload size to be used, e.g., by supplying the `TODO` option to `hcp`, or by specifying the payload on a per-destination basis in the configuration file. + +## Performance Configuration -* The receiver sends a NACK packets four times per RTT to provide timely feedback to congestion control. - The NACK packet layout is identical to the ACK packet layout. - - NACK packets are only sent if non-empty. - Hence, if no path uses PCC, or no recent packet loss has been observed, no NACKs are sent. +Depending on your performance requirements and your specific bottlenecks, the following configuration options may help improve performance: -#### Termination +- On machines with multiple NUMA nodes, it may be beneficial to bind the Hercules server process to CPU cores "closer" to the network card. + To do so, install the `numactl` utility and adjust the file `/usr/local/lib/systemd/system/hercules-server.service` so it reads `ExecStart=/usr/bin/numactl -l --cpunodebind=netdev: -- /home/marco/hercules-server`, replacing `` with your network interface. -1. Once the receiver has received all chunks, it sends one more ACK for the entire range and terminates. -1. When the sender receives this last ACK, it determines that all chunks have been received and terminates. +- Setting the option `XDPZeroCopy = true` can substantially improve performance, but whether it is supported depends on the combination of network card and driver in your setup. -## Issues, Todos, Future Work +- Increasing the number of worker threads via the option `NumThreads` can also improve performance. -* [ ] Flow control: if the receiver is slower than the sender (e.g. because it needs to write stuff to disk) it just drops packets. - The congestion control naturally solves this too, but is fairly slow to adapt. - Maybe a simple window size would work. -* [ ] Abort of transmission not handled (if one side is stopped, the other side will wait forever). -* [ ] Replace paths used for sending before they expire (for very long transmissions) -* [ ] Optimisations; check sum computations, file write (would be broken for huge files), ... diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..78c2e82 --- /dev/null +++ b/doc/api.md @@ -0,0 +1,46 @@ +# Hercules HTTP API + +The Monitor's API supports the following operations via HTTP GET requests. + +### `/submit`: Submitting a new transfer +Parameters: +- `file`: Path to source file, from the server's point of view +- `dest`: SCION address of the destination Hercules server +- `destfile`: Destination file path +- `payloadlen`: (Optional) override automatic MTU selection and use the specified payload length instead. + +Example: `localhost:8000/submit?file=infile&destfile=outfile&dest=64-2:0:c,148.187.128.136:8000` + +Returns: `OK id`, where `id` is an integer identifying the submitted job on success, an HTTP error code otherwise. + +### `/status`: Check a transfer's status +Parameters: +- `id`: An id previously returned by `/submit`. + +Returns: `OK status state error time_elapsed bytes_acked` on success, an HTTP error code otherwise. +- `status` is the monitor's internal transfer status and one of `TransferStatus`, as defined in the go code. +- `state` is an integer corresponding to the transfers current status (one of `session_state`, as defined in `errors.h`) +- `error` is an integer corresponding to the transfers error state (one of `session_error`, as defined in `errors.h`) +- `time_elapsed` is an integer representing the number of seconds elapsed since the server started this transfer. +- `bytes_acked` is the number of bytes acknowledged by the receiver. + +### `/cancel`: Cancel a transfer +Parameters: +- `id`: An id previously returned by `/submit`. + +Returns: `OK`on success, an HTTP error code otherwise. + +### `/server`: Returns the server's SCION address +This functionality is provided for integration with FTS. + +Parameters: None + +Returns: `OK addr`, where `addr` is the server's SCION address. + +### `/stat`: Retrieve stat information on a file +This is provided for compatibility with FTS, but also (optionally) used by hcp. + +Parameters: +- `file`: Path to file (or directory) + +Returns: `OK exists size`, where `exists` is 1 if the file exists, 0 otherwise; `size` is the file's size in bytes. If `file` is a directory, `size` is the size of all regular files contained in the directory and its subdirectories. diff --git a/doc/developers.md b/doc/developers.md new file mode 100644 index 0000000..4ac5f8b --- /dev/null +++ b/doc/developers.md @@ -0,0 +1,30 @@ +# Development + +See the main readme for how to build Hercules from source. +When debugging or developing it may be desirable to run Hercules manually and +not via systemd, for example to attach a debugger. To do so, simply: + +1. Start the monitor: `sudo ./hercules-monitor` +2. In a second shell, start the server: `sudo ./hercules-server` + +- It may be useful to uncomment some of the lines marked "for debugging" at the + top of the Makefile. +- The script `test.sh` is a very simple test utility. It will try 3 sets of + transfers: a file, a directory, and 2 files concurrently. + You can use it to sanity-check any code changes you make. + You will need to point the script to two hosts to use for the test transfers. + In order to use it, adjust the definitions at the top of the file + (hostnames, addresses and interfaces). + The script further relies on you having ssh keys set up for those two hosts. + Depending on the network cards you may need to comment in the two lines with + `ConfigureQueues = false`. +- The `xdpdump` tool + () is useful + for seeing packets received via XDP. Similar to `tcpdump`, but for XDP. + +## Docs +Documentation pertaining to the server is located in the `doc/` directory, and +in `hcp/` for `hcp`. +If you make changes to the manual files, run `make docs` to rebuild the +markdown versions of the man pages. + diff --git a/doc/hercules-monitor.1 b/doc/hercules-monitor.1 new file mode 100644 index 0000000..a267392 --- /dev/null +++ b/doc/hercules-monitor.1 @@ -0,0 +1,70 @@ +.Dd October 29, 2024 +.Dt HERCULES-MONITOR 1 +.Os +.Sh NAME +.Nm hercules-monitor +.Nd "Monitor component of the Hercules file transfer system" +.Sh SYNOPSIS +.Nm hercules-monitor +.Bk -words +.Op Fl c Ar conffile +.Ek +.Sh DESCRIPTION +.Nm +is the monitor component of the Hercules file transfer sytem. +The monitor is the link between users and the Hercules server. +Users interact with the monitor via its HTTP API. +The monitor interacts with the server component via a local Unix socket. +Hercules is configured via a configuration file (see +.Xr hercules.conf 5 ) . +.Pp +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c Ar conffile +Use the specified configuration file. +By default, +.Nm +will first look for a file named +.Pa hercules.conf +in its working directory, then for the default config file, +.Pa /usr/local/etc/hercules.conf . +See +.Xr hercules.conf 5 +for configuration options. +.El +.Sh ENVIRONMENT +.Bl -tag -width SCION_DAEMON_ADDRESS +.It Ev SCION_DAEMON_ADDRESS +If the SCION daemon is listening on a non-default port, +.Ev SCION_DAEMON_ADDRESS +can be set to its listening address and port. +.El +.Sh FILES +.Bl -tag -width Ds -compact +.It Pa /usr/local/etc/hercules.conf +Default configuration file +.It Pa /var/run/herculesmon.sock +Default Unix socket path +.El +.\" .Sh EXIT STATUS +.\" .Sh DIAGNOSTICS +.Sh SEE ALSO +.Xr hcp 1 , +.Xr hercules-server 1 , +.Xr hercules.conf 5 , +.Xr hercules 7 +.Pp +Further information about Hercules is available on +.Lk https://github.com/netsec-ethz/hercules . +For more information about SCION, please see +.Lk https://scion-architecture.net . +.Sh AUTHORS +.An Network Security Group, ETH Zürich +.Sh CAVEATS +See +.Xr hercules.conf 5 Ns s CAVEATS . diff --git a/doc/hercules-monitor.1.md b/doc/hercules-monitor.1.md new file mode 100644 index 0000000..e1941d1 --- /dev/null +++ b/doc/hercules-monitor.1.md @@ -0,0 +1,81 @@ +HERCULES-MONITOR(1) - General Commands Manual + +# NAME + +**hercules-monitor** - Monitor component of the Hercules file transfer system + +# SYNOPSIS + +**hercules-monitor** +\[**-c** *conffile*] + +# DESCRIPTION + +**hercules-monitor** +is the monitor component of the Hercules file transfer sytem. +The monitor is the link between users and the Hercules server. +Users interact with the monitor via its HTTP API. +The monitor interacts with the server component via a local Unix socket. +Hercules is configured via a configuration file (see +hercules.conf(5)). + +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. + +The options are as follows: + +**-c** *conffile* + +> Use the specified configuration file. +> By default, +> **hercules-monitor** +> will first look for a file named +> *hercules.conf* +> in its working directory, then for the default config file, +> */usr/local/etc/hercules.conf*. +> See +> hercules.conf(5) +> for configuration options. + +# ENVIRONMENT + +`SCION_DAEMON_ADDRESS` + +> If the SCION daemon is listening on a non-default port, +> `SCION_DAEMON_ADDRESS` +> can be set to its listening address and port. + +# FILES + +*/usr/local/etc/hercules.conf* + +> Default configuration file + +*/var/run/herculesmon.sock* + +> Default Unix socket path + +# SEE ALSO + +hcp(1), +hercules-server(1), +hercules.conf(5), +hercules(7) + +Further information about Hercules is available on +[https://github.com/netsec-ethz/hercules](https://github.com/netsec-ethz/hercules). +For more information about SCION, please see +[https://scion-architecture.net](https://scion-architecture.net). + +# AUTHORS + +Network Security Group, ETH Zürich + +# CAVEATS + +See +hercules.conf(5)s CAVEATS. + +Void Linux - October 29, 2024 diff --git a/doc/hercules-server.1 b/doc/hercules-server.1 new file mode 100644 index 0000000..96d5917 --- /dev/null +++ b/doc/hercules-server.1 @@ -0,0 +1,64 @@ +.Dd October 29, 2024 +.Dt HERCULES-SERVER 1 +.Os +.Sh NAME +.Nm hercules-server +.Nd "Server component of the Hercules file transfer system" +.Sh SYNOPSIS +.Nm hercules-server +.Bk -words +.Op Fl c Ar conffile +.Ek +.Sh DESCRIPTION +.Nm +is the server component of the Hercules file transfer sytem. +The server's task is to run the actual file transfers. +The server receives tasks from the monitor and informs the monitor of +transfer progress via a local Unix socket. +Hercules is configured via a configuration file (see +.Xr hercules.conf 5 ) . +.Pp +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c Ar conffile +Use the specified configuration file. +By default, +.Nm +will first look for a file named +.Pa hercules.conf +in its working directory, then for the default config file, +.Pa /usr/local/etc/hercules.conf . +See +.Xr hercules.conf 5 +for configuration options. +.El +.\" .Sh ENVIRONMENT +.Sh FILES +.Bl -tag -width Ds -compact +.It Pa /usr/local/etc/hercules.conf +Default configuration file +.It Pa /var/run/hercules.sock +Default Unix socket path +.El +.\" .Sh EXIT STATUS +.\" .Sh DIAGNOSTICS +.Sh SEE ALSO +.Xr hcp 1 , +.Xr hercules-monitor 1 , +.Xr hercules.conf 5 , +.Xr hercules 7 +.Pp +Further information about Hercules is available on +.Lk https://github.com/netsec-ethz/hercules . +For more information about SCION, please see +.Lk https://scion-architecture.net . +.Sh AUTHORS +.An Network Security Group, ETH Zürich +.Sh CAVEATS +See +.Xr hercules.conf 5 Ns s CAVEATS . diff --git a/doc/hercules-server.1.md b/doc/hercules-server.1.md new file mode 100644 index 0000000..5e79da1 --- /dev/null +++ b/doc/hercules-server.1.md @@ -0,0 +1,73 @@ +HERCULES-SERVER(1) - General Commands Manual + +# NAME + +**hercules-server** - Server component of the Hercules file transfer system + +# SYNOPSIS + +**hercules-server** +\[**-c** *conffile*] + +# DESCRIPTION + +**hercules-server** +is the server component of the Hercules file transfer sytem. +The server's task is to run the actual file transfers. +The server receives tasks from the monitor and informs the monitor of +transfer progress via a local Unix socket. +Hercules is configured via a configuration file (see +hercules.conf(5)). + +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. + +The options are as follows: + +**-c** *conffile* + +> Use the specified configuration file. +> By default, +> **hercules-server** +> will first look for a file named +> *hercules.conf* +> in its working directory, then for the default config file, +> */usr/local/etc/hercules.conf*. +> See +> hercules.conf(5) +> for configuration options. + +# FILES + +*/usr/local/etc/hercules.conf* + +> Default configuration file + +*/var/run/hercules.sock* + +> Default Unix socket path + +# SEE ALSO + +hcp(1), +hercules-monitor(1), +hercules.conf(5), +hercules(7) + +Further information about Hercules is available on +[https://github.com/netsec-ethz/hercules](https://github.com/netsec-ethz/hercules). +For more information about SCION, please see +[https://scion-architecture.net](https://scion-architecture.net). + +# AUTHORS + +Network Security Group, ETH Zürich + +# CAVEATS + +See +hercules.conf(5)s CAVEATS. + +Void Linux - October 29, 2024 diff --git a/doc/hercules.7 b/doc/hercules.7 new file mode 100644 index 0000000..e2fd6c4 --- /dev/null +++ b/doc/hercules.7 @@ -0,0 +1,104 @@ +.Dd October 30, 2024 +.Dt HERCULES 7 +.Os +.Sh NAME +.Nm Hercules +.Nd "SCION-native fast bulk data transfers" +.Sh DESCRIPTION +.Nm +is a high-speed SCION-native bulk data transfer application. +Hercules achieves high transfer rates by combining the Linux kernel AF_XDP +express data path and PCC congestion control with a custom data transfer +protocol. +Hercules can take advantage of SCION's native multipath capabilities to transfer +data using multiple network paths simultaneously. +Hercules supports transferring entire directories in one go. +.Pp +The Hercules server is intended to run on dedicated machines. +A Hercules server installation consists of two components, i.e., +two separate processes that communicate via a Unix socket. +.Bl -bullet +.It +The monitor is responsible for handling SCION paths and exposes a HTTP API which +clients can use to submit new transfers, check the status of ongoing transfers, +or cancel them. +.It +The server carries out the actual file transfers. +.El +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. +.Pp +Clients interact with Hercules via its HTTP API. +The easiest way for users to transfer files is using the provided +.Xr hcp 1 +command line tool. +.Sh EXAMPLES +The following example scenario illustrates how Hercules is intended to be used +and how the various components interact. +Assume we want to use Hercules to transfer data between two locations, +from +.Ar A +to +.Ar B . +We will need to set up an instance of Hercules, ideally on a dedicated machine, +at each location. +Both machines need a working SCION endhost stack. +Instructions on setting up a SCION endhost can be found at +.Lk https://docs.scion.org/projects/scion-applications/en/latest/applications/access.html . +Assume the SCION addresses of the two machines are +.Ql 64-2:0:9,10.1.1.1 +and +.Ql 64-2:0:c,10.2.2.2 , +respectively, and that the corresponding network interfaces on both machines are +called +.Ql eth0 . +We will use SCION/UDP port 10000 for Hercules on both hosts. +We will use the default TCP port 8000 for the HTTP API. +With Hercules installed on both machines, we set the following configuration +options on the two machines: +.Pp +On the machine at +.Ar A : +.Bd -literal +ListenAddress = "64-2:0:9,10.1.1.1:10000" +Interfaces = [ "eth0" ] +.Ed +.Pp +On the machine at +.Ar B : +.Bd -literal +ListenAddress = "64-2:0:c,10.2.2.2:10000" +Interfaces = [ "eth0" ] +.Ed +.Pp +We can now start the Hercules server on both machines. +With the provided systemd files, this is done with the following command: +.Dl # systemctl start hercules-server +Note that this will start both the Hercules server and monitor processes. +.Pp +Now, we can use +.Xr hcp 1 +to copy the file +.Pa /tmp/hercules.in +from +.Ar A +to +.Ar B +by running the following command on +.Ar A : +.Dl $ hcp localhost:8000 /tmp/hercules.in 64-2:0:c,10.2.2.2:10000 \ +/tmp/hercules.out +.Sh SEE ALSO +.Xr hcp 1 , +.Xr hercules-monitor 1 , +.Xr hercules-server 1 , +.Xr hercules.conf 5 , +.Pp +Further information about Hercules is available on +.Lk https://github.com/netsec-ethz/hercules . +For more information about SCION, please see +.Lk https://scion-architecture.net . +.Sh AUTHORS +.An Network Security Group, ETH Zürich diff --git a/doc/hercules.7.md b/doc/hercules.7.md new file mode 100644 index 0000000..900485e --- /dev/null +++ b/doc/hercules.7.md @@ -0,0 +1,112 @@ +HERCULES(7) - Miscellaneous Information Manual + +# NAME + +**Hercules** - SCION-native fast bulk data transfers + +# DESCRIPTION + +**Hercules** +is a high-speed SCION-native bulk data transfer application. +Hercules achieves high transfer rates by combining the Linux kernel AF\_XDP +express data path and PCC congestion control with a custom data transfer +protocol. +Hercules can take advantage of SCION's native multipath capabilities to transfer +data using multiple network paths simultaneously. +Hercules supports transferring entire directories in one go. + +The Hercules server is intended to run on dedicated machines. +A Hercules server installation consists of two components, i.e., +two separate processes that communicate via a Unix socket. + +* The monitor is responsible for handling SCION paths and exposes a HTTP API which + clients can use to submit new transfers, check the status of ongoing transfers, + or cancel them. + +* The server carries out the actual file transfers. + +The monitor and server processes must be started and stopped together, you +should not restart one without also restarting the other. +The provided systemd service files ensure this, if you use a different method +to run Hercules you must ensure this yourself. + +Clients interact with Hercules via its HTTP API. +The easiest way for users to transfer files is using the provided +hcp(1) +command line tool. + +# EXAMPLES + +The following example scenario illustrates how Hercules is intended to be used +and how the various components interact. +Assume we want to use Hercules to transfer data between two locations, +from +*A* +to +*B*. +We will need to set up an instance of Hercules, ideally on a dedicated machine, +at each location. +Both machines need a working SCION endhost stack. +Instructions on setting up a SCION endhost can be found at +[https://docs.scion.org/projects/scion-applications/en/latest/applications/access.html](https://docs.scion.org/projects/scion-applications/en/latest/applications/access.html). +Assume the SCION addresses of the two machines are +'`64-2:0:9,10.1.1.1`' +and +'`64-2:0:c,10.2.2.2`', +respectively, and that the corresponding network interfaces on both machines are +called +'`eth0`'. +We will use SCION/UDP port 10000 for Hercules on both hosts. +We will use the default TCP port 8000 for the HTTP API. +With Hercules installed on both machines, we set the following configuration +options on the two machines: + +On the machine at +*A*: + + ListenAddress = "64-2:0:9,10.1.1.1:10000" + Interfaces = [ "eth0" ] + +On the machine at +*B*: + + ListenAddress = "64-2:0:c,10.2.2.2:10000" + Interfaces = [ "eth0" ] + +We can now start the Hercules server on both machines. +With the provided systemd files, this is done with the following command: + + # systemctl start hercules-server + +Note that this will start both the Hercules server and monitor processes. + +Now, we can use +hcp(1) +to copy the file +*/tmp/hercules.in* +from +*A* +to +*B* +by running the following command on +*A*: + + $ hcp localhost:8000 /tmp/hercules.in 64-2:0:c,10.2.2.2:10000 /tmp/hercules.out + +# SEE ALSO + +hcp(1), +hercules-monitor(1), +hercules-server(1), +hercules.conf(5), + +Further information about Hercules is available on +[https://github.com/netsec-ethz/hercules](https://github.com/netsec-ethz/hercules). +For more information about SCION, please see +[https://scion-architecture.net](https://scion-architecture.net). + +# AUTHORS + +Network Security Group, ETH Z"urich + +Void Linux - October 30, 2024 diff --git a/doc/hercules.conf.5 b/doc/hercules.conf.5 new file mode 100644 index 0000000..660196c --- /dev/null +++ b/doc/hercules.conf.5 @@ -0,0 +1,263 @@ +.\" -*- mode: nroff -*- +.\" .Dd $Mdocdate$ +.Dd October 29, 2024 +.Dt HERCULES.CONF 5 +.Os +.Sh NAME +.Nm hercules.conf +.Nd "Hercules configuration file" +.Sh DESCRIPTION +.Nm +is the configuration file for the Hercules file transfer system. +This configuration file is used by +.Xr hercules-server 1 +and +.Xr hercules-monitor 1 . +Its default location is +.Pa /usr/local/etc/hercules.conf . +The configuration file is in TOML format. +.Pp +The following two options must be set, as they have no default values: +.Bl -tag -width Ds +.It Ic ListenAddress Ns = Ns Ar str +This specifies the SCION/UDP address Hercules will listen on +for incoming transfers. +.Pp +Example: ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" +.It Ic Interfaces Ns = Ns [ Ar str , ] +The network interface Hercules should use for data traffic. +Hercules will load an XDP program on this interface. +.Pp +Example: Interfaces = ["eth0"] +.El +.Pp +It is +.Em strongly +recommended to also set the +.Ic DropUser +option, described below. +See +.Sx CAVEATS +for more information. +.Ss GENERAL CONFIGURATION +The following general configuration options are available: +.Bl -tag -width Ds +.It Ic DefaultNumPaths Ns = Ns Ar int +Specify how many SCION path to use for data transfers. +This is an upper limit, if fewer paths are available only those will be used. +This value may be overridden on a per-destination basis, see +.Sx PER-DESTINATION OVERRIDES . +The default value is +.Ar 1 . +.Pp +Example: DefaultNumPaths = 2 +.It Ic MonitorSocket Ns = Ns Ar str +Path to the monitor's Unix socket. +The default value is +.Pa /var/run/herculesmon.sock . +.It Ic ServerSocket Ns = Ns Ar str +Path to the server's Unix socket. +The default value is +.Pa /var/run/hercules.sock . +.It Ic MonitorHTTP Ns = Ns Ar str +The address and port on which to expose the Hercules HTTP API. +This option may be set to the special string "disabled" +to disable the HTTP API. +The default value is +.Ar ":8000" . +.Pp +Example: MonitorHTTP = "0.0.0.0:1818" +.It Ic DropUser Ns = Ns Ar str +Name of a local system user. +If specified, the server will drop its privileges to this user after startup. +If unspecified, the server will run as root. +Running the server as root is discouraged, as it presents a security risk. +See +.Sx CAVEATS +for more information. +.Pp +Example: DropUser = "_hercules" +.It Ic EnablePCC Ns = Ns Ar bool +Setting this option to +.Ar false +disables PCC congestion control and Hercules will send as fast as possible, +up to its rate limit (see below). +This may be useful for testing, but sending without congestion control across +public networks is probably a bad idea. +The default value is +.Ar true . +.It Ic RateLimit Ns = Ns Ar int +This option limits Hercules' sending rate. +The limit is applied to each transfer indivudually. +The value is in packets per second. +The default value is +.Ar 3'333'333 . +.It Ic NumThreads Ns = Ns Ar int +Set the number of RX/TX worker threads to use. +Setting this number to 2, for example, will start 2 RX worker threads +and 2 TX workers. +Depending on the bottleneck in your setup, increasing this number will +improve performance. +Hercules spawns other threads, too, so this is +.Em not +the total number of threads used by Hercules. +The default value is +.Ar 1 . +.It Ic XDPZeroCopy Ns = Ns Ar bool +If your combination of NIC/drivers supports XDP in zero-copy mode, +enabling it here will likely improve performance. +The default value is +.Ar false . +.It Ic Queue Ns = Ns Ar int +Specify the NIC RX queue on which to receive packets. +The default value is +.Ar 0 . +.It Ic ConfigureQueues Ns = Ns Ar bool +For Hercules to receive traffic, packets must be redirected to the queue +specified above. +Hercules will try to configure this automatically, but this +behaviour can be overridden, e.g. if you wish to set custom rules or automatic +configuration fails. +If you set this to false, you must manually ensure packets end up in the +right queue. +Some network interfaces do not support multiple queues, in which case automatic +configuration will fail and the server will not start with this option enabled. +In such cases, you may simply set this option to +.Ar false +without further configuration. +The default value is +.Ar true . +.El +.Ss PER-DESTINATION OVERRIDES +The maximum number of paths and payload size to use can be overridden, +either for a single destination host or an entire destination AS. +Additionally, the paths to use towards each destination can be specified via +path rules. +In case both an AS rule and a Host rule match a destination, the Host rule +takes precedence. +Choosing specific paths is useful if too many paths to the destination are +available, or if certain paths are known to perform better. +Choosing a specific payload length is useful if the MTU listed in the SCION +path metadata is higher than the actual MTU the path(s) can support. +In such a case, Hercules' automatic payload size selection will fail, and it +must be set manually. +.Pp +Destination-host rules are set as follows: +.Bl -tag -width Ds +.It Bq Bq Ic DestinationHosts +.Bl -tag -width Ds -compact +.It Ic HostAddr Ns = Ns Ar str +The destination host this rule applies to. +.It Op Ic NumPaths Ns = Ns Ar int +The maximum number of paths to use towards the destination. +Specifying this is optional, if not set the value of +.Ic DefaultNumPaths +will be used. +.It Op Ic PathSpec Ns = Ns [[ Ar str , ] ,] +A list of AS-interface sequences that must be present on the paths towards +the destination. +Specifying this is optional, if not set no path restrictions are applied. +.It Op Ic Payloadlen Ns = Ns Ar int +The payload length to use for packets towards this destination. +Note that the payload length does not include the Hercules, UDP or SCION +headers. +Hence, the value should be set slightly lower than the actual maximum MTU. +Usually, a value of ca. 100 bytes less than the MTU is fine, but it may need to +be smaller for longer paths. +Specifying this is optional, if not set Hercules will attempt to pick the +right payload size based on the SCION path metadata and the MTU of the sending +interface. +.El +.It Bq Bq Ic DestinationASes +.Bl -tag -width Ds -compact +.It Ic IA Ns = Ns Ar str +The destination ISD-AS this rule applies to +.It Op Ic NumPaths Ns = Ns Ar int +.It Op Ic PathSpec Ns = Ns [[ Ar str , ] ,] +.It Op Ic Payloadlen Ns = Ns Ar int +These options work the same as in the +.Ic DestinationHosts +rules described above. +.El +.El +.Pp +Example: The following set of rules specifies that +.Bl -bullet +.It +For transfers to the host +.Em 17-ffaa:1:fe2,1.1.1.1 : +.Bl -bullet -compact +.It +Transfers may use up to 42 paths. +.It +The paths must contain either the AS-interface sequence + 17-f:f:f 1 -> 17:f:f:a 2 + OR 1-f:0:0 22 . +.El +.It +For transfers to the host +.Em 18-a:b:c,2.2.2.2 : +.Bl -bullet -compact +.It +Up to two paths should be used. +.It +Automatic MTU selection is overridden and a payload length of 1000B is used. +.El +.It +For transfers to any other host in AS +.Em 18-a:b:c : +.Bl -bullet -compact +.It +A payload length of 1400 should be used. +.El +.El +.Pp +Example: +.Bd -literal +[[DestinationHosts]] +HostAddr = "17-ffa:1:fe2,1.1.1.1" +NumPaths = 42 +PathSpec = [ +["17-f:f:f 1", "17-f:f:a 2"], +["1-f:0:0 22"], +] + +[[DestinationHosts]] +HostAddr = "18-a:b:c,2.2.2.2" +NumPaths = 2 +Payloadlen = 1000 + +[[DestinationASes]] +IA = "18-a:b:c" +Payloadlen = 1400 +.Ed +.Sh FILES +.Bl -tag -width Ds -compact +.It Pa /usr/local/etc/hercules.conf +Default configuration file +.It Pa /usr/local/share/doc/hercules/hercules.conf.sample +Example config file showcasing the available options. +.El +.Sh SEE ALSO +.Xr hcp 1 , +.Xr hercules-monitor 1 , +.Xr hercules-server 1 , +.Xr hercules 7 +.Pp +Further information about Hercules is available on +.Lk https://github.com/netsec-ethz/hercules . +For more information about SCION, please see +.Lk https://scion-architecture.net . +.Sh AUTHORS +.An Network Security Group, ETH Zürich +.Sh CAVEATS +Two security issues are present when Hercules is run as the root user: +First, because the receiving-side Hercules server simply writes data to the file +specified by the sender and no authentication of the sender is performed, +a sender may overwrite arbitrary system files. +Second, because the sending-side Hercules server simply copies data from the +file specified by the user and no authentication of the user is performed, +a user may copy arbitrary system files to the destination server. +To mitigate these issues, it is recommended that you set the +.Ic DropUser +option described above. diff --git a/doc/hercules.conf.5.md b/doc/hercules.conf.5.md new file mode 100644 index 0000000..596864a --- /dev/null +++ b/doc/hercules.conf.5.md @@ -0,0 +1,298 @@ +HERCULES.CONF(5) - File Formats Manual + +# NAME + +**hercules.conf** - Hercules configuration file + +# DESCRIPTION + +**hercules.conf** +is the configuration file for the Hercules file transfer system. +This configuration file is used by +hercules-server(1) +and +hercules-monitor(1). +Its default location is +*/usr/local/etc/hercules.conf*. +The configuration file is in TOML format. + +The following two options must be set, as they have no default values: + +**ListenAddress**=*str* + +> This specifies the SCION/UDP address Hercules will listen on +> for incoming transfers. + +> Example: ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" + +**Interfaces**=\[*str*,] + +> The network interface Hercules should use for data traffic. +> Hercules will load an XDP program on this interface. + +> Example: Interfaces = \["eth0"] + +It is +*strongly* +recommended to also set the +**DropUser** +option, described below. +See +*CAVEATS* +for more information. + +## GENERAL CONFIGURATION + +The following general configuration options are available: + +**DefaultNumPaths**=*int* + +> Specify how many SCION path to use for data transfers. +> This is an upper limit, if fewer paths are available only those will be used. +> This value may be overridden on a per-destination basis, see +> *PER-DESTINATION OVERRIDES*. +> The default value is +> *1*. + +> Example: DefaultNumPaths = 2 + +**MonitorSocket**=*str* + +> Path to the monitor's Unix socket. +> The default value is +> */var/run/herculesmon.sock*. + +**ServerSocket**=*str* + +> Path to the server's Unix socket. +> The default value is +> */var/run/hercules.sock*. + +**MonitorHTTP**=*str* + +> The address and port on which to expose the Hercules HTTP API. +> This option may be set to the special string "disabled" +> to disable the HTTP API. +> The default value is +> *:8000*. + +> Example: MonitorHTTP = "0.0.0.0:1818" + +**DropUser**=*str* + +> Name of a local system user. +> If specified, the server will drop its privileges to this user after startup. +> If unspecified, the server will run as root. +> Running the server as root is discouraged, as it presents a security risk. +> See +> *CAVEATS* +> for more information. + +> Example: DropUser = "\_hercules" + +**EnablePCC**=*bool* + +> Setting this option to +> *false* +> disables PCC congestion control and Hercules will send as fast as possible, +> up to its rate limit (see below). +> This may be useful for testing, but sending without congestion control across +> public networks is probably a bad idea. +> The default value is +> *true*. + +**RateLimit**=*int* + +> This option limits Hercules' sending rate. +> The limit is applied to each transfer indivudually. +> The value is in packets per second. +> The default value is +> *3'333'333*. + +**NumThreads**=*int* + +> Set the number of RX/TX worker threads to use. +> Setting this number to 2, for example, will start 2 RX worker threads +> and 2 TX workers. +> Depending on the bottleneck in your setup, increasing this number will +> improve performance. +> Hercules spawns other threads, too, so this is +> *not* +> the total number of threads used by Hercules. +> The default value is +> *1*. + +**XDPZeroCopy**=*bool* + +> If your combination of NIC/drivers supports XDP in zero-copy mode, +> enabling it here will likely improve performance. +> The default value is +> *false*. + +**Queue**=*int* + +> Specify the NIC RX queue on which to receive packets. +> The default value is +> *0*. + +**ConfigureQueues**=*bool* + +> For Hercules to receive traffic, packets must be redirected to the queue +> specified above. +> Hercules will try to configure this automatically, but this +> behaviour can be overridden, e.g. if you wish to set custom rules or automatic +> configuration fails. +> If you set this to false, you must manually ensure packets end up in the +> right queue. +> Some network interfaces do not support multiple queues, in which case automatic +> configuration will fail and the server will not start with this option enabled. +> In such cases, you may simply set this option to +> *false* +> without further configuration. +> The default value is +> *true*. + +## PER-DESTINATION OVERRIDES + +The maximum number of paths and payload size to use can be overridden, +either for a single destination host or an entire destination AS. +Additionally, the paths to use towards each destination can be specified via +path rules. +In case both an AS rule and a Host rule match a destination, the Host rule +takes precedence. +Choosing specific paths is useful if too many paths to the destination are +available, or if certain paths are known to perform better. +Choosing a specific payload length is useful if the MTU listed in the SCION +path metadata is higher than the actual MTU the path(s) can support. +In such a case, Hercules' automatic payload size selection will fail, and it +must be set manually. + +Destination-host rules are set as follows: + +\[\[**DestinationHosts**]] + +> **HostAddr**=*str* + +> > The destination host this rule applies to. + +> \[**NumPaths**=*int*] + +> > The maximum number of paths to use towards the destination. +> > Specifying this is optional, if not set the value of +> > **DefaultNumPaths** +> > will be used. + +> \[**PathSpec**=\[\[ *str*,] *,]*] + +> > A list of AS-interface sequences that must be present on the paths towards +> > the destination. +> > Specifying this is optional, if not set no path restrictions are applied. + +> \[**Payloadlen**=*int*] + +> > The payload length to use for packets towards this destination. +> > Note that the payload length does not include the Hercules, UDP or SCION +> > headers. +> > Hence, the value should be set slightly lower than the actual maximum MTU. +> > Usually, a value of ca. 100 bytes less than the MTU is fine, but it may need to +> > be smaller for longer paths. +> > Specifying this is optional, if not set Hercules will attempt to pick the +> > right payload size based on the SCION path metadata and the MTU of the sending +> > interface. + +\[\[**DestinationASes**]] + +> **IA**=*str* + +> > The destination ISD-AS this rule applies to + +> \[**NumPaths**=*int*] + +> \[**PathSpec**=\[\[ *str*,] *,]*] + +> \[**Payloadlen**=*int*] + +> > These options work the same as in the +> > **DestinationHosts** +> > rules described above. + +Example: The following set of rules specifies that + +* For transfers to the host + *17-ffaa:1:fe2,1.1.1.1*: + + * Transfers may use up to 42 paths. + * The paths must contain either the AS-interface sequence + 17-f:f:f 1 -> 17:f:f:a 2 + OR 1-f:0:0 22 . + +* For transfers to the host + *18-a:b:c,2.2.2.2*: + + * Up to two paths should be used. + * Automatic MTU selection is overridden and a payload length of 1000B is used. + +* For transfers to any other host in AS + *18-a:b:c*: + + * A payload length of 1400 should be used. + +Example: + + [[DestinationHosts]] + HostAddr = "17-ffa:1:fe2,1.1.1.1" + NumPaths = 42 + PathSpec = [ + ["17-f:f:f 1", "17-f:f:a 2"], + ["1-f:0:0 22"], + ] + + [[DestinationHosts]] + HostAddr = "18-a:b:c,2.2.2.2" + NumPaths = 2 + Payloadlen = 1000 + + [[DestinationASes]] + IA = "18-a:b:c" + Payloadlen = 1400 + +# FILES + +*/usr/local/etc/hercules.conf* + +> Default configuration file + +*/usr/local/share/doc/hercules/hercules.conf.sample* + +> Example config file showcasing the available options. + +# SEE ALSO + +hcp(1), +hercules-monitor(1), +hercules-server(1), +hercules(7) + +Further information about Hercules is available on +[https://github.com/netsec-ethz/hercules](https://github.com/netsec-ethz/hercules). +For more information about SCION, please see +[https://scion-architecture.net](https://scion-architecture.net). + +# AUTHORS + +Network Security Group, ETH Zürich + +# CAVEATS + +Two security issues are present when Hercules is run as the root user: +First, because the receiving-side Hercules server simply writes data to the file +specified by the sender and no authentication of the sender is performed, +a sender may overwrite arbitrary system files. +Second, because the sending-side Hercules server simply copies data from the +file specified by the user and no authentication of the user is performed, +a user may copy arbitrary system files to the destination server. +To mitigate these issues, it is recommended that you set the +**DropUser** +option described above. + +Void Linux - October 29, 2024 diff --git a/doc/protocol.md b/doc/protocol.md new file mode 100644 index 0000000..09b05dc --- /dev/null +++ b/doc/protocol.md @@ -0,0 +1,99 @@ +# Hercules Protocol + +The transmitter splits the file into chunks of the same size. All the chunks are transmitted (in order). +The receiver acknowledges the chunks at regular intervals. +Once the sender has transmitted all chunks once, it will start to retransmit all chunks that have not been acknowledged in time. +This is repeated until all chunks are acked. + + +--- + + +All packets have the following basic layout: + + | index | path | flags | seqnr | payload ... | + | u32 | u8 | u8 | u32 | ... | + + +> **NOTE**: Integers are transmitted little endian (host endianness). + +The flags field is zero for regular data and (N)ACK packets. +The flags field has the lowest bit set for packets referring to the transfer +of a directory index. +The flags field is zero for initial packets, since those don't refer to +either in particular. + +For control packets (handshake and acknowledgements, either sender to receiver or receiver to sender), index is `UINT_MAX`. +For all control packets, the first byte of the payload contains the control packet type. +The following control packet types exist: + + 0: Handshake packet + 1: ACK packet + 2: NACK packet + 3: RTT measurement packet + +For data packets (sender to receiver), the index field is the index of the chunk being transmitted. +This is **not** a packet sequence number, as chunks may be retransmitted; hence the separate field `seqnr` contains the per-path sequence number. +A NACK packet is always associated with a path. + +If path is not `UINT8_MAX`, it is used to account the packet to a specific path. +This is used to provide quick feedback to the PCC algorithm, if enabled. + + +#### Handshake + +1. Sender sends initial packet: + + | filesize | chunksize | timestamp | path index | flags | index_len | dir_index... | + | u64 | u32 | u64 | u32 | u8 | u64 | ... | + + Where `num entries` is `UINT8_MAX` to distinguish handshake replies from ACKs. + + Flags: + - 0-th bit: `SET_RETURN_PATH` The receiver should use this path for sending + ACKs from now on. + - 1st bit: `HS_CONFIRM`: Indicates that the packet is a reflected HS packet, confirming the handshake. + - 2nd bit: `NEW_TRANSFER`: Indicates that the packet is trying to start a new transfer (not just a path update). + - 3rd bit: `INDEX_FOLLOWS`: The directory index is larger than the space available in the handshake packet, it will need to be transferred separately before the actual data transfer can start. + +1. Receiver replies immediately with the same packet (with `HS_CONFIRM` set). + + This first packet is used to determine an approximate round trip time. + + The receiver proceeds to prepare the file mapping etc. + +1. Receiver replies with an empty ACK signaling "Clear to send" + +##### Path handshakes + +Every time the sender starts using a new path or the receiver starts using a new +return path, the sender will update the RTT estimate used by PCC. +In order to achieve this, it sends a handshake (identical to the above) on the +affected path(s). +The receiver replies immediately with the same packet (using the current return path). + +#### Data transmit + +* The sender sends (un-acknowledged) chunks in data packets at chosen send rate +* The receiver sends ACK packets for the entire file at 100ms intervals. + + ACK packets consist of a list of `begin`,`end` pairs declaring that chunks + with index `i` in `begin <= i < end` have been received. + Lists longer than the packet payload size are transmitted as multiple + independent packets with identical structure. + + + | begin, end | begin, end | begin, end | ... + | u32 u32 | u32 u32 | u32 u32 | ... + +* The receiver sends a NACK packets four times per RTT to provide timely feedback to congestion control. + The NACK packet layout is identical to the ACK packet layout. + + NACK packets are only sent if non-empty. + Hence, if no path uses PCC, or no recent packet loss has been observed, no NACKs are sent. + +#### Termination + +1. Once the receiver has received all chunks, it sends one more ACK for the entire range and terminates. +1. When the sender receives this last ACK, it determines that all chunks have been received and terminates. + diff --git a/hcp/.gitignore b/hcp/.gitignore new file mode 100644 index 0000000..eda8573 --- /dev/null +++ b/hcp/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +.idea +*.log +tmp/ + +hcp diff --git a/hcp/README b/hcp/README index 3262201..60708c8 100644 --- a/hcp/README +++ b/hcp/README @@ -11,6 +11,8 @@ Hercules server listening on `64-2:0:9,192.168.4.2:10000`, run the following com `hcp localhost:8000 hercules.in 64-2:0:9,192.168.4.2:10000 hercules.out` +See hcp(1) or hcp.1.md for more information. + Building ========== `go build` diff --git a/hcp/hcp.1 b/hcp/hcp.1 new file mode 100644 index 0000000..0f0f3e2 --- /dev/null +++ b/hcp/hcp.1 @@ -0,0 +1,95 @@ +.Dd October 29, 2024 +.Dt HCP 1 +.Os +.Sh NAME +.Nm hcp +.Nd Copy files using Hercules +.Sh SYNOPSIS +.Nm hcp +.Bk -words +.Op OPTIONS +.Ar SOURCE-API +.Ar SOURCE-PATH +.Ar DEST-ADDR +.Ar DEST-PATH +.Ek +.Sh DESCRIPTION +.Nm +is an easy-to-use tool to copy files using the Hercules file transfer system. +It interacts with the Hercules server's API on behalf of the user. +It instructs the source Hercules server, whose API is exposed on +.Ar SOURCE-API +to transfer the file (or directory) +.Ar SOURCE-PATH +to the destination Hercules server whose SCION/UDP address is +.Ar DEST-ADDR +and store it under +.Ar DEST-PATH . +Once a transfer is submitted, +.Nm +will periodically poll the Hercules server for information about the transfer's +progress and show this information to the user. +.Pp +Note that the paths must be supplied from the point of view of the source and +destination Hercules servers, respectively. +If +.Nm +is run from a different machine than the source Hercules server, the source file +must first be made available to the Hercules server. +Doing so is outside the scope of this tool, +but this may be achieved by means of a network mount, or by attaching storage +media containing the file to the machine running the Hercules server. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl i Ar poll_freq +How frequently to poll the server for transfer status updates. +The argument is a go duration string. +The default polling frequency is +.Ar 1s , +that is, poll every second. +.It Fl n +Do not ask the server for the file's total size before submitting the transfer. +With this option set, the progress bar and time estimates are not shown. +.It Fl version +Print version information and exit. +.El +.\" .Sh EXIT STATUS +.Sh EXAMPLES +If you are running this tool on the same machine as the source +Hercules server and want to transfer the file +.Pa /tmp/hercules.in +to +.Pa /tmp/hercules.out , +with the destination Hercules server listening on +.Ar 64-2:0:9,192.168.4.2:10000 , +run the following command: +.Pp +.Dl $ hcp localhost:8000 /tmp/hercules.in 64-2:0:9,192.168.4.2:10000 \ +/tmp/hercules.out +.Pp +If your are running the Hercules server on a dedicated machine, with its API +accessible on +.Ar 10.0.0.1:8000 , +you have copied the file you want to transfer, +.Pa hercules.in , +to a network share mounted at +.Pa /mnt/data +on the Hercules server, and want to submit run hcp from a different host: +.Pp +.Dl $ hcp 10.0.0.1:8000 /mnt/data/hercules.in 64-2:0:9,192.168.4.2:10000 \ +/tmp/hercules.out +.\" .Sh DIAGNOSTICS +.Sh SEE ALSO +.Xr hercules-monitor 1 , +.Xr hercules-server 1 , +.Xr hercules.conf 5 , +.Xr hercules 7 +.Pp +Further information about Hercules is available on +.Lk https://github.com/netsec-ethz/hercules . +For more information about SCION, please see +.Lk https://scion-architecture.net . +.\" .Sh CAVEATS +.Sh AUTHORS +.An Network Security Group, ETH Zürich diff --git a/hcp/hcp.1.md b/hcp/hcp.1.md new file mode 100644 index 0000000..8554256 --- /dev/null +++ b/hcp/hcp.1.md @@ -0,0 +1,103 @@ +HCP(1) - General Commands Manual + +# NAME + +**hcp** - Copy files using Hercules + +# SYNOPSIS + +**hcp** +\[OPTIONS] +*SOURCE-API* +*SOURCE-PATH* +*DEST-ADDR* +*DEST-PATH* + +# DESCRIPTION + +**hcp** +is an easy-to-use tool to copy files using the Hercules file transfer system. +It interacts with the Hercules server's API on behalf of the user. +It instructs the source Hercules server, whose API is exposed on +*SOURCE-API* +to transfer the file (or directory) +*SOURCE-PATH* +to the destination Hercules server whose SCION/UDP address is +*DEST-ADDR* +and store it under +*DEST-PATH*. +Once a transfer is submitted, +**hcp** +will periodically poll the Hercules server for information about the transfer's +progress and show this information to the user. + +Note that the paths must be supplied from the point of view of the source and +destination Hercules servers, respectively. +If +**hcp** +is run from a different machine than the source Hercules server, the source file +must first be made available to the Hercules server. +Doing so is outside the scope of this tool, +but this may be achieved by means of a network mount, or by attaching storage +media containing the file to the machine running the Hercules server. + +The options are as follows: + +**-i** *poll\_freq* + +> How frequently to poll the server for transfer status updates. +> The argument is a go duration string. +> The default polling frequency is +> *1s*, +> that is, poll every second. + +**-n** + +> Do not ask the server for the file's total size before submitting the transfer. +> With this option set, the progress bar and time estimates are not shown. + +**-version** + +> Print version information and exit. + +# EXAMPLES + +If you are running this tool on the same machine as the source +Hercules server and want to transfer the file +*/tmp/hercules.in* +to +*/tmp/hercules.out*, +with the destination Hercules server listening on +*64-2:0:9,192.168.4.2:10000*, +run the following command: + + $ hcp localhost:8000 /tmp/hercules.in 64-2:0:9,192.168.4.2:10000 /tmp/hercules.out + +If your are running the Hercules server on a dedicated machine, with its API +accessible on +*10.0.0.1:8000*, +you have copied the file you want to transfer, +*hercules.in*, +to a network share mounted at +*/mnt/data* +on the Hercules server, and want to submit run hcp from a different host: + + $ hcp 10.0.0.1:8000 /mnt/data/hercules.in 64-2:0:9,192.168.4.2:10000 /tmp/hercules.out + +# SEE ALSO + +hercules-monitor(1), +hercules-server(1), +hercules.conf(5), +hercules(7) + +Further information about Hercules is available on +[https://github.com/netsec-ethz/hercules](https://github.com/netsec-ethz/hercules). +For more information about SCION, please see +[https://scion-architecture.net](https://scion-architecture.net). + +# AUTHORS + +Network Security Group, ETH Zürich + +Void Linux - October 29, 2024 diff --git a/hercules.conf b/hercules.conf index 4e2c8fd..4c09c58 100644 --- a/hercules.conf +++ b/hercules.conf @@ -1,9 +1,15 @@ -# This is the default config file +# This is the Hercules configuration file. +# See hercules.conf(5) for more information. +# If you installed Hercules to the default location, an example is available +# at /usr/local/share/doc/hercules/hercules.conf.sample . # SCION address the Hercules server should listen on -# ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" +ListenAddress = "replaceme//17-ffaa:1:fe2,192.168.10.141:8000" # Network interfaces to use for Hercules Interfaces = [ -# "eth0", + "replaceme//eth0", ] + +# Drop privileges to the specified user after startup +# DropUser = "_hercules" diff --git a/hercules.conf.sample b/hercules.conf.sample new file mode 100644 index 0000000..2367108 --- /dev/null +++ b/hercules.conf.sample @@ -0,0 +1,99 @@ +# Sample config for Hercules. + +# By default, up to `DefaultNumPaths` paths will be used. +DefaultNumPaths = 1 + +# Path to the monitor's unix socket +MonitorSocket = "var/run/herculesmon.sock" +# +# Path to the server's unix socket +ServerSocket = "var/run/hercules.sock" + +# SCION address the Hercules server should listen on +ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" + +# Listenting address for the monitor (HTTP) +# Set to "disabled" to disable +MonitorHTTP = ":8000" + +# Listening address for the monitor (HTTPS) +# Set to "disabled" to disable +MonitorHTTPS = "disabled" + +# Drop privileges to the specified user after startup +DropUser = "_hercules" + +# Path to the server's certificate and key for TLS +TLSCert = "cert.pem" +TLSKey = "key.pem" + +# Paths to certificates used for validation of TLS client certificates +ClientCACerts = [ +"cert.pem", +] + +# Network interfaces to use for Hercules +Interfaces = [ +"eth0", +] + +# If the NIC/drivers support XDP in zerocopy mode, enabling it +# will improve performance. +XDPZeroCopy = true + +# Specify the NIC RX queue on which to receive packets +Queue = 0 + +# For Hercules to receive traffic, packets must be redirected to the queue +# specified above. Hercules will try to configure this automatically, but this +# behaviour can be overridden, e.g. if you wish to set custom rules or automatic +# configuration fails. If you set this to false, you must manually ensure +# packets end up in the right queue. +ConfigureQueues = false + +# Disabling congestion control is possible, but probably a bad idea +EnablePCC = false + +# This sets a sending rate limit (in pps) +# that applies to transfers individually +RateLimit = 100000 + +# The number of RX/TX worker threads to use +NumThreads = 2 + +# The number and choice of paths can be overridden on a destination-host +# or destination-AS basis. In case both an AS and Host rule match, the Host +# rule takes precedence. + +# This Host rule specifies that, for the host 17-ffaa:1:fe2,1.1.1.1, +# - Transfers may use up to 42 paths +# - The paths must contain either the AS-interface sequence +# 17-f:f:f 1 > 17:f:f:a 2 +# OR 1-f:0:0 22 +[[DestinationHosts]] +HostAddr = "17-ffa:1:fe2,1.1.1.1" +NumPaths = 42 +PathSpec = [ +["17-f:f:f 1", "17-f:f:a 2"], +["1-f:0:0 22"], +] + +# In this case the number of paths is set to 2, but the exact set of paths +# is left unspecified. +# We additionally override automatic MTU selection based on SCION path metadata +# and use a payload length of 1000B for all transfers to this host. +[[DestinationHosts]] +HostAddr = "18-a:b:c,2.2.2.2" +NumPaths = 2 +Payloadlen = 1000 + + +# Similarly, but for an entire destination AS instead of a specific host +[[DestinationASes]] +IA = "17-a:b:c" +NumPaths = 2 + +# Specify mapping of certificates to system users and groups to use for +# file permission checks. +[UserMap] +"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" = {User = "marco", Group = "marco"} From 64e45e57d14710aec729a923e61b4ef000e0d048 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 28 Oct 2024 16:12:55 +0100 Subject: [PATCH 080/112] add hcp to Makefile, upgrade go (docker) to 1.22.8 --- Dockerfile | 36 ++++-------------------------------- Makefile | 7 ++++++- hcp/hcp.go | 12 +++++++++++- 3 files changed, 21 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9382736..9aef775 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# ubuntu/focal with go-1.21.6 +# ubuntu/focal with go-1.22.8 # copy pasted from # https://github.com/docker-library/golang/blob/master/1.21/bullseye/Dockerfile # but with a different base image (ubuntu:focal instead of debian:bullseye) @@ -36,43 +36,15 @@ RUN set -eux; \ ENV PATH /usr/local/go/bin:$PATH -ENV GOLANG_VERSION 1.21.6 +ENV GOLANG_VERSION 1.22.8 RUN set -eux; \ arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \ url=; \ case "$arch" in \ 'amd64') \ - url='https://dl.google.com/go/go1.21.6.linux-amd64.tar.gz'; \ - sha256='3f934f40ac360b9c01f616a9aa1796d227d8b0328bf64cb045c7b8c4ee9caea4'; \ - ;; \ - 'armhf') \ - url='https://dl.google.com/go/go1.21.6.linux-armv6l.tar.gz'; \ - sha256='6a8eda6cc6a799ff25e74ce0c13fdc1a76c0983a0bb07c789a2a3454bf6ec9b2'; \ - ;; \ - 'arm64') \ - url='https://dl.google.com/go/go1.21.6.linux-arm64.tar.gz'; \ - sha256='e2e8aa88e1b5170a0d495d7d9c766af2b2b6c6925a8f8956d834ad6b4cacbd9a'; \ - ;; \ - 'i386') \ - url='https://dl.google.com/go/go1.21.6.linux-386.tar.gz'; \ - sha256='05d09041b5a1193c14e4b2db3f7fcc649b236c567f5eb93305c537851b72dd95'; \ - ;; \ - 'mips64el') \ - url='https://dl.google.com/go/go1.21.6.linux-mips64le.tar.gz'; \ - sha256='eb309a611dfec52b98805e05bafbe769d3d5966aef05f17ec617c89ee5a9e484'; \ - ;; \ - 'ppc64el') \ - url='https://dl.google.com/go/go1.21.6.linux-ppc64le.tar.gz'; \ - sha256='e872b1e9a3f2f08fd4554615a32ca9123a4ba877ab6d19d36abc3424f86bc07f'; \ - ;; \ - 'riscv64') \ - url='https://dl.google.com/go/go1.21.6.linux-riscv64.tar.gz'; \ - sha256='86a2fe6597af4b37d98bca632f109034b624786a8d9c1504d340661355ed31f7'; \ - ;; \ - 's390x') \ - url='https://dl.google.com/go/go1.21.6.linux-s390x.tar.gz'; \ - sha256='92894d0f732d3379bc414ffdd617eaadad47e1d72610e10d69a1156db03fc052'; \ + url='https://go.dev/dl/go1.22.8.linux-amd64.tar.gz'; \ + sha256='5f467d29fc67c7ae6468cb6ad5b047a274bae8180cac5e0b7ddbfeba3e47e18f'; \ ;; \ *) echo >&2 "error: unsupported architecture '$arch' (likely packaging update needed)"; exit 1 ;; \ esac; \ diff --git a/Makefile b/Makefile index 3c5dc60..5d62b21 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ TARGET_SERVER := hercules-server TARGET_MONITOR := hercules-monitor +TARGET_HCP := hcp CC := gcc CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 @@ -31,13 +32,14 @@ SRCS := $(wildcard *.c) OBJS := $(SRCS:.c=.o) DEPS := $(OBJS:.o=.d) MONITORFILES := $(wildcard monitor/*) +HCPFILES := $(wildcard hcp/*) VERSION := $(shell (ref=$$(git describe --tags --long --dirty 2>/dev/null) && echo $$(git rev-parse --abbrev-ref HEAD)-$$ref) ||\ echo $$(git rev-parse --abbrev-ref HEAD)-untagged-$$(git describe --tags --dirty --always)) CFLAGS += -DHERCULES_VERSION="\"$(VERSION)\"" -all: $(TARGET_MONITOR) $(TARGET_SERVER) +all: $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_HCP) install: all ifndef DESTDIR @@ -54,6 +56,9 @@ $(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99 @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s docker exec hercules-builder $(CC) -o $@ $(OBJS) bpf_prgms.s $(LDFLAGS) +$(TARGET_HCP): $(HCPFILES) $(wildcard *.h) builder + docker exec -w /`basename $(PWD)`/hcp hercules-builder go build -ldflags "-X main.startupVersion=${VERSION}" + %.o: %.c builder docker exec hercules-builder $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ diff --git a/hcp/hcp.go b/hcp/hcp.go index 7a26dfb..29b7fe6 100644 --- a/hcp/hcp.go +++ b/hcp/hcp.go @@ -16,6 +16,8 @@ import ( "github.com/scionproto/scion/pkg/snet" ) +var startupVersion string + type apiStatus struct { status int state int @@ -26,14 +28,21 @@ type apiStatus struct { func main() { flag.Usage = func() { - fmt.Printf("Usage: %s [OPTION]... SOURCE-API SOURCE-PATH DEST-ADDR DEST-PATH\n", os.Args[0]) + fmt.Printf("This is hcp %s\nUsage: %s [OPTION]... SOURCE-API SOURCE-PATH DEST-ADDR DEST-PATH\n", startupVersion, os.Args[0]) flag.PrintDefaults() } poll_interval := flag.Duration("i", time.Second*1, "Poll frequency") no_stat_file := flag.Bool("n", false, "Don't stat source file") + show_version := flag.Bool("version", false, "Print version and exit") + // TODO payload size flag.Parse() + if *show_version { + fmt.Printf("This is hcp %s\n", startupVersion) + os.Exit(0) + } + if flag.NArg() != 4 { flag.Usage() os.Exit(1) @@ -116,6 +125,7 @@ func main() { if info.state == C.SESSION_STATE_DONE { finished = true + bar.Finish() bar.Exit() fmt.Println() if info.error != C.SESSION_ERROR_OK { From 7e37e790fae5512a53fb4bd74ebab88307289d72 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 29 Oct 2024 11:05:49 +0100 Subject: [PATCH 081/112] print warning when running as root --- hercules.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hercules.c b/hercules.c index 1a047f7..230facb 100644 --- a/hercules.c +++ b/hercules.c @@ -3913,6 +3913,10 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Error parsing DropUser\n"); exit(1); } + fprintf(stderr, + "WARNING: DropUser config option not set. Running Hercules as root is " + "not secure!\n" + "See the documentation for more information.\n"); } // Listening address toml_datum_t listen_addr = toml_string_in(conf, "ListenAddress"); From 8d92d31335954b295ceb80990d55b52faab183f2 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 30 Oct 2024 21:38:44 +0100 Subject: [PATCH 082/112] modify makefile to allow building without docker --- Makefile | 52 +++++++++++++++++++++++++++++++--------------------- README.md | 20 ++++++++++++++++---- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 74ce21d..af35a32 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ TARGET_SERVER := hercules-server TARGET_MONITOR := hercules-monitor -TARGET_HCP := hcp +TARGET_HCP := hcp/hcp CC := gcc CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 @@ -32,7 +32,7 @@ SRCS := $(wildcard *.c) OBJS := $(SRCS:.c=.o) DEPS := $(OBJS:.o=.d) MONITORFILES := $(wildcard monitor/*) -HCPFILES := $(wildcard hcp/*) +HCPFILES := $(filter-out $(TARGET_HCP),$(wildcard hcp/*)) VERSION := $(shell (ref=$$(git describe --tags --long --dirty 2>/dev/null) && echo $$(git rev-parse --abbrev-ref HEAD)-$$ref) ||\ echo $$(git rev-parse --abbrev-ref HEAD)-untagged-$$(git describe --tags --dirty --always)) @@ -44,10 +44,11 @@ PREFIX ?= /usr/local all: $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_HCP) -install: +install: all install -d $(DESTDIR)$(PREFIX)/bin/ install $(TARGET_MONITOR) $(DESTDIR)$(PREFIX)/bin/ install $(TARGET_SERVER) $(DESTDIR)$(PREFIX)/bin/ + install $(TARGET_HCP) $(DESTDIR)$(PREFIX)/bin/ install -d $(DESTDIR)$(PREFIX)/etc/ install hercules.conf $(DESTDIR)$(PREFIX)/etc/ @@ -59,52 +60,61 @@ install: install doc/hercules-server.1 $(DESTDIR)$(PREFIX)/share/man/man1/ install doc/hercules-monitor.1 $(DESTDIR)$(PREFIX)/share/man/man1/ install hcp/hcp.1 $(DESTDIR)$(PREFIX)/share/man/man1/ + install -d $(DESTDIR)$(PREFIX)/share/man/man5/ + install doc/hercules.conf.5 $(DESTDIR)$(PREFIX)/share/man/man5/ + install -d $(DESTDIR)$(PREFIX)/share/man/man7/ + install doc/hercules.7 $(DESTDIR)$(PREFIX)/share/man/man7/ + +# Hack to allow building both in docker and natively: +# Prefixing the target with docker_ should use the builder image. +# e.g., make docker_all +docker_%: builder + docker exec hercules-builder $(MAKE) $* # List all headers as dependency because we include a header file via cgo (which in turn may include other headers) -$(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) builder - docker exec -w /`basename $(PWD)`/monitor hercules-builder go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" +$(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) + cd monitor && go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" -$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a builder +$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a @# update modification dates in assembly, so that the new version gets loaded @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s - docker exec hercules-builder $(CC) -o $@ $(OBJS) bpf_prgms.s $(LDFLAGS) + $(CC) -o $@ $(OBJS) bpf_prgms.s $(LDFLAGS) -$(TARGET_HCP): $(HCPFILES) $(wildcard *.h) builder - docker exec -w /`basename $(PWD)`/hcp hercules-builder go build -ldflags "-X main.startupVersion=${VERSION}" +$(TARGET_HCP): $(HCPFILES) $(wildcard *.h) + cd hcp && go build -ldflags "-X main.startupVersion=${VERSION}" -%.o: %.c builder - docker exec hercules-builder $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ +hcp: $(TARGET_HCP) -hercules: builder hercules.h hercules.go hercules.c bpf_prgm/redirect_userspace.o bpf/src/libbpf.a - docker exec hercules-builder go build -ldflags "-X main.startupVersion=$${startupVersion}" +%.o: %.c + $(CC) $(DEPFLAGS) $(CFLAGS) -c $< -o $@ bpf_prgm/%.ll: bpf_prgm/%.c - docker exec hercules-builder clang -S -target bpf -D __BPF_TRACING__ -I. -Wall -O2 -emit-llvm -c -g -o $@ $< + clang -S -target bpf -D __BPF_TRACING__ -I. -Wall -O2 -emit-llvm -c -g -o $@ $< bpf_prgm/%.o: bpf_prgm/%.ll - docker exec hercules-builder llc -march=bpf -filetype=obj -o $@ $< + llc -march=bpf -filetype=obj -o $@ $< # explicitly list intermediates for dependency resolution bpf_prgm/redirect_userspace.ll: -bpf/src/libbpf.a: builder +bpf/src/libbpf.a: @if [ ! -d bpf/src ]; then \ echo "Error: Need libbpf submodule"; \ echo "May need to run git submodule update --init"; \ exit 1; \ else \ - docker exec -w /`basename $(PWD)`/bpf/src hercules-builder $(MAKE) all OBJDIR=.; \ + cd bpf/src && $(MAKE) all OBJDIR=.; \ mkdir -p build; \ - docker exec -w /`basename $(PWD)`/bpf/src hercules-builder $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ + cd bpf/src && $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ fi -tomlc99/libtoml.a: builder +tomlc99/libtoml.a: @if [ ! -d tomlc99 ]; then \ echo "Error: Need libtoml submodule"; \ echo "May need to run git submodule update --init"; \ exit 1; \ else \ - docker exec -w /`basename $(PWD)`/tomlc99 hercules-builder $(MAKE) all; \ + cd tomlc99 && $(MAKE) all; \ fi @@ -125,7 +135,7 @@ builder_image: docker build -t hercules-builder --build-arg UID=$(shell id -u) --build-arg GID=$(shell id -g) . clean: - rm -rf $(TARGET_MONITOR) $(TARGET_SERVER) $(OBJS) $(DEPS) + rm -rf $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_HCP) $(OBJS) $(DEPS) rm -f hercules mockules/mockules docker container rm -f hercules-builder || true docker rmi hercules-builder || true diff --git a/README.md b/README.md index e92b364..19e26ac 100644 --- a/README.md +++ b/README.md @@ -114,11 +114,23 @@ You can then build Hercules, either using Docker or natively. Hercules can be built from source using Docker and the provided `Dockerfile` which prepares the required build environment. -To build Hercules using Docker, simply run `make`. This will build the server and monitor executables. -With `sudo make install`, you can then install Hercules to your machine. -By default, this will install Hercules to `/usr/local/`. +To build Hercules using Docker, simply run `make docker_all`. +This will build the server and monitor executables, as well as the `hcp` tool. + +> You may prefix any of the makefile targets with `docker_` to use Docker +> instead of your native environment. + +### Native Build + +To build Hercules without Docker, you must have its dependencies installed. -### TODO Native Build +To build Hercules, run `make all`. +This will build the server and monitor executables, as well as the `hcp` tool. + +## Installing + +Once built, you can install Hercules to your machine with `sudo make install`. +By default, this will install Hercules to `/usr/local/`. ## Debugging and Development From 82588762aacaa4a9aaa32e85dae1cbfd077f0b5a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 12:45:52 +0100 Subject: [PATCH 083/112] add dependencies to build instructions --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 19e26ac..d91dd2a 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,20 @@ This will build the server and monitor executables, as well as the `hcp` tool. ### Native Build -To build Hercules without Docker, you must have its dependencies installed. +To build Hercules without Docker, you must have the following installed: + ++ llvm ++ clang ++ git ++ Go >= 1.22 ++ libz ++ libelf ++ Linux kernel headers ++ gcc-multilib + +On Ubuntu you can install the required packages as follows: +`# apt install build-essential llvm clang git golang libz-dev libelf-dev +linux-headers-generic gcc-multilib` To build Hercules, run `make all`. This will build the server and monitor executables, as well as the `hcp` tool. From beac3cb2f205cfd802c0179d3658c62c1b8127f9 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 13:00:52 +0100 Subject: [PATCH 084/112] update go mod files --- hcp/go.mod | 10 +- hcp/go.sum | 25 +-- monitor/go.mod | 81 +++---- monitor/go.sum | 599 ++++++++++--------------------------------------- 4 files changed, 169 insertions(+), 546 deletions(-) diff --git a/hcp/go.mod b/hcp/go.mod index 39b04a0..a5cc200 100644 --- a/hcp/go.mod +++ b/hcp/go.mod @@ -1,10 +1,10 @@ module hcp -go 1.22.5 +go 1.22.8 require ( - github.com/schollz/progressbar/v3 v3.14.6 - github.com/scionproto/scion v0.11.0 + github.com/schollz/progressbar/v3 v3.17.0 + github.com/scionproto/scion v0.12.0 ) require ( @@ -21,7 +21,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect google.golang.org/protobuf v1.34.1 // indirect ) diff --git a/hcp/go.sum b/hcp/go.sum index b79bcc1..d62df67 100644 --- a/hcp/go.sum +++ b/hcp/go.sum @@ -2,6 +2,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= +github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -14,10 +16,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= @@ -37,10 +37,10 @@ github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdf github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= -github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= -github.com/scionproto/scion v0.11.0 h1:bvty7qEBRm1PJ0/XorF/tZ/Jq89yTc9IfTMRduLafAw= -github.com/scionproto/scion v0.11.0/go.mod h1:paxrF6VreownCN7E7Rdri6ifXMkiq3leFGoP6n/BFC4= +github.com/schollz/progressbar/v3 v3.17.0 h1:Fv+vG6O6jnJwdjCelvfyYO7sF2jaUGQVmdH4CxcZdsQ= +github.com/schollz/progressbar/v3 v3.17.0/go.mod h1:5H4fLgifX+KeQCsEJnZTOepgZLe1jFF1lpPXb68IJTA= +github.com/scionproto/scion v0.12.0 h1:NbBa1HAxWOXr40C8YuanGhJ3g5hYlJetR5YevKtnHGQ= +github.com/scionproto/scion v0.12.0/go.mod h1:jOmbOiLREf4zn6cNrFqto35rP3eH6RhDJEmrjmJIUUI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -72,11 +72,10 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/monitor/go.mod b/monitor/go.mod index 2134875..a70894a 100644 --- a/monitor/go.mod +++ b/monitor/go.mod @@ -1,65 +1,56 @@ module monitor -go 1.21 - -toolchain go1.21.6 +go 1.22.8 require ( - github.com/BurntSushi/toml v0.3.1 + github.com/BurntSushi/toml v1.4.0 github.com/google/gopacket v1.1.19 github.com/inconshreveable/log15 v2.16.0+incompatible - github.com/scionproto/scion v0.10.0 - github.com/vishvananda/netlink v1.2.1-beta.2 + github.com/scionproto/scion v0.12.0 + github.com/vishvananda/netlink v1.3.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dchest/cmac v1.0.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-stack/stack v1.8.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/stretchr/objx v0.5.0 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect - github.com/uber/jaeger-lib v2.0.0+incompatible // indirect - github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.8.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.14.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.11.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.12.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect - google.golang.org/grpc v1.57.2 // indirect - google.golang.org/protobuf v1.31.0 // indirect - lukechampine.com/uint128 v1.2.0 // indirect - modernc.org/cc/v3 v3.40.0 // indirect - modernc.org/ccgo/v3 v3.16.13 // indirect - modernc.org/libc v1.22.5 // indirect - modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.5.0 // indirect - modernc.org/opt v0.1.3 // indirect - modernc.org/sqlite v1.24.0 // indirect - modernc.org/strutil v1.1.3 // indirect - modernc.org/token v1.0.1 // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/vishvananda/netns v0.0.4 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect + modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect + modernc.org/libc v1.50.5 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.29.9 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect ) diff --git a/monitor/go.sum b/monitor/go.sum index f078e0e..51dda76 100644 --- a/monitor/go.sum +++ b/monitor/go.sum @@ -1,65 +1,21 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/cmac v1.0.0 h1:Vaorm9FVpO2P+YmRdH0RVCUB1XF3Ge1yg9scPvJphyk= github.com/dchest/cmac v1.0.0/go.mod h1:0zViPqHm8iZwwMl1cuK3HqK7Tu4Q7DV4EuMIOUwBVQ0= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -68,556 +24,233 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/google/pprof v0.0.0-20240509144519-723abb6459b7 h1:velgFPYr1X9TDwLIfkV7fWqsFlf7TeP11M/7kPd/dVI= +github.com/google/pprof v0.0.0-20240509144519-723abb6459b7/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/inconshreveable/log15 v2.16.0+incompatible h1:6nvMKxtGcpgm7q0KiGs+Vc+xDvUXaBqsPKHWKsinccw= github.com/inconshreveable/log15 v2.16.0+incompatible/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= +github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/scionproto/scion v0.10.0 h1:OcjLpOaaT8uoTGsOAj1TRcqz4BJzLw/uKcWLNRfrUWY= -github.com/scionproto/scion v0.10.0/go.mod h1:N5p5gAbL5is+q85ohxSjo+WzFn8u5NM0Y0YwocXRF7U= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/scionproto/scion v0.12.0 h1:NbBa1HAxWOXr40C8YuanGhJ3g5hYlJetR5YevKtnHGQ= +github.com/scionproto/scion v0.12.0/go.mod h1:jOmbOiLREf4zn6cNrFqto35rP3eH6RhDJEmrjmJIUUI= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.0.0+incompatible h1:iMSCV0rmXEogjNWPh2D0xk9YVKvrtGoHJNe9ebLu/pw= -github.com/uber/jaeger-lib v2.0.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= -github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= +github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 h1:lv6/DhyiFFGsmzxbsUUTOkN29II+zeWHxvT8Lpdxsv0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.57.2 h1:uw37EN34aMFFXB2QPW7Tq6tdTbind1GpRxw5aOX3a5k= -google.golang.org/grpc v1.57.2/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= -google.golang.org/grpc/examples v0.0.0-20230222033013-5353eaa44095 h1:ijVKWXLMbG/RK63KfOQ1lEVpEApj174fkw073gxZf3w= -google.golang.org/grpc/examples v0.0.0-20230222033013-5353eaa44095/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc/examples v0.0.0-20240321213419-eb5828bae753 h1:crPucDOfTtZF6lBfOiv4ex+5g+TFoNjyiSrSDJUpYPc= +google.golang.org/grpc/examples v0.0.0-20240321213419-eb5828bae753/go.mod h1:fYxPglWChrD7bqbWtDwno019ra5SPuE1c3i+4YAvado= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= -lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= -modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= -modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= -modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= -modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/cc/v4 v4.21.0 h1:D/gLKtcztomvWbsbvBKo3leKQv+86f+DdqEZBBXhnag= +modernc.org/cc/v4 v4.21.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.17.3 h1:t2CQci84jnxKw3GGnHvjGKjiNZeZqyQx/023spkk4hU= +modernc.org/ccgo/v4 v4.17.3/go.mod h1:1FCbAtWYJoKuc+AviS+dH+vGNtYmFJqBeRWjmnDWsIg= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= +modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.50.5 h1:ZzeUd0dIc/sUtoPTCYIrgypkuzoGzNu6kbEWj2VuEmk= +modernc.org/libc v1.50.5/go.mod h1:rhzrUx5oePTSTIzBgM0mTftwWHK8tiT9aNFUt1mldl0= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI= -modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= -modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= -modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= -modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= -modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= -modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= -modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.29.9 h1:9RhNMklxJs+1596GNuAX+O/6040bvOwacTxuFcRuQow= +modernc.org/sqlite v1.29.9/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 911e1c2a3504a425fa7bdab699e7c389deff3524 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 13:16:09 +0100 Subject: [PATCH 085/112] make building with clang possible --- Makefile | 4 ++-- bitset.h | 6 +++--- congestion_control.h | 16 ++++++++-------- doc/developers.md | 14 ++++++++++---- frame_queue.h | 4 ++-- hercules.c | 6 +++--- hercules.h | 12 ++++++------ send_queue.c | 2 +- send_queue.h | 6 +++--- 9 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index af35a32..1026ee2 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ TARGET_SERVER := hercules-server TARGET_MONITOR := hercules-monitor TARGET_HCP := hcp/hcp -CC := gcc +CC := clang CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 # CFLAGS += -DNDEBUG -# CFLAGS += -Wall -Wextra +CFLAGS += -Wall -Wextra ## Options: # Print rx/tx session stats diff --git a/bitset.h b/bitset.h index 232e835..afe242a 100644 --- a/bitset.h +++ b/bitset.h @@ -25,10 +25,10 @@ /** Simple bit-set that keeps track of number of elements in the set. */ struct bitset { - unsigned int *bitmap; + _Atomic unsigned int *bitmap; u32 num; - u32 num_set; - u32 max_set; + _Atomic u32 num_set; + _Atomic u32 max_set; pthread_spinlock_t lock; }; diff --git a/congestion_control.h b/congestion_control.h index dd6223f..0e7fa1e 100644 --- a/congestion_control.h +++ b/congestion_control.h @@ -34,15 +34,15 @@ struct ccontrol_state { // Monitoring interval values sequence_number mi_seq_start; - sequence_number mi_seq_end; + _Atomic sequence_number mi_seq_end; sequence_number excess_npkts; sequence_number mi_seq_min; - sequence_number mi_seq_max; - sequence_number mi_seq_max_rcvd; - u32 num_nacks, num_nack_pkts; + _Atomic sequence_number mi_seq_max; + _Atomic sequence_number mi_seq_max_rcvd; + _Atomic u32 num_nacks, num_nack_pkts; struct bitset mi_nacked; - sequence_number last_seqnr; + _Atomic sequence_number last_seqnr; u32 prev_rate; u32 curr_rate; @@ -52,9 +52,9 @@ struct ccontrol_state { int adjust_iter; unsigned long mi_start; unsigned long mi_end; - u32 mi_tx_npkts; - u32 mi_tx_npkts_monitored; - u32 total_tx_npkts; + _Atomic u32 mi_tx_npkts; + _Atomic u32 mi_tx_npkts_monitored; + _Atomic u32 total_tx_npkts; u32 rate_before_rcts; struct rct rcts[RCTS_INTERVALS]; diff --git a/doc/developers.md b/doc/developers.md index 4ac5f8b..c8a2dcc 100644 --- a/doc/developers.md +++ b/doc/developers.md @@ -1,14 +1,19 @@ # Development See the main readme for how to build Hercules from source. -When debugging or developing it may be desirable to run Hercules manually and -not via systemd, for example to attach a debugger. To do so, simply: -1. Start the monitor: `sudo ./hercules-monitor` -2. In a second shell, start the server: `sudo ./hercules-server` +- When debugging or developing it may be desirable to run Hercules manually and + not via systemd, for example to attach a debugger. To do so, simply: + + 1. Start the monitor: `sudo ./hercules-monitor` + 2. In a second shell, start the server: `sudo ./hercules-server` - It may be useful to uncomment some of the lines marked "for debugging" at the top of the Makefile. + +- You may catch some bugs with clang's static analyzer: + `scan-build make hercules-server`. + - The script `test.sh` is a very simple test utility. It will try 3 sets of transfers: a file, a directory, and 2 files concurrently. You can use it to sanity-check any code changes you make. @@ -18,6 +23,7 @@ not via systemd, for example to attach a debugger. To do so, simply: The script further relies on you having ssh keys set up for those two hosts. Depending on the network cards you may need to comment in the two lines with `ConfigureQueues = false`. + - The `xdpdump` tool () is useful for seeing packets received via XDP. Similar to `tcpdump`, but for XDP. diff --git a/frame_queue.h b/frame_queue.h index 1ce6604..856a4e9 100644 --- a/frame_queue.h +++ b/frame_queue.h @@ -22,8 +22,8 @@ struct frame_queue { // reduce the memory footprint by using 16 bit ints instead of full 64 bits u16 *addrs; - u64 prod; - u64 cons; + _Atomic u64 prod; + _Atomic u64 cons; u16 size; u16 index_mask; }; diff --git a/hercules.c b/hercules.c index 230facb..c9cda37 100644 --- a/hercules.c +++ b/hercules.c @@ -1898,7 +1898,7 @@ static char *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_id } #ifdef RANDOMIZE_FLOWID -static short flowIdCtr = 0; +static _Atomic short flowIdCtr = 0; #endif #ifdef RANDOMIZE_UNDERLAY_SRC static short src_port_ctr = 0; @@ -2572,7 +2572,7 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, /// PCC #define NACK_TRACE_SIZE (1024*1024) -static u32 nack_trace_count = 0; +static _Atomic u32 nack_trace_count = 0; static struct { long long sender_timestamp; long long receiver_timestamp; @@ -2592,7 +2592,7 @@ static void nack_trace_push(u64 timestamp, u32 nr) { } #define PCC_TRACE_SIZE (1024*1024) -static u32 pcc_trace_count = 0; +static _Atomic u32 pcc_trace_count = 0; static struct { u64 time; sequence_number range_start, range_end, mi_min, mi_max; diff --git a/hercules.h b/hercules.h index 7cd1e20..492e797 100644 --- a/hercules.h +++ b/hercules.h @@ -67,7 +67,7 @@ struct receiver_state_per_path { struct bitset seq_rcvd; sequence_number nack_end; sequence_number prev_nack_end; - u64 rx_npkts; + _Atomic u64 rx_npkts; }; // Information specific to the receiving side of a session @@ -101,7 +101,7 @@ struct receiver_state { u32 ack_nr; u64 next_nack_round_start; u64 next_ack_round_start; - u8 num_tracked_paths; + _Atomic u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; u16 src_port; // The UDP/SCION port to use when sending packets (LE) @@ -191,15 +191,15 @@ struct hercules_session { struct send_queue *send_queue; u64 last_pkt_sent; //< Used for HS retransmit interval - u64 last_pkt_rcvd; //< Used for timeout detection - u64 last_new_pkt_rcvd; //< If we only receive packets containing + _Atomic u64 last_pkt_rcvd; //< Used for timeout detection + _Atomic u64 last_new_pkt_rcvd; //< If we only receive packets containing // already-seen chunks for a while something is // probably wrong. (Only used by receiver) u64 last_path_update; u64 last_monitor_update; - size_t rx_npkts; // Number of sent/received packets (for stats) - size_t tx_npkts; + _Atomic size_t rx_npkts; // Number of sent/received packets (for stats) + _Atomic size_t tx_npkts; struct hercules_app_addr peer; //< UDP/SCION address of peer (big endian) u64 jobid; //< The monitor's ID for this job diff --git a/send_queue.c b/send_queue.c index a008557..382c6d9 100644 --- a/send_queue.c +++ b/send_queue.c @@ -76,7 +76,7 @@ bool send_queue_pop(struct send_queue *queue, struct send_queue_unit *unit) } // blocks if queue empty -void send_queue_pop_wait(struct send_queue *queue, struct send_queue_unit *unit, bool *block) +void send_queue_pop_wait(struct send_queue *queue, struct send_queue_unit *unit, _Atomic bool *block) { while(!send_queue_pop(queue, unit)) { if(block && !atomic_load(block)) { diff --git a/send_queue.h b/send_queue.h index 021bbf9..418f0af 100644 --- a/send_queue.h +++ b/send_queue.h @@ -36,8 +36,8 @@ _Static_assert(sizeof(struct send_queue_unit) == 64, "struct send_queue_unit sho struct send_queue { struct send_queue_unit *units; u32 size; - u32 head; - u32 tail; + _Atomic u32 head; + _Atomic u32 tail; void *units_base; }; @@ -60,6 +60,6 @@ bool send_queue_pop(struct send_queue *queue, struct send_queue_unit *unit); // Pops a send_queue_unit off the queue and fills it into *unit. // If the queue is empty and block is true, this function blocks until some send_queue_unit is available. // As soon as *block is false, send_queue_pop_wait stops blocking. -void send_queue_pop_wait(struct send_queue *queue, struct send_queue_unit *unit, bool *block); +void send_queue_pop_wait(struct send_queue *queue, struct send_queue_unit *unit, _Atomic bool *block); #endif //__HERCULES_SEND_QUEUE_H__ From cd72e37da6bf33ad4534fd8332f0e2aaca387198 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 15:22:45 +0100 Subject: [PATCH 086/112] add clangd and clang-format config files --- .clang-format | 6 ++++++ .clangd | 2 ++ gfal2-hercules/.clang-format | 0 gfal2-hercules/.gitignore | 2 ++ gfal2-hercules/compile_flags.txt | 6 ++++++ 5 files changed, 16 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 gfal2-hercules/.clang-format create mode 100644 gfal2-hercules/.gitignore create mode 100644 gfal2-hercules/compile_flags.txt diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..79d4e45 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +--- +BasedOnStyle: Google +UseTab: Always +IndentWidth: 4 +TabWidth: 4 +... diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..59b51ea --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +Diagnostics: + Suppress: atomic_op_needs_atomic diff --git a/gfal2-hercules/.clang-format b/gfal2-hercules/.clang-format new file mode 100644 index 0000000..e69de29 diff --git a/gfal2-hercules/.gitignore b/gfal2-hercules/.gitignore new file mode 100644 index 0000000..a18bcd3 --- /dev/null +++ b/gfal2-hercules/.gitignore @@ -0,0 +1,2 @@ +*.so +*.d diff --git a/gfal2-hercules/compile_flags.txt b/gfal2-hercules/compile_flags.txt new file mode 100644 index 0000000..ff8e52a --- /dev/null +++ b/gfal2-hercules/compile_flags.txt @@ -0,0 +1,6 @@ +-std=c99 +-Wall +-Wextra +-I/usr/local/include/gfal2 +-I/usr/include/glib-2.0 +-I/usr/lib64/glib-2.0/include From d0ee2d5c8ab33c959b289b21270a06b65e7d5eb7 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 15:06:34 +0100 Subject: [PATCH 087/112] update service files --- dist/hercules-monitor.service | 4 ++-- dist/hercules-server.service | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dist/hercules-monitor.service b/dist/hercules-monitor.service index 79f9e19..19c8400 100644 --- a/dist/hercules-monitor.service +++ b/dist/hercules-monitor.service @@ -3,8 +3,8 @@ Description=Hercules Monitor BindsTo=hercules-server.service [Service] -ExecStart=/home/marco/hercules-monitor +ExecStart=/usr/local/bin/hercules-monitor StandardOutput=syslog StandardError=syslog -WorkingDirectory=/home/marco +WorkingDirectory=/tmp/ TimeoutSec=5 diff --git a/dist/hercules-server.service b/dist/hercules-server.service index 4e1546f..8a259b1 100644 --- a/dist/hercules-server.service +++ b/dist/hercules-server.service @@ -4,8 +4,8 @@ BindsTo=hercules-monitor.service After=hercules-monitor.service [Service] -ExecStart=/home/marco/hercules-server +ExecStart=/usr/local/bin/hercules-server StandardOutput=syslog StandardError=syslog -WorkingDirectory=/home/marco +WorkingDirectory=/tmp/ TimeoutSec=5 From 283aaad2534b40120914356216319ff142eb7c20 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 15:10:56 +0100 Subject: [PATCH 088/112] add version field to header --- hercules.c | 29 ++++++++++++++++++++++++----- hercules.h | 5 +++++ packet.h | 1 + 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/hercules.c b/hercules.c index c9cda37..600be4e 100644 --- a/hercules.c +++ b/hercules.c @@ -851,6 +851,7 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, u8 flags sequence_number seqnr, const char *data, size_t n, size_t payloadlen) { struct hercules_header *hdr = (struct hercules_header *)rbudp_pkt; + hdr->version = HERCULES_HEADER_VERSION; hdr->chunk_idx = chunk_idx; hdr->path = path_idx; hdr->flags = flags; @@ -899,12 +900,21 @@ static bool rx_received_all(const struct receiver_state *rx_state, static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { + struct hercules_header *hdr = (struct hercules_header *)pkt; + if (length < sizeof(u8)) { + debug_printf("packet too short!"); + return false; + } + if (hdr->version != HERCULES_HEADER_VERSION) { + debug_printf("Hercules version mismatch: received %u, expected %u", + hdr->version, HERCULES_HEADER_VERSION); + return false; + } if(length < rbudp_headerlen + rx_state->chunklen) { debug_printf("packet too short: have %lu, expect %d", length, rbudp_headerlen + rx_state->chunklen ); return false; } - struct hercules_header *hdr = (struct hercules_header *)pkt; bool is_index_transfer = hdr->flags & PKT_FLAG_IS_INDEX; u32 chunk_idx = hdr->chunk_idx; @@ -3550,13 +3560,23 @@ static void events_p(void *arg) { .ia = scionaddrhdr->src_ia}; const size_t rbudp_len = len - (rbudp_pkt - buf); - if (rbudp_len < sizeof(u32)) { + if (rbudp_len < sizeof(u8)){ + debug_printf("Ignoring, length too short"); + continue; + } + u8 pkt_version = *rbudp_pkt; + if (pkt_version != HERCULES_HEADER_VERSION) { + debug_printf("Hercules version mismatch: received %u, expected %u", + pkt_version, HERCULES_HEADER_VERSION); + continue; + } + if (rbudp_len < rbudp_headerlen) { debug_printf("Ignoring, length too short"); continue; } - u32 chunk_idx; - memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); + struct hercules_header *h = (struct hercules_header *)rbudp_pkt; + u32 chunk_idx = h->chunk_idx; if (chunk_idx != UINT_MAX) { // Only data packets can have another value and we don't handle // them here @@ -3568,7 +3588,6 @@ static void events_p(void *arg) { debug_print_rbudp_pkt(rbudp_pkt, true); - struct hercules_header *h = (struct hercules_header *)rbudp_pkt; const char *pl = rbudp_pkt + rbudp_headerlen; struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; diff --git a/hercules.h b/hercules.h index 492e797..eddcadd 100644 --- a/hercules.h +++ b/hercules.h @@ -28,6 +28,11 @@ #include "packet.h" #include "errors.h" +// The version is included in the packet headers. +// We check whether the version of received packets matches ours. +// If you make incompatible changes to the headers or Hercules' behaviour, +// change this version to avoid incompatible servers interacting. +#define HERCULES_HEADER_VERSION 1 #define HERCULES_DEFAULT_CONFIG_PATH "hercules.conf" #define HERCULES_MAX_HEADERLEN 256 // NOTE: The maximum packet size is limited by the size of a single XDP frame diff --git a/packet.h b/packet.h index 82bef95..50bb2e6 100644 --- a/packet.h +++ b/packet.h @@ -101,6 +101,7 @@ struct scmp_message { // The header used by both control and data packets struct hercules_header { + __u8 version; __u32 chunk_idx; __u8 path; __u8 flags; From dbb5215fd9f044ee17a2bdd31f3626baada6c982 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 31 Oct 2024 15:56:25 +0100 Subject: [PATCH 089/112] look for config in /usr/local/etc --- README.md | 2 +- hercules.c | 17 +++++++++++++---- hercules.h | 5 ++++- monitor/config.go | 19 ++++++++++++++++--- monitor/monitor.go | 3 ++- 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d91dd2a..c24dce2 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ See the [developer guide](doc/developers.md). Depending on your performance requirements and your specific bottlenecks, the following configuration options may help improve performance: - On machines with multiple NUMA nodes, it may be beneficial to bind the Hercules server process to CPU cores "closer" to the network card. - To do so, install the `numactl` utility and adjust the file `/usr/local/lib/systemd/system/hercules-server.service` so it reads `ExecStart=/usr/bin/numactl -l --cpunodebind=netdev: -- /home/marco/hercules-server`, replacing `` with your network interface. + To do so, install the `numactl` utility and adjust the file `/usr/local/lib/systemd/system/hercules-server.service` so it reads `ExecStart=/usr/bin/numactl -l --cpunodebind=netdev: -- /usr/local/bin/hercules-server`, replacing `` with your network interface. - Setting the option `XDPZeroCopy = true` can substantially improve performance, but whether it is supported depends on the combination of network card and driver in your setup. diff --git a/hercules.c b/hercules.c index 600be4e..5610732 100644 --- a/hercules.c +++ b/hercules.c @@ -3875,15 +3875,24 @@ int main(int argc, char *argv[]) { } // Open and parse config file + // We use the command line option, if supplied, otherwise look for + // the files specified by HERCULES_CWD_CONFIG_PATH, + // and then HERCULES_DEFAULT_CONFIG_PATH. FILE *config_file; char errbuf[200]; if (config_path != NULL) { config_file = fopen(config_path, "r"); - debug_printf("Using config file %s", config_path); + fprintf(stderr, "Using config file %s\n", config_path); } else { - config_file = fopen(HERCULES_DEFAULT_CONFIG_PATH, "r"); - debug_printf("Using default config file %s", - HERCULES_DEFAULT_CONFIG_PATH); + char *config_paths[2] = {HERCULES_CWD_CONFIG_PATH, + HERCULES_DEFAULT_CONFIG_PATH}; + for (int p = 0; p < 2; p++) { + config_file = fopen(config_paths[p], "r"); + if (config_file) { + fprintf(stderr, "Using config file %s\n", config_paths[p]); + break; + } + } } if (!config_file) { fprintf(stderr, "Cannot open config file!\n"); diff --git a/hercules.h b/hercules.h index eddcadd..50e4a91 100644 --- a/hercules.h +++ b/hercules.h @@ -33,7 +33,10 @@ // If you make incompatible changes to the headers or Hercules' behaviour, // change this version to avoid incompatible servers interacting. #define HERCULES_HEADER_VERSION 1 -#define HERCULES_DEFAULT_CONFIG_PATH "hercules.conf" +// Default config file +#define HERCULES_DEFAULT_CONFIG_PATH "/usr/local/etc/hercules.conf" +// Config file in current working dir +#define HERCULES_CWD_CONFIG_PATH "hercules.conf" #define HERCULES_MAX_HEADERLEN 256 // NOTE: The maximum packet size is limited by the size of a single XDP frame // (page size - metadata overhead). This is around 3500, but the exact value diff --git a/monitor/config.go b/monitor/config.go index 3f52a62..d007b4f 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -121,11 +121,24 @@ const defaultMonitorHTTPS = "disabled" // Decode the config file and fill in any unspecified values with defaults. // Will exit if an error occours or a required value is not specified. -func readConfig(configFile string) (MonitorConfig, PathRules) { +func readConfig(cmdline string) (MonitorConfig, PathRules) { var config MonitorConfig - meta, err := toml.DecodeFile(configFile, &config) + var meta toml.MetaData + var err error + if cmdline != "" { + meta, err = toml.DecodeFile(cmdline, &config) + } else { + for _, c := range []string{cwdConfigPath, defaultConfigPath} { + meta, err = toml.DecodeFile(c, &config) + if err == nil || !os.IsNotExist(err) { + fmt.Printf("Using configuration file %v\n", c) + break + } + } + } + if err != nil { - fmt.Printf("Error reading configuration file (%v): %v\n", configFile, err) + fmt.Printf("Error reading configuration file: %v\n", err) os.Exit(1) } if len(meta.Undecoded()) > 0 { diff --git a/monitor/monitor.go b/monitor/monitor.go index ed01dd8..7b2c914 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -21,6 +21,7 @@ import "C" const HerculesMaxPktsize = C.HERCULES_MAX_PKTSIZE const defaultConfigPath = C.HERCULES_DEFAULT_CONFIG_PATH +const cwdConfigPath = C.HERCULES_CWD_CONFIG_PATH const defaultMonitorSocket = C.HERCULES_DEFAULT_MONITOR_SOCKET // Select paths and serialize headers for a given transfer @@ -110,7 +111,7 @@ var startupVersion string func main() { fmt.Printf("Starting Hercules monitor [%v]\n", startupVersion) var configFile string - flag.StringVar(&configFile, "c", defaultConfigPath, "Path to the monitor configuration file") + flag.StringVar(&configFile, "c", "", "Path to the configuration file") flag.Parse() config, pathRules = readConfig(configFile) From 86adc4e38828e7a527477af5920488251087650b Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 1 Nov 2024 13:02:55 +0100 Subject: [PATCH 090/112] make target to build packages --- .fpm | 6 ++++++ .gitignore | 4 ++++ Dockerfile | 9 +++++++++ Makefile | 47 ++++++++++++++++++++++++++++++++++++++++++----- README.md | 1 + doc/developers.md | 9 ++++++++- 6 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 .fpm diff --git a/.fpm b/.fpm new file mode 100644 index 0000000..9ba44af --- /dev/null +++ b/.fpm @@ -0,0 +1,6 @@ +-s dir +--name hercules-server +--description "Hercules file transfer server" +--url "https://github.com/netsec-ethz/hercules" +--maintainer "Network Security Group, ETH Zuerich" +-C pkgroot diff --git a/.gitignore b/.gitignore index 2700932..3c480d0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ hercules-monitor *.d monitor/monitor mockules/mockules +pkgroot +*.deb +*.rpm +*.tar diff --git a/Dockerfile b/Dockerfile index 9aef775..95acdbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,10 @@ FROM ubuntu:focal ARG UID=1001 ARG GID=1001 +# to avoid interactive timezone query +ENV TZ Europe/Zürich +ENV DEBIAN_FRONTEND noninteractive + # install cgo-related dependencies RUN set -eux; \ apt-get update; \ @@ -31,11 +35,16 @@ RUN set -eux; \ libpcap-dev \ gcc-multilib \ build-essential \ + ruby \ + rpm \ ; \ rm -rf /var/lib/apt/lists/* ENV PATH /usr/local/go/bin:$PATH +RUN gem install dotenv -v 2.8.1 +RUN gem install fpm + ENV GOLANG_VERSION 1.22.8 RUN set -eux; \ diff --git a/Makefile b/Makefile index 1026ee2..9628b15 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,10 @@ install: all install -d $(DESTDIR)$(PREFIX)/share/doc/hercules/ install hercules.conf.sample $(DESTDIR)$(PREFIX)/share/doc/hercules/ + install -d $(DESTDIR)$(PREFIX)/lib/systemd/system/ + install dist/hercules-monitor.service $(DESTDIR)$(PREFIX)/lib/systemd/system/ + install dist/hercules-server.service $(DESTDIR)$(PREFIX)/lib/systemd/system/ + install -d $(DESTDIR)$(PREFIX)/share/man/man1/ install doc/hercules-server.1 $(DESTDIR)$(PREFIX)/share/man/man1/ install doc/hercules-monitor.1 $(DESTDIR)$(PREFIX)/share/man/man1/ @@ -134,11 +138,6 @@ builder_image: @docker images | grep hercules-builder -q || \ docker build -t hercules-builder --build-arg UID=$(shell id -u) --build-arg GID=$(shell id -g) . -clean: - rm -rf $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_HCP) $(OBJS) $(DEPS) - rm -f hercules mockules/mockules - docker container rm -f hercules-builder || true - docker rmi hercules-builder || true MANFILES := $(wildcard doc/*.[157]) hcp/hcp.1 MDFILES := $(addsuffix .md,$(MANFILES)) @@ -150,4 +149,42 @@ MDFILES := $(addsuffix .md,$(MANFILES)) docs: $(MDFILES) +# Packages +# Relies on the fpm tool to build packages. +# More arguments to fpm are specified in the file .fpm +# The package is called hercules-server, because there is already +# one named hercules in Ubuntu's default repos. +PKG_VERSION ?= $(shell git describe --tags 2>/dev/null) + +.PHONY: packages pkg_deb pkg_rpm pkg_tar +packages: pkg_deb pkg_rpm pkg_tar +pkg_deb: + @$(if $(PKG_VERSION),,$(error PKG_VERSION not set and no git tag!)) + @echo Packaging version $(PKG_VERSION) + mkdir pkgroot + DESTDIR=pkgroot $(MAKE) install + fpm -t deb --version $(PKG_VERSION) + rm -rf pkgroot +pkg_rpm: + @$(if $(PKG_VERSION),,$(error PKG_VERSION not set and no git tag!)) + @echo Packaging version $(PKG_VERSION) + mkdir pkgroot + DESTDIR=pkgroot $(MAKE) install + fpm -t rpm --version $(PKG_VERSION) + rm -rf pkgroot +pkg_tar: + @$(if $(PKG_VERSION),,$(error PKG_VERSION not set and no git tag!)) + @echo Packaging version $(PKG_VERSION) + mkdir pkgroot + DESTDIR=pkgroot $(MAKE) install + fpm -t tar --version $(PKG_VERSION) + rm -rf pkgroot + +clean: + rm -rf $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_HCP) $(OBJS) $(DEPS) + rm -rf pkgroot *.deb *.rpm *.tar + rm -f hercules mockules/mockules + docker container rm -f hercules-builder || true + docker rmi hercules-builder || true + -include $(DEPS) diff --git a/README.md b/README.md index c24dce2..3f5d461 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ By default, this will install Hercules to `/usr/local/`. ## Debugging and Development See the [developer guide](doc/developers.md). +The file also contains instructions on how to build packages. ## Troubleshooting diff --git a/doc/developers.md b/doc/developers.md index c8a2dcc..1d5c7c1 100644 --- a/doc/developers.md +++ b/doc/developers.md @@ -27,8 +27,15 @@ See the main readme for how to build Hercules from source. - The `xdpdump` tool () is useful for seeing packets received via XDP. Similar to `tcpdump`, but for XDP. + +# Packaging + +The `fpm` tool is used to create packages. +You will need to install ruby and `gem install fpm`. +Then, to create `deb`, `rpm`, and `tar` files: `make packages` +(or `make docker_packages` to use the Docker environment) -## Docs +# Docs Documentation pertaining to the server is located in the `doc/` directory, and in `hcp/` for `hcp`. If you make changes to the manual files, run `make docs` to rebuild the From 1d0e0155ce43d8b9e536451abc9260d6ee851553 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 5 Nov 2024 11:01:18 +0100 Subject: [PATCH 091/112] add payload length option to hcp --- README.md | 2 +- hcp/hcp.1 | 6 ++++++ hcp/hcp.1.md | 8 ++++++++ hcp/hcp.go | 13 +++++++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3f5d461..2e1db77 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ The file also contains instructions on how to build packages. Hercules attempts to automatically pick the right packet size based on the MTU in the SCION path metadata and the sending interface. In some cases, however, this information is not accurate and the really supported MTU is smaller. - To work around this, you can manually specify the payload size to be used, e.g., by supplying the `TODO` option to `hcp`, or by specifying the payload on a per-destination basis in the configuration file. + To work around this, you can manually specify the payload size to be used, e.g., by supplying the `-l` option to `hcp`, or by specifying the payload length on a per-destination basis in the configuration file. ## Performance Configuration diff --git a/hcp/hcp.1 b/hcp/hcp.1 index 0f0f3e2..e529413 100644 --- a/hcp/hcp.1 +++ b/hcp/hcp.1 @@ -48,6 +48,12 @@ The argument is a go duration string. The default polling frequency is .Ar 1s , that is, poll every second. +.It Fl l Ar payload_length +Manually set the payload size to use for this transfer. +This is useful if Hercules' automatic selection does not work, for example, +if a path advertises a MTU larger than what it really supports. +Note that the packet length includes the headers in addition to the payload, +so the payload length must set to a value smaller than the MTU. .It Fl n Do not ask the server for the file's total size before submitting the transfer. With this option set, the progress bar and time estimates are not shown. diff --git a/hcp/hcp.1.md b/hcp/hcp.1.md index 8554256..eccaefd 100644 --- a/hcp/hcp.1.md +++ b/hcp/hcp.1.md @@ -51,6 +51,14 @@ The options are as follows: > *1s*, > that is, poll every second. +**-l** *payload\_length* + +> Manually set the payload size to use for this transfer. +> This is useful if Hercules' automatic selection does not work, for example, +> if a path advertises a MTU larger than what it really supports. +> Note that the packet length includes the headers in addition to the payload, +> so the payload length must set to a value smaller than the MTU. + **-n** > Do not ask the server for the file's total size before submitting the transfer. diff --git a/hcp/hcp.go b/hcp/hcp.go index 29b7fe6..e5fdda0 100644 --- a/hcp/hcp.go +++ b/hcp/hcp.go @@ -34,7 +34,7 @@ func main() { poll_interval := flag.Duration("i", time.Second*1, "Poll frequency") no_stat_file := flag.Bool("n", false, "Don't stat source file") show_version := flag.Bool("version", false, "Print version and exit") - // TODO payload size + payload_len := flag.Int("l", 0, "Manually set payload length") flag.Parse() @@ -72,7 +72,7 @@ func main() { signal.Notify(cancelChan, os.Kill) signal.Notify(cancelChan, os.Interrupt) - job_id, err := submit(src_api, src_path, dst_addr, dst_path) + job_id, err := submit(src_api, src_path, dst_addr, dst_path, *payload_len) if err != nil { fmt.Println(err) os.Exit(2) @@ -125,7 +125,9 @@ func main() { if info.state == C.SESSION_STATE_DONE { finished = true - bar.Finish() + if info.error == C.SESSION_ERROR_OK { + bar.Finish() + } bar.Exit() fmt.Println() if info.error != C.SESSION_ERROR_OK { @@ -137,8 +139,11 @@ func main() { } -func submit(src_api, src_path, dst_addr, dst_path string) (int, error) { +func submit(src_api, src_path, dst_addr, dst_path string, payload_len int) (int, error) { submit_url := fmt.Sprintf("http://%s/submit?file=%s&dest=%s&destfile=%s", src_api, src_path, dst_addr, dst_path) + if payload_len != 0 { + submit_url += fmt.Sprintf("&payloadlen=%d", payload_len) + } submit_response, err := http.Get(submit_url) if err != nil { return 0, err From 63960ceee83eb72a72c3a9f0187da8c81f7c4534 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 5 Nov 2024 11:40:24 +0100 Subject: [PATCH 092/112] add timeout to path selection --- monitor/pathstodestination.go | 57 +++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 9ba7371..8da8130 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "net" + "time" log "github.com/inconshreveable/log15" "github.com/scionproto/scion/pkg/snet" @@ -106,7 +107,7 @@ func (ptd *PathsToDestination) choosePaths() bool { maxPayloadlen := HerculesMaxPktsize for _, path := range ptd.paths { if !path.enabled { - continue; + continue } pathMTU := int(path.path.Metadata().MTU) underlayHeaderLen, scionHeaderLen := getPathHeaderlen(path.path) @@ -138,29 +139,45 @@ func (ptd *PathsToDestination) choosePaths() bool { func (ptd *PathsToDestination) chooseNewPaths(availablePaths []PathWithInterface) bool { updated := false - // pick paths - picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) + // Because this path selection takes too long when many paths are available + // (tens of seconds), we run it with a timeout and fall back to using the + // first few paths if it takes too long. + ch := make(chan int, 1) + timeout, cancel := context.WithTimeout(context.Background(), time.Second*1) + defer cancel() + var pathSet []PathWithInterface - disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) - maxRuleIdx := 0 // the highest index of a PathSpec that is used (to be minimized) - for i := ptd.dst.numPaths; i > 0; i-- { - picker.reset(i) - for picker.nextRuleSet() { // iterate through different choices of PathSpecs to use - if pathSet != nil && maxRuleIdx < picker.maxRuleIdx() { // ignore rule set, if path set with lower maxRuleIndex is known - continue // due to the iteration order, we cannot break here - } - for picker.nextPick() { // iterate through different choices of paths obeying the rules of the current set of PathSpecs - curDisjointness := picker.disjointnessScore() - if pathSet == nil || disjointness < curDisjointness { // maximize disjointness - disjointness = curDisjointness - maxRuleIdx = picker.maxRuleIdx() - pathSet = picker.getPaths() + go func() { // pick paths + picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) + disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) + maxRuleIdx := 0 // the highest index of a PathSpec that is used (to be minimized) + for i := ptd.dst.numPaths; i > 0; i-- { + picker.reset(i) + for picker.nextRuleSet() { // iterate through different choices of PathSpecs to use + if pathSet != nil && maxRuleIdx < picker.maxRuleIdx() { // ignore rule set, if path set with lower maxRuleIndex is known + continue // due to the iteration order, we cannot break here + } + for picker.nextPick() { // iterate through different choices of paths obeying the rules of the current set of PathSpecs + curDisjointness := picker.disjointnessScore() + if pathSet == nil || disjointness < curDisjointness { // maximize disjointness + disjointness = curDisjointness + maxRuleIdx = picker.maxRuleIdx() + pathSet = picker.getPaths() + } } } + if pathSet != nil { // if no path set of size i found, try with i-1 + break + } } - if pathSet != nil { // if no path set of size i found, try with i-1 - break - } + ch <- 1 + }() + + select { + case <-timeout.Done(): + log.Warn(fmt.Sprintf("[Destination %s] Path selection took too long! Using first few paths", ptd.dst.hostAddr.IA)) + pathSet = availablePaths + case <-ch: } log.Info(fmt.Sprintf("[Destination %s] using %d paths:", ptd.dst.hostAddr.IA, len(pathSet))) From ca5099f46b0e0b9731f7470321718f0133e0bb66 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 5 Nov 2024 14:36:23 +0100 Subject: [PATCH 093/112] add chroot option --- README.md | 4 +- doc/hercules.conf.5 | 19 +++++++- doc/hercules.conf.5.md | 21 ++++++++- hercules.c | 25 +++++++++++ hercules.conf.sample | 6 +++ hercules.h | 1 + monitor/config.go | 1 + sampleconf.toml | 99 ------------------------------------------ 8 files changed, 71 insertions(+), 105 deletions(-) delete mode 100644 sampleconf.toml diff --git a/README.md b/README.md index 2e1db77..525e1db 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ To get started, filling in the following information is required: In both cases you should replace the entire string, including the `replaceme//` markers. -While the above two settings are sufficient to run Hercules, we **strongly recommend** additionally setting the option `DropUser`. -Hercules will then drop its privileges to the specified user after startup and thus use the provided user's permissions for filesystem access. +While the above two settings are sufficient to run Hercules, we **strongly recommend** additionally setting the options `DropUser` and/or `ChrootDir`. +Hercules will then drop its privileges to the specified user after startup and thus use the provided user's permissions and path to restrict filesystem access. Hence, you should ensure the specified user has the appropriate read/write permissions on the paths you intend to send from/receive to. If you omit this option, Hercules will run as root. See [the configuration documentation](doc/hercules.conf.5.md#CAVEATS) for a discussion of the security implications. diff --git a/doc/hercules.conf.5 b/doc/hercules.conf.5 index 660196c..3418715 100644 --- a/doc/hercules.conf.5 +++ b/doc/hercules.conf.5 @@ -35,7 +35,9 @@ It is .Em strongly recommended to also set the .Ic DropUser -option, described below. +and/or +.Ic ChrootDir +options, described below. See .Sx CAVEATS for more information. @@ -77,6 +79,17 @@ See for more information. .Pp Example: DropUser = "_hercules" +.It Ic ChrootDir Ns = Ns Ar str +If specified, the server process' root directory and working directory will be +set to this path after startup. +Note that it is possible to escape from a chroot under some circumstances; +see +.Xr chroot 2 +for more information. +When setting this option, note that the file paths supplied by users will be +interpreted relative to this new directory. +.Pp +Example: DropUser = "/mnt/data/" .It Ic EnablePCC Ns = Ns Ar bool Setting this option to .Ar false @@ -260,4 +273,6 @@ file specified by the user and no authentication of the user is performed, a user may copy arbitrary system files to the destination server. To mitigate these issues, it is recommended that you set the .Ic DropUser -option described above. +and/or +.Ic ChrootDir +options described above. diff --git a/doc/hercules.conf.5.md b/doc/hercules.conf.5.md index 596864a..153e6f3 100644 --- a/doc/hercules.conf.5.md +++ b/doc/hercules.conf.5.md @@ -36,7 +36,9 @@ It is *strongly* recommended to also set the **DropUser** -option, described below. +and/or +**ChrootDir** +options, described below. See *CAVEATS* for more information. @@ -90,6 +92,19 @@ The following general configuration options are available: > Example: DropUser = "\_hercules" +**ChrootDir**=*str* + +> If specified, the server process' root directory and working directory will be +> set to this path after startup. +> Note that it is possible to escape from a chroot under some circumstances; +> see +> chroot(2) +> for more information. +> When setting this option, note that the file paths supplied by users will be +> interpreted relative to this new directory. + +> Example: DropUser = "/mnt/data/" + **EnablePCC**=*bool* > Setting this option to @@ -293,6 +308,8 @@ file specified by the user and no authentication of the user is performed, a user may copy arbitrary system files to the destination server. To mitigate these issues, it is recommended that you set the **DropUser** -option described above. +and/or +**ChrootDir** +options described above. Void Linux - October 29, 2024 diff --git a/hercules.c b/hercules.c index 5610732..19f0550 100644 --- a/hercules.c +++ b/hercules.c @@ -3781,6 +3781,20 @@ void hercules_main(struct hercules_server *server) { exit(1); } + // Chroot + if (server->config.chroot_dir){ + ret = chroot(server->config.chroot_dir); + if (ret != 0) { + fprintf(stderr, "Error in chroot\n"); + exit(1); + } + ret = chdir("/"); + if (ret != 0) { + fprintf(stderr, "Error changing to chroot dir\n"); + exit(1); + } + } + // Drop privileges ret = setgid(server->config.drop_gid); if (ret != 0) { @@ -3946,6 +3960,17 @@ int main(int argc, char *argv[]) { "not secure!\n" "See the documentation for more information.\n"); } + + toml_datum_t chroot_dir = toml_string_in(conf, "ChrootDir"); + if (chroot_dir.ok) { + config.chroot_dir = chroot_dir.u.s; + } else { + if (toml_key_exists(conf, "ChrootDir")) { + fprintf(stderr, "Error parsing ChrootDir\n"); + exit(1); + } + } + // Listening address toml_datum_t listen_addr = toml_string_in(conf, "ListenAddress"); if (!listen_addr.ok) { diff --git a/hercules.conf.sample b/hercules.conf.sample index 2367108..40ceeff 100644 --- a/hercules.conf.sample +++ b/hercules.conf.sample @@ -23,6 +23,12 @@ MonitorHTTPS = "disabled" # Drop privileges to the specified user after startup DropUser = "_hercules" +# Chroot to the specified path after startup +# If set, the working directory will also be set to this path. +# Note that this means that the file paths supplied by users when submitting +# transfers will be interpreted relative to this directory. +ChrootDir = "/mnt/data/hercules" + # Path to the server's certificate and key for TLS TLSCert = "cert.pem" TLSKey = "key.pem" diff --git a/hercules.h b/hercules.h index 50e4a91..636fb7f 100644 --- a/hercules.h +++ b/hercules.h @@ -234,6 +234,7 @@ struct hercules_config { char *server_socket; uid_t drop_uid; gid_t drop_gid; + char *chroot_dir; u32 xdp_flags; int xdp_mode; int queue; diff --git a/monitor/config.go b/monitor/config.go index d007b4f..bff9333 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -70,6 +70,7 @@ type MonitorConfig struct { // The following are not used by the monitor, they are listed here for completeness ServerSocket string DropUser string + ChrootDir string XDPZeroCopy bool Queue int ConfigureQueues bool diff --git a/sampleconf.toml b/sampleconf.toml deleted file mode 100644 index 9c34a36..0000000 --- a/sampleconf.toml +++ /dev/null @@ -1,99 +0,0 @@ -# Sample config for Hercules monitor - -# By default, up to `DefaultNumPaths` paths will be used. -DefaultNumPaths = 1 - -# Path to the monitor's unix socket -MonitorSocket = "var/run/herculesmon.sock" -# -# Path to the server's unix socket -ServerSocket = "var/run/hercules.sock" - -# SCION address the Hercules server should listen on -ListenAddress = "17-ffaa:1:fe2,192.168.10.141:8000" - -# Listenting address for the monitor (HTTP) -# Set to "disabled" to disable -MonitorHTTP = ":8000" - -# Listening address for the monitor (HTTPS) -# Set to "disabled" to disable -MonitorHTTPS = "disabled" - -# Drop privileges to the specified user after startup -DropUser = "_hercules" - -# Path to the server's certificate and key for TLS -TLSCert = "cert.pem" -TLSKey = "key.pem" - -# Paths to certificates used for validation of TLS client certificates -ClientCACerts = [ -"cert.pem", -] - -# Network interfaces to use for Hercules -Interfaces = [ -"eth0", -] - -# If the NIC/drivers support XDP in zerocopy mode, enabling it -# will improve performance. -XDPZeroCopy = true - -# Specify the NIC RX queue on which to receive packets -Queue = 0 - -# For Hercules to receive traffic, packets must be redirected to the queue -# specified above. Hercules will try to configure this automatically, but this -# behaviour can be overridden, e.g. if you wish to set custom rules or automatic -# configuration fails. If you set this to false, you must manually ensure -# packets end up in the right queue. -ConfigureQueues = false - -# Disabling congestion control is possible, but probably a bad idea -EnablePCC = false - -# This sets a sending rate limit (in pps) -# that applies to transfers individually -RateLimit = 100000 - -# The number of RX/TX worker threads to use -NumThreads = 2 - -# The number and choice of paths can be overridden on a destination-host -# or destination-AS basis. In case both an AS and Host rule match, the Host -# rule takes precedence. - -# This Host rule specifies that, for the host 17-ffaa:1:fe2,1.1.1.1, -# - Transfers may use up to 42 paths -# - The paths must contain either the AS-interface sequence -# 17-f:f:f 1 > 17:f:f:a 2 -# OR 1-f:0:0 22 -[[DestinationHosts]] -HostAddr = "17-ffa:1:fe2,1.1.1.1" -NumPaths = 42 -PathSpec = [ -["17-f:f:f 1", "17-f:f:a 2"], -["1-f:0:0 22"], -] - -# In this case the number of paths is set to 2, but the exact set of paths -# is left unspecified. -# We additionally override automatic MTU selection based on SCION path metadata -# and use a payload length of 1000B for all transfers to this host. -[[DestinationHosts]] -HostAddr = "18-a:b:c,2.2.2.2" -NumPaths = 2 -Payloadlen = 1000 - - -# Similarly, but for an entire destination AS instead of a specific host -[[DestinationASes]] -IA = "17-a:b:c" -NumPaths = 2 - -# Specify mapping of certificates to system users and groups to use for -# file permission checks. -[UserMap] -"O=Internet Widgits Pty Ltd,ST=Some-State,C=AU" = {User = "marco", Group = "marco"} From 303230347334d6f90cae621735ed1ee2fade4305 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 6 Nov 2024 10:44:22 +0100 Subject: [PATCH 094/112] improve error messages --- hercules.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/hercules.c b/hercules.c index 19f0550..5c46732 100644 --- a/hercules.c +++ b/hercules.c @@ -1191,7 +1191,8 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { f = open((char *)entry->path, O_RDWR); } if (f == -1) { - fprintf(stderr, "Error opening %s\n", (char *)entry->path); + fprintf(stderr, "Error opening %s: %s\n", (char *)entry->path, + strerror(errno)); encountered_err = true; break; } @@ -1236,7 +1237,7 @@ static char *rx_mmap(char *index, size_t index_size, size_t total_filesize) { fprintf(stderr, "!> Directory already exists: %s\n", (char *)entry->path); } else { - debug_printf("mkdir err"); + debug_printf("mkdir err: %s", strerror(errno)); encountered_err = true; break; } @@ -2394,7 +2395,7 @@ static char *prepare_dir_index(char *fname, u64 *index_size_o, char *fts_arg[2] = {fname, NULL}; fts = fts_open(fts_arg, FTS_PHYSICAL, NULL); // Don't follow symlinks if (fts == NULL) { - fprintf(stderr, "Error opening %s\n", fname); + fprintf(stderr, "Error opening %s: %s\n", fname, strerror(errno)); return NULL; } @@ -2545,7 +2546,8 @@ static char *tx_mmap(char *fname, char *dstname, size_t *filesize, if (entry->type == INDEX_TYPE_FILE) { int f = open((char *)entry->path, O_RDONLY); if (f == -1) { - fprintf(stderr, "Error opening %s\n", (char *)entry->path); + fprintf(stderr, "Error opening %s: %s\n", (char *)entry->path, + strerror(errno)); encountered_err = true; break; } @@ -3777,7 +3779,7 @@ void hercules_main(struct hercules_server *server) { int ret = xdp_setup(server); if (ret != 0){ - fprintf(stderr, "Error in XDP setup!\n"); + fprintf(stderr, "Error in XDP setup!\n%s\n", strerror(errno)); exit(1); } @@ -3785,12 +3787,12 @@ void hercules_main(struct hercules_server *server) { if (server->config.chroot_dir){ ret = chroot(server->config.chroot_dir); if (ret != 0) { - fprintf(stderr, "Error in chroot\n"); + fprintf(stderr, "Error in chroot\n%s\n", strerror(errno)); exit(1); } ret = chdir("/"); if (ret != 0) { - fprintf(stderr, "Error changing to chroot dir\n"); + fprintf(stderr, "Error changing to chroot dir\n%s\n", strerror(errno)); exit(1); } } @@ -3798,12 +3800,12 @@ void hercules_main(struct hercules_server *server) { // Drop privileges ret = setgid(server->config.drop_gid); if (ret != 0) { - fprintf(stderr, "Error in setgid\n"); + fprintf(stderr, "Error in setgid\n%s\n", strerror(errno)); exit(1); } ret = setuid(server->config.drop_uid); if (ret != 0) { - fprintf(stderr, "Error in setuid\n"); + fprintf(stderr, "Error in setuid\n%s\n", strerror(errno)); exit(1); } // Start the NACK sender thread @@ -3955,10 +3957,6 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Error parsing DropUser\n"); exit(1); } - fprintf(stderr, - "WARNING: DropUser config option not set. Running Hercules as root is " - "not secure!\n" - "See the documentation for more information.\n"); } toml_datum_t chroot_dir = toml_string_in(conf, "ChrootDir"); @@ -3971,6 +3969,13 @@ int main(int argc, char *argv[]) { } } + if (config.drop_uid == 0 && config.chroot_dir == NULL) { + fprintf(stderr, + "WARNING: DropUser or ChrootDir config option not set." + "Running Hercules as root is not secure!\n" + "See the documentation for more information.\n"); + } + // Listening address toml_datum_t listen_addr = toml_string_in(conf, "ListenAddress"); if (!listen_addr.ok) { @@ -4119,12 +4124,12 @@ int main(int argc, char *argv[]) { act.sa_flags = SA_RESETHAND; ret = sigaction(SIGINT, &act, NULL); if (ret == -1) { - fprintf(stderr, "Error registering signal handler\n"); + fprintf(stderr, "Error registering signal handler\n%s\n", strerror(errno)); exit(1); } ret = sigaction(SIGTERM, &act, NULL); if (ret == -1) { - fprintf(stderr, "Error registering signal handler\n"); + fprintf(stderr, "Error registering signal handler\n%s\n", strerror(errno)); exit(1); } From 87dde8af609e106697e7e030a834de9392c149fd Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 6 Nov 2024 11:00:35 +0100 Subject: [PATCH 095/112] Revert "add version field to header" This reverts commit 5b50898564dedeaecdaa28d5f5404b2caeceab49. --- hercules.c | 29 +++++------------------------ hercules.h | 5 ----- packet.h | 1 - 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/hercules.c b/hercules.c index 5c46732..63ff49e 100644 --- a/hercules.c +++ b/hercules.c @@ -851,7 +851,6 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, u8 flags sequence_number seqnr, const char *data, size_t n, size_t payloadlen) { struct hercules_header *hdr = (struct hercules_header *)rbudp_pkt; - hdr->version = HERCULES_HEADER_VERSION; hdr->chunk_idx = chunk_idx; hdr->path = path_idx; hdr->flags = flags; @@ -900,21 +899,12 @@ static bool rx_received_all(const struct receiver_state *rx_state, static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) { - struct hercules_header *hdr = (struct hercules_header *)pkt; - if (length < sizeof(u8)) { - debug_printf("packet too short!"); - return false; - } - if (hdr->version != HERCULES_HEADER_VERSION) { - debug_printf("Hercules version mismatch: received %u, expected %u", - hdr->version, HERCULES_HEADER_VERSION); - return false; - } if(length < rbudp_headerlen + rx_state->chunklen) { debug_printf("packet too short: have %lu, expect %d", length, rbudp_headerlen + rx_state->chunklen ); return false; } + struct hercules_header *hdr = (struct hercules_header *)pkt; bool is_index_transfer = hdr->flags & PKT_FLAG_IS_INDEX; u32 chunk_idx = hdr->chunk_idx; @@ -3562,23 +3552,13 @@ static void events_p(void *arg) { .ia = scionaddrhdr->src_ia}; const size_t rbudp_len = len - (rbudp_pkt - buf); - if (rbudp_len < sizeof(u8)){ - debug_printf("Ignoring, length too short"); - continue; - } - u8 pkt_version = *rbudp_pkt; - if (pkt_version != HERCULES_HEADER_VERSION) { - debug_printf("Hercules version mismatch: received %u, expected %u", - pkt_version, HERCULES_HEADER_VERSION); - continue; - } - if (rbudp_len < rbudp_headerlen) { + if (rbudp_len < sizeof(u32)) { debug_printf("Ignoring, length too short"); continue; } - struct hercules_header *h = (struct hercules_header *)rbudp_pkt; - u32 chunk_idx = h->chunk_idx; + u32 chunk_idx; + memcpy(&chunk_idx, rbudp_pkt, sizeof(u32)); if (chunk_idx != UINT_MAX) { // Only data packets can have another value and we don't handle // them here @@ -3590,6 +3570,7 @@ static void events_p(void *arg) { debug_print_rbudp_pkt(rbudp_pkt, true); + struct hercules_header *h = (struct hercules_header *)rbudp_pkt; const char *pl = rbudp_pkt + rbudp_headerlen; struct hercules_control_packet *cp = (struct hercules_control_packet *)pl; diff --git a/hercules.h b/hercules.h index 636fb7f..7fe047d 100644 --- a/hercules.h +++ b/hercules.h @@ -28,11 +28,6 @@ #include "packet.h" #include "errors.h" -// The version is included in the packet headers. -// We check whether the version of received packets matches ours. -// If you make incompatible changes to the headers or Hercules' behaviour, -// change this version to avoid incompatible servers interacting. -#define HERCULES_HEADER_VERSION 1 // Default config file #define HERCULES_DEFAULT_CONFIG_PATH "/usr/local/etc/hercules.conf" // Config file in current working dir diff --git a/packet.h b/packet.h index 50bb2e6..82bef95 100644 --- a/packet.h +++ b/packet.h @@ -101,7 +101,6 @@ struct scmp_message { // The header used by both control and data packets struct hercules_header { - __u8 version; __u32 chunk_idx; __u8 path; __u8 flags; From 13cc04ce6c388d778a5636346b7bf8efdfa27191 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 6 Nov 2024 12:43:24 +0100 Subject: [PATCH 096/112] add TxOnly and RxOnly options to use fewer threads --- README.md | 2 + doc/hercules.conf.5 | 8 ++++ doc/hercules.conf.5.md | 12 ++++++ hcp/hcp.go | 1 + hercules.c | 91 ++++++++++++++++++++++++++++++------------ hercules.conf.sample | 6 +++ hercules.h | 2 + monitor/config.go | 2 + 8 files changed, 98 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 525e1db..f2d7c68 100644 --- a/README.md +++ b/README.md @@ -190,3 +190,5 @@ Depending on your performance requirements and your specific bottlenecks, the fo - Increasing the number of worker threads via the option `NumThreads` can also improve performance. +- Especially on machines with few CPU cores the options `TxOnly` and `RxOnly` will improve performance. + diff --git a/doc/hercules.conf.5 b/doc/hercules.conf.5 index 3418715..e368797 100644 --- a/doc/hercules.conf.5 +++ b/doc/hercules.conf.5 @@ -116,6 +116,14 @@ Hercules spawns other threads, too, so this is the total number of threads used by Hercules. The default value is .Ar 1 . +.It Ic RxOnly Ns = Ns Ar bool +Run the server in receive-only mode, do not start the TX threads. +The default value is +.Ar false . +.It Ic TxOnly Ns = Ns Ar bool +Run the server in send-only mode, do not start the RX threads. +The default value is +.Ar false . .It Ic XDPZeroCopy Ns = Ns Ar bool If your combination of NIC/drivers supports XDP in zero-copy mode, enabling it here will likely improve performance. diff --git a/doc/hercules.conf.5.md b/doc/hercules.conf.5.md index 153e6f3..cb1d56f 100644 --- a/doc/hercules.conf.5.md +++ b/doc/hercules.conf.5.md @@ -137,6 +137,18 @@ The following general configuration options are available: > The default value is > *1*. +**RxOnly**=*bool* + +> Run the server in receive-only mode, do not start the TX threads. +> The default value is +> *false*. + +**TxOnly**=*bool* + +> Run the server in send-only mode, do not start the RX threads. +> The default value is +> *false*. + **XDPZeroCopy**=*bool* > If your combination of NIC/drivers supports XDP in zero-copy mode, diff --git a/hcp/hcp.go b/hcp/hcp.go index e5fdda0..741683a 100644 --- a/hcp/hcp.go +++ b/hcp/hcp.go @@ -98,6 +98,7 @@ func main() { case <-cancelChan: fmt.Println("Cancelling transfer, C-c again to quit without waiting") cancel(src_api, job_id) + bar.Describe("Waiting for server to confirm cancellation") signal.Reset() default: } diff --git a/hercules.c b/hercules.c index 63ff49e..a5e062f 100644 --- a/hercules.c +++ b/hercules.c @@ -3789,41 +3789,56 @@ void hercules_main(struct hercules_server *server) { fprintf(stderr, "Error in setuid\n%s\n", strerror(errno)); exit(1); } - // Start the NACK sender thread - debug_printf("starting NACK trickle thread"); - pthread_t trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); - // Start the ACK sender thread - debug_printf("starting ACK trickle thread"); - pthread_t trickle_acks = start_thread(NULL, rx_trickle_acks, server); - - - // Start the RX worker threads + pthread_t trickle_nacks; + pthread_t trickle_acks; pthread_t rx_workers[server->config.n_threads]; - for (int i = 0; i < server->config.n_threads; i++) { - debug_printf("starting thread rx_p %d", i); - rx_workers[i] = start_thread(NULL, rx_p, server->worker_args[i]); + if (!server->config.tx_only) { + // Start the NACK sender thread + debug_printf("starting NACK trickle thread"); + trickle_nacks = start_thread(NULL, rx_trickle_nacks, server); + + // Start the ACK sender thread + debug_printf("starting ACK trickle thread"); + trickle_acks = start_thread(NULL, rx_trickle_acks, server); + + // Start the RX worker threads + for (int i = 0; i < server->config.n_threads; i++) { + debug_printf("starting thread rx_p %d", i); + rx_workers[i] = start_thread(NULL, rx_p, server->worker_args[i]); + } } - // Start the TX worker threads + pthread_t tx_p_thread; pthread_t tx_workers[server->config.n_threads]; - for (int i = 0; i < server->config.n_threads; i++) { - debug_printf("starting thread tx_send_p %d", i); - tx_workers[i] = start_thread(NULL, tx_send_p, server->worker_args[i]); - } + if (!server->config.rx_only) { + // Start the TX worker threads + for (int i = 0; i < server->config.n_threads; i++) { + debug_printf("starting thread tx_send_p %d", i); + tx_workers[i] = start_thread(NULL, tx_send_p, server->worker_args[i]); + } - // Start the TX scheduler thread - debug_printf("starting thread tx_p"); - pthread_t tx_p_thread = start_thread(NULL, tx_p, server); + // Start the TX scheduler thread + debug_printf("starting thread tx_p"); + tx_p_thread = start_thread(NULL, tx_p, server); + } events_p(server); - join_thread(server, trickle_acks); - join_thread(server, trickle_nacks); - join_thread(server, tx_p_thread); - for (int i = 0; i < server->config.n_threads; i++){ - join_thread(server, rx_workers[i]); - join_thread(server, tx_workers[i]); + if (!server->config.tx_only) { + join_thread(server, trickle_acks); + join_thread(server, trickle_nacks); + } + if (!server->config.rx_only) { + join_thread(server, tx_p_thread); + } + for (int i = 0; i < server->config.n_threads; i++) { + if (!server->config.tx_only) { + join_thread(server, rx_workers[i]); + } + if (!server->config.rx_only) { + join_thread(server, tx_workers[i]); + } } xdp_teardown(server); @@ -4026,6 +4041,30 @@ int main(int argc, char *argv[]) { } } + // RX/TX only + toml_datum_t tx_only = toml_bool_in(conf, "TxOnly"); + if (tx_only.ok) { + config.tx_only = tx_only.u.b; + } else { + if (toml_key_exists(conf, "TxOnly")) { + fprintf(stderr, "Error parsing TxOnly\n"); + exit(1); + } + } + toml_datum_t rx_only = toml_bool_in(conf, "RxOnly"); + if (rx_only.ok) { + config.rx_only = rx_only.u.b; + } else { + if (toml_key_exists(conf, "RxOnly")) { + fprintf(stderr, "Error parsing RxOnly\n"); + exit(1); + } + } + if (config.tx_only && config.rx_only) { + fprintf(stderr, "Error: Both TxOnly and RxOnly set"); + exit(1); + } + // Worker threads toml_datum_t nthreads = toml_int_in(conf, "NumThreads"); if (nthreads.ok) { diff --git a/hercules.conf.sample b/hercules.conf.sample index 40ceeff..65ba635 100644 --- a/hercules.conf.sample +++ b/hercules.conf.sample @@ -67,6 +67,12 @@ RateLimit = 100000 # The number of RX/TX worker threads to use NumThreads = 2 +# Run the server in receive-only mode, do not start the TX threads. +RxOnly = true + +# Run the server in send-only mode, do not start the RX threads. +TxOnly = true + # The number and choice of paths can be overridden on a destination-host # or destination-AS basis. In case both an AS and Host rule match, the Host # rule takes precedence. diff --git a/hercules.h b/hercules.h index 7fe047d..7bb60ef 100644 --- a/hercules.h +++ b/hercules.h @@ -236,6 +236,8 @@ struct hercules_config { bool configure_queues; bool enable_pcc; int rate_limit; // Sending rate limit, only used when PCC is enabled + bool tx_only; // Run in send-only mode, do not start RX threads. + bool rx_only; // Run in receive-only mode, do not start TX threads. int n_threads; // Number of RX/TX worker threads struct hercules_app_addr local_addr; u16 port_min; // Lowest port on which to accept packets (in HOST diff --git a/monitor/config.go b/monitor/config.go index bff9333..46caa87 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -75,6 +75,8 @@ type MonitorConfig struct { Queue int ConfigureQueues bool EnablePCC bool + TxOnly bool + RxOnly bool RateLimit int NumThreads int } From 9c2dac642a7668f78222121cc8e9a4dd87bc1639 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 6 Nov 2024 13:23:22 +0100 Subject: [PATCH 097/112] small fix for pathset timeout --- monitor/pathstodestination.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/monitor/pathstodestination.go b/monitor/pathstodestination.go index 8da8130..3b607c0 100644 --- a/monitor/pathstodestination.go +++ b/monitor/pathstodestination.go @@ -146,7 +146,7 @@ func (ptd *PathsToDestination) chooseNewPaths(availablePaths []PathWithInterface timeout, cancel := context.WithTimeout(context.Background(), time.Second*1) defer cancel() - var pathSet []PathWithInterface + var computedPathSet []PathWithInterface go func() { // pick paths picker := makePathPicker(ptd.dst.pathSpec, availablePaths, ptd.dst.numPaths) disjointness := 0 // negative number denoting how many network interfaces are shared among paths (to be maximized) @@ -154,30 +154,32 @@ func (ptd *PathsToDestination) chooseNewPaths(availablePaths []PathWithInterface for i := ptd.dst.numPaths; i > 0; i-- { picker.reset(i) for picker.nextRuleSet() { // iterate through different choices of PathSpecs to use - if pathSet != nil && maxRuleIdx < picker.maxRuleIdx() { // ignore rule set, if path set with lower maxRuleIndex is known + if computedPathSet != nil && maxRuleIdx < picker.maxRuleIdx() { // ignore rule set, if path set with lower maxRuleIndex is known continue // due to the iteration order, we cannot break here } for picker.nextPick() { // iterate through different choices of paths obeying the rules of the current set of PathSpecs curDisjointness := picker.disjointnessScore() - if pathSet == nil || disjointness < curDisjointness { // maximize disjointness + if computedPathSet == nil || disjointness < curDisjointness { // maximize disjointness disjointness = curDisjointness maxRuleIdx = picker.maxRuleIdx() - pathSet = picker.getPaths() + computedPathSet = picker.getPaths() } } } - if pathSet != nil { // if no path set of size i found, try with i-1 + if computedPathSet != nil { // if no path set of size i found, try with i-1 break } } ch <- 1 }() + var pathSet []PathWithInterface select { case <-timeout.Done(): log.Warn(fmt.Sprintf("[Destination %s] Path selection took too long! Using first few paths", ptd.dst.hostAddr.IA)) - pathSet = availablePaths + pathSet = availablePaths[:min(len(availablePaths), ptd.dst.numPaths)] case <-ch: + pathSet = computedPathSet } log.Info(fmt.Sprintf("[Destination %s] using %d paths:", ptd.dst.hostAddr.IA, len(pathSet))) From e37bcffc2c6e46c4a0f7a9c4dd5d6bfcffdd581d Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 8 Nov 2024 09:35:17 +0100 Subject: [PATCH 098/112] small fix in makefile, service file --- Makefile | 11 ++++++++--- dist/hercules-server.service | 1 + hercules.c | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 9628b15..f8665f7 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ bpf/src/libbpf.a: else \ cd bpf/src && $(MAKE) all OBJDIR=.; \ mkdir -p build; \ - cd bpf/src && $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ + $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ fi tomlc99/libtoml.a: @@ -122,7 +122,7 @@ tomlc99/libtoml.a: fi -.PHONY: builder builder_image clean +.PHONY: builder builder_image # mockules: builder mockules/main.go mockules/network.go # docker exec -w /`basename $(PWD)`/mockules hercules-builder go build @@ -180,11 +180,16 @@ pkg_tar: fpm -t tar --version $(PKG_VERSION) rm -rf pkgroot -clean: +.PHONY: clean clean-small +# Clean files only, but don't remove the docker container. +clean-small: rm -rf $(TARGET_MONITOR) $(TARGET_SERVER) $(TARGET_HCP) $(OBJS) $(DEPS) rm -rf pkgroot *.deb *.rpm *.tar + +clean: clean-small rm -f hercules mockules/mockules docker container rm -f hercules-builder || true docker rmi hercules-builder || true + -include $(DEPS) diff --git a/dist/hercules-server.service b/dist/hercules-server.service index 8a259b1..b85b8d5 100644 --- a/dist/hercules-server.service +++ b/dist/hercules-server.service @@ -9,3 +9,4 @@ StandardOutput=syslog StandardError=syslog WorkingDirectory=/tmp/ TimeoutSec=5 +LimitCORE=infinity diff --git a/hercules.c b/hercules.c index a5e062f..dec48c0 100644 --- a/hercules.c +++ b/hercules.c @@ -569,11 +569,13 @@ static u8 parse_scmp_packet(const struct scmp_message *scmp, size_t length, break; case SCMP_EXT_IF_DOWN: pkt = (const char *)scmp->msg.ext_down.offending_packet; + debug_printf("extifdown: src ia 0x%llx", scmp->msg.ext_down.ia); offset += offsetof(struct scmp_message, msg.ext_down.offending_packet); break; case SCMP_INT_CONN_DOWN: pkt = (const char *)scmp->msg.int_down.offending_packet; + debug_printf("intdown: src ia 0x%llx", scmp->msg.int_down.ia); offset += offsetof(struct scmp_message, msg.int_down.offending_packet); break; @@ -747,7 +749,7 @@ static const char *parse_pkt(const struct hercules_server *server, *scmp_offending_path_o = parse_scmp_packet(scmp_msg, length - next_offset, scmp_offending_dst_port_o); #else - debug_printf("Received SCMP error, ignoring"); + /* debug_printf("Received SCMP error, ignoring"); */ #endif } else { debug_printf("unknown SCION L4: %u", next_header); @@ -1902,7 +1904,7 @@ static char *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_id static _Atomic short flowIdCtr = 0; #endif #ifdef RANDOMIZE_UNDERLAY_SRC -static short src_port_ctr = 0; +static _Atomic short src_port_ctr = 0; #endif static inline void tx_handle_send_queue_unit_for_iface( From 47a0b433318e29361f26cedb59fee2e9b83c9ffd Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 13 Nov 2024 13:03:17 +0100 Subject: [PATCH 099/112] looping acks For large files and with many lost packets, the number of ACK packets reuquired to convey all ACK ranges can grow too large, leading to the sender receiving more ACKs than it can process. Change ACK sending as follows: Acks are still sent at a regular interval, but only up to 5 packets at once. Acks sent in the next interval pick up where the previous one left off, so acks now "loop" over multiple intervals. --- bpf_prgms.s | 2 +- hercules.c | 30 ++++++++++++++++++++++-------- hercules.h | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/bpf_prgms.s b/bpf_prgms.s index 7ce3e1a..b5133fb 100644 --- a/bpf_prgms.s +++ b/bpf_prgms.s @@ -1,7 +1,7 @@ .section ".rodata" -# load bpf_prgm_redirect_userspace 88fc5453564d43b556649eee52e3239a +# load bpf_prgm_redirect_userspace a658699d4009ed41f0bfbb8f2cf768c7 .globl bpf_prgm_redirect_userspace .type bpf_prgm_redirect_userspace, STT_OBJECT .globl bpf_prgm_redirect_userspace_size diff --git a/hercules.c b/hercules.c index dec48c0..b23021b 100644 --- a/hercules.c +++ b/hercules.c @@ -1536,13 +1536,27 @@ static void rx_send_acks(struct hercules_server *server, struct receiver_state * const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); // send an empty ACK to keep connection alive until first packet arrives - u32 curr = fill_ack_pkt(rx_state, 0, &control_pkt.payload.ack, max_entries, is_index_transfer); - send_control_pkt(server, rx_state->session, &control_pkt, &path, rx_state->src_port, is_index_transfer); - for(; curr < rx_state->total_chunks;) { - curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries, is_index_transfer); - if(control_pkt.payload.ack.num_acks == 0) break; - send_control_pkt(server, rx_state->session, &control_pkt, &path, rx_state->src_port, is_index_transfer); - } + debug_printf("starting ack at %u", rx_state->next_chunk_to_ack); + u32 curr = + fill_ack_pkt(rx_state, rx_state->next_chunk_to_ack, + &control_pkt.payload.ack, max_entries, is_index_transfer); + send_control_pkt(server, rx_state->session, &control_pkt, &path, + rx_state->src_port, is_index_transfer); + unsigned pkts = 1; + for (; curr < rx_state->total_chunks && pkts < 5;) { + curr = fill_ack_pkt(rx_state, curr, &control_pkt.payload.ack, max_entries, + is_index_transfer); + if (control_pkt.payload.ack.num_acks == 0) break; + send_control_pkt(server, rx_state->session, &control_pkt, &path, + rx_state->src_port, is_index_transfer); + pkts++; + } + rx_state->next_chunk_to_ack = curr; + if (control_pkt.payload.ack.num_acks == 0 || + curr > rx_state->received_chunks.max_set) { + rx_state->next_chunk_to_ack = 0; + } + debug_printf("packets in ack batch: %u", pkts); } @@ -2825,7 +2839,7 @@ static void rx_trickle_acks(void *arg) { quit_session(session_rx, SESSION_ERROR_OK); } } - rx_state->next_ack_round_start = now + ACK_RATE_TIME_MS * 1e6; + rx_state->next_ack_round_start = get_nsecs() + ACK_RATE_TIME_MS * 1e6; } } } diff --git a/hercules.h b/hercules.h index 7bb60ef..460e080 100644 --- a/hercules.h +++ b/hercules.h @@ -104,6 +104,7 @@ struct receiver_state { u32 ack_nr; u64 next_nack_round_start; u64 next_ack_round_start; + u32 next_chunk_to_ack; _Atomic u8 num_tracked_paths; bool is_pcc_benchmark; struct receiver_state_per_path path_state[256]; From d8f966d3a7ccca47abc63ca9837e629c2b898272 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 20 Nov 2024 10:30:36 +0100 Subject: [PATCH 100/112] move to libxdp --- .gitmodules | 7 +- Dockerfile | 7 +- Makefile | 21 +++-- README.md | 3 + bitset.h | 5 +- bpf | 1 - bpf_prgm/redirect_userspace.c | 42 ++++----- bpf_prgms.s | 2 +- hercules.c | 14 +-- hercules.h | 5 +- xdp-tools | 1 + xdp.c | 168 +++++++++++++++++++++------------- xdp.h | 6 +- 13 files changed, 166 insertions(+), 116 deletions(-) delete mode 160000 bpf create mode 160000 xdp-tools diff --git a/.gitmodules b/.gitmodules index 7b39c30..247e342 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,6 @@ -[submodule "bpf"] - path = bpf - url = https://github.com/libbpf/libbpf/ - ignore = untracked +[submodule "xdp-tools"] + path = xdp-tools + url = https://github.com/xdp-project/xdp-tools.git [submodule "tomlc99"] path = tomlc99 url = https://github.com/cktan/tomlc99.git diff --git a/Dockerfile b/Dockerfile index 95acdbf..855e11d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN set -eux; \ gpg \ gpg-agent \ dirmngr \ - clang \ + clang-12 \ llvm \ libelf-dev \ libpcap-dev \ @@ -37,11 +37,14 @@ RUN set -eux; \ build-essential \ ruby \ rpm \ + m4 \ ; \ rm -rf /var/lib/apt/lists/* ENV PATH /usr/local/go/bin:$PATH +RUN ln -s /usr/bin/clang-12 /usr/bin/clang + RUN gem install dotenv -v 2.8.1 RUN gem install fpm @@ -81,6 +84,8 @@ RUN set -eux; \ # https://github.com/docker-library/golang/issues/472 ENV GOTOOLCHAIN=local +RUN git config --global --add safe.directory "*" + RUN groupadd --gid $GID --non-unique buildboy RUN useradd buildboy --create-home --shell /bin/bash --non-unique --uid $UID --gid $GID USER buildboy diff --git a/Makefile b/Makefile index f8665f7..3a319de 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ TARGET_SERVER := hercules-server TARGET_MONITOR := hercules-monitor TARGET_HCP := hcp/hcp -CC := clang +CC := gcc CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 # CFLAGS += -DNDEBUG CFLAGS += -Wall -Wextra @@ -25,7 +25,7 @@ CFLAGS += -DPRINT_STATS # CFLAGS += -DDEBUG_PRINT_PKTS # print received/sent packets (lots of noise!) -LDFLAGS = -g3 -l:libbpf.a -Lbpf/src -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) +LDFLAGS = -g3 -l:libxdp.a -l:libbpf.a -Ltomlc99 -lm -lelf -latomic -pthread -lz -ltoml -z noexecstack $(ASAN_FLAG) DEPFLAGS := -MP -MD SRCS := $(wildcard *.c) @@ -79,7 +79,7 @@ docker_%: builder $(TARGET_MONITOR): $(MONITORFILES) $(wildcard *.h) cd monitor && go build -o "../$@" -ldflags "-X main.startupVersion=${VERSION}" -$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o bpf/src/libbpf.a tomlc99/libtoml.a +$(TARGET_SERVER): $(OBJS) bpf_prgm/redirect_userspace.o tomlc99/libtoml.a @# update modification dates in assembly, so that the new version gets loaded @sed -i -e "s/\(load bpf_prgm_redirect_userspace\)\( \)\?\([0-9a-f]\{32\}\)\?/\1 $$(md5sum bpf_prgm/redirect_userspace.c | head -c 32)/g" bpf_prgms.s $(CC) -o $@ $(OBJS) bpf_prgms.s $(LDFLAGS) @@ -101,15 +101,17 @@ bpf_prgm/%.o: bpf_prgm/%.ll # explicitly list intermediates for dependency resolution bpf_prgm/redirect_userspace.ll: -bpf/src/libbpf.a: - @if [ ! -d bpf/src ]; then \ - echo "Error: Need libbpf submodule"; \ +.PHONY: libxdp +libxdp: + @if [ ! -d xdp-tools/lib ]; then \ + echo "Error: Need libxdp submodule"; \ echo "May need to run git submodule update --init"; \ exit 1; \ else \ - cd bpf/src && $(MAKE) all OBJDIR=.; \ - mkdir -p build; \ - $(MAKE) install_headers DESTDIR=build OBJDIR=.; \ + cd xdp-tools && ./configure && \ + cd lib && make && \ + cd libxdp && make install && \ + cd ../libbpf/src && make install; \ fi tomlc99/libtoml.a: @@ -133,6 +135,7 @@ builder: builder_image docker run -t --entrypoint cat --name hercules-builder -v $(PWD):/`basename $(PWD)` -w /`basename $(PWD)` -d hercules-builder @docker container ls --format={{.Names}} | grep hercules-builder -q || \ docker start hercules-builder + @docker exec hercules-builder ls /usr/local/include/xdp >/dev/null 2>&1 || docker exec -u0 hercules-builder make libxdp builder_image: @docker images | grep hercules-builder -q || \ diff --git a/README.md b/README.md index f2d7c68..4a2bd88 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,9 @@ On Ubuntu you can install the required packages as follows: `# apt install build-essential llvm clang git golang libz-dev libelf-dev linux-headers-generic gcc-multilib` +You will additionally need `libbpf` and `libxdp`. You can install them manually +or run `sudo make libxdp` to install them to your system from the git submodules. + To build Hercules, run `make all`. This will build the server and monitor executables, as well as the `hcp` tool. diff --git a/bitset.h b/bitset.h index afe242a..3b990a9 100644 --- a/bitset.h +++ b/bitset.h @@ -21,7 +21,6 @@ #include #include #include -#include "bpf/src/libbpf_util.h" /** Simple bit-set that keeps track of number of elements in the set. */ struct bitset { @@ -50,7 +49,7 @@ static inline bool bitset__check(struct bitset *s, u32 i) static inline bool bitset__set_mt_safe(struct bitset *s, u32 i) { pthread_spin_lock(&s->lock); - libbpf_smp_rmb(); + asm volatile("":::"memory"); // XXX why is this here? unsigned int bit = 1u << i % HERCULES_BITSET_WORD_BITS; unsigned int prev = atomic_fetch_or(&s->bitmap[i / HERCULES_BITSET_WORD_BITS], bit); if(!(prev & bit)) { @@ -65,7 +64,7 @@ static inline bool bitset__set_mt_safe(struct bitset *s, u32 i) pthread_spin_unlock(&s->lock); return false; } - libbpf_smp_wmb(); + asm volatile("":::"memory"); pthread_spin_unlock(&s->lock); return true; } diff --git a/bpf b/bpf deleted file mode 160000 index b6dd2f2..0000000 --- a/bpf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6dd2f2b7df4d3bd35d64aaf521d9ad18d766f53 diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index 19bda6b..0c53907 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -8,37 +8,37 @@ #include #include #include -#include "packet.h" +#include "../packet.h" -#include +#include -struct bpf_map_def SEC("maps") xsks_map = { - .type = BPF_MAP_TYPE_XSKMAP, - .key_size = sizeof(__u32), - .value_size = sizeof(__u32), - .max_entries = MAX_NUM_SOCKETS, -}; +struct { + __uint(type, BPF_MAP_TYPE_XSKMAP); + __type(key, __u32); + __type(value, __u32); + __uint(max_entries, MAX_NUM_SOCKETS); +} xsks_map SEC(".maps"); -struct bpf_map_def SEC("maps") num_xsks = { - .type = BPF_MAP_TYPE_ARRAY, - .key_size = sizeof(__u32), - .value_size = sizeof(__u32), - .max_entries = 1, -}; +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u32); + __uint(max_entries, 1); +} num_xsks SEC(".maps"); -struct bpf_map_def SEC("maps") local_addr = { - .type = BPF_MAP_TYPE_ARRAY, - .key_size = sizeof(__u32), - .value_size = sizeof(struct hercules_app_addr), - .max_entries = 1, -}; +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, struct hercules_app_addr); + __uint(max_entries, 1); +} local_addr SEC(".maps"); static int redirect_count = 0; static __u32 zero = 0; SEC("xdp") -int xdp_prog_redirect_userspace(struct xdp_md *ctx) +int hercules_redirect_userspace(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; diff --git a/bpf_prgms.s b/bpf_prgms.s index b5133fb..49f5526 100644 --- a/bpf_prgms.s +++ b/bpf_prgms.s @@ -1,7 +1,7 @@ .section ".rodata" -# load bpf_prgm_redirect_userspace a658699d4009ed41f0bfbb8f2cf768c7 +# load bpf_prgm_redirect_userspace 200483b874234102b9a03950db2172d8 .globl bpf_prgm_redirect_userspace .type bpf_prgm_redirect_userspace, STT_OBJECT .globl bpf_prgm_redirect_userspace_size diff --git a/hercules.c b/hercules.c index c2e431c..55729d2 100644 --- a/hercules.c +++ b/hercules.c @@ -3,6 +3,7 @@ // Copyright(c) 2019 ETH Zurich. #include "hercules.h" +#include #include "packet.h" #include #include @@ -38,9 +39,8 @@ #include #include "linux/bpf_util.h" -#include "bpf/src/libbpf.h" -#include "bpf/src/bpf.h" -#include "bpf/src/xsk.h" +#include +#include #include "linux/filter.h" // actually linux/tools/include/linux/filter.h #include "toml.h" @@ -1594,7 +1594,7 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s //sequence_number start = nack_end; bool sent = false; pthread_spin_lock(&path_state->seq_rcvd.lock); - libbpf_smp_rmb(); + asm volatile("":::"memory"); // XXX why is this here? for(u32 curr = path_state->nack_end; curr < path_state->seq_rcvd.num;) { // Data to send curr = fill_nack_pkt(curr, &control_pkt.payload.ack, max_entries, &path_state->seq_rcvd); @@ -1624,7 +1624,7 @@ static void rx_send_path_nacks(struct hercules_server *server, struct receiver_s send_eth_frame(server, &path, buf); atomic_fetch_add(&rx_state->session->tx_npkts, 1); } - libbpf_smp_wmb(); + asm volatile("":::"memory"); // XXX why is this here? pthread_spin_unlock(&path_state->seq_rcvd.lock); path_state->nack_end = nack_end; } @@ -1707,11 +1707,11 @@ static void tx_register_acks_index(const struct rbudp_ack_pkt *ack, struct sende static void pop_completion_ring(struct hercules_server *server, struct xsk_umem_info *umem) { u32 idx; - size_t entries = xsk_ring_cons__peek(&umem->cq, SIZE_MAX, &idx); + u32 entries = xsk_ring_cons__peek(&umem->cq, INT32_MAX, &idx); if(entries > 0) { u16 num = frame_queue__prod_reserve(&umem->available_frames, entries); if(num < entries) { // there are less frames in the loop than the number of slots in frame_queue - debug_printf("trying to push %ld frames, only got %d slots in frame_queue", entries, num); + debug_printf("trying to push %u frames, only got %d slots in frame_queue", entries, num); exit_with_error(server, EINVAL); } for(u16 i = 0; i < num; i++) { diff --git a/hercules.h b/hercules.h index b6218a9..cda6e4a 100644 --- a/hercules.h +++ b/hercules.h @@ -22,7 +22,8 @@ #include #include -#include "bpf/src/xsk.h" +#include +#include #include "congestion_control.h" #include "frame_queue.h" #include "packet.h" @@ -220,7 +221,7 @@ struct hercules_interface { char ifname[IFNAMSIZ]; int ifid; int queue; - u32 prog_id; + struct xdp_program *xdp_prog; int ethtool_rule; u32 num_sockets; struct xsk_umem_info *umem; diff --git a/xdp-tools b/xdp-tools new file mode 160000 index 0000000..9085a51 --- /dev/null +++ b/xdp-tools @@ -0,0 +1 @@ +Subproject commit 9085a513365ac1bf8318f70b8c084efafed3312a diff --git a/xdp.c b/xdp.c index dae995e..c497bfe 100644 --- a/xdp.c +++ b/xdp.c @@ -2,26 +2,34 @@ #include #include +#include -#include "bpf/src/bpf.h" +#include "utils.h" +#include +#include #include "bpf_prgms.h" #include "hercules.h" void remove_xdp_program(struct hercules_server *server) { for (int i = 0; i < server->num_ifaces; i++) { - u32 curr_prog_id = 0; - if (bpf_get_link_xdp_id(server->ifaces[i].ifid, &curr_prog_id, - server->config.xdp_flags)) { - printf("bpf_get_link_xdp_id failed\n"); - exit(EXIT_FAILURE); + enum xdp_attach_mode mode = xdp_program__is_attached( + server->ifaces[i].xdp_prog, server->ifaces[i].ifid); + if (!mode) { + fprintf(stderr, "Program not attached on %s?\n", + server->ifaces[i].ifname); + continue; } - if (server->ifaces[i].prog_id == curr_prog_id) - bpf_set_link_xdp_fd(server->ifaces[i].ifid, -1, - server->config.xdp_flags); - else if (!curr_prog_id) - printf("couldn't find a prog id on a given interface\n"); - else - printf("program on interface changed, not removing\n"); + int err = xdp_program__detach(server->ifaces[i].xdp_prog, + server->ifaces[i].ifid, mode, 0); + char errmsg[1024]; + if (err) { + libxdp_strerror(err, errmsg, sizeof(errmsg)); + fprintf(stderr, "Error detaching XDP program from %s: %s\n", + server->ifaces[i].ifname, errmsg); + continue; + } + xdp_program__close(server->ifaces[i].xdp_prog); + server->ifaces[i].xdp_prog = NULL; } } @@ -190,11 +198,7 @@ int unconfigure_rx_queues(struct hercules_server *server) { return error; } -int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj) { - static const int log_buf_size = 16 * 1024; - char log_buf[log_buf_size]; - int prog_fd; - +int load_bpf(const void *prgm, ssize_t prgm_size, struct xdp_program **prog_o) { char tmp_file[] = "/tmp/hrcbpfXXXXXX"; int fd = mkstemp(tmp_file); if (fd < 0) { @@ -205,38 +209,22 @@ int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj) { return -EXIT_FAILURE; } - struct bpf_object *_obj; - if (obj == NULL) { - obj = &_obj; + struct xdp_program *prog = xdp_program__open_file(tmp_file, "xdp", NULL); + int err = libxdp_get_error(prog); + char errmsg[1024]; + if (err) { + libxdp_strerror(err, errmsg, sizeof(errmsg)); + debug_printf("aaa prog %s", errmsg); + fprintf(stderr, "Error loading XDP program: %s\n", errmsg); + return 1; } - int ret = bpf_prog_load(tmp_file, BPF_PROG_TYPE_XDP, obj, &prog_fd); - debug_printf("error loading file(%s): %d %s", tmp_file, -ret, - strerror(-ret)); + int unlink_ret = unlink(tmp_file); if (0 != unlink_ret) { fprintf(stderr, "Could not remove temporary file, error: %d", unlink_ret); } - if (ret != 0) { - printf("BPF log buffer:\n%s", log_buf); - return ret; - } - return prog_fd; -} - -int set_bpf_prgm_active(struct hercules_server *server, - struct hercules_interface *iface, int prog_fd) { - int err = - bpf_set_link_xdp_fd(iface->ifid, prog_fd, server->config.xdp_flags); - if (err) { - return 1; - } - - int ret = bpf_get_link_xdp_id(iface->ifid, &iface->prog_id, - server->config.xdp_flags); - if (ret) { - return 1; - } + *prog_o = prog; return 0; } @@ -260,15 +248,73 @@ int load_xsk_redirect_userspace(struct hercules_server *server, struct worker_args *args[], int num_threads) { debug_printf("Loading XDP program for redirection"); for (int i = 0; i < server->num_ifaces; i++) { - struct bpf_object *obj; - int prog_fd = load_bpf(bpf_prgm_redirect_userspace, - bpf_prgm_redirect_userspace_size, &obj); - if (prog_fd < 0) { + int err; + char errmsg[1024]; + struct xdp_program *prog; + int ret = load_bpf(bpf_prgm_redirect_userspace, + bpf_prgm_redirect_userspace_size, &prog); + if (ret) { + return 1; + } + + // Check if there's already xdp programs on the interface. + // If possible, the program is added to the list of loaded programs. + // If our redirect program is already present (eg. because we crashed and thus + // didn't remove it), we try to replace it. + struct xdp_multiprog *multi = + xdp_multiprog__get_from_ifindex(server->ifaces[i].ifid); + if (xdp_multiprog__is_legacy(multi)) { + // In this case we cannot add ours and we don't know if it's safe to remove + // the other program + fprintf(stderr, + "Error: A legacy XDP program is already loaded on interface %s\n", + server->ifaces[i].ifname); + return 1; + } + for (struct xdp_program *ifprog = xdp_multiprog__next_prog(NULL, multi); + ifprog != NULL; ifprog = xdp_multiprog__next_prog(ifprog, multi)) { + debug_printf("iface program: %s, prio %u", xdp_program__name(ifprog), + xdp_program__run_prio(ifprog)); + if (!strcmp(xdp_program__name(ifprog), "hercules_redirect_userspace")) { + // If our redirect program is already loaded, we replace it + // XXX Relies on nobody else naming a program + // hercules_redirect_userspace, so multiple Hercules instances per + // machine are not possible. That could be solved with priorities, for + // example. + fprintf(stderr, + ">>> Hercules XDP program already loaded on interface, " + "replacing.\n"); + err = xdp_program__detach(ifprog, server->ifaces[i].ifid, + XDP_MODE_UNSPEC, 0); + if (err) { + libxdp_strerror(err, errmsg, sizeof(errmsg)); + fprintf(stderr, + "Error detaching XDP program from interface %s: %s\n", + server->ifaces[i].ifname, errmsg); + return 1; + } + ifprog = xdp_multiprog__next_prog(NULL, multi); + } + } + + err = xdp_program__attach(prog, server->ifaces[i].ifid, XDP_MODE_UNSPEC, 0); + if (err) { + libxdp_strerror(err, errmsg, sizeof(errmsg)); + fprintf(stderr, "Error attaching XDP program to interface %s: %s\n", + server->ifaces[i].ifname, errmsg); return 1; } + enum xdp_attach_mode mode = + xdp_program__is_attached(prog, server->ifaces[i].ifid); + if (!mode) { + fprintf(stderr, "Program not attached?\n"); + return 1; + } + fprintf(stderr, "XDP program attached in mode: %d\n", mode); + server->ifaces[i].xdp_prog = prog; // push XSKs - int xsks_map_fd = bpf_object__find_map_fd_by_name(obj, "xsks_map"); + int xsks_map_fd = bpf_object__find_map_fd_by_name(xdp_program__bpf_obj(prog), "xsks_map"); if (xsks_map_fd < 0) { return 1; } @@ -282,17 +328,17 @@ int load_xsk_redirect_userspace(struct hercules_server *server, // push XSKs meta int zero = 0; - int num_xsks_fd = bpf_object__find_map_fd_by_name(obj, "num_xsks"); + int num_xsks_fd = bpf_object__find_map_fd_by_name(xdp_program__bpf_obj(prog), "num_xsks"); if (num_xsks_fd < 0) { return 1; } - int ret = bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); + ret = bpf_map_update_elem(num_xsks_fd, &zero, &num_threads, 0); if (ret == -1) { return 1; } // push local address - int local_addr_fd = bpf_object__find_map_fd_by_name(obj, "local_addr"); + int local_addr_fd = bpf_object__find_map_fd_by_name(xdp_program__bpf_obj(prog), "local_addr"); if (local_addr_fd < 0) { return 1; } @@ -301,11 +347,6 @@ int load_xsk_redirect_userspace(struct hercules_server *server, if (ret == -1) { return 1; } - - ret = set_bpf_prgm_active(server, &server->ifaces[i], prog_fd); - if (ret) { - return 1; - } } return 0; } @@ -373,14 +414,15 @@ int xdp_setup(struct hercules_server *server) { &xsk->xsk, server->ifaces[i].ifname, server->config.queue, umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); if (ret) { + fprintf(stderr, "Error creating XDP socket\n"); return -ret; } - ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, - &server->ifaces[i].prog_id, - server->config.xdp_flags); - if (ret) { - return -ret; - } + /* ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, */ + /* &server->ifaces[i].prog_id, */ + /* server->config.xdp_flags); */ + /* if (ret) { */ + /* return -ret; */ + /* } */ server->ifaces[i].xsks[t] = xsk; } server->ifaces[i].num_sockets = server->config.n_threads; diff --git a/xdp.h b/xdp.h index d2cf728..745463e 100644 --- a/xdp.h +++ b/xdp.h @@ -2,6 +2,7 @@ #define HERCULES_XDP_H_ #include "hercules.h" +#include // Remove the XDP program loaded on all the server's interfaces void remove_xdp_program(struct hercules_server *server); @@ -27,10 +28,7 @@ int configure_rx_queues(struct hercules_server *server); // Remove ethtool rules previously set by configure_rx_queues int unconfigure_rx_queues(struct hercules_server *server); -int load_bpf(const void *prgm, ssize_t prgm_size, struct bpf_object **obj); - -int set_bpf_prgm_active(struct hercules_server *server, - struct hercules_interface *iface, int prog_fd); +int load_bpf(const void *prgm, ssize_t prgm_size, struct xdp_program **prog_o); int xsk_map__add_xsk(xskmap map, int index, struct xsk_socket_info *xsk); From d53164b7c86c678178379324bc1558a27b0da9bf Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 26 Nov 2024 13:53:04 +0100 Subject: [PATCH 101/112] load in frags mode --- Makefile | 2 +- bpf_prgm/redirect_userspace.c | 2 +- bpf_prgms.s | 2 +- hercules.h | 6 +++++- monitor.h | 2 +- xdp.c | 39 +++++++++++++++++++++++++++++------ xdp.h | 7 ++++--- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 3a319de..15cdbbd 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TARGET_MONITOR := hercules-monitor TARGET_HCP := hcp/hcp CC := gcc -CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 +CFLAGS = -O3 -g3 -std=gnu11 -D_GNU_SOURCE -Itomlc99 -Ixdp-tools/lib/libbpf/include/uapi -Ixdp-tools/headers # CFLAGS += -DNDEBUG CFLAGS += -Wall -Wextra diff --git a/bpf_prgm/redirect_userspace.c b/bpf_prgm/redirect_userspace.c index 0c53907..9ad5798 100644 --- a/bpf_prgm/redirect_userspace.c +++ b/bpf_prgm/redirect_userspace.c @@ -37,7 +37,7 @@ struct { static int redirect_count = 0; static __u32 zero = 0; -SEC("xdp") +SEC("xdp.frags") int hercules_redirect_userspace(struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; diff --git a/bpf_prgms.s b/bpf_prgms.s index 49f5526..8375f36 100644 --- a/bpf_prgms.s +++ b/bpf_prgms.s @@ -1,7 +1,7 @@ .section ".rodata" -# load bpf_prgm_redirect_userspace 200483b874234102b9a03950db2172d8 +# load bpf_prgm_redirect_userspace 547691a10d344c02d8cb0e437f1b0e09 .globl bpf_prgm_redirect_userspace .type bpf_prgm_redirect_userspace, STT_OBJECT .globl bpf_prgm_redirect_userspace_size diff --git a/hercules.h b/hercules.h index cda6e4a..3fedd1b 100644 --- a/hercules.h +++ b/hercules.h @@ -39,7 +39,11 @@ // depends on the driver. We're being conservative here. Support for larger // packets is possible by using xdp in multibuffer mode, but this requires code // to handle multi-buffer packets. -#define HERCULES_MAX_PKTSIZE 3000 +#define HERCULES_MAX_PKTSIZE 9000 +// Size of fragments when constructing jumbo frames for tx. +// Note that, on rx packets are fragmented by xdp, so this value is irrelevant +// there. +#define HERCULES_FRAG_SIZE 3000 #define HERCULES_FILENAME_SIZE 1000 // Batch size for send/receive operations #define BATCH_SIZE 64 diff --git a/monitor.h b/monitor.h index ba3c363..84184ee 100644 --- a/monitor.h +++ b/monitor.h @@ -45,7 +45,7 @@ int monitor_bind_daemon_socket(char *server, char *monitor); // Maximum size of variable-length fields in socket messages. Since we pass // entire packets to the monitor to get reply paths, this must be at least as // large as HERCULES_MAX_PKT_SIZE. -#define SOCKMSG_MAX_PAYLOAD 5000 +#define SOCKMSG_MAX_PAYLOAD 10000 _Static_assert(SOCKMSG_MAX_PAYLOAD >= HERCULES_MAX_PKTSIZE, "Socket messages too small"); diff --git a/xdp.c b/xdp.c index c497bfe..59cad76 100644 --- a/xdp.c +++ b/xdp.c @@ -1,6 +1,7 @@ #include "xdp.h" #include +#include #include #include @@ -209,7 +210,7 @@ int load_bpf(const void *prgm, ssize_t prgm_size, struct xdp_program **prog_o) { return -EXIT_FAILURE; } - struct xdp_program *prog = xdp_program__open_file(tmp_file, "xdp", NULL); + struct xdp_program *prog = xdp_program__open_file(tmp_file, "xdp.frags", NULL); int err = libxdp_get_error(prog); char errmsg[1024]; if (err) { @@ -313,6 +314,23 @@ int load_xsk_redirect_userspace(struct hercules_server *server, fprintf(stderr, "XDP program attached in mode: %d\n", mode); server->ifaces[i].xdp_prog = prog; + debug_printf("program supports frags? %d", xdp_program__xdp_frags_support(prog)); + + // XXX It should be possible to check whether multi-buffer (jumbo-frames) are + // supported with the following code, but this always returns 0. However, it + // also returns 0 for zero-copy support on machines that are known to support + // zero-copy (eg. zapdos), so something is wrong. Same thing happens if you use + // the xdp-loader utility (from xdp-tools, it uses the same approach) to query + // for feature support. + /* LIBBPF_OPTS(bpf_xdp_query_opts, opts); */ + /* err = bpf_xdp_query(server->ifaces[i].ifid, 0, &opts); */ + /* if (err) { */ + /* debug_printf("query err"); */ + /* return 1; */ + /* } */ + /* debug_printf("opts %#llx, zc frags %#x", opts.feature_flags, opts.xdp_zc_max_segs); */ + + // push XSKs int xsks_map_fd = bpf_object__find_map_fd_by_name(xdp_program__bpf_obj(prog), "xsks_map"); if (xsks_map_fd < 0) { @@ -410,12 +428,21 @@ int xdp_setup(struct hercules_server *server) { cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; cfg.xdp_flags = server->config.xdp_flags; cfg.bind_flags = server->config.xdp_mode; - ret = xsk_socket__create_shared( - &xsk->xsk, server->ifaces[i].ifname, server->config.queue, - umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); + + cfg.bind_flags |= XDP_USE_SG; + ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, + server->config.queue, umem->umem, &xsk->rx, + &xsk->tx, &umem->fq, &umem->cq, &cfg); if (ret) { - fprintf(stderr, "Error creating XDP socket\n"); - return -ret; + fprintf(stderr, "Error creating XDP socket in multibuffer mode\n"); + cfg.bind_flags = server->config.xdp_mode; + ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, + server->config.queue, umem->umem, &xsk->rx, + &xsk->tx, &umem->fq, &umem->cq, &cfg); + if (ret) { + fprintf(stderr, "Error creating XDP socket\n"); + return -ret; + } } /* ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, */ /* &server->ifaces[i].prog_id, */ diff --git a/xdp.h b/xdp.h index 745463e..17e151c 100644 --- a/xdp.h +++ b/xdp.h @@ -1,9 +1,11 @@ #ifndef HERCULES_XDP_H_ #define HERCULES_XDP_H_ -#include "hercules.h" #include +#include "hercules.h" + + // Remove the XDP program loaded on all the server's interfaces void remove_xdp_program(struct hercules_server *server); @@ -30,8 +32,7 @@ int unconfigure_rx_queues(struct hercules_server *server); int load_bpf(const void *prgm, ssize_t prgm_size, struct xdp_program **prog_o); -int xsk_map__add_xsk(xskmap map, int index, - struct xsk_socket_info *xsk); +int xsk_map__add_xsk(xskmap map, int index, struct xsk_socket_info *xsk); int load_xsk_redirect_userspace(struct hercules_server *server, struct worker_args *args[], int num_threads); From 7a365bb30292d5c20ff5de2ce41696c2e3b73352 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 27 Nov 2024 16:17:36 +0100 Subject: [PATCH 102/112] jumbo rx/tx (with additional copy) --- hercules.c | 147 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 106 insertions(+), 41 deletions(-) diff --git a/hercules.c b/hercules.c index 55729d2..b0b3ccc 100644 --- a/hercules.c +++ b/hercules.c @@ -540,6 +540,10 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec if(offset == UINT32_MAX) { offset = *(int *)pkt; } + if (offset > length) { + debug_printf("Offset past end of packet (fragmented?)"); + return NULL; + } if(check) { struct udphdr *l4udph = (struct udphdr *)(pkt + offset) - 1; u16 header_checksum = l4udph->check; @@ -1091,11 +1095,39 @@ static void rx_receive_batch(struct hercules_server *server, u64 frame_addrs[BATCH_SIZE]; for (size_t i = 0; i < rcvd; i++) { - u64 addr = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->addr; - frame_addrs[i] = addr; - u32 len = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i)->len; - const char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); - const char *rbudp_pkt = parse_pkt_fast_path(pkt, len, true, UINT32_MAX); + u8 pktbuf[9000]; + u64 pkt_total_len = 0; + void *next = pktbuf; + for (int f = 0; f < 3; f++) { + // For jumbo frames, our packet may consist of up to 3 fragments. + // In two cases this is not possible and we just drop the packet: + // XXX We currently just drop the packet if it is split across read batches, as + // the beginning and ending fragments of the packet may have been read by two + // different threads and we have no way of reconstructing it. + const struct xdp_desc *desc = xsk_ring_cons__rx_desc(&xsk->rx, idx_rx + i); + u64 addr = desc->addr; + assert(i < rcvd); + frame_addrs[i] = addr; + u32 len = desc->len; + /* debug_printf("frag len %u, opts %#x", len, desc->options); */ + next = mempcpy(next, xsk_umem__get_data(xsk->umem->buffer, addr), len); + pkt_total_len += len; + bool continues_in_next_frag = (desc->options & XDP_PKT_CONTD); + if (continues_in_next_frag) { + i++; + if (i >= rcvd) { + // packet continues in next batch (possibly another thread) + // XXX can we do something other than dropping this packet? + goto release; + } + assert(f != 2 && "Packet consisting of more than 3 fragments?"); + } else { + break; + } + } + + const char *pkt = pktbuf; + const char *rbudp_pkt = parse_pkt_fast_path(pkt, pkt_total_len, true, UINT32_MAX); if (!rbudp_pkt) { debug_printf("Unparseable packet on XDP socket, ignoring"); continue; @@ -1142,12 +1174,13 @@ static void rx_receive_batch(struct hercules_server *server, &old_last_pkt_rcvd, now); } if (!handle_rbudp_data_pkt(session_rx->rx_state, rbudp_pkt, - len - (rbudp_pkt - pkt))) { + pkt_total_len - (rbudp_pkt - pkt))) { debug_printf("Non-data packet on XDP socket? Ignoring."); } } atomic_fetch_add(&session_rx->rx_npkts, 1); } +release: xsk_ring_cons__release(&xsk->rx, rcvd); submit_rx_frames(xsk->umem, frame_addrs, rcvd); } @@ -1326,7 +1359,7 @@ static bool rx_update_reply_path( return false; } assert(rx_sample_len > 0); - assert(rx_sample_len <= XSK_UMEM__DEFAULT_FRAME_SIZE); + assert(rx_sample_len <= HERCULES_MAX_PKTSIZE); int ret = monitor_get_reply_path(server->usock, rx_sample_buf, rx_sample_len, @@ -1970,17 +2003,17 @@ static void claim_tx_frames(struct hercules_server *server, struct hercules_inte for(size_t i = 0; i < num_frames; i++) { addrs[i] = frame_queue__cons_fetch(&iface->umem->available_frames, i); + /* debug_printf("claimed frame %p", addrs[i]); */ } frame_queue__pop(&iface->umem->available_frames, num_frames); pthread_spin_unlock(&iface->umem->frames_lock); } -static char *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_idx, size_t framelen) +static struct xdp_desc *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 prod_tx_idx) { - xsk_ring_prod__tx_desc(&xsk->tx, prod_tx_idx)->addr = addr; - xsk_ring_prod__tx_desc(&xsk->tx, prod_tx_idx)->len = framelen; - char *pkt = xsk_umem__get_data(xsk->umem->buffer, addr); - return pkt; + struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, prod_tx_idx); + tx_desc->addr = addr; + return tx_desc; } #ifdef RANDOMIZE_FLOWID @@ -1992,9 +2025,10 @@ static _Atomic short src_port_ctr = 0; static inline void tx_handle_send_queue_unit_for_iface( struct sender_state *tx_state, struct xsk_socket_info *xsk, int ifid, - u64 frame_addrs[SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, - u32 thread_id, bool is_index_transfer) { + u64 frame_addrs[3*SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, + u32 thread_id, bool is_index_transfer, int frames_per_chunk) { u32 num_chunks_in_unit = 0; + u8 pktbuf[9000]; /// HACK copy, remove me struct path_set *pathset = pathset_read(tx_state, thread_id); for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { if (unit->paths[i] == UINT8_MAX) { @@ -2010,8 +2044,9 @@ static inline void tx_handle_send_queue_unit_for_iface( } u32 idx; - if (xsk_ring_prod__reserve(&xsk->tx, num_chunks_in_unit, &idx) != - num_chunks_in_unit) { + u32 to_reserve = num_chunks_in_unit * frames_per_chunk; + if (xsk_ring_prod__reserve(&xsk->tx, to_reserve, &idx) != + to_reserve) { // As there are fewer frames in the loop than slots in the TX ring, this // should not happen exit_with_error(NULL, EINVAL); @@ -2055,10 +2090,16 @@ static inline void tx_handle_send_queue_unit_for_iface( umin64(tx_state->chunklen, tx_state->index_size - chunk_start); } - void *pkt = prepare_frame(xsk, frame_addrs[current_frame], - idx + current_frame, path->framelen); - frame_addrs[current_frame] = -1; - current_frame++; + struct xdp_desc *tx_descs[3]; + void *frames_data[3]; + for (int i = 0; i < frames_per_chunk; i++){ + /* debug_printf("using frame %p", frame_addrs[current_frame]); */ + tx_descs[i] = prepare_frame(xsk, frame_addrs[current_frame], idx+current_frame); + frames_data[i] = xsk_umem__get_data(xsk->umem->buffer, frame_addrs[current_frame]); + frame_addrs[current_frame] = -1; + current_frame++; + } + void *pkt = pktbuf; // HACK memcpy void *rbudp_pkt = mempcpy(pkt, path->header.header, path->headerlen); #ifdef RANDOMIZE_FLOWID @@ -2091,19 +2132,36 @@ static inline void tx_handle_send_queue_unit_for_iface( fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, payload + chunk_start, len, path->payloadlen); stitch_checksum_with_dst(path, path->header.checksum, pkt); - } - xsk_ring_prod__submit(&xsk->tx, num_chunks_in_unit); + + u64 pkt_total_len = path->headerlen + path->payloadlen; + u64 pkt_bytes_left = pkt_total_len; + u64 pkt_bytes[3]; + void *next_frag = pktbuf; + for (int f = 0; f < frames_per_chunk; f++) { + pkt_bytes[f] = pkt_bytes_left > HERCULES_FRAG_SIZE ? HERCULES_FRAG_SIZE + : pkt_bytes_left; + memcpy(frames_data[f], next_frag, pkt_bytes[f]); + next_frag += pkt_bytes[f]; + pkt_bytes_left -= pkt_bytes[f]; + tx_descs[f]->len = pkt_bytes[f]; + tx_descs[f]->options = (pkt_bytes_left > 0) ? XDP_PKT_CONTD : 0; + } + /* debug_printf("copied 1:%llu | 2:%llu | 3:%llu", pkt_bytes[0], pkt_bytes[1], */ + /* pkt_bytes[2]); */ + assert(pkt_bytes_left == 0 && "Packet did not fit in fragments?"); + } + xsk_ring_prod__submit(&xsk->tx, to_reserve); } static inline void tx_handle_send_queue_unit( struct hercules_server *server, struct sender_state *tx_state, struct xsk_socket_info *xsks[], - u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT], - struct send_queue_unit *unit, u32 thread_id, bool is_index_transfer) { + u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT*3], + struct send_queue_unit *unit, u32 thread_id, bool is_index_transfer, int frames_per_chunk) { for (int i = 0; i < server->num_ifaces; i++) { tx_handle_send_queue_unit_for_iface( tx_state, xsks[i], server->ifaces[i].ifid, frame_addrs[i], unit, - thread_id, is_index_transfer); + thread_id, is_index_transfer, frames_per_chunk); } } @@ -2145,16 +2203,13 @@ static void produce_batch(struct hercules_server *server, } static inline void allocate_tx_frames(struct hercules_server *server, - u64 frame_addrs[][SEND_QUEUE_ENTRIES_PER_UNIT]) + u64 frame_addrs[server->num_ifaces][3*SEND_QUEUE_ENTRIES_PER_UNIT], + int num_chunks, + int frames_per_chunk) { for(int i = 0; i < server->num_ifaces; i++) { - int num_frames; - for(num_frames = 0; num_frames < SEND_QUEUE_ENTRIES_PER_UNIT; num_frames++) { - if(frame_addrs[i][num_frames] != (u64) -1) { - break; - } - } - claim_tx_frames(server, &server->ifaces[i], frame_addrs[i], num_frames); + int num_frames = num_chunks * frames_per_chunk; + claim_tx_frames(server, &server->ifaces[i], (u64 *)&(frame_addrs[i]), num_frames); } } @@ -2862,17 +2917,27 @@ static void tx_send_p(void *arg) { } num_chunks_in_unit++; } - u64 frame_addrs[server->num_ifaces][SEND_QUEUE_ENTRIES_PER_UNIT]; - memset(frame_addrs, 0xFF, sizeof(frame_addrs)); - for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { - if (i >= num_chunks_in_unit) { - frame_addrs[0][i] = 0; - } + + // We need to allocate 1,2 or 3 frames per chunk, depending on the payload length. + // TODO move this to session creation, no need to recompute + // TODO before setting, check frags supported + int frames_per_chunk = 1; + if (session_tx->payloadlen + HERCULES_MAX_HEADERLEN > HERCULES_FRAG_SIZE) { + frames_per_chunk = 2; + } + if (session_tx->payloadlen + HERCULES_MAX_HEADERLEN > 2*HERCULES_FRAG_SIZE) { + frames_per_chunk = 3; } - allocate_tx_frames(server, frame_addrs); + + // We need to claim up to 3 frames per chunk and the chunks may need to be sent + // via different interfaces + u64 frame_addrs[server->num_ifaces][3*SEND_QUEUE_ENTRIES_PER_UNIT]; + memset(frame_addrs, 0xFF, sizeof(frame_addrs)); + allocate_tx_frames(server, frame_addrs, num_chunks_in_unit, frames_per_chunk); + tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, frame_addrs, &unit, args->id, - is_index_transfer); + is_index_transfer, frames_per_chunk); atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); if (++batch > 5){ for (int i = 0; i < server->num_ifaces; i++){ From 31d6a1770b2b0ce4e1de09f39a28a2a8bc2baf69 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 13 Nov 2024 13:03:29 +0100 Subject: [PATCH 103/112] fix possible segfault --- hercules.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hercules.c b/hercules.c index b23021b..7e51d55 100644 --- a/hercules.c +++ b/hercules.c @@ -345,8 +345,10 @@ static void destroy_session_tx(struct hercules_server *server, struct path_set *pathset = session->tx_state->pathset; for (u32 i = 0; i < pathset->n_paths; i++) { - destroy_ccontrol_state(pathset->paths[i].cc_state); - pathset->paths[i].cc_state = NULL; + if (pathset->paths[i].cc_state) { + destroy_ccontrol_state(pathset->paths[i].cc_state); + pathset->paths[i].cc_state = NULL; + } } FREE_NULL(session->tx_state->pathset); From bab4acbce2433453b3bc44fe45fa0a4a5dba620c Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Thu, 28 Nov 2024 11:00:31 +0100 Subject: [PATCH 104/112] use multibuf only if supported, config option --- doc/hercules.conf.5 | 7 ++++++ doc/hercules.conf.5.md | 9 ++++++++ hercules.c | 52 ++++++++++++++++++++++++++++++++---------- hercules.conf.sample | 5 ++++ hercules.h | 11 ++++++--- monitor/config.go | 1 + xdp.c | 17 +++++++++----- 7 files changed, 81 insertions(+), 21 deletions(-) diff --git a/doc/hercules.conf.5 b/doc/hercules.conf.5 index e368797..7326897 100644 --- a/doc/hercules.conf.5 +++ b/doc/hercules.conf.5 @@ -129,6 +129,13 @@ If your combination of NIC/drivers supports XDP in zero-copy mode, enabling it here will likely improve performance. The default value is .Ar false . +.It Ic XDPMultiBuffer Ns = Ns Ar bool +If the system does not support XDP in multibuffer mode, this option can be used +to disable it. +As this functionality is required for jumbo frame support, +disabling it limits the packet size to 3000B. +The default value is +.Ar true . .It Ic Queue Ns = Ns Ar int Specify the NIC RX queue on which to receive packets. The default value is diff --git a/doc/hercules.conf.5.md b/doc/hercules.conf.5.md index cb1d56f..266dcd7 100644 --- a/doc/hercules.conf.5.md +++ b/doc/hercules.conf.5.md @@ -156,6 +156,15 @@ The following general configuration options are available: > The default value is > *false*. +**XDPMultiBuffer**=*bool* + +> If the system does not support XDP in multibuffer mode, this option can be used +> to disable it. +> As this functionality is required for jumbo frame support, +> disabling it limits the packet size to 3000B. +> The default value is +> *true*. + **Queue**=*int* > Specify the NIC RX queue on which to receive packets. diff --git a/hercules.c b/hercules.c index b0b3ccc..1b4770e 100644 --- a/hercules.c +++ b/hercules.c @@ -318,6 +318,13 @@ static struct hercules_session *make_session( s->error = SESSION_ERROR_NONE; s->payloadlen = payloadlen; debug_printf("Using payloadlen %u", payloadlen); + s->frames_per_chunk = 1; + if (s->payloadlen + HERCULES_MAX_HEADERLEN > HERCULES_FRAG_SIZE) { + s->frames_per_chunk = 2; + } + if (s->payloadlen + HERCULES_MAX_HEADERLEN > 2*HERCULES_FRAG_SIZE) { + s->frames_per_chunk = 3; + } s->jobid = job_id; s->peer = *peer_addr; s->last_pkt_sent = 0; @@ -2013,6 +2020,8 @@ static struct xdp_desc *prepare_frame(struct xsk_socket_info *xsk, u64 addr, u32 { struct xdp_desc *tx_desc = xsk_ring_prod__tx_desc(&xsk->tx, prod_tx_idx); tx_desc->addr = addr; + tx_desc->len = 0; + tx_desc->options = 0; return tx_desc; } @@ -2918,26 +2927,17 @@ static void tx_send_p(void *arg) { num_chunks_in_unit++; } - // We need to allocate 1,2 or 3 frames per chunk, depending on the payload length. - // TODO move this to session creation, no need to recompute - // TODO before setting, check frags supported - int frames_per_chunk = 1; - if (session_tx->payloadlen + HERCULES_MAX_HEADERLEN > HERCULES_FRAG_SIZE) { - frames_per_chunk = 2; - } - if (session_tx->payloadlen + HERCULES_MAX_HEADERLEN > 2*HERCULES_FRAG_SIZE) { - frames_per_chunk = 3; - } + assert (server->have_frags_support || session_tx->frames_per_chunk == 1); // We need to claim up to 3 frames per chunk and the chunks may need to be sent // via different interfaces u64 frame_addrs[server->num_ifaces][3*SEND_QUEUE_ENTRIES_PER_UNIT]; memset(frame_addrs, 0xFF, sizeof(frame_addrs)); - allocate_tx_frames(server, frame_addrs, num_chunks_in_unit, frames_per_chunk); + allocate_tx_frames(server, frame_addrs, num_chunks_in_unit, session_tx->frames_per_chunk); tx_handle_send_queue_unit(server, session_tx->tx_state, args->xsks, frame_addrs, &unit, args->id, - is_index_transfer, frames_per_chunk); + is_index_transfer, session_tx->frames_per_chunk); atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); if (++batch > 5){ for (int i = 0; i < server->num_ifaces; i++){ @@ -3194,6 +3194,17 @@ static void new_tx_if_available(struct hercules_server *server) { free(destname); return; } + if (!server->have_frags_support && + (size_t)payloadlen + HERCULES_MAX_HEADERLEN > HERCULES_FRAG_SIZE) { + debug_printf( + "MTU too large: would exceed %uB and running without frags support.", + HERCULES_FRAG_SIZE); + monitor_update_job(server->usock, jobid, SESSION_STATE_DONE, + SESSION_ERROR_BAD_MTU, 0, 0); + free(fname); + free(destname); + return; + } size_t filesize; void *index; @@ -3676,6 +3687,11 @@ static void events_p(void *arg) { continue; } + // Drop packets larger than a single fragment if running without frags support + if (len > HERCULES_FRAG_SIZE && !server->have_frags_support) { + continue; + } + u8 scmp_bad_path = PCC_NO_PATH; u16 scmp_bad_port = 0; const char *rbudp_pkt = @@ -4058,6 +4074,7 @@ int main(int argc, char *argv[]) { config.queue = 0; config.configure_queues = true; config.enable_pcc = true; + config.enable_multibuf = true; config.rate_limit = 3333333; config.n_threads = 1; config.xdp_mode = XDP_COPY; @@ -4229,6 +4246,17 @@ int main(int argc, char *argv[]) { } } + // XDP multibuf enable + toml_datum_t enable_multibuf = toml_bool_in(conf, "XDPMultiBuffer"); + if (enable_multibuf.ok) { + config.enable_multibuf = (enable_multibuf.u.b); + } else { + if (toml_key_exists(conf, "XDPMultiBuffer")) { + fprintf(stderr, "Error parsing XDPMultiBuffer\n"); + exit(1); + } + } + // RX/TX only toml_datum_t tx_only = toml_bool_in(conf, "TxOnly"); if (tx_only.ok) { diff --git a/hercules.conf.sample b/hercules.conf.sample index 65ba635..c008e09 100644 --- a/hercules.conf.sample +++ b/hercules.conf.sample @@ -50,6 +50,11 @@ XDPZeroCopy = true # Specify the NIC RX queue on which to receive packets Queue = 0 +# If the system does not support XDP in multibuffer mode, it can be disabled. +# As this functionality is required for jumbo frame support, +# disabling it limits the packet size to 3000B. +XDPMultiBuffer = false + # For Hercules to receive traffic, packets must be redirected to the queue # specified above. Hercules will try to configure this automatically, but this # behaviour can be overridden, e.g. if you wish to set custom rules or automatic diff --git a/hercules.h b/hercules.h index 3fedd1b..454cc08 100644 --- a/hercules.h +++ b/hercules.h @@ -215,9 +215,11 @@ struct hercules_session { struct hercules_app_addr peer; //< UDP/SCION address of peer (big endian) u64 jobid; //< The monitor's ID for this job - u32 payloadlen; //< The payload length used for this transfer. Note that - // the payload length includes the rbudp header while the - // chunk length does not. + u32 payloadlen; //< The payload length used for this transfer. Note that + // the payload length includes the rbudp header while the + // chunk length does not. + u32 frames_per_chunk; // How many umem frames required per packet when running + // in multibuffer mode }; /// SERVER @@ -244,6 +246,7 @@ struct hercules_config { int queue; bool configure_queues; bool enable_pcc; + bool enable_multibuf; int rate_limit; // Sending rate limit, only used when PCC is enabled bool tx_only; // Run in send-only mode, do not start RX threads. bool rx_only; // Run in receive-only mode, do not start TX threads. @@ -273,6 +276,8 @@ struct hercules_server { // waiting to be freed unsigned int *ifindices; + bool have_frags_support; // Whether we managed to bind with xdp multibuffer + // support enabled on all interfaces. int num_ifaces; struct hercules_interface ifaces[]; }; diff --git a/monitor/config.go b/monitor/config.go index 46caa87..a86562a 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -75,6 +75,7 @@ type MonitorConfig struct { Queue int ConfigureQueues bool EnablePCC bool + XDPMultiBuffer bool TxOnly bool RxOnly bool RateLimit int diff --git a/xdp.c b/xdp.c index 59cad76..67c7e3c 100644 --- a/xdp.c +++ b/xdp.c @@ -370,6 +370,11 @@ int load_xsk_redirect_userspace(struct hercules_server *server, } int xdp_setup(struct hercules_server *server) { + server->have_frags_support = true; + if (!server->config.enable_multibuf) { + debug_printf("Multibuf disabled by config"); + server->have_frags_support = false; + } for (int i = 0; i < server->num_ifaces; i++) { debug_printf("Preparing interface %d", i); // Prepare UMEM for XSK sockets @@ -429,6 +434,11 @@ int xdp_setup(struct hercules_server *server) { cfg.xdp_flags = server->config.xdp_flags; cfg.bind_flags = server->config.xdp_mode; + // Kernel 6.6 is required to bind with the following flag (jumbo frame support). + // We try to bind with the flag and again without, if the first one fails, to + // test for support. + // XXX This does not mean the underlying driver/nic supports it, querying for + // that seems not to work. See the comment in load_xsk_redirect_userspace above. cfg.bind_flags |= XDP_USE_SG; ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, server->config.queue, umem->umem, &xsk->rx, @@ -443,13 +453,8 @@ int xdp_setup(struct hercules_server *server) { fprintf(stderr, "Error creating XDP socket\n"); return -ret; } + server->have_frags_support = false; } - /* ret = bpf_get_link_xdp_id(server->ifaces[i].ifid, */ - /* &server->ifaces[i].prog_id, */ - /* server->config.xdp_flags); */ - /* if (ret) { */ - /* return -ret; */ - /* } */ server->ifaces[i].xsks[t] = xsk; } server->ifaces[i].num_sockets = server->config.n_threads; From f7d1618e07c9859c39a1940e2f22a9963ee8c646 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 15 Nov 2024 13:05:18 +0100 Subject: [PATCH 105/112] make sure tx_p does not get stuck --- hercules.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hercules.c b/hercules.c index 7e51d55..014db04 100644 --- a/hercules.c +++ b/hercules.c @@ -2047,6 +2047,9 @@ static void produce_batch(struct hercules_server *server, u32 num_chunks_in_unit; struct send_queue_unit *unit = NULL; for(chk = 0; chk < num_chunks; chk++) { + if (!(session_state_is_running(session->state))){ + return; + } if(unit == NULL) { unit = send_queue_reserve(session->send_queue); num_chunks_in_unit = 0; From 62c3bcd0c475194ada6202fe1172d29f1690c01c Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 2 Dec 2024 11:20:14 +0100 Subject: [PATCH 106/112] remove copy from multibuf TX --- hercules.c | 169 +++++++++++++++++++++++++++++++++----------- libscion_checksum.h | 2 +- 2 files changed, 127 insertions(+), 44 deletions(-) diff --git a/hercules.c b/hercules.c index 1b4770e..7cb026b 100644 --- a/hercules.c +++ b/hercules.c @@ -852,6 +852,42 @@ static void stitch_checksum_with_dst(const struct hercules_path *path, u16 preco mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); } +// Used when the original header (and, thus, the checksum) does not contain the +// correct destination port. Multibuffer (xdp fragments) version +static void stitch_checksum_with_dst_multibuf(const struct hercules_path *path, + u16 precomputed_checksum, + void *frames_data[3], + struct xdp_desc *tx_descs[3]) { + chk_input chk_input_s; + chk_input *chksum_struc = init_chk_input(&chk_input_s, 6); + assert(chksum_struc); + + char *payload = frames_data[0] + path->headerlen; + u16 udp_src_le = ntohs(*(u16 *)(payload - 8)); + u16 udp_dst_le = ntohs(*(u16 *)(payload - 6)); + + precomputed_checksum = + ~precomputed_checksum; // take one complement of precomputed checksum + chk_add_chunk(chksum_struc, (u8 *)&precomputed_checksum, + 2); // add precomputed header checksum + chk_add_chunk(chksum_struc, (u8 *)&udp_src_le, 2); + chk_add_chunk(chksum_struc, (u8 *)&udp_dst_le, 2); + + chk_add_chunk(chksum_struc, (u8 *)payload, + tx_descs[0]->len - path->headerlen); // add payload (frag 1) + if (tx_descs[1]) { + chk_add_chunk(chksum_struc, (u8 *)frames_data[1], + tx_descs[1]->len); // add payload (frag 2) + } + if (tx_descs[2]) { + chk_add_chunk(chksum_struc, (u8 *)frames_data[2], + tx_descs[2]->len); // add payload (frag 3) + } + u16 pkt_checksum = checksum(chksum_struc); + + mempcpy(payload - 2, &pkt_checksum, sizeof(pkt_checksum)); +} + // Used when the original header (and checksum) already contains the correct destination port. static void stitch_checksum(const struct hercules_path *path, u16 precomputed_checksum, char *pkt) { @@ -886,6 +922,70 @@ static void fill_rbudp_pkt(void *rbudp_pkt, u32 chunk_idx, u8 path_idx, u8 flags debug_print_rbudp_pkt(rbudp_pkt, false); } +// Multibuffer (xdp frags) version of fill_rbudp_pkt +static void fill_pkt_multibuf(void *frame_data[3], + struct xdp_desc *frame_desc[3], u32 chunk_idx, + u8 path_idx, u8 flags, sequence_number seqnr, + const char *data, size_t n, + const struct hercules_path *path) { + void *rbudp_pkt = + mempcpy(frame_data[0], path->header.header, path->headerlen); + struct hercules_header *hdr = (struct hercules_header *)rbudp_pkt; + hdr->chunk_idx = chunk_idx; + hdr->path = path_idx; + hdr->flags = flags; + hdr->seqno = seqnr; + + int chunk_bytes_left = n; // data bytes to be copied + int padding_bytes_left = (rbudp_headerlen + n < path->payloadlen) + ? path->payloadlen - rbudp_headerlen - n + : 0; + + for (int f = 0; f < 3; f++) { + int frame_space_left = HERCULES_FRAG_SIZE; + void *pkt_payload = frame_data[f]; + if (pkt_payload == NULL) { + break; + } + int frag_len = 0; + if (f == 0) { + // Fragment 0 already contains the headers + frame_space_left = HERCULES_FRAG_SIZE - path->headerlen - + rbudp_headerlen; // space left in frame + pkt_payload = hdr->data; + frag_len = path->headerlen + rbudp_headerlen; + } + + int data_bytes = frame_space_left > chunk_bytes_left ? chunk_bytes_left + : frame_space_left; + pkt_payload = mempcpy(pkt_payload, data, data_bytes); + data += data_bytes; + chunk_bytes_left -= data_bytes; + frame_space_left -= data_bytes; + frag_len += data_bytes; + + int padding_bytes = frame_space_left > padding_bytes_left + ? padding_bytes_left + : frame_space_left; + memset(pkt_payload, 0, padding_bytes); + pkt_payload += padding_bytes; + padding_bytes_left -= padding_bytes; + frame_space_left -= padding_bytes; + frag_len += padding_bytes; + + frame_desc[f]->len = frag_len; + frame_desc[f]->options = + (chunk_bytes_left > 0 || padding_bytes_left > 0) ? + XDP_PKT_CONTD : 0; + /* debug_printf("copied> frag %d: %d bytes", f, frag_len); */ + if (chunk_bytes_left == 0 && padding_bytes_left == 0) { + break; + } + } + assert(chunk_bytes_left == 0 && "Packet did not fit in fragments?"); + assert(padding_bytes_left == 0 && "Padding did not fit in fragments?"); +} + // Check an initial (HS) packet and return a pointer to it in *parsed_pkt static bool rbudp_check_initial(struct hercules_control_packet *pkt, size_t len, struct rbudp_initial_pkt **parsed_pkt) { @@ -2037,7 +2137,6 @@ static inline void tx_handle_send_queue_unit_for_iface( u64 frame_addrs[3*SEND_QUEUE_ENTRIES_PER_UNIT], struct send_queue_unit *unit, u32 thread_id, bool is_index_transfer, int frames_per_chunk) { u32 num_chunks_in_unit = 0; - u8 pktbuf[9000]; /// HACK copy, remove me struct path_set *pathset = pathset_read(tx_state, thread_id); for (u32 i = 0; i < SEND_QUEUE_ENTRIES_PER_UNIT; i++) { if (unit->paths[i] == UINT8_MAX) { @@ -2099,8 +2198,8 @@ static inline void tx_handle_send_queue_unit_for_iface( umin64(tx_state->chunklen, tx_state->index_size - chunk_start); } - struct xdp_desc *tx_descs[3]; - void *frames_data[3]; + struct xdp_desc *tx_descs[3] = {NULL, NULL, NULL}; + void *frames_data[3] = {NULL, NULL, NULL}; for (int i = 0; i < frames_per_chunk; i++){ /* debug_printf("using frame %p", frame_addrs[current_frame]); */ tx_descs[i] = prepare_frame(xsk, frame_addrs[current_frame], idx+current_frame); @@ -2108,22 +2207,8 @@ static inline void tx_handle_send_queue_unit_for_iface( frame_addrs[current_frame] = -1; current_frame++; } - void *pkt = pktbuf; // HACK memcpy - void *rbudp_pkt = mempcpy(pkt, path->header.header, path->headerlen); + void *pkt = frames_data[0]; -#ifdef RANDOMIZE_FLOWID - short *flowId = (short *)&( - (char *)pkt)[44]; // ethernet hdr (14), ip hdr (20), udp hdr (8), - // offset of flowId in scion hdr - // XXX ^ ignores first 4 bits of flowId - *flowId = atomic_fetch_add(&flowIdCtr, 1); -#endif -#ifdef RANDOMIZE_UNDERLAY_SRC - short *src_port = - (short *)&((char *)pkt)[34]; // Ethernet (14) + IP (20), src port - // is first 2 bytes of udp header - *src_port = atomic_fetch_add(&src_port_ctr, 1); -#endif u8 track_path = PCC_NO_PATH; // put path_idx iff PCC is enabled sequence_number seqnr = 0; if (path->cc_state != NULL) { @@ -2136,28 +2221,26 @@ static inline void tx_handle_send_queue_unit_for_iface( flags |= PKT_FLAG_IS_INDEX; payload = tx_state->index; } + + fill_pkt_multibuf(frames_data, tx_descs, chunk_idx, track_path, flags, seqnr, + payload + chunk_start, len, path); stitch_dst_port(path, tx_state->session->peer.port, pkt); stitch_src_port(path, tx_state->src_port, pkt); - fill_rbudp_pkt(rbudp_pkt, chunk_idx, track_path, flags, seqnr, - payload + chunk_start, len, path->payloadlen); - stitch_checksum_with_dst(path, path->header.checksum, pkt); - - u64 pkt_total_len = path->headerlen + path->payloadlen; - u64 pkt_bytes_left = pkt_total_len; - u64 pkt_bytes[3]; - void *next_frag = pktbuf; - for (int f = 0; f < frames_per_chunk; f++) { - pkt_bytes[f] = pkt_bytes_left > HERCULES_FRAG_SIZE ? HERCULES_FRAG_SIZE - : pkt_bytes_left; - memcpy(frames_data[f], next_frag, pkt_bytes[f]); - next_frag += pkt_bytes[f]; - pkt_bytes_left -= pkt_bytes[f]; - tx_descs[f]->len = pkt_bytes[f]; - tx_descs[f]->options = (pkt_bytes_left > 0) ? XDP_PKT_CONTD : 0; - } - /* debug_printf("copied 1:%llu | 2:%llu | 3:%llu", pkt_bytes[0], pkt_bytes[1], */ - /* pkt_bytes[2]); */ - assert(pkt_bytes_left == 0 && "Packet did not fit in fragments?"); +#ifdef RANDOMIZE_FLOWID + short *flowId = (short *)&( + (char *)pkt)[44]; // ethernet hdr (14), ip hdr (20), udp hdr (8), + // offset of flowId in scion hdr + // XXX ^ ignores first 4 bits of flowId + *flowId = atomic_fetch_add(&flowIdCtr, 1); +#endif +#ifdef RANDOMIZE_UNDERLAY_SRC + short *src_port = + (short *)&((char *)pkt)[34]; // Ethernet (14) + IP (20), src port + // is first 2 bytes of udp header + *src_port = atomic_fetch_add(&src_port_ctr, 1); +#endif + stitch_checksum_with_dst_multibuf(path, path->header.checksum, frames_data, + tx_descs); } xsk_ring_prod__submit(&xsk->tx, to_reserve); } @@ -2939,11 +3022,11 @@ static void tx_send_p(void *arg) { frame_addrs, &unit, args->id, is_index_transfer, session_tx->frames_per_chunk); atomic_fetch_add(&session_tx->tx_npkts, num_chunks_in_unit); - if (++batch > 5){ - for (int i = 0; i < server->num_ifaces; i++){ - kick_tx(server, args->xsks[i]); - } - batch = 0; + if (++batch > 5) { + for (int i = 0; i < server->num_ifaces; i++) { + kick_tx(server, args->xsks[i]); + } + batch = 0; } } } diff --git a/libscion_checksum.h b/libscion_checksum.h index 73ae09a..1541586 100644 --- a/libscion_checksum.h +++ b/libscion_checksum.h @@ -1,7 +1,7 @@ #ifndef _CHECKSUM_H_ #define _CHECKSUM_H_ -#define SCION_MAX_CHECKSUM_CHUNKS 5 +#define SCION_MAX_CHECKSUM_CHUNKS 6 typedef struct { uint8_t idx; From 06665ff24e6ffff5edc3340dfa9450007ddc95a5 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Fri, 15 Nov 2024 11:10:17 +0100 Subject: [PATCH 107/112] add error control packets --- hercules.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++-- hercules.h | 3 ++ packet.h | 13 +++++++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/hercules.c b/hercules.c index 014db04..c2e431c 100644 --- a/hercules.c +++ b/hercules.c @@ -148,7 +148,11 @@ static inline struct hercules_interface *get_interface_by_id(struct hercules_ser // by the events_p thread. static inline void quit_session(struct hercules_session *s, enum session_error err) { - s->error = err; + enum session_error none = SESSION_ERROR_NONE; + int ret = atomic_compare_exchange_strong(&s->error, &none, err); + if (ret) { + debug_printf("Stopping session with error: %s", hercules_strerror(err)); + } } static u32 ack__max_num_entries(u32 len) @@ -253,6 +257,10 @@ void debug_print_rbudp_pkt(const char *pkt, bool recv) { case CONTROL_PACKET_TYPE_RTT: printf("%s RTT\n", prefix); break; + case CONTROL_PACKET_TYPE_ERR: + printf("%s ERROR: %llu (%s)\n", prefix, cp->payload.err.hercules_error, + hercules_strerror(cp->payload.err.hercules_error)); + break; default: printf("%s ?? UNKNOWN CONTROL PACKET TYPE", prefix); break; @@ -1096,6 +1104,9 @@ static void rx_receive_batch(struct hercules_server *server, struct hercules_session *session_rx = lookup_session_rx(server, pkt_dst_port); + if (session_rx && session_rx->state == SESSION_STATE_DONE){ + session_rx->rx_state->send_err = true; + } if (session_rx == NULL || !session_state_is_running(session_rx->state)) { continue; @@ -1538,7 +1549,7 @@ static void rx_send_acks(struct hercules_server *server, struct receiver_state * const size_t max_entries = ack__max_num_entries(path.payloadlen - rbudp_headerlen - sizeof(control_pkt.type)); // send an empty ACK to keep connection alive until first packet arrives - debug_printf("starting ack at %u", rx_state->next_chunk_to_ack); + /* debug_printf("starting ack at %u", rx_state->next_chunk_to_ack); */ u32 curr = fill_ack_pkt(rx_state, rx_state->next_chunk_to_ack, &control_pkt.payload.ack, max_entries, is_index_transfer); @@ -1558,7 +1569,7 @@ static void rx_send_acks(struct hercules_server *server, struct receiver_state * curr > rx_state->received_chunks.max_set) { rx_state->next_chunk_to_ack = 0; } - debug_printf("packets in ack batch: %u", pkts); + /* debug_printf("packets in ack batch: %u", pkts); */ } @@ -1847,6 +1858,62 @@ static void tx_send_rtt(struct hercules_server *server, session->last_pkt_sent = timestamp; } +static void tx_send_error(struct hercules_server *server, + struct hercules_session *session) { + /* debug_printf("Sending error packet"); */ + if (!session->tx_state || !session->tx_state->pathset || session->tx_state->pathset->n_paths == 0) { + return; + } + /* debug_printf("Current state: %d, err %d", session->state, session->error); */ + const struct hercules_path *path = &session->tx_state->pathset->paths[0]; + + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path->header.header, path->headerlen); + + struct hercules_control_packet pld = { + .type = CONTROL_PACKET_TYPE_ERR, + .payload.err = {session->error}, + }; + + stitch_src_port(path, session->tx_state->src_port, buf); + stitch_dst_port(path, session->peer.port, buf); + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&pld, + sizeof(pld), path->payloadlen); + stitch_checksum_with_dst(path, path->header.checksum, buf); + + send_eth_frame(server, path, buf); + atomic_fetch_add(&session->tx_npkts, 1); +} + +static void rx_send_error(struct hercules_server *server, + struct hercules_session *session) { + /* debug_printf("Sending error packet"); */ + if (!session->rx_state) { + return; + } + struct hercules_path path; + if(!rx_get_reply_path(session->rx_state, &path)) { + debug_printf("no reply path"); + return; + } + + char buf[HERCULES_MAX_PKTSIZE]; + void *rbudp_pkt = mempcpy(buf, path.header.header, path.headerlen); + + struct hercules_control_packet pld = { + .type = CONTROL_PACKET_TYPE_ERR, + .payload.err = {session->error}, + }; + + stitch_src_port(&path, session->rx_state->src_port, buf); + fill_rbudp_pkt(rbudp_pkt, UINT_MAX, PCC_NO_PATH, 0, 0, (char *)&pld, + sizeof(pld), path.payloadlen); + stitch_checksum(&path, path.header.checksum, buf); + + send_eth_frame(server, &path, buf); + atomic_fetch_add(&session->tx_npkts, 1); +} + static void rate_limit_tx(struct sender_state *tx_state) { if(tx_state->prev_tx_npkts_queued + RATE_LIMIT_CHECK > tx_state->tx_npkts_queued) @@ -3438,6 +3505,18 @@ static void rx_send_cts(struct hercules_server *server, int s) { } } +// Send an error if rx_p has received a packet for a closed session. +static void rx_send_err(struct hercules_server *server, int s) { + struct hercules_session *session_rx = server->sessions_rx[s]; + if (session_rx != NULL && session_rx->state == SESSION_STATE_DONE) { + struct receiver_state *rx_state = session_rx->rx_state; + if (rx_state->send_err) { + rx_send_error(server, session_rx); + rx_state->send_err = false; + } + } +} + // To stop a session, any thread may set its error to something other than // ERROR_NONE (generally via quit_session()). If the error is set, this changes // the sessions state to DONE. The state update needs to happen in the events_p @@ -3501,6 +3580,7 @@ static void events_p(void *arg) { new_tx_if_available(server); tx_retransmit_initial(server, current_slot, now); rx_send_cts(server, current_slot); + rx_send_err(server, current_slot); #ifdef PRINT_STATS if (now > lastprint + print_stats_interval) { print_session_stats(server, now, tx_stats, rx_stats); @@ -3600,6 +3680,15 @@ static void events_p(void *arg) { lookup_session_rx(server, pkt_dst_port); struct hercules_session *session_tx = lookup_session_tx(server, pkt_dst_port); + if (session_rx != NULL && session_rx->state == SESSION_STATE_DONE && + cp->type != CONTROL_PACKET_TYPE_ERR) { + rx_send_error(server, session_rx); + } + if (session_tx != NULL && session_tx->state == SESSION_STATE_DONE && + cp->type != CONTROL_PACKET_TYPE_ERR) { + tx_send_error(server, session_tx); + continue; + } switch (cp->type) { case CONTROL_PACKET_TYPE_INITIAL:; @@ -3746,6 +3835,19 @@ static void events_p(void *arg) { } break; + case CONTROL_PACKET_TYPE_ERR: + debug_printf("ERR received"); + debug_print_rbudp_pkt(rbudp_pkt, true); + if (session_rx && src_matches_address(session_rx, &pkt_source)) { + quit_session(session_rx, cp->payload.err.hercules_error); + count_received_pkt(session_rx, h->path); + } + if (session_tx && src_matches_address(session_tx, &pkt_source)) { + quit_session(session_tx, cp->payload.err.hercules_error); + count_received_pkt(session_tx, h->path); + } + break; + default: debug_printf("Received control packet of unknown type"); break; diff --git a/hercules.h b/hercules.h index 460e080..b6218a9 100644 --- a/hercules.h +++ b/hercules.h @@ -112,6 +112,9 @@ struct receiver_state { u64 start_time; // Start/end time of the current transfer u64 end_time; u64 sent_initial_at; + _Atomic bool send_err; // Whether the control thread should send an error. Set + // by the receive thread when it receives a packet for + // an already stopped session. }; /// SENDER diff --git a/packet.h b/packet.h index 82bef95..de4fd00 100644 --- a/packet.h +++ b/packet.h @@ -158,16 +158,29 @@ struct rbudp_ack_pkt { } acks[256]; //!< list of ranges that are ACKed }; +// Packets used to communicate errors to peer +// When a packet is received that is destined to a session +// which is in state SESSION_STATE_DONE, an error control packet containing the +// session's error is sent in reply. +// Upon reception of an error packet, the error is extracted, applied to the +// originating session and the session stopped. +// No error packets are sent in reply to received error packets. +struct rbudp_err_pkt { + __u64 hercules_error; +}; + #define CONTROL_PACKET_TYPE_INITIAL 0 #define CONTROL_PACKET_TYPE_ACK 1 #define CONTROL_PACKET_TYPE_NACK 2 #define CONTROL_PACKET_TYPE_RTT 3 +#define CONTROL_PACKET_TYPE_ERR 4 struct hercules_control_packet { __u8 type; union { struct rbudp_initial_pkt initial; struct rbudp_ack_pkt ack; + struct rbudp_err_pkt err; } payload; }; From 7913ad6efa38ed84fa47e99177ee7a16b30af69f Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 2 Dec 2024 15:32:09 +0100 Subject: [PATCH 108/112] remove copy from multibuf RX --- hercules.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 153 insertions(+), 14 deletions(-) diff --git a/hercules.c b/hercules.c index 7cb026b..1e3c94f 100644 --- a/hercules.c +++ b/hercules.c @@ -534,6 +534,85 @@ u16 scion_udp_checksum(const u8 *buf, int len) return computed_checksum; } +// Multibuffer (xdp frags) version of scion_udp_checksum +u16 scion_udp_checksum_multibuf(const void *pkt_data[3], + const struct xdp_desc *pkt_descs[3], int len) { + chk_input chk_input_s; + chk_input *input = init_chk_input( + &chk_input_s, 4); // initialize checksum_parse for 4 chunks + if (!input) { + debug_printf("Unable to initialize checksum input: %p", input); + return 0; + } + + struct scionhdr *scionh = + (struct scionhdr *)(pkt_data[0] + sizeof(struct ether_header) + + sizeof(struct iphdr) + sizeof(struct udphdr)); + u8 *scionh_ptr = (u8 *)scionh; + + // XXX construct a pseudo header that is compatible with the checksum + // computation in scionproto/go/lib/slayers/scion.go + u32 pseudo_header_size = sizeof(struct scionaddrhdr_ipv4) + + sizeof(struct udphdr) + 2 * sizeof(u32); + u32 pseudo_header[pseudo_header_size / sizeof(u32)]; + + // SCION address header + const u32 *addr_hdr = (u32 *)(scionh_ptr + sizeof(struct scionhdr)); + size_t i = 0; + for (; i < sizeof(struct scionaddrhdr_ipv4) / sizeof(u32); i++) { + pseudo_header[i] = ntohl(addr_hdr[i]); + } + + pseudo_header[i++] = len; + + __u8 next_header = scionh->next_header; + size_t next_offset = scionh->header_len * SCION_HEADER_LINELEN; + if (next_header == SCION_HEADER_HBH) { + next_header = *(scionh_ptr + next_offset); + next_offset += + (*(scionh_ptr + next_offset + 1) + 1) * SCION_HEADER_LINELEN; + } + if (next_header == SCION_HEADER_E2E) { + next_header = *(scionh_ptr + next_offset); + next_offset += + (*(scionh_ptr + next_offset + 1) + 1) * SCION_HEADER_LINELEN; + } + + pseudo_header[i++] = next_header; + + // UDP header + const u32 *udp_hdr = + (const u32 *)(scionh_ptr + next_offset); // skip over SCION header and + // extension headers + for (int offset = i; i - offset < sizeof(struct udphdr) / sizeof(u32); + i++) { + pseudo_header[i] = ntohl(udp_hdr[i - offset]); + } + pseudo_header[i - 1] &= 0xFFFF0000; // zero-out UDP checksum + chk_add_chunk(input, (u8 *)pseudo_header, pseudo_header_size); + + // Length in UDP header includes header size, so subtract it. + struct udphdr *udphdr = (struct udphdr *)udp_hdr; + u16 payload_len = ntohs(udphdr->len) - sizeof(struct udphdr); + if (payload_len != len - sizeof(struct udphdr)) { + debug_printf("Invalid payload_len: Got %u, Expected: %d", payload_len, + len - (int)sizeof(struct udphdr)); + return 0; + } + const u8 *payload = (u8 *)(udphdr + 1); // skip over UDP header + u64 hdr_bytes = ((u64)udphdr) - ((u64)pkt_data[0]); + chk_add_chunk(input, payload, pkt_descs[0]->len - hdr_bytes - 8); + if (pkt_descs[1]) { + chk_add_chunk(input, pkt_data[1], pkt_descs[1]->len); + } + if (pkt_descs[2]) { + chk_add_chunk(input, pkt_data[2], pkt_descs[2]->len); + } + + u16 computed_checksum = checksum(input); + return computed_checksum; +} + // Parse ethernet/IP/UDP/SCION/UDP packet, // this is an extension to the parse_pkt // function below only doing the checking @@ -572,6 +651,39 @@ static const char *parse_pkt_fast_path(const char *pkt, size_t length, bool chec return pkt + offset; } +static u64 parse_pkt_fast_path_multibuf(const void *pkt_data[3], + const struct xdp_desc *pkt_descs[3], + size_t length, bool check, + size_t offset) { + if (offset == UINT32_MAX) { + offset = *(int *)pkt_data[0]; + } + if (offset > length) { + debug_printf("Offset past end of packet (fragmented?)"); + return 0; + } + if (offset > pkt_descs[0]->len) { + debug_printf("Headers extend past end of first fragment!"); + return 0; + } + if (check) { + struct udphdr *l4udph = (struct udphdr *)(pkt_data[0] + offset) - 1; + u16 header_checksum = l4udph->check; + if (header_checksum != 0) { + u16 computed_checksum = scion_udp_checksum_multibuf( + pkt_data, pkt_descs, length - offset + sizeof(struct udphdr)); + if (header_checksum != computed_checksum) { + debug_printf( + "Checksum in SCION/UDP header %u " + "does not match computed checksum %u", + ntohs(header_checksum), ntohs(computed_checksum)); + return 0; + } + } + } + return offset; +} + // The SCMP packet contains a copy of the offending message we sent, parse it to // figure out which path/session the SCMP message is referring to. // Returns the offending path's id, or PCC_NO_PATH on failure. @@ -1020,14 +1132,19 @@ static bool rx_received_all(const struct receiver_state *rx_state, return (rx_state->received_chunks.num_set == rx_state->total_chunks); } -static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *pkt, size_t length) +static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const void *pkt_data[3], const struct xdp_desc *pkt_descs[3], size_t rbudp_pkt_offset, u64 pkt_total_len) { - if(length < rbudp_headerlen + rx_state->chunklen) { - debug_printf("packet too short: have %lu, expect %d", length, rbudp_headerlen + rx_state->chunklen ); + size_t rbudp_length = pkt_total_len - rbudp_pkt_offset; + if(rbudp_length < rbudp_headerlen + rx_state->chunklen) { + debug_printf("packet too short: have %lu, expect %d", rbudp_length, rbudp_headerlen + rx_state->chunklen ); + return false; + } + if (pkt_descs[0]->len < rbudp_pkt_offset + rbudp_headerlen){ + debug_printf("first fragment too short for rbudp header"); return false; } - struct hercules_header *hdr = (struct hercules_header *)pkt; + struct hercules_header *hdr = (struct hercules_header *)( pkt_data[0] + rbudp_pkt_offset); bool is_index_transfer = hdr->flags & PKT_FLAG_IS_INDEX; u32 chunk_idx = hdr->chunk_idx; @@ -1100,7 +1217,6 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p } if(!prev) { char *target_ptr = rx_state->mem; - const char *payload = pkt + rbudp_headerlen; const size_t chunk_start = (size_t)chunk_idx * rx_state->chunklen; size_t len = umin64(rx_state->chunklen, rx_state->filesize - chunk_start); @@ -1108,7 +1224,28 @@ static bool handle_rbudp_data_pkt(struct receiver_state *rx_state, const char *p target_ptr = rx_state->index; len = umin64(rx_state->chunklen, rx_state->index_size - chunk_start); } - memcpy(target_ptr + chunk_start, payload, len); + + u64 chunk_bytes_left = len; + void *copy_dst = target_ptr + chunk_start; + for (int f = 0; f < 3; f++){ + if (!pkt_descs[f]){ + break; + } + u64 frag_bytes_left = pkt_descs[f]->len; + const void *frag_data_start = pkt_data[f]; + if (f == 0){ + frag_bytes_left -= (rbudp_headerlen + rbudp_pkt_offset); + frag_data_start = pkt_data[0] + rbudp_headerlen + rbudp_pkt_offset; + } + u64 to_copy = (chunk_bytes_left > frag_bytes_left) ? frag_bytes_left : chunk_bytes_left; + copy_dst = mempcpy(copy_dst, frag_data_start, to_copy); + chunk_bytes_left -= to_copy; + if (chunk_bytes_left == 0) { + break; + } + } + assert(chunk_bytes_left == 0 && "Chunk incomplete after copy?"); + // Update last new pkt timestamp u64 now = get_nsecs(); u64 old_ts = atomic_load(&rx_state->session->last_new_pkt_rcvd); @@ -1202,9 +1339,9 @@ static void rx_receive_batch(struct hercules_server *server, u64 frame_addrs[BATCH_SIZE]; for (size_t i = 0; i < rcvd; i++) { - u8 pktbuf[9000]; + const void *pkt_data[3] = {NULL, NULL, NULL}; + const struct xdp_desc *pkt_descs[3] = {NULL, NULL, NULL}; u64 pkt_total_len = 0; - void *next = pktbuf; for (int f = 0; f < 3; f++) { // For jumbo frames, our packet may consist of up to 3 fragments. // In two cases this is not possible and we just drop the packet: @@ -1217,7 +1354,8 @@ static void rx_receive_batch(struct hercules_server *server, frame_addrs[i] = addr; u32 len = desc->len; /* debug_printf("frag len %u, opts %#x", len, desc->options); */ - next = mempcpy(next, xsk_umem__get_data(xsk->umem->buffer, addr), len); + pkt_data[f] = xsk_umem__get_data(xsk->umem->buffer, addr); + pkt_descs[f] = desc; pkt_total_len += len; bool continues_in_next_frag = (desc->options & XDP_PKT_CONTD); if (continues_in_next_frag) { @@ -1233,12 +1371,13 @@ static void rx_receive_batch(struct hercules_server *server, } } - const char *pkt = pktbuf; - const char *rbudp_pkt = parse_pkt_fast_path(pkt, pkt_total_len, true, UINT32_MAX); - if (!rbudp_pkt) { + const u64 rbudp_pkt_offset = parse_pkt_fast_path_multibuf( + pkt_data, pkt_descs, pkt_total_len, true, UINT32_MAX); + if (!rbudp_pkt_offset) { debug_printf("Unparseable packet on XDP socket, ignoring"); continue; } + const void *rbudp_pkt = pkt_data[0] + rbudp_pkt_offset; u16 pkt_dst_port = ntohs(*(u16 *)(rbudp_pkt - 6)); struct hercules_session *session_rx = @@ -1280,8 +1419,8 @@ static void rx_receive_batch(struct hercules_server *server, atomic_compare_exchange_strong(&session_rx->last_pkt_rcvd, &old_last_pkt_rcvd, now); } - if (!handle_rbudp_data_pkt(session_rx->rx_state, rbudp_pkt, - pkt_total_len - (rbudp_pkt - pkt))) { + if (!handle_rbudp_data_pkt(session_rx->rx_state, pkt_data, pkt_descs, + rbudp_pkt_offset, pkt_total_len)) { debug_printf("Non-data packet on XDP socket? Ignoring."); } } From d2fb21cb80f657d1c672d95cfb04f35a83c7b31a Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 27 Nov 2024 16:22:18 +0100 Subject: [PATCH 109/112] hcp: complain if dest port not set --- hcp/hcp.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hcp/hcp.go b/hcp/hcp.go index 741683a..eff1872 100644 --- a/hcp/hcp.go +++ b/hcp/hcp.go @@ -53,12 +53,17 @@ func main() { dst_path := flag.Arg(3) // Try to parse to catch errors - _, err := snet.ParseUDPAddr(dst_addr) + dst_parsed, err := snet.ParseUDPAddr(dst_addr) if err != nil { fmt.Println("Invalid destination address.", err) os.Exit(2) } + if dst_parsed.Host.Port == 0 { + fmt.Println("Destination port not set!"); + os.Exit(2) + } + filesize := -1 if !*no_stat_file { filesize, err = stat(src_api, src_path) From 82b1555ca980408fa2fc66df94416a249af7d152 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Wed, 4 Dec 2024 10:26:45 +0100 Subject: [PATCH 110/112] fix possible memory leak --- hercules.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hercules.c b/hercules.c index 1e3c94f..4efc434 100644 --- a/hercules.c +++ b/hercules.c @@ -3620,6 +3620,7 @@ static void tx_update_paths(struct hercules_server *server, int s, u64 now) { calloc(old_pathset->n_paths, sizeof(*replaced_cc)); if (replaced_cc == NULL) { free(paths); + free(new_pathset); return; } From 0df87c3343143fe29d15e4ea01b08955c00c2d05 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Mon, 9 Dec 2024 15:51:10 +0100 Subject: [PATCH 111/112] auto-detect zc support, allow disabling via config --- README.md | 5 ++++- doc/hercules.conf.5 | 4 ++-- doc/hercules.conf.5.md | 4 ++-- hercules.c | 5 +++-- hercules.conf.sample | 2 ++ xdp.c | 51 +++++++++++++++++++++++++++--------------- 6 files changed, 46 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 4a2bd88..e5666e6 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,10 @@ Depending on your performance requirements and your specific bottlenecks, the fo - On machines with multiple NUMA nodes, it may be beneficial to bind the Hercules server process to CPU cores "closer" to the network card. To do so, install the `numactl` utility and adjust the file `/usr/local/lib/systemd/system/hercules-server.service` so it reads `ExecStart=/usr/bin/numactl -l --cpunodebind=netdev: -- /usr/local/bin/hercules-server`, replacing `` with your network interface. -- Setting the option `XDPZeroCopy = true` can substantially improve performance, but whether it is supported depends on the combination of network card and driver in your setup. +- Using XDP in zero-copy mode can substantially improve performance, but whether it is supported depends on the combination of network card and driver in your setup. Hercules will attempt to use zero-copy mode automatically, if it appears to be supported. Note that some network cards require updating drivers/firmware to enable zero-copy mode. + +- Using larger packets (jumbo frames) can also improve performance. Hercules supports jumbo frames up to a MTU of 9000 bytes. Note, however, that support for jumbo frames (via XDP multibuffer/fragments) requires at least kernel 6.6. On older versions the packet size is limited to 3000 bytes. + Further, support for jumbo frames in combination with zero-copy mode is device-dependent. To use jumbo frames on such a device, disable zero-copy in Hercules' file. - Increasing the number of worker threads via the option `NumThreads` can also improve performance. diff --git a/doc/hercules.conf.5 b/doc/hercules.conf.5 index 7326897..34b17f6 100644 --- a/doc/hercules.conf.5 +++ b/doc/hercules.conf.5 @@ -127,8 +127,8 @@ The default value is .It Ic XDPZeroCopy Ns = Ns Ar bool If your combination of NIC/drivers supports XDP in zero-copy mode, enabling it here will likely improve performance. -The default value is -.Ar false . +Zero-copy mode should be enabled automatically, if supported, +so only set this option if you need to override that. .It Ic XDPMultiBuffer Ns = Ns Ar bool If the system does not support XDP in multibuffer mode, this option can be used to disable it. diff --git a/doc/hercules.conf.5.md b/doc/hercules.conf.5.md index 266dcd7..0ccde29 100644 --- a/doc/hercules.conf.5.md +++ b/doc/hercules.conf.5.md @@ -153,8 +153,8 @@ The following general configuration options are available: > If your combination of NIC/drivers supports XDP in zero-copy mode, > enabling it here will likely improve performance. -> The default value is -> *false*. +> Zero-copy mode should be enabled automatically, if supported, +> so only set this option if you need to override that. **XDPMultiBuffer**=*bool* diff --git a/hercules.c b/hercules.c index 4efc434..3bf1251 100644 --- a/hercules.c +++ b/hercules.c @@ -3,6 +3,7 @@ // Copyright(c) 2019 ETH Zurich. #include "hercules.h" +#include #include #include "packet.h" #include @@ -4300,7 +4301,7 @@ int main(int argc, char *argv[]) { config.enable_multibuf = true; config.rate_limit = 3333333; config.n_threads = 1; - config.xdp_mode = XDP_COPY; + config.xdp_mode = XDP_MODE_UNSPEC; // Parse command line args (there is only one) int opt; @@ -4461,7 +4462,7 @@ int main(int argc, char *argv[]) { // XDP Zerocopy toml_datum_t zerocopy_enabled = toml_bool_in(conf, "XDPZeroCopy"); if (zerocopy_enabled.ok) { - config.xdp_mode = (zerocopy_enabled.u.b) ? XDP_ZEROCOPY : XDP_COPY; + config.xdp_mode = (zerocopy_enabled.u.b) ? XDP_MODE_NATIVE : XDP_MODE_SKB; } else { if (toml_key_exists(conf, "XDPZeroCopy")) { fprintf(stderr, "Error parsing XDPZeroCopy\n"); diff --git a/hercules.conf.sample b/hercules.conf.sample index c008e09..59e2f26 100644 --- a/hercules.conf.sample +++ b/hercules.conf.sample @@ -45,6 +45,8 @@ Interfaces = [ # If the NIC/drivers support XDP in zerocopy mode, enabling it # will improve performance. +# Zerocopy should be used automatically, if supported, +# so there should be no need to set this option manually. XDPZeroCopy = true # Specify the NIC RX queue on which to receive packets diff --git a/xdp.c b/xdp.c index 67c7e3c..d1cb291 100644 --- a/xdp.c +++ b/xdp.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -298,7 +299,7 @@ int load_xsk_redirect_userspace(struct hercules_server *server, } } - err = xdp_program__attach(prog, server->ifaces[i].ifid, XDP_MODE_UNSPEC, 0); + err = xdp_program__attach(prog, server->ifaces[i].ifid, server->config.xdp_mode, 0); if (err) { libxdp_strerror(err, errmsg, sizeof(errmsg)); fprintf(stderr, "Error attaching XDP program to interface %s: %s\n", @@ -316,20 +317,6 @@ int load_xsk_redirect_userspace(struct hercules_server *server, debug_printf("program supports frags? %d", xdp_program__xdp_frags_support(prog)); - // XXX It should be possible to check whether multi-buffer (jumbo-frames) are - // supported with the following code, but this always returns 0. However, it - // also returns 0 for zero-copy support on machines that are known to support - // zero-copy (eg. zapdos), so something is wrong. Same thing happens if you use - // the xdp-loader utility (from xdp-tools, it uses the same approach) to query - // for feature support. - /* LIBBPF_OPTS(bpf_xdp_query_opts, opts); */ - /* err = bpf_xdp_query(server->ifaces[i].ifid, 0, &opts); */ - /* if (err) { */ - /* debug_printf("query err"); */ - /* return 1; */ - /* } */ - /* debug_printf("opts %#llx, zc frags %#x", opts.feature_flags, opts.xdp_zc_max_segs); */ - // push XSKs int xsks_map_fd = bpf_object__find_map_fd_by_name(xdp_program__bpf_obj(prog), "xsks_map"); @@ -418,6 +405,32 @@ int xdp_setup(struct hercules_server *server) { return EINVAL; } + // XXX It should be possible to check whether multi-buffer (jumbo-frames) are + // supported with the following code, but this always returns 0. However, it + // also returns 0 for zero-copy support on machines that are known to support + // zero-copy (eg. zapdos), so something is wrong. Same thing happens if you use + // the xdp-loader utility (from xdp-tools, it uses the same approach) to query + // for feature support. + LIBBPF_OPTS(bpf_xdp_query_opts, opts); + int err = bpf_xdp_query(server->ifaces[i].ifid, 0, &opts); + if (err) { + fprintf(stderr, "Error querying device features"); + return 1; + } + // If the device does ZC, check it supports the required number of fragments, + // too (this value is different for ZC/non-ZC). + // Skip check if the user disabled zc via config. + debug_printf("opts %#llx, zc frags %#x", opts.feature_flags, + opts.xdp_zc_max_segs); + if (opts.feature_flags & NETDEV_XDP_ACT_XSK_ZEROCOPY && + opts.xdp_zc_max_segs < 3 && (server->config.xdp_mode != XDP_MODE_SKB)) { + fprintf(stderr, + "Device supports zero-copy, but not with enough fragments. " + "Disabling jumbo frame support.\n Try disabling zero-copy if you " + "want to use jumbo frames.\n"); + server->have_frags_support = false; + } + // Create XSK sockets for (int t = 0; t < server->config.n_threads; t++) { struct xsk_socket_info *xsk; @@ -432,7 +445,7 @@ int xdp_setup(struct hercules_server *server) { cfg.tx_size = XSK_RING_PROD__DEFAULT_NUM_DESCS; cfg.libbpf_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD; cfg.xdp_flags = server->config.xdp_flags; - cfg.bind_flags = server->config.xdp_mode; + cfg.bind_flags = 0; // Kernel 6.6 is required to bind with the following flag (jumbo frame support). // We try to bind with the flag and again without, if the first one fails, to @@ -444,8 +457,10 @@ int xdp_setup(struct hercules_server *server) { server->config.queue, umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); if (ret) { - fprintf(stderr, "Error creating XDP socket in multibuffer mode\n"); - cfg.bind_flags = server->config.xdp_mode; + fprintf(stderr, + "Error creating XDP socket in multibuffer mode. Disabling jumbo " + "frames.\n"); + cfg.bind_flags = 0; ret = xsk_socket__create_shared(&xsk->xsk, server->ifaces[i].ifname, server->config.queue, umem->umem, &xsk->rx, &xsk->tx, &umem->fq, &umem->cq, &cfg); From 3d32c7f45eca6b777537144a40ae0451f478d364 Mon Sep 17 00:00:00 2001 From: Marco Pioppini Date: Tue, 10 Dec 2024 14:51:34 +0100 Subject: [PATCH 112/112] add copyright/license header to new files --- errors.h | 14 ++++++++++++++ gfal2-hercules/gfal_hercules_plugin.c | 14 ++++++++++++++ gfal2-hercules/gfal_hercules_plugin.h | 14 ++++++++++++++ gfal2-hercules/gfal_hercules_transfer.c | 14 ++++++++++++++ gfal2-hercules/gfal_hercules_transfer.h | 14 ++++++++++++++ hcp/hcp.go | 14 ++++++++++++++ monitor.c | 14 ++++++++++++++ monitor.h | 14 ++++++++++++++ monitor/config.go | 14 ++++++++++++++ monitor/http_api.go | 14 ++++++++++++++ monitor/monitor.go | 14 ++++++++++++++ monitor/scionheader.go | 14 ++++++++++++++ 12 files changed, 168 insertions(+) diff --git a/errors.h b/errors.h index fb4cd3f..e1f9997 100644 --- a/errors.h +++ b/errors.h @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef HERCULES_ERRORS_H #define HERCULES_ERRORS_H diff --git a/gfal2-hercules/gfal_hercules_plugin.c b/gfal2-hercules/gfal_hercules_plugin.c index 0553062..e24e824 100644 --- a/gfal2-hercules/gfal_hercules_plugin.c +++ b/gfal2-hercules/gfal_hercules_plugin.c @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "gfal_hercules_plugin.h" #include "gfal_hercules_transfer.h" #include diff --git a/gfal2-hercules/gfal_hercules_plugin.h b/gfal2-hercules/gfal_hercules_plugin.h index 21aea99..e343754 100644 --- a/gfal2-hercules/gfal_hercules_plugin.h +++ b/gfal2-hercules/gfal_hercules_plugin.h @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef GFAL_HERCULES_PLUGIN_H #define GFAL_HERCULES_PLUGIN_H diff --git a/gfal2-hercules/gfal_hercules_transfer.c b/gfal2-hercules/gfal_hercules_transfer.c index dc14527..87401d0 100644 --- a/gfal2-hercules/gfal_hercules_transfer.c +++ b/gfal2-hercules/gfal_hercules_transfer.c @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "gfal_hercules_transfer.h" #include "../errors.h" #include "common/gfal_common.h" diff --git a/gfal2-hercules/gfal_hercules_transfer.h b/gfal2-hercules/gfal_hercules_transfer.h index eb64162..493ab06 100644 --- a/gfal2-hercules/gfal_hercules_transfer.h +++ b/gfal2-hercules/gfal_hercules_transfer.h @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef GFAL_HERCULES_TRANSFER_H_ #define GFAL_HERCULES_TRANSFER_H_ diff --git a/hcp/hcp.go b/hcp/hcp.go index eff1872..13897b6 100644 --- a/hcp/hcp.go +++ b/hcp/hcp.go @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main // #include "../monitor.h" diff --git a/monitor.c b/monitor.c index 8578b51..42e36bb 100644 --- a/monitor.c +++ b/monitor.c @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "monitor.h" #include diff --git a/monitor.h b/monitor.h index 84184ee..41cf2d0 100644 --- a/monitor.h +++ b/monitor.h @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef HERCULES_MONITOR_H_ #define HERCULES_MONITOR_H_ #include diff --git a/monitor/config.go b/monitor/config.go index a86562a..f897602 100644 --- a/monitor/config.go +++ b/monitor/config.go @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( diff --git a/monitor/http_api.go b/monitor/http_api.go index 6bb2585..ee24103 100644 --- a/monitor/http_api.go +++ b/monitor/http_api.go @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( diff --git a/monitor/monitor.go b/monitor/monitor.go index 7b2c914..4cab6ae 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( diff --git a/monitor/scionheader.go b/monitor/scionheader.go index 77410ce..32853e2 100644 --- a/monitor/scionheader.go +++ b/monitor/scionheader.go @@ -1,3 +1,17 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import (