rsrv: when binding to a specific interface also bind to iface bcast

An oddness of BSD sockets (not winsock) is that binding to
INADDR_ANY will receive unicast and broadcast, but binding to
a specific interface address receives only unicast.  The trick
is to bind a second socket to the interface broadcast address,
which will then receive only broadcasts.
This commit is contained in:
Michael Davidsaver
2016-01-11 20:59:07 -05:00
committed by Michael Davidsaver
parent 77a5f0db77
commit 991ff308e0
3 changed files with 73 additions and 23 deletions

View File

@@ -115,30 +115,31 @@ static void clean_addrq(void)
*/
void cast_server(void *pParm)
{
osiSockAddrNode *paddrNode;
cast_config *conf = pParm;
osiSockAddr *paddrNode = &conf->pAddr;
struct sockaddr_in sin;
int status;
int count=0;
struct sockaddr_in new_recv_addr;
osiSocklen_t recv_addr_size;
osiSockIoctl_t nchars;
SOCKET recv_sock;
recv_addr_size = sizeof(new_recv_addr);
if( IOC_cast_sock!=0 && IOC_cast_sock!=INVALID_SOCKET ) {
epicsSocketDestroy ( IOC_cast_sock );
}
/*
* Open the socket.
* Use ARPA Internet address format and datagram socket.
*/
if ( ( IOC_cast_sock = epicsSocketCreate (AF_INET, SOCK_DGRAM, 0) ) == INVALID_SOCKET ) {
if ( ( recv_sock = epicsSocketCreate (AF_INET, SOCK_DGRAM, 0) ) == INVALID_SOCKET ) {
epicsPrintf ("CAS: cast socket creation error\n");
epicsThreadSuspendSelf ();
}
if(conf->reply_sock==INVALID_SOCKET)
conf->reply_sock = recv_sock; /* assume that the socket capable of unicast send is created first */
/*
* some concern that vxWorks will run out of mBuf's
* if this change is made
@@ -164,19 +165,17 @@ void cast_server(void *pParm)
}
#endif
epicsSocketEnableAddressUseForDatagramFanout ( IOC_cast_sock );
epicsSocketEnableAddressUseForDatagramFanout ( recv_sock );
paddrNode = (osiSockAddrNode *) ellFirst ( &casIntfAddrList );
memcpy(&sin, &paddrNode->addr.ia, sizeof (sin));
memcpy(&sin, &paddrNode->ia, sizeof (sin));
/* get server's Internet address */
if( bind(IOC_cast_sock, (struct sockaddr *)&sin, sizeof (sin)) < 0){
if( bind(recv_sock, (struct sockaddr *)&sin, sizeof (sin)) < 0){
char sockErrBuf[64];
epicsSocketConvertErrnoToString (
sockErrBuf, sizeof ( sockErrBuf ) );
epicsPrintf ("CAS: UDP server port bind error was \"%s\"\n", sockErrBuf );
epicsSocketDestroy ( IOC_cast_sock );
epicsSocketDestroy ( recv_sock );
epicsThreadSuspendSelf ();
}
@@ -186,7 +185,7 @@ void cast_server(void *pParm)
*
*/
while ( TRUE ) {
prsrv_cast_client = create_client ( IOC_cast_sock, IPPROTO_UDP );
prsrv_cast_client = create_client ( conf->reply_sock, IPPROTO_UDP );
if ( prsrv_cast_client ) {
break;
}
@@ -200,11 +199,15 @@ void cast_server(void *pParm)
*/
rsrv_version_reply ( prsrv_cast_client );
/* these pointers become invalid after signaling casudp_startStopEvent */
conf = NULL;
paddrNode = NULL;
epicsEventSignal(casudp_startStopEvent);
while (TRUE) {
status = recvfrom (
IOC_cast_sock,
recv_sock,
prsrv_cast_client->recv.buf,
prsrv_cast_client->recv.maxstk,
0,
@@ -292,7 +295,7 @@ void cast_server(void *pParm)
* allow messages to batch up if more are comming
*/
nchars = 0; /* supress purify warning */
status = socket_ioctl(IOC_cast_sock, FIONREAD, &nchars);
status = socket_ioctl(recv_sock, FIONREAD, &nchars);
if (status<0) {
errlogPrintf ("CA cast server: Unable to fetch N characters pending\n");
cas_send_dg_msg (prsrv_cast_client);

View File

@@ -74,7 +74,6 @@ void rsrv_online_notify_task(void *pParm)
char buf[16];
unsigned priorityOfUDP;
epicsThreadBooleanStatus tbs;
epicsThreadId tid;
taskwdInsert (epicsThreadGetIdSelf(),NULL,NULL);
@@ -223,14 +222,59 @@ void rsrv_online_notify_task(void *pParm)
casudp_startStopEvent = epicsEventMustCreate(epicsEventEmpty);
casudp_ctl = ctlPause;
tid = epicsThreadCreate ( "CAS-UDP", priorityOfUDP,
epicsThreadGetStackSize (epicsThreadStackMedium),
cast_server, 0 );
if ( tid == 0 ) {
epicsPrintf ( "CAS: unable to start UDP daemon thread\n" );
{
/* casudp_startStopEvent ensures that this struct
* lives until the cast_server thread(s) are done with it.
*/
cast_config config;
config.pAddr = ((osiSockAddrNode *) ellFirst ( &casIntfAddrList ))->addr;
config.reply_sock = INVALID_SOCKET;
epicsThreadMustCreate ( "CAS-UDP", priorityOfUDP,
epicsThreadGetStackSize (epicsThreadStackMedium),
cast_server, &config );
epicsEventMustWait(casudp_startStopEvent);
#if !defined(_WIN32)
/* An oddness of BSD sockets (not winsock) is that binding to
* INADDR_ANY will receive unicast and broadcast, but binding to
* a specific interface address receives only unicast. The trick
* is to bind a second socket to the interface broadcast address,
* which will then receive only broadcasts.
*/
if (config.pAddr.ia.sin_addr.s_addr != htonl(INADDR_ANY)) {
ELLLIST bcastList = ELLLIST_INIT;
osiSockDiscoverBroadcastAddresses (&bcastList,
sock, &config.pAddr); // match addr
if(ellCount(&bcastList)==0) {
errlogPrintf("CAS UDP: failed to find interface broadcast address\n");
} else {
osiSockAddrNode *pNode = (osiSockAddrNode*)ellFirst(&bcastList);
assert(config.pAddr.sa.sa_family==pNode->addr.sa.sa_family);
if (config.pAddr.ia.sin_addr.s_addr != pNode->addr.ia.sin_addr.s_addr) {
/* copy the address, keep the port */
config.pAddr.ia.sin_addr.s_addr = pNode->addr.ia.sin_addr.s_addr;
epicsThreadMustCreate ( "CAS-UDPB", priorityOfUDP,
epicsThreadGetStackSize (epicsThreadStackMedium),
cast_server, &config );
epicsEventMustWait(casudp_startStopEvent);
}
}
ellFree(&bcastList);
}
#endif
}
epicsEventMustWait(casudp_startStopEvent);
epicsEventSignal(beacon_startStopEvent);
while (TRUE) {

View File

@@ -137,6 +137,10 @@ struct event_ext {
char modified; /* mod & ev flw ctrl enbl */
};
typedef struct {
osiSockAddr pAddr;
SOCKET reply_sock;
} cast_config;
enum ctl {ctlInit, ctlRun, ctlPause, ctlExit};
@@ -161,7 +165,6 @@ enum ctl {ctlInit, ctlRun, ctlPause, ctlExit};
GLBLTYPE int CASDEBUG;
GLBLTYPE SOCKET IOC_sock;
GLBLTYPE SOCKET IOC_cast_sock;
GLBLTYPE unsigned short ca_server_port;
GLBLTYPE ELLLIST clientQ; /* locked by clientQlock */
GLBLTYPE ELLLIST beaconAddrList;