/** * Copyright - See the COPYRIGHT that is included with this distribution. * pvxs is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. */ #include #include #include #include #include #include #include #include #include #include "utilpvt.h" #include "evhelper.h" #include "udp_collector.h" using namespace pvxs; DEFINE_LOGGER(applog, "mshim"); namespace { void usage(const char* argv0) { std::cerr<< "Usage: "<[@iface]]... [-F]\n" "\n" " -L Interface address to listen on.\n" " -L [@iface] Join multicast group, optionally via a certain interface\n" " to override the default selected by the OS.\n" " -F Forward received packets to destination unicast/broadcast address.\n" " -F [,ttl#][@iface] Forward received packets to destination multicast group.\n" " Optionally override OS default TTL and outbound interface selected\n" " by the OS.\n" " -p Default port number. (overrides $EPICS_PVA_BROADCAST_PORT)\n" " -h Show this message.\n" " -V Show versions.\n" "\n" " Compatibility shim for IPv4 multicast by non-aware PVA clients/servers.\n" "\n" " Examples:\n" "\n" " 1. Forwarding searches from local clients to a multicast group via the default interface.\n" " 2. Forwarding beacons from multicast group via the default interface to local clients.\n" "\n" " "< destinations; // effectively local to UDPManager worker std::vector scratch; App() :ifmap(IfaceMap::instance()) ,scratch(0x10000) { auto bind_addr(SockAddr::any(sockTx.af)); sockTx.bind(bind_addr); sockTx.mcast_loop(true); } void onSearch(const UDPManager::Search& msg) { FixedBuf buf(true, scratch); auto save_header = buf.save(); buf._skip(8); to_wire(buf, msg.searchID); auto save_flags = buf.save(); to_wire(buf, { uint8_t(msg.mustReply ? pva_search_flags::MustReply : 0u), 0,0,0 }); assert(!msg.replyDest.isAny() && msg.replyDest.family()==AF_INET); // UDPManager has already handled this case to_wire(buf, msg.replyDest); to_wire(buf, uint16_t(msg.replyDest.port())); size_t nproto = msg.otherproto.size(); if(msg.protoTCP) nproto++; to_wire(buf, Size{nproto}); if(msg.protoTCP) to_wire(buf, "tcp"); for(auto& prot : msg.otherproto) { to_wire(buf, prot); } to_wire(buf, uint16_t(msg.names.size())); for(auto& name : msg.names) { to_wire(buf, name.id); to_wire(buf, name.name); } if(!buf.good()) { log_warn_printf(applog, "Unable to construct CMD_SEARCH to forward. %s:%d\n", buf.file(), buf.line()); return; } auto bufsize = buf.save()-save_header; { FixedBuf buf(true, save_header, 8u); to_wire(buf, Header{CMD_SEARCH, 0, uint32_t(bufsize-8u)}); } for(auto& dest : destinations) { if(dest.addr.isMCast() || ifmap.is_broadcast(dest.addr)) { *save_flags &= ~pva_search_flags::Unicast; } else { *save_flags |= pva_search_flags::Unicast; } sockTx.mcast_prep_sendto(dest); auto ret = sendto(sockTx.sock, (char*)scratch.data(), bufsize, 0, &dest.addr->sa, dest.addr.size()); if(ret < 0) { int err = evutil_socket_geterror(sockTx.sock); if(err==SOCK_EWOULDBLOCK || err==EAGAIN || err==SOCK_EINTR) { break; // too bad, better luck next time } else { log_warn_printf(applog, "Unable to send search to %s, skip. (%d) %s\n", std::string(SB()< %s -> %s?\n", msg.origSrc.tostring().c_str(), msg.replyDest.tostring().c_str(), std::string(SB()<(buf, msg.guid.data(), false, __FILE__, __LINE__); to_wire(buf, uint32_t(0u)); // skip flags, seq, and change count. unused assert(!msg.server.isAny() && msg.server.family()==AF_INET); // UDPManager has already handled this case to_wire(buf, msg.server); to_wire(buf, uint16_t(msg.server.port())); to_wire(buf, msg.proto); // "NULL" serverStatus to_wire(buf, uint8_t(0xff)); if(!buf.good()) { log_warn_printf(applog, "Unable to construct CMD_SEARCH to forward. %s:%d\n", buf.file(), buf.line()); return; } auto bufsize = buf.save()-save_header; { FixedBuf buf(true, save_header, 8u); to_wire(buf, Header{CMD_SEARCH, 0, uint32_t(bufsize-8u)}); } for(auto& dest : destinations) { sockTx.mcast_prep_sendto(dest); auto ret = sendto(sockTx.sock, (char*)scratch.data(), bufsize, 0, &dest.addr->sa, dest.addr.size()); if(ret < 0) { int err = evutil_socket_geterror(sockTx.sock); if(err==SOCK_EWOULDBLOCK || err==EAGAIN || err==SOCK_EINTR) { break; // too bad, better luck next time } else { log_warn_printf(applog, "Unable to send beacon to %s, skip. (%d) %s\n", std::string(SB()< %s -> %s?\n", msg.src.tostring().c_str(), msg.server.tostring().c_str(), std::string(SB()<> listeners; auto onSearch = [&app](const UDPManager::Search& msg) {app.onSearch(msg);}; auto onBeacon = [&app](const UDPManager::Beacon& msg) {app.onBeacon(msg);}; { int opt; while ((opt = getopt(argc, argv, "L:F:phV")) != -1) { switch(opt) { case 'L': { SockEndpoint ep(parseEP(optarg, conf)); listeners.push_back(manager.onSearch(ep, onSearch)); listeners.push_back(manager.onBeacon(ep, onBeacon)); break; } case 'F': app.destinations.push_back(parseEP(optarg, conf)); break; case 'p': conf.udp_port = parseTo(optarg); break; case 'h': usage(argv[0]); return 0; case 'V': std::cout<start(); } epicsEvent done; SigInt H([&done]() { done.signal(); }); done.wait(); return 0; }catch(std::exception& e){ std::cerr<