diff --git a/src/os/WIN32/osdSockExt.cpp b/src/os/WIN32/osdSockExt.cpp index 46ea752..29e6195 100644 --- a/src/os/WIN32/osdSockExt.cpp +++ b/src/os/WIN32/osdSockExt.cpp @@ -36,14 +36,17 @@ DEFINE_LOGGER(logiface, "pvxs.iface"); static LPFN_WSARECVMSG WSARecvMsg; +static +LPFN_WSASENDMSG WSASendMsg; static void oseDoOnce() { evsocket dummy(AF_INET, SOCK_DGRAM, 0); - GUID guid = WSAID_WSARECVMSG; + GUID guid; DWORD nout; + guid = WSAID_WSARECVMSG; if(WSAIoctl(dummy.sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &WSARecvMsg, sizeof(WSARecvMsg), @@ -51,8 +54,18 @@ void oseDoOnce() { cantProceed("Unable to get &WSARecvMsg: %d", WSAGetLastError()); } - if(!WSARecvMsg) - cantProceed("Unable to get &WSARecvMsg!!"); + + guid = WSAID_WSASENDMSG; + if(WSAIoctl(dummy.sock, SIO_GET_EXTENSION_FUNCTION_POINTER, + &guid, sizeof(guid), + &WSASendMsg, sizeof(WSASendMsg), + &nout, nullptr, nullptr)) + { + cantProceed("Unable to get &WSASendMsg: %d", WSAGetLastError()); + } + + if(!WSARecvMsg || !WSASendMsg) + cantProceed("Unable to get &WSARecvMsg or &WSASendMsg"); evsocket::canIPv6 = evsocket::init_canIPv6(); evsocket::ipstack = is_wine() ? evsocket::Linsock : evsocket::Winsock; @@ -134,6 +147,58 @@ int recvfromx::call() } } +int sendtox::call() +{ + WSAMSG msg{}; + + WSABUF iov = {(ULONG)buflen, (char*)buf}; + msg.lpBuffers = &iov; + msg.dwBufferCount = 1u; + + msg.name = const_cast(&(*dst)->sa); + msg.namelen = dst->size(); + + alignas (WSACMSGHDR) char cbuf[ + // only need space for IPv4 option(s) or IPv6 option, never both. + impl::cmax(WSA_CMSG_SPACE(sizeof(in_pktinfo)), WSA_CMSG_SPACE(sizeof(in6_pktinfo))) + ] = {}; + + if(srcif && src && src->family()==AF_INET) { + auto cmsg(reinterpret_cast(cbuf)); + cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(in_pktinfo)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + auto info(reinterpret_cast(WSA_CMSG_DATA(cmsg))); + memset(info, 0, sizeof(*info)); + info->ipi_ifindex = srcif; + if(src) { + info->ipi_addr = (*src)->in.sin_addr; // source in IP header + } + msg.Control.buf = cbuf; + msg.Control.len = WSA_CMSG_SPACE(sizeof(in_pktinfo)); + + } else if(srcif && src && src->family()==AF_INET6) { + auto cmsg(reinterpret_cast(cbuf)); + cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(in6_pktinfo)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + auto info(reinterpret_cast(WSA_CMSG_DATA(cmsg))); + memset(info, 0, sizeof(*info)); + info->ipi6_ifindex = srcif; + if(src) { + info->ipi6_addr = (*src)->in6.sin6_addr; // source in IP header + } + msg.Control.buf = cbuf; + msg.Control.len = WSA_CMSG_SPACE(sizeof(in6_pktinfo)); + // real winsock insists that Control.len includes padding + } + + DWORD nSent = 0; + if(WSASendMsg(sock, &msg, 0, &nSent, nullptr, nullptr)) + return -1; + return nSent; +} + namespace impl { #ifndef GAA_FLAG_INCLUDE_ALL_INTERFACES diff --git a/src/os/default/osdSockExt.cpp b/src/os/default/osdSockExt.cpp index 5f0550c..c9a2683 100644 --- a/src/os/default/osdSockExt.cpp +++ b/src/os/default/osdSockExt.cpp @@ -197,6 +197,60 @@ int recvfromx::call() return ret; } +int sendtox::call() +{ + msghdr msg{}; + + iovec iov = {const_cast(buf), buflen}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1u; + + msg.msg_name = const_cast(&(*dst)->sa); + msg.msg_namelen = dst->size(); + + // only need space for IPv4 option(s) or IPv6 option, never both. + alignas (cmsghdr) char cbuf[impl::cmax(0 +#ifdef IP_PKTINFO + + CMSG_SPACE(sizeof(in_pktinfo)) +#endif + , CMSG_SPACE(sizeof(in6_pktinfo))) + ] = {}; + +#ifdef IP_PKTINFO + if(srcif && src && src->family()==AF_INET) { + auto cmsg(reinterpret_cast(cbuf)); + cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + auto info(reinterpret_cast(CMSG_DATA(cmsg))); + memset(info, 0, sizeof(*info)); + info->ipi_ifindex = srcif; + if(src) { + info->ipi_addr = (*src)->in.sin_addr; // source in IP header + info->ipi_spec_dst = (*src)->in.sin_addr; // source use in local routing table lookup + } + msg.msg_control = cbuf; + msg.msg_controllen = cmsg->cmsg_len; + } else +#endif + if(srcif && src && src->family()==AF_INET6) { + auto cmsg(reinterpret_cast(cbuf)); + cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + auto info(reinterpret_cast(CMSG_DATA(cmsg))); + memset(info, 0, sizeof(*info)); + info->ipi6_ifindex = srcif; + if(src) { + info->ipi6_addr = (*src)->in6.sin6_addr; // source in IP header + } + msg.msg_control = cbuf; + msg.msg_controllen = cmsg->cmsg_len; + } + + return sendmsg(sock, &msg, 0); +} + namespace impl { decltype (IfaceMap::byIndex) IfaceMap::_refresh() { diff --git a/src/osiSockExt.h b/src/osiSockExt.h index 6bd9ea8..4b8a615 100644 --- a/src/osiSockExt.h +++ b/src/osiSockExt.h @@ -214,6 +214,18 @@ struct recvfromx { int call(); }; +struct sendtox { + evutil_socket_t sock; + const void *buf; + size_t buflen; + const SockAddr* dst; + const SockAddr* src; // if !NULL override UDP source address + uint64_t srcif; // if !=0 override routing to send through this interface + + PVXS_API + int call(); +}; + } // namespace pvxs #endif // OSISOCKEXT_H diff --git a/test/testsock.cpp b/test/testsock.cpp index 2cc35ba..aee29ed 100644 --- a/test/testsock.cpp +++ b/test/testsock.cpp @@ -220,6 +220,7 @@ void test_udp(int af) } A.enable_IP_PKTINFO(); + B.enable_IP_PKTINFO(); try{ A.bind(bind_addr); }catch(std::system_error& e){ @@ -237,24 +238,59 @@ void test_udp(int af) testNotEq(send_addr.port(), 0); testNotEq(send_addr.port(), bind_addr.port()); - uint8_t msg[] = {0x12, 0x34, 0x56, 0x78}; - int ret = sendto(B.sock, (char*)msg, sizeof(msg), 0, &bind_addr->sa, bind_addr.size()); - testOk(ret==(int)sizeof(msg), "Send test ret==%d(%d)", ret, EVUTIL_SOCKET_ERROR()); + { + uint8_t msg[] = {0x12, 0x34, 0x56, 0x78}; + int ret = sendtox{B.sock, (char*)msg, sizeof(msg), &bind_addr}.call(); + testOk(ret==(int)sizeof(msg), "Send test ret==%d(%d)", ret, EVUTIL_SOCKET_ERROR()); + } - uint8_t rxbuf[8] = {}; - SockAddr src; - SockAddr dest; + SockAddr reply_to, reply_from; + uint64_t reply_from_iface = 0; + { + testDiag("Call recvfrom()"); - testDiag("Call recvfrom()"); - ret = recvfromx{A.sock, (char*)rxbuf, sizeof(rxbuf), &src, &dest}.call(); - // only the destination address is captured, not the port - if(dest.family()!=AF_UNSPEC) - dest.setPort(bind_addr.port()); + uint8_t rxbuf[8] = {}; + SockAddr src, dest; + auto rx(recvfromx{A.sock, (char*)rxbuf, sizeof(rxbuf), &src, &dest}); + int ret = rx.call(); + // only the destination address is captured, not the port + if(dest.family()!=AF_UNSPEC) + dest.setPort(bind_addr.port()); - testOk(ret==4 && rxbuf[0]==0x12 && rxbuf[1]==0x34 && rxbuf[2]==0x56 && rxbuf[3]==0x78, - "Recv'd %d(%d) [%u, %u, %u, %u]", ret, EVUTIL_SOCKET_ERROR(), rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]); - testEq(src, send_addr); - testEq(dest, bind_addr); + testOk(ret==4 && rxbuf[0]==0x12 && rxbuf[1]==0x34 && rxbuf[2]==0x56 && rxbuf[3]==0x78, + "Recv'd %d(%d) [%u, %u, %u, %u]", ret, EVUTIL_SOCKET_ERROR(), rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]); + testEq(src, send_addr); + testEq(dest, bind_addr); + + reply_to = src; + reply_from = dest; + reply_from_iface = rx.dstif; + } + + { + testDiag("Call sendto() with source addr override"); + uint8_t msg[] = {0x9a, 0xbc, 0xde, 0xf0}; + // reply to "src" from "dest" + int ret = sendtox{A.sock, (char*)msg, sizeof(msg), &reply_to, &reply_from, reply_from_iface}.call(); + testOk(ret==(int)sizeof(msg), "Send test ret==%d(%d)", ret, EVUTIL_SOCKET_ERROR()); + } + + { + testDiag("Call recvfrom()"); + + uint8_t rxbuf[8] = {}; + SockAddr src, dest; + auto rx(recvfromx{B.sock, (char*)rxbuf, sizeof(rxbuf), &src, &dest}); + int ret = rx.call(); + // only the destination address is captured, not the port + if(dest.family()!=AF_UNSPEC) + dest.setPort(send_addr.port()); + + testOk(ret==4 && rxbuf[0]==0x9a && rxbuf[1]==0xbc && rxbuf[2]==0xde && rxbuf[3]==0xf0, + "Recv'd %d(%d) [%u, %u, %u, %u]", ret, EVUTIL_SOCKET_ERROR(), rxbuf[0], rxbuf[1], rxbuf[2], rxbuf[3]); + testEq(src, reply_from); + testEq(dest, reply_to); + } } void test_local_mcast() @@ -510,7 +546,7 @@ MAIN(testsock) { SockAttach attach; logger_config_env(); - testPlan(93); + testPlan(101); testSetup(); testStackID(); testEndPoint();