add sendtox

wrapper sendmsg() and WSASendMsg()

Linux and Windows support IPv4 IP_PKTINFO.
BSD, Linux, and Windows support IPV6_PKTINFO

So far RTEMS and OSX, the extra sendto() overrides
will be ignored.
This commit is contained in:
Michael Davidsaver
2025-02-05 17:58:00 -08:00
parent a2b424cba2
commit 80c63888ed
4 changed files with 186 additions and 19 deletions
+68 -3
View File
@@ -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<sockaddr*>(&(*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<WSACMSGHDR*>(cbuf));
cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(in_pktinfo));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
auto info(reinterpret_cast<in_pktinfo*>(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<WSACMSGHDR*>(cbuf));
cmsg->cmsg_len = WSA_CMSG_LEN(sizeof(in6_pktinfo));
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
auto info(reinterpret_cast<in6_pktinfo*>(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
+54
View File
@@ -197,6 +197,60 @@ int recvfromx::call()
return ret;
}
int sendtox::call()
{
msghdr msg{};
iovec iov = {const_cast<void*>(buf), buflen};
msg.msg_iov = &iov;
msg.msg_iovlen = 1u;
msg.msg_name = const_cast<sockaddr*>(&(*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<cmsghdr*>(cbuf));
cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
auto info(reinterpret_cast<in_pktinfo*>(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<cmsghdr*>(cbuf));
cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
auto info(reinterpret_cast<in6_pktinfo*>(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() {
+12
View File
@@ -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
+52 -16
View File
@@ -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();