Files
epics-base/modules/libcom/test/osiSockTest.c
Andrew Johnson 3c99391d93 Added SPDX License ID to all EPICS-original source files
In some cases the license-identification header was missing,
so I added that as well. Replaced the remaining headers that
specifically identified "Versions 3.13.7 and higher".

Makefiles and the build system were deliberately excluded.
2020-08-03 11:53:01 -05:00

416 lines
11 KiB
C

/*************************************************************************\
* Copyright (c) 2017 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "epicsAssert.h"
#include "dbDefs.h"
#include "osiSock.h"
#include "epicsTime.h"
#include "epicsThread.h"
#include "epicsUnitTest.h"
#include "testMain.h"
/* This could easily be generalized to test more options */
static
void udpBroadcast(SOCKET s, int put)
{
int status;
int flag = put;
osiSocklen_t len = sizeof(flag);
status = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&flag, len);
testOk(status >= 0, "setsockopt BROADCAST := %d", put);
status = getsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&flag, &len);
testOk(status >= 0 && len == sizeof(flag) && !flag == !put,
"getsockopt BROADCAST => %d", flag);
}
static
void multiCastLoop(SOCKET s, int put)
{
int status;
osiSockOptMcastLoop_t flag = put;
osiSocklen_t len = sizeof(flag);
status = setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP,
(char *)&flag, len);
testOk(status >= 0, "setsockopt MULTICAST_LOOP := %d", put);
status = getsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&flag, &len);
testOk(status >= 0 && len == sizeof(flag) && !flag == !put,
"getsockopt MULTICAST_LOOP => %d", (int) flag);
}
static
void multiCastTTL(SOCKET s, int put)
{
int status;
osiSockOptMcastTTL_t flag = put;
osiSocklen_t len = sizeof(flag);
status = setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL,
(char *)&flag, len);
testOk(status >= 0, "setsockopt IP_MULTICAST_TTL := %d", put);
status = getsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&flag, &len);
testOk(status >= 0 && len == sizeof(flag) && !flag == !put,
"getsockopt IP_MULTICAST_TTL => %d", (int) flag);
}
static
void udpSockTest(void)
{
SOCKET s;
testDiag("udpSockTest()");
s = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
testOk(s != INVALID_SOCKET, "epicsSocketCreate INET, DGRAM, 0");
udpBroadcast(s, 1);
udpBroadcast(s, 0);
multiCastLoop(s, 1);
multiCastLoop(s, 0);
/* Defaults to 1, setting to 0 makes no sense */
multiCastTTL(s, 2);
multiCastTTL(s, 1);
epicsSocketDestroy(s);
}
static
int doBind(int expect, SOCKET S, unsigned* port)
{
osiSockAddr addr;
int ret;
memset(&addr, 0, sizeof(addr));
addr.ia.sin_family = AF_INET;
addr.ia.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.ia.sin_port = htons(*port);
ret = bind(S, &addr.sa, sizeof(addr.ia));
if(ret) {
testOk(expect==1, "bind() to %u error %d, %d", *port, ret, SOCKERRNO);
return 1;
} else {
osiSocklen_t slen = sizeof(addr);
ret = getsockname(S, &addr.sa, &slen);
if(ret) {
testFail("Unable to find sock name after binding");
return 1;
} else {
*port = ntohs(addr.ia.sin_port);
testOk(expect==0, "bind() to port %u", *port);
return 0;
}
}
}
static
void tcpSockReuseBindTest(int reuse)
{
SOCKET A, B;
unsigned port=0; /* choose random port */
testDiag("tcpSockReuseBindTest(%d)", reuse);
A = epicsSocketCreate(AF_INET, SOCK_STREAM, 0);
B = epicsSocketCreate(AF_INET, SOCK_STREAM, 0);
if(A==INVALID_SOCKET || B==INVALID_SOCKET)
testAbort("Insufficient sockets");
if(reuse) {
testDiag("epicsSocketEnableAddressReuseDuringTimeWaitState");
epicsSocketEnableAddressReuseDuringTimeWaitState(A);
epicsSocketEnableAddressReuseDuringTimeWaitState(B);
}
doBind(0, A, &port);
if(listen(A, 4))
testFail("listen(A) -> %d", (int)SOCKERRNO);
doBind(1, B, &port);
epicsSocketDestroy(A);
epicsSocketDestroy(B);
}
static
void udpSockFanoutBindTest(void)
{
SOCKET A, B, C;
unsigned port=0; /* choose random port */
testDiag("udpSockFanoutBindTest()");
A = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
B = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
C = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
if(A==INVALID_SOCKET || B==INVALID_SOCKET || C==INVALID_SOCKET)
testAbort("Insufficient sockets");
/* not A */
epicsSocketEnableAddressUseForDatagramFanout(B);
epicsSocketEnableAddressUseForDatagramFanout(C);
testDiag("First test if epicsSocketEnableAddressUseForDatagramFanout() is necessary");
doBind(0, A, &port);
doBind(1, B, &port); /* expect failure */
epicsSocketDestroy(A);
testDiag("Now the real test");
doBind(0, B, &port);
doBind(0, C, &port);
epicsSocketDestroy(B);
epicsSocketDestroy(C);
}
struct CASearch {
epicsUInt16 cmd, size, dtype, dcnt;
epicsUInt32 p1, p2;
char body[16];
};
STATIC_ASSERT(sizeof(struct CASearch)==32);
union CASearchU {
struct CASearch msg;
char bytes[sizeof(struct CASearch)];
};
static
unsigned nsuccess;
static
const unsigned nrepeat = 6u;
struct TInfo {
SOCKET sock;
unsigned id;
epicsUInt32 key;
epicsUInt8 rxmask;
epicsUInt8 dupmask;
};
static
void udpSockFanoutTestRx(void* raw)
{
struct TInfo *info = raw;
epicsTimeStamp start, now;
#ifdef _WIN32
/* ms */
DWORD timeout = 10000;
#else
struct timeval timeout;
memset(&timeout, 0, sizeof(struct timeval));
timeout.tv_sec = 10;
timeout.tv_usec = 0;
#endif
unsigned nremain = nrepeat;
(void)epicsTimeGetCurrent(&start);
now = start;
testDiag("RX%u start", info->id);
if(setsockopt(info->sock, SOL_SOCKET, SO_RCVTIMEO, (void*)&timeout, sizeof(timeout))) {
testFail("Unable to set socket timeout");
return;
}
while(epicsTimeDiffInSeconds(&now, &start)<=5.0) {
union CASearchU buf;
osiSockAddr src;
osiSocklen_t srclen = sizeof(src);
int n = recvfrom(info->sock, buf.bytes, sizeof(buf.bytes), 0, &src.sa, &srclen);
buf.bytes[sizeof(buf.bytes)-1] = '\0';
if(n<0) {
testDiag("recvfrom error (%d)", (int)SOCKERRNO);
break;
} else if((n==sizeof(buf.bytes)) && buf.msg.cmd==htons(6) && buf.msg.size==htons(16)
&&buf.msg.dtype==htons(5) && buf.msg.dcnt==0 && strcmp(buf.msg.body, "totallyinvalid")==0)
{
unsigned ord = ntohl(buf.msg.p1)-info->key;
testDiag("RX%u success %u", info->id, ord);
if(ord<8) {
const epicsUInt8 mask = 1u<<ord;
if(info->rxmask&mask)
info->dupmask|=mask;
info->rxmask|=mask;
}
if(0==--nremain)
break;
} else {
testDiag("RX ignore");
}
}
testDiag("RX%u end", info->id);
}
static
void udpSockFanoutTestIface(const osiSockAddr* addr)
{
SOCKET sender;
struct TInfo rx1, rx2;
epicsThreadId trx1, trx2;
epicsThreadOpts topts = EPICS_THREAD_OPTS_INIT;
int opt = 1;
unsigned i;
osiSockAddr any;
epicsUInt32 key = 0xdeadbeef ^ ntohl(addr->ia.sin_addr.s_addr);
union CASearchU buf;
topts.joinable = 1;
/* we bind to any for lack of a portable way to find the
* interface address from the interface broadcast address
*/
memset(&any, 0, sizeof(any));
any.ia.sin_family = AF_INET;
any.ia.sin_addr.s_addr = htonl(INADDR_ANY);
any.ia.sin_port = addr->ia.sin_port;
buf.msg.cmd = htons(6);
buf.msg.size = htons(16);
buf.msg.dtype = htons(5);
buf.msg.dcnt = htons(0); /* version 0, which newer servers should ignore */
/* .p1 and .p2 set below */
memcpy(buf.msg.body, "tota" "llyi" "nval" "id\0\0", 16);
sender = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
rx1.sock = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
rx2.sock = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0);
if((sender==INVALID_SOCKET) || (rx1.sock==INVALID_SOCKET) || (rx2.sock==INVALID_SOCKET))
testAbort("Unable to allocate test socket(s)");
rx1.id = 1;
rx2.id = 2;
rx1.key = rx2.key = key;
rx1.rxmask = rx2.rxmask = 0u;
rx1.dupmask = rx2.dupmask = 0u;
if(setsockopt(sender, SOL_SOCKET, SO_BROADCAST, (void*)&opt, sizeof(opt))!=0) {
testFail("setsockopt SOL_SOCKET, SO_BROADCAST error -> %d", (int)SOCKERRNO);
}
epicsSocketEnableAddressUseForDatagramFanout(rx1.sock);
epicsSocketEnableAddressUseForDatagramFanout(rx2.sock);
if(bind(rx1.sock, &any.sa, sizeof(any)))
testFail("Can't bind test socket rx1 %d", (int)SOCKERRNO);
if(bind(rx2.sock, &any.sa, sizeof(any)))
testFail("Can't bind test socket rx2 %d", (int)SOCKERRNO);
trx1 = epicsThreadCreateOpt("rx1", &udpSockFanoutTestRx, &rx1, &topts);
trx2 = epicsThreadCreateOpt("rx2", &udpSockFanoutTestRx, &rx2, &topts);
for(i=0; i<nrepeat; i++) {
int ret;
/* don't spam */
epicsThreadSleep(0.5);
buf.msg.p1 = buf.msg.p2 = htonl(key + i);
ret = sendto(sender, buf.bytes, sizeof(buf.bytes), 0, &addr->sa, sizeof(*addr));
if(ret!=(int)sizeof(buf.bytes))
testDiag("sendto() error %d (%d)", ret, (int)SOCKERRNO);
}
epicsThreadMustJoin(trx1);
epicsThreadMustJoin(trx2);
testDiag("Result: RX1 %x:%x RX2 %x:%x",
rx1.rxmask, rx1.dupmask, rx2.rxmask, rx2.dupmask);
/* success if any one packet was seen by both sockets */
if(rx1.rxmask & rx2.rxmask)
nsuccess++;
epicsSocketDestroy(sender);
epicsSocketDestroy(rx1.sock);
epicsSocketDestroy(rx2.sock);
}
/* This test violates the principle of unittest isolation by broadcasting
* on the well known CA search port on all interfaces. There is no
* portable way to avoid this. (eg. 127.255.255.255 is Linux specific)
*/
static
void udpSockFanoutTest()
{
ELLLIST ifaces = ELLLIST_INIT;
ELLNODE *cur;
SOCKET dummy;
osiSockAddr match;
int foundNotLo = 0;
testDiag("udpSockFanoutTest()");
memset(&match, 0, sizeof(match));
match.ia.sin_family = AF_INET;
match.ia.sin_addr.s_addr = htonl(INADDR_ANY);
if((dummy = epicsSocketCreate(AF_INET, SOCK_DGRAM, 0))==INVALID_SOCKET)
testAbort("Unable to allocate discovery socket");
osiSockDiscoverBroadcastAddresses(&ifaces, dummy, &match);
for(cur = ellFirst(&ifaces); cur; cur = ellNext(cur)) {
char name[64];
osiSockAddrNode* node = CONTAINER(cur, osiSockAddrNode, node);
node->addr.ia.sin_port = htons(5064);
(void)sockAddrToDottedIP(&node->addr.sa, name, sizeof(name));
testDiag("Interface %s", name);
if(node->addr.ia.sin_addr.s_addr!=htonl(INADDR_LOOPBACK)) {
testDiag("Not LO");
foundNotLo = 1;
}
udpSockFanoutTestIface(&node->addr);
}
ellFree(&ifaces);
testOk(foundNotLo, "Found non-loopback interface");
testOk(nsuccess>0, "Successes %u", nsuccess);
epicsSocketDestroy(dummy);
}
MAIN(osiSockTest)
{
int status;
testPlan(24);
status = osiSockAttach();
testOk(status, "osiSockAttach");
udpSockTest();
udpSockFanoutBindTest();
udpSockFanoutTest();
tcpSockReuseBindTest(0);
tcpSockReuseBindTest(1);
osiSockRelease();
return testDone();
}