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:
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user