This is Unofficial EPICS BASE Doxygen Site
repeater.cpp
Go to the documentation of this file.
1 /*************************************************************************\
2 * Copyright (c) 2002 The University of Chicago, as Operator of Argonne
3 * National Laboratory.
4 * Copyright (c) 2002 The Regents of the University of California, as
5 * Operator of Los Alamos National Laboratory.
6 * EPICS BASE is distributed subject to a Software License Agreement found
7 * in file LICENSE that is included with this distribution.
8 \*************************************************************************/
9 /*
10  *
11  * REPEATER.cpp
12  *
13  * CA broadcast repeater
14  *
15  * Author: Jeff Hill
16  * Date: 3-27-90
17  *
18  * PURPOSE:
19  * Broadcasts fan out over the LAN, but old IP kernels do not allow
20  * two processes on the same machine to get the same broadcast
21  * (and modern IP kernels do not allow two processes on the same machine
22  * to receive the same unicast).
23  *
24  * This code fans out UDP messages sent to the CA repeater port
25  * to all CA client processes that have subscribed.
26  *
27  */
28 
29 /*
30  * It would be preferable to avoid using the repeater on multicast enhanced
31  * IP kernels, but this is not going to work in all situations because
32  * (according to Steven's TCP/IP illustrated volume I) if a broadcast is
33  * received it goes to all sockets on the same port, but if a unicast is
34  * received it goes to only one of the sockets on the same port (we can only
35  * guess at which one it will be).
36  *
37  * I have observed this behavior under winsock II:
38  * o only one of the sockets on the same port receives the message if we
39  * send to the loopback address
40  * o both of the sockets on the same port receives the message if we send
41  * to the broadcast address
42  */
43 
44 /* verifyClients() Mechanism
45  *
46  * This is required because Solaris and HPUX have half baked versions
47  * of sockets.
48  *
49  * As written, the repeater should be robust against situations where the
50  * IP kernel doesn't implement UDP disconnect on receiving ICMP port
51  * unreachable errors from the destination process. As I recall, this
52  * change was required in the repeater code when we ported from sunos4 to
53  * Solaris. To avoid unreasonable overhead, I decided at the time to check
54  * the validity of all existing connections only when a new client
55  * registers with the repeater (and not when fanning out each beacon
56  * received). -- Jeff
57  */
58 
59 #include <string>
60 #include <stdexcept>
61 #include <stdio.h>
62 
63 #define epicsAssertAuthor "Jeff Hill johill@lanl.gov"
64 
65 #include "tsDLList.h"
66 #include "envDefs.h"
67 #include "tsFreeList.h"
68 #include "osiWireFormat.h"
69 #include "taskwd.h"
70 #include "errlog.h"
71 
72 #include "iocinf.h"
73 #include "caProto.h"
74 #include "udpiiu.h"
75 #include "repeaterClient.h"
76 #include "addrList.h"
77 
78 
79 /*
80  * these can be external since there is only one instance
81  * per machine so we dont care about reentrancy
82  */
83 static tsDLList < repeaterClient > client_list;
84 
85 static const unsigned short PORT_ANY = 0u;
86 
87 /*
88  * makeSocket()
89  */
90 static int makeSocket ( unsigned short port, bool reuseAddr, SOCKET * pSock )
91 {
92  SOCKET sock = epicsSocketCreate ( AF_INET, SOCK_DGRAM, 0 );
93 
94  if ( sock == INVALID_SOCKET ) {
95  *pSock = sock;
96  return SOCKERRNO;
97  }
98 
99  /*
100  * no need to bind if unconstrained
101  */
102  if ( port != PORT_ANY ) {
103  int status;
104  union {
105  struct sockaddr_in ia;
106  struct sockaddr sa;
107  } bd;
108 
109  memset ( (char *) &bd, 0, sizeof (bd) );
110  bd.ia.sin_family = AF_INET;
111  bd.ia.sin_addr.s_addr = htonl ( INADDR_ANY );
112  bd.ia.sin_port = htons ( port );
113  status = bind ( sock, &bd.sa, (int) sizeof(bd) );
114  if ( status < 0 ) {
115  status = SOCKERRNO;
116  epicsSocketDestroy ( sock );
117  return status;
118  }
119  if ( reuseAddr ) {
121  }
122  }
123  *pSock = sock;
124  return 0;
125 }
126 
128  from ( fromIn ), sock ( INVALID_SOCKET )
129 {
130 #ifdef DEBUG
131  unsigned port = ntohs ( from.ia.sin_port );
132  debugPrintf ( ( "new client %u\n", port ) );
133 #endif
134 }
135 
137 {
138  int status;
139 
140  if ( int sockerrno = makeSocket ( PORT_ANY, false, & this->sock ) ) {
141  char sockErrBuf[64];
143  sockErrBuf, sizeof ( sockErrBuf ), sockerrno );
144  fprintf ( stderr, "%s: no client sock because \"%s\"\n",
145  __FILE__, sockErrBuf );
146  return false;
147  }
148 
149  status = ::connect ( this->sock, &this->from.sa, sizeof ( this->from.sa ) );
150  if ( status < 0 ) {
151  char sockErrBuf[64];
153  sockErrBuf, sizeof ( sockErrBuf ) );
154  fprintf ( stderr, "%s: unable to connect client sock because \"%s\"\n",
155  __FILE__, sockErrBuf );
156  return false;
157  }
158 
159  return true;
160 }
161 
163 {
164  int status;
165 
166  caHdr confirm;
167  memset ( (char *) &confirm, '\0', sizeof (confirm) );
169  confirm.m_available = this->from.ia.sin_addr.s_addr;
170  status = send ( this->sock, (char *) &confirm,
171  sizeof (confirm), 0 );
172  if ( status >= 0 ) {
173  assert ( status == sizeof ( confirm ) );
174  return true;
175  }
176  else if ( SOCKERRNO == SOCK_ECONNREFUSED ) {
177  return false;
178  }
179  else {
180  char sockErrBuf[64];
182  sockErrBuf, sizeof ( sockErrBuf ) );
183  debugPrintf ( ( "CA Repeater: confirm req err was \"%s\"\n", sockErrBuf) );
184  return false;
185  }
186 }
187 
188 bool repeaterClient::sendMessage ( const void *pBuf, unsigned bufSize )
189 {
190  int status;
191 
192  status = send ( this->sock, (char *) pBuf, bufSize, 0 );
193  if ( status >= 0 ) {
194  assert ( static_cast <unsigned> ( status ) == bufSize );
195 #ifdef DEBUG
196  epicsUInt16 port = ntohs ( this->from.ia.sin_port );
197  debugPrintf ( ("Sent to %u\n", port ) );
198 #endif
199  return true;
200  }
201  else {
202  int errnoCpy = SOCKERRNO;
203  if ( errnoCpy == SOCK_ECONNREFUSED ) {
204 #ifdef DEBUG
205  epicsUInt16 port = ntohs ( this->from.ia.sin_port );
206  debugPrintf ( ("Client refused message %u\n", port ) );
207 #endif
208  }
209  else {
210  char sockErrBuf[64];
211  epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) );
212  debugPrintf ( ( "CA Repeater: UDP send err was \"%s\"\n", sockErrBuf) );
213  }
214  return false;
215  }
216 }
217 
219 {
220  if ( this->sock != INVALID_SOCKET ) {
221  epicsSocketDestroy ( this->sock );
222  }
223 #ifdef DEBUG
224  epicsUInt16 port = ntohs ( this->from.ia.sin_port );
225  debugPrintf ( ( "Deleted client %u\n", port ) );
226 #endif
227 }
228 
229 void repeaterClient::operator delete ( void * )
230 {
231  // Visual C++ .net appears to require operator delete if
232  // placement operator delete is defined? I smell a ms rat
233  // because if I declare placement new and delete, but
234  // comment out the placement delete definition there are
235  // no undefined symbols.
236  errlogPrintf ( "%s:%d this compiler is confused about placement delete - memory was probably leaked",
237  __FILE__, __LINE__ );
238 }
239 
240 void * repeaterClient::operator new ( size_t size,
242 {
243  return freeList.allocate ( size );
244 }
245 
246 #ifdef CXX_PLACEMENT_DELETE
247 void repeaterClient::operator delete ( void *pCadaver,
249 {
250  freeList.release ( pCadaver );
251 }
252 #endif
253 
254 inline unsigned short repeaterClient::port () const
255 {
256  return ntohs ( this->from.ia.sin_port );
257 }
258 
259 inline bool repeaterClient::identicalAddress ( const osiSockAddr &fromIn )
260 {
261  if ( fromIn.sa.sa_family == this->from.sa.sa_family ) {
262  if ( fromIn.ia.sin_port == this->from.ia.sin_port) {
263  if ( fromIn.ia.sin_addr.s_addr == this->from.ia.sin_addr.s_addr ) {
264  return true;
265  }
266  }
267  }
268  return false;
269 }
270 
271 inline bool repeaterClient::identicalPort ( const osiSockAddr &fromIn )
272 {
273  if ( fromIn.sa.sa_family == this->from.sa.sa_family ) {
274  if ( fromIn.ia.sin_port == this->from.ia.sin_port) {
275  return true;
276  }
277  }
278  return false;
279 }
280 
282 {
283  SOCKET tmpSock;
284  int sockerrno = makeSocket ( this->port (), false, & tmpSock );
285 
286  if ( sockerrno == SOCK_EADDRINUSE ) {
287  // Normal result, client using port
288  return true;
289  }
290 
291  if ( sockerrno == 0 ) {
292  // Client went away, released port
293  epicsSocketDestroy ( tmpSock );
294  }
295  else {
296  char sockErrBuf[64];
298  sockErrBuf, sizeof ( sockErrBuf ), sockerrno );
299  fprintf ( stderr, "CA Repeater: Bind test error \"%s\"\n",
300  sockErrBuf );
301  }
302  return false;
303 }
304 
305 
306 /*
307  * verifyClients()
308  */
309 static void verifyClients ( tsFreeList < repeaterClient, 0x20 > & freeList )
310 {
311  static tsDLList < repeaterClient > theClients;
312  repeaterClient *pclient;
313 
314  while ( ( pclient = client_list.get () ) ) {
315  if ( pclient->verify () ) {
316  theClients.add ( *pclient );
317  }
318  else {
319  pclient->~repeaterClient ();
320  freeList.release ( pclient );
321  }
322  }
323  client_list.add ( theClients );
324 }
325 
326 /*
327  * fanOut()
328  */
329 static void fanOut ( const osiSockAddr & from, const void * pMsg,
330  unsigned msgSize, tsFreeList < repeaterClient, 0x20 > & freeList )
331 {
332  static tsDLList < repeaterClient > theClients;
333  repeaterClient *pclient;
334 
335  while ( ( pclient = client_list.get () ) ) {
336  theClients.add ( *pclient );
337  /* Dont reflect back to sender */
338  if ( pclient->identicalAddress ( from ) ) {
339  continue;
340  }
341 
342  if ( ! pclient->sendMessage ( pMsg, msgSize ) ) {
343  if ( ! pclient->verify () ) {
344  theClients.remove ( *pclient );
345  pclient->~repeaterClient ();
346  freeList.release ( pclient );
347  }
348  }
349  }
350 
351  client_list.add ( theClients );
352 }
353 
354 /*
355  * register_new_client()
356  */
357 static void register_new_client ( osiSockAddr & from,
359 {
360  bool newClient = false;
361  int status;
362 
363  if ( from.sa.sa_family != AF_INET ) {
364  return;
365  }
366 
367  /*
368  * the repeater and its clients must be on the same host
369  */
370  if ( INADDR_LOOPBACK != ntohl ( from.ia.sin_addr.s_addr ) ) {
371  static SOCKET testSock = INVALID_SOCKET;
372  static bool init = false;
373 
374  if ( ! init ) {
375  SOCKET sock;
376  if ( int sockerrno = makeSocket ( PORT_ANY, true, & sock ) ) {
377  char sockErrBuf[64];
379  sockErrBuf, sizeof ( sockErrBuf ), sockerrno );
380  fprintf ( stderr, "%s: Unable to create repeater bind test socket because \"%s\"\n",
381  __FILE__, sockErrBuf );
382  }
383  else {
384  testSock = sock;
385  }
386  init = true;
387  }
388 
389  /*
390  * Unfortunately on 3.13 beta 11 and before the
391  * repeater would not always allow the loopback address
392  * as a local client address so current clients alternate
393  * between the address of the first non-loopback interface
394  * found and the loopback addresss when subscribing with
395  * the CA repeater until all CA repeaters have been updated
396  * to current code.
397  */
398  if ( testSock != INVALID_SOCKET ) {
399  osiSockAddr addr;
400 
401  addr = from;
402  addr.ia.sin_port = PORT_ANY;
403 
404  /* we can only bind to a local address */
405  status = bind ( testSock, &addr.sa, sizeof ( addr ) );
406  if ( status ) {
407  return;
408  }
409  }
410  else {
411  return;
412  }
413  }
414 
415  tsDLIter < repeaterClient > pclient = client_list.firstIter ();
416  while ( pclient.valid () ) {
417  if ( pclient->identicalPort ( from ) ) {
418  break;
419  }
420  pclient++;
421  }
422 
423  repeaterClient *pNewClient;
424  if ( pclient.valid () ) {
425  pNewClient = pclient.pointer ();
426  }
427  else {
428  pNewClient = new ( freeList ) repeaterClient ( from );
429  if ( ! pNewClient ) {
430  fprintf ( stderr, "%s: no memory for new client\n", __FILE__ );
431  return;
432  }
433  if ( ! pNewClient->connect () ) {
434  pNewClient->~repeaterClient ();
435  freeList.release ( pNewClient );
436  return;
437  }
438  client_list.add ( *pNewClient );
439  newClient = true;
440  }
441 
442  if ( ! pNewClient->sendConfirm () ) {
443  client_list.remove ( *pNewClient );
444  pNewClient->~repeaterClient ();
445  freeList.release ( pNewClient );
446 # ifdef DEBUG
447  epicsUInt16 port = ntohs ( from.ia.sin_port );
448  debugPrintf ( ( "Deleted repeater client=%u (error while sending ack)\n",
449  port ) );
450 # endif
451  }
452 
453  /*
454  * send a noop message to all other clients so that we dont
455  * accumulate sockets when there are no beacons
456  */
457  caHdr noop;
458  memset ( (char *) &noop, '\0', sizeof ( noop ) );
460  fanOut ( from, &noop, sizeof ( noop ), freeList );
461 
462  if ( newClient ) {
463  /*
464  * For HPUX and Solaris we need to verify that the clients
465  * have not gone away - because an ICMP error return does not
466  * get through to send(), which returns no error code.
467  *
468  * This is done each time that a new client is created.
469  * See also the note in the file header.
470  *
471  * This is done here in order to avoid deleting a client
472  * prior to sending its confirm message.
473  */
474  verifyClients ( freeList );
475  }
476 }
477 
478 
479 /*
480  * ca_repeater ()
481  */
482 void ca_repeater ()
483 {
485  int size;
486  SOCKET sock;
487  osiSockAddr from;
488  unsigned short port;
489  char * pBuf;
490 
491  pBuf = new char [MAX_UDP_RECV];
492 
493  {
494  bool success = osiSockAttach();
495  assert ( success );
496  }
497 
499  static_cast <unsigned short> (CA_REPEATER_PORT) );
500  if ( int sockerrno = makeSocket ( port, true, & sock ) ) {
501  /*
502  * test for server was already started
503  */
504  if ( sockerrno == SOCK_EADDRINUSE ) {
505  osiSockRelease ();
506  debugPrintf ( ( "CA Repeater: exiting because a repeater is already running\n" ) );
507  delete [] pBuf;
508  return;
509  }
510  char sockErrBuf[64];
512  sockErrBuf, sizeof ( sockErrBuf ), sockerrno );
513  fprintf ( stderr, "%s: Unable to create repeater socket because \"%s\" - fatal\n",
514  __FILE__, sockErrBuf );
515  osiSockRelease ();
516  delete [] pBuf;
517  return;
518  }
519 
520 #ifdef IP_ADD_MEMBERSHIP
521  /*
522  * join UDP socket to any multicast groups
523  */
524  {
525  ELLLIST casBeaconAddrList = ELLLIST_INIT;
526  ELLLIST casMergeAddrList = ELLLIST_INIT;
527 
528  /*
529  * collect user specified beacon address list;
530  * check BEACON_ADDR_LIST list first; if no result, take CA_ADDR_LIST
531  */
532  if(!addAddrToChannelAccessAddressList(&casMergeAddrList,&EPICS_CAS_BEACON_ADDR_LIST,port,0)) {
533  addAddrToChannelAccessAddressList(&casMergeAddrList,&EPICS_CA_ADDR_LIST,port,0);
534  }
535 
536  /* First clean up */
537  removeDuplicateAddresses(&casBeaconAddrList, &casMergeAddrList , 0);
538 
539  osiSockAddrNode *pNode;
540  for(pNode = (osiSockAddrNode*)ellFirst(&casBeaconAddrList);
541  pNode;
542  pNode = (osiSockAddrNode*)ellNext(&pNode->node))
543  {
544 
545  if(pNode->addr.ia.sin_family==AF_INET) {
546  epicsUInt32 top = ntohl(pNode->addr.ia.sin_addr.s_addr)>>24;
547  if(top>=224 && top<=239) {
548 
549  /* This is a multi-cast address */
550  struct ip_mreq mreq;
551 
552  memset(&mreq, 0, sizeof(mreq));
553  mreq.imr_multiaddr = pNode->addr.ia.sin_addr;
554  mreq.imr_interface.s_addr = INADDR_ANY;
555 
556  if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
557  (char *) &mreq, sizeof(mreq)) != 0) {
558  char name[40];
559  char sockErrBuf[64];
560  epicsSocketConvertErrnoToString (sockErrBuf, sizeof ( sockErrBuf ) );
561  ipAddrToDottedIP (&pNode->addr.ia, name, sizeof(name));
562  errlogPrintf("caR: Socket mcast join to %s failed: %s\n", name, sockErrBuf );
563  }
564  }
565  }
566  }
567  }
568 #endif
569 
570  debugPrintf ( ( "CA Repeater: Attached and initialized\n" ) );
571 
572  while ( true ) {
573  osiSocklen_t from_size = sizeof ( from );
574  size = recvfrom ( sock, pBuf, MAX_UDP_RECV, 0,
575  &from.sa, &from_size );
576  if ( size < 0 ) {
577  int errnoCpy = SOCKERRNO;
578  // Avoid spurious ECONNREFUSED bug in linux
579  if ( errnoCpy == SOCK_ECONNREFUSED ) {
580  continue;
581  }
582  // Avoid ECONNRESET from connected socket in windows
583  if ( errnoCpy == SOCK_ECONNRESET ) {
584  continue;
585  }
586  char sockErrBuf[64];
588  sockErrBuf, sizeof ( sockErrBuf ) );
589  fprintf ( stderr, "CA Repeater: unexpected UDP recv err: %s\n",
590  sockErrBuf );
591  continue;
592  }
593 
594  caHdr * pMsg = ( caHdr * ) pBuf;
595 
596  /*
597  * both zero length message and a registration message
598  * will register a new client
599  */
600  if ( ( (size_t) size) >= sizeof (*pMsg) ) {
602  register_new_client ( from, freeList );
603 
604  /*
605  * strip register client message
606  */
607  pMsg++;
608  size -= sizeof ( *pMsg );
609  if ( size==0 ) {
610  continue;
611  }
612  }
614  if ( pMsg->m_available == 0u ) {
615  pMsg->m_available = from.ia.sin_addr.s_addr;
616  }
617  }
618  }
619  else if ( size == 0 ) {
620  register_new_client ( from, freeList );
621  continue;
622  }
623 
624  fanOut ( from, pMsg, size, freeList );
625  }
626 }
627 
628 /*
629  * caRepeaterThread ()
630  */
631 extern "C" void caRepeaterThread ( void * /* pDummy */ )
632 {
634  ca_repeater ();
635 }
636 
637 
#define SOCK_ECONNREFUSED
Definition: osdSock.h:58
LIBCA_API void epicsStdCall removeDuplicateAddresses(struct ELLLIST *pDestList, ELLLIST *pSrcList, int silent)
Definition: iocinf.cpp:123
#define INADDR_LOOPBACK
Definition: osdSock.h:76
T * pointer() const
Definition: tsDLList.h:678
#define CA_REPEATER_PORT
Definition: caProto.h:54
LIBCOM_API void epicsStdCall epicsSocketDestroy(SOCKET s)
Definition: osdSock.c:117
#define REPEATER_REGISTER
Definition: caProto.h:107
void taskwdInsert(epicsThreadId tid, TASKWDFUNC callback, void *usr)
Definition: taskwd.c:176
#define INVALID_SOCKET
Definition: osdSock.h:32
#define assert(exp)
Declare that a condition should be true.
Definition: epicsAssert.h:70
osiSockAddr addr
Definition: osiSock.h:163
void add(T &item)
Definition: tsDLList.h:313
#define SOCK_EADDRINUSE
Definition: osdSock.h:56
pvd::Status status
#define CA_PROTO_VERSION
Definition: caProto.h:83
int osiSocklen_t
Definition: osdSock.h:36
void ca_repeater()
Definition: repeater.cpp:482
void osiSockRelease()
Definition: osdSock.c:62
struct sockaddr sa
Definition: osiSock.h:158
LIBCA_API int epicsStdCall addAddrToChannelAccessAddressList(struct ELLLIST *pList, const ENV_PARAM *pEnv, unsigned short port, int ignoreNonDefaultPort)
Definition: iocinf.cpp:74
unsigned short epicsUInt16
Definition: epicsTypes.h:41
ca_uint32_t m_available
Definition: caProto.h:166
struct sockaddr_in ia
Definition: osiSock.h:157
Routines to get and set EPICS environment parameters.
tsDLIterConst< T > firstIter() const
Definition: tsDLList.h:459
epicsPlacementDeleteOperator((void *, tsFreeList< repeaterClient, 0x20 > &)) private SOCKE sock)
#define NULL
Definition: catime.c:38
unsigned short port() const
Definition: repeater.cpp:254
unsigned int epicsUInt32
Definition: epicsTypes.h:43
#define ELLLIST_INIT
Value of an empty list.
Definition: ellLib.h:63
bool valid() const
Definition: tsDLList.h:607
void epicsSocketConvertErrnoToString(char *pBuf, unsigned bufSize)
#define SOCK_ECONNRESET
Definition: osdSock.h:53
LIBCOM_API SOCKET epicsStdCall epicsSocketCreate(int domain, int type, int protocol)
Definition: osdSock.c:71
void caRepeaterThread(void *)
Definition: repeater.cpp:631
ca_uint16_t m_cmmd
Definition: caProto.h:161
#define ellNext(PNODE)
Find the next node in list.
Definition: ellLib.h:99
LIBCOM_API const ENV_PARAM EPICS_CA_REPEATER_PORT
LIBCOM_API void epicsStdCall epicsSocketEnableAddressReuseDuringTimeWaitState(SOCKET s)
LIBCOM_API unsigned short epicsStdCall envGetInetPortConfigParam(const ENV_PARAM *pEnv, unsigned short defaultPort)
Get value of a port number configuration parameter.
Definition: envSubr.c:398
int SOCKET
Definition: osdSock.h:31
repeaterClient(const osiSockAddr &from)
Definition: repeater.cpp:127
LIBCOM_API const ENV_PARAM EPICS_CA_ADDR_LIST
int errlogPrintf(const char *pFormat,...)
Definition: errlog.c:105
bool connect()
Definition: repeater.cpp:136
#define REPEATER_CONFIRM
Definition: caProto.h:100
LIBCOM_API const ENV_PARAM EPICS_CAS_BEACON_ADDR_LIST
bool sendConfirm()
Definition: repeater.cpp:162
#define SOCKERRNO
Definition: osdSock.h:33
#define MAX_UDP_RECV
Definition: caProto.h:61
T * get()
Definition: tsDLList.h:261
bool identicalPort(const osiSockAddr &from)
Definition: repeater.cpp:271
ELLNODE node
Definition: osiSock.h:162
void release(void *p)
Definition: tsFreeList.h:176
int osiSockAttach()
Definition: osdSock.c:54
void remove(T &item)
Definition: tsDLList.h:219
#define debugPrintf(argsInParen)
Definition: iocinf.h:30
#define stderr
Definition: epicsStdio.h:32
bool sendMessage(const void *pBuf, unsigned bufSize)
Definition: repeater.cpp:188
List header type.
Definition: ellLib.h:56
bool identicalAddress(const osiSockAddr &from)
Definition: repeater.cpp:259
#define CA_PROTO_RSRV_IS_UP
Definition: caProto.h:96
void epicsSocketConvertErrorToString(char *pBuf, unsigned bufSize, int theSockError)
unsigned epicsStdCall ipAddrToDottedIP(const struct sockaddr_in *paddr, char *pBuf, unsigned bufSize)
Definition: osiSock.c:144
#define ellFirst(PLIST)
Find the first node in list.
Definition: ellLib.h:89
LIBCOM_API epicsThreadId epicsStdCall epicsThreadGetIdSelf(void)
Definition: osdThread.c:810