627 lines
17 KiB
C++
627 lines
17 KiB
C++
/*************************************************************************\
|
|
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
|
|
* National Laboratory.
|
|
* Copyright (c) 2002 The Regents of the University of California, as
|
|
* Operator of Los Alamos National Laboratory.
|
|
* EPICS BASE Versions 3.13.7
|
|
* and higher are distributed subject to a Software License Agreement found
|
|
* in file LICENSE that is included with this distribution.
|
|
\*************************************************************************/
|
|
/*
|
|
* Author: Jeffrey O. Hill
|
|
* hill@luke.lanl.gov
|
|
* (505) 665 1831
|
|
*/
|
|
|
|
//
|
|
//
|
|
// Should I fetch the MTU from the outgoing interface?
|
|
//
|
|
//
|
|
|
|
#include "server.h"
|
|
#include "addrList.h"
|
|
#include "casIODIL.h"
|
|
|
|
/*
|
|
* forcePort ()
|
|
*/
|
|
static void forcePort (ELLLIST *pList, unsigned short port)
|
|
{
|
|
osiSockAddrNode *pNode;
|
|
|
|
pNode = reinterpret_cast <osiSockAddrNode *> (ellFirst ( pList ));
|
|
while ( pNode ) {
|
|
if ( pNode->addr.sa.sa_family == AF_INET ) {
|
|
pNode->addr.ia.sin_port = htons (port);
|
|
}
|
|
pNode = reinterpret_cast <osiSockAddrNode *> (ellNext ( &pNode->node ));
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// casDGIntfIO::casDGIntfIO()
|
|
//
|
|
casDGIntfIO::casDGIntfIO (caServerI &serverIn, const caNetAddr &addr,
|
|
bool autoBeaconAddr, bool addConfigBeaconAddr) :
|
|
casDGClient (serverIn)
|
|
{
|
|
ELLLIST BCastAddrList;
|
|
osiSockAddr serverAddr;
|
|
osiSockAddr serverBCastAddr;
|
|
int status;
|
|
unsigned short beaconPort;
|
|
|
|
ellInit ( &BCastAddrList );
|
|
ellInit ( &this->beaconAddrList );
|
|
|
|
if ( ! osiSockAttach () ) {
|
|
throw S_cas_internal;
|
|
}
|
|
|
|
this->sock = casDGIntfIO::makeSockDG();
|
|
if (this->sock==INVALID_SOCKET) {
|
|
throw S_cas_internal;
|
|
}
|
|
|
|
this->beaconSock = casDGIntfIO::makeSockDG();
|
|
if (this->beaconSock==INVALID_SOCKET) {
|
|
socket_close (this->sock);
|
|
throw S_cas_internal;
|
|
}
|
|
|
|
//
|
|
// Fetch port configuration from EPICS environment variables
|
|
//
|
|
if (envGetConfigParamPtr(&EPICS_CAS_SERVER_PORT)) {
|
|
this->dgPort = envGetInetPortConfigParam (&EPICS_CAS_SERVER_PORT,
|
|
static_cast <unsigned short> (CA_SERVER_PORT));
|
|
}
|
|
else {
|
|
this->dgPort = envGetInetPortConfigParam (&EPICS_CA_SERVER_PORT,
|
|
static_cast <unsigned short> (CA_SERVER_PORT));
|
|
}
|
|
if (envGetConfigParamPtr(&EPICS_CAS_BEACON_PORT)) {
|
|
beaconPort = envGetInetPortConfigParam (&EPICS_CAS_BEACON_PORT,
|
|
static_cast <unsigned short> (CA_REPEATER_PORT));
|
|
}
|
|
else {
|
|
beaconPort = envGetInetPortConfigParam (&EPICS_CA_REPEATER_PORT,
|
|
static_cast <unsigned short> (CA_REPEATER_PORT));
|
|
}
|
|
|
|
//
|
|
// set up the primary address of the server
|
|
//
|
|
serverAddr.ia = addr;
|
|
serverAddr.ia.sin_port = htons (this->dgPort);
|
|
|
|
//
|
|
// discover beacon addresses associated with this interface
|
|
//
|
|
{
|
|
osiSockAddrNode *pAddr;
|
|
ELLLIST tmpList;
|
|
|
|
ellInit ( &tmpList );
|
|
osiSockDiscoverBroadcastAddresses (&tmpList,
|
|
this->sock, &serverAddr); // match addr
|
|
removeDuplicateAddresses ( &BCastAddrList, &tmpList, 1 );
|
|
if (ellCount(&BCastAddrList)<1) {
|
|
errMessage (S_cas_noInterface, "- unable to continue");
|
|
socket_close (this->sock);
|
|
throw S_cas_noInterface;
|
|
}
|
|
pAddr = reinterpret_cast <osiSockAddrNode *> (ellFirst (&BCastAddrList));
|
|
serverBCastAddr.ia = pAddr->addr.ia;
|
|
serverBCastAddr.ia.sin_port = htons (this->dgPort);
|
|
|
|
if ( autoBeaconAddr ) {
|
|
forcePort ( &BCastAddrList, beaconPort );
|
|
}
|
|
else {
|
|
// avoid use of ellFree because problems on windows occur if the
|
|
// free is in a different DLL than the malloc
|
|
while ( ELLNODE * pnode = ellGet ( & BCastAddrList ) ) {
|
|
free ( pnode );
|
|
}
|
|
}
|
|
}
|
|
|
|
status = bind ( this->sock, &serverAddr.sa, sizeof (serverAddr) );
|
|
if ( status < 0 ) {
|
|
char buf[64];
|
|
int errnoCpy = SOCKERRNO;
|
|
ipAddrToA ( &serverAddr.ia, buf, sizeof ( buf ) );
|
|
errPrintf ( S_cas_bindFail, __FILE__, __LINE__,
|
|
"- bind UDP IP addr=%s failed because %s", buf, SOCKERRSTR ( errnoCpy ) );
|
|
socket_close (this->sock);
|
|
throw S_cas_bindFail;
|
|
}
|
|
|
|
if ( addConfigBeaconAddr ) {
|
|
|
|
//
|
|
// by default use EPICS_CA_ADDR_LIST for the
|
|
// beacon address list
|
|
//
|
|
const ENV_PARAM *pParam;
|
|
|
|
if ( envGetConfigParamPtr ( & EPICS_CAS_INTF_ADDR_LIST ) ||
|
|
envGetConfigParamPtr ( & EPICS_CAS_BEACON_ADDR_LIST ) ) {
|
|
pParam = & EPICS_CAS_BEACON_ADDR_LIST;
|
|
}
|
|
else {
|
|
pParam = & EPICS_CA_ADDR_LIST;
|
|
}
|
|
|
|
//
|
|
// add in the configured addresses
|
|
//
|
|
addAddrToChannelAccessAddressList (
|
|
& BCastAddrList, pParam, beaconPort );
|
|
}
|
|
|
|
removeDuplicateAddresses ( & this->beaconAddrList, & BCastAddrList, 0 );
|
|
|
|
{
|
|
ELLLIST parsed, filtered;
|
|
ellInit ( & parsed );
|
|
ellInit ( & filtered );
|
|
// we dont care what port they are coming from
|
|
addAddrToChannelAccessAddressList ( & parsed, & EPICS_CAS_IGNORE_ADDR_LIST, 0 );
|
|
removeDuplicateAddresses ( & filtered, & parsed, true );
|
|
|
|
while ( ELLNODE * pRawNode = ellGet ( & filtered ) ) {
|
|
assert ( offsetof (osiSockAddrNode, node) == 0 );
|
|
osiSockAddrNode * pNode = reinterpret_cast < osiSockAddrNode * > ( pRawNode );
|
|
if ( pNode->addr.sa.sa_family == AF_INET ) {
|
|
ipIgnoreEntry * pIPI = new ipIgnoreEntry ( pNode->addr.ia.sin_addr.s_addr );
|
|
this->ignoreTable.add ( * pIPI );
|
|
}
|
|
else {
|
|
errlogPrintf (
|
|
"Expected IP V4 address - EPICS_CAS_IGNORE_ADDR_LIST entry ignored\n" );
|
|
}
|
|
free ( pNode );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Solaris specific:
|
|
// If they are binding to a particular interface then
|
|
// we will also need to bind to the broadcast address
|
|
// for that interface (if it has one). This allows
|
|
// broadcast packets to be received, but we must reply
|
|
// through the "normal" UDP binding or the client will
|
|
// appear to receive replies from the broadcast address.
|
|
// Since it should never be valid to fill in the UDP
|
|
// source address as the broadcast address, then we must
|
|
// conclude that the Solaris implementation is broken.
|
|
//
|
|
// WIN32 specific:
|
|
// On windows this appears to only create problems because
|
|
// they correctly allow broadcast to be received when
|
|
// binding to a particular interface's IP address, and
|
|
// always fill in this interface's address as the reply
|
|
// address.
|
|
//
|
|
#if !defined(_WIN32)
|
|
if (serverAddr.ia.sin_addr.s_addr != htonl(INADDR_ANY)) {
|
|
|
|
this->bcastRecvSock = casDGIntfIO::makeSockDG ();
|
|
if (this->bcastRecvSock==INVALID_SOCKET) {
|
|
socket_close (this->sock);
|
|
throw S_cas_internal;
|
|
}
|
|
|
|
status = bind ( this->bcastRecvSock, &serverBCastAddr.sa,
|
|
sizeof (serverBCastAddr.sa) );
|
|
if (status<0) {
|
|
char buf[64];
|
|
int errnoCpy = SOCKERRNO;
|
|
ipAddrToA ( & serverBCastAddr.ia, buf, sizeof ( buf ) );
|
|
errPrintf ( S_cas_bindFail, __FILE__, __LINE__,
|
|
"- bind UDP IP addr=%s failed because %s",
|
|
buf, SOCKERRSTR ( errnoCpy ) );
|
|
socket_close ( this->sock );
|
|
socket_close ( this->bcastRecvSock );
|
|
throw S_cas_bindFail;
|
|
}
|
|
}
|
|
else {
|
|
this->bcastRecvSock=INVALID_SOCKET;
|
|
}
|
|
#else
|
|
this->bcastRecvSock=INVALID_SOCKET;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// use an initialize routine ?
|
|
//
|
|
casDGIntfIO::~casDGIntfIO()
|
|
{
|
|
if ( this->sock != INVALID_SOCKET ) {
|
|
socket_close ( this->sock );
|
|
}
|
|
|
|
if ( this->bcastRecvSock != INVALID_SOCKET ) {
|
|
socket_close ( this->bcastRecvSock );
|
|
}
|
|
|
|
if ( this->beaconSock != INVALID_SOCKET ) {
|
|
socket_close ( this->beaconSock );
|
|
}
|
|
|
|
// avoid use of ellFree because problems on windows occur if the
|
|
// free is in a different DLL than the malloc
|
|
ELLNODE * nnode = this->beaconAddrList.node.next;
|
|
while ( nnode )
|
|
{
|
|
ELLNODE * pnode = nnode;
|
|
nnode = nnode->next;
|
|
free ( pnode );
|
|
}
|
|
|
|
this->ignoreTable.traverse ( & ipIgnoreEntry::destroy );
|
|
|
|
osiSockRelease ();
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::show()
|
|
//
|
|
void casDGIntfIO::show (unsigned level) const
|
|
{
|
|
printf ( "casDGIntfIO at %p\n",
|
|
static_cast <const void *> ( this ) );
|
|
printChannelAccessAddressList (&this->beaconAddrList);
|
|
this->casDGClient::show (level);
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::xSetNonBlocking()
|
|
//
|
|
void casDGIntfIO::xSetNonBlocking()
|
|
{
|
|
int status;
|
|
osiSockIoctl_t yes = TRUE;
|
|
|
|
status = socket_ioctl(this->sock, FIONBIO, &yes); // X aCC 392
|
|
if (status<0) {
|
|
errlogPrintf("%s:CAS: UDP non blocking IO set fail because \"%s\"\n",
|
|
__FILE__, SOCKERRSTR(SOCKERRNO));
|
|
}
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::osdRecv()
|
|
//
|
|
inBufClient::fillCondition
|
|
casDGIntfIO::osdRecv ( char * pBufIn, bufSizeT size, // X aCC 361
|
|
fillParameter parm, bufSizeT & actualSize, caNetAddr & fromOut )
|
|
{
|
|
int status;
|
|
osiSocklen_t addrSize;
|
|
sockaddr addr;
|
|
SOCKET sockThisTime;
|
|
|
|
if ( parm == fpUseBroadcastInterface ) {
|
|
sockThisTime = this->bcastRecvSock;
|
|
}
|
|
else {
|
|
sockThisTime = this->sock;
|
|
}
|
|
|
|
addrSize = ( osiSocklen_t ) sizeof ( addr );
|
|
status = recvfrom ( sockThisTime, pBufIn, size, 0,
|
|
&addr, &addrSize );
|
|
if ( status <= 0 ) {
|
|
if ( status < 0 ) {
|
|
int errnoCpy = SOCKERRNO;
|
|
if ( errnoCpy != SOCK_EWOULDBLOCK ) {
|
|
errlogPrintf ( "CAS: UDP recv error was %s",
|
|
SOCKERRSTR ( errnoCpy ) );
|
|
}
|
|
}
|
|
return casFillNone;
|
|
}
|
|
else {
|
|
// filter out and discard frames received from the ignore list
|
|
if ( this->ignoreTable.numEntriesInstalled () > 0 ) {
|
|
if ( addr.sa_family == AF_INET ) {
|
|
sockaddr_in * pIP =
|
|
reinterpret_cast < sockaddr_in * > ( & addr );
|
|
ipIgnoreEntry comapre ( pIP->sin_addr.s_addr );
|
|
if ( this->ignoreTable.lookup ( comapre ) ) {
|
|
return casFillNone;
|
|
}
|
|
}
|
|
}
|
|
fromOut = addr;
|
|
actualSize = static_cast < bufSizeT > ( status );
|
|
return casFillProgress;
|
|
}
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::osdSend()
|
|
//
|
|
outBufClient::flushCondition
|
|
casDGIntfIO::osdSend ( const char * pBufIn, bufSizeT size, // X aCC 361
|
|
const caNetAddr & to )
|
|
{
|
|
int status;
|
|
|
|
//
|
|
// (char *) cast below is for brain dead wrs prototype
|
|
//
|
|
struct sockaddr dest = to;
|
|
status = sendto ( this->sock, (char *) pBufIn, size, 0,
|
|
& dest, sizeof ( dest ) );
|
|
if ( status >= 0 ) {
|
|
assert ( size == (unsigned) status );
|
|
return outBufClient::flushProgress;
|
|
}
|
|
else {
|
|
int errnoCpy = SOCKERRNO;
|
|
if ( errnoCpy != SOCK_EWOULDBLOCK ) {
|
|
char buf[64];
|
|
sockAddrToA ( & dest, buf, sizeof ( buf ) );
|
|
errlogPrintf (
|
|
"CAS: UDP socket send to \"%s\" failed because \"%s\"\n",
|
|
buf, SOCKERRSTR(errnoCpy));
|
|
}
|
|
return outBufClient::flushNone;
|
|
}
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::incomingBytesPresent()
|
|
//
|
|
// ok to return a size of one here when a datagram is present, and
|
|
// zero otherwise.
|
|
//
|
|
bufSizeT casDGIntfIO::incomingBytesPresent () const // X aCC 361
|
|
{
|
|
int status;
|
|
osiSockIoctl_t nchars = 0;
|
|
|
|
status = socket_ioctl ( this->sock, FIONREAD, & nchars ); // X aCC 392
|
|
if ( status < 0 ) {
|
|
int localError = SOCKERRNO;
|
|
if (
|
|
localError != SOCK_ECONNABORTED &&
|
|
localError != SOCK_ECONNRESET &&
|
|
localError != SOCK_EPIPE &&
|
|
localError != SOCK_ETIMEDOUT )
|
|
{
|
|
errlogPrintf ( "CAS: FIONREAD failed because \"%s\"\n",
|
|
SOCKERRSTR ( localError ) );
|
|
}
|
|
return 0u;
|
|
}
|
|
else if ( nchars < 0 ) {
|
|
return 0u;
|
|
}
|
|
else {
|
|
return ( bufSizeT ) nchars;
|
|
}
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::sendBeaconIO()
|
|
//
|
|
void casDGIntfIO::sendBeaconIO ( char & msg, unsigned length,
|
|
aitUint16 & portField, aitUint32 & addrField )
|
|
{
|
|
caNetAddr addr = this->serverAddress ();
|
|
struct sockaddr_in inetAddr = addr.getSockIP();
|
|
osiSockAddrNode *pAddr;
|
|
int status;
|
|
char buf[64];
|
|
|
|
portField = inetAddr.sin_port; // the TCP port
|
|
|
|
for (pAddr = reinterpret_cast <osiSockAddrNode *> ( ellFirst ( & this->beaconAddrList ) );
|
|
pAddr; pAddr = reinterpret_cast <osiSockAddrNode *> ( ellNext ( & pAddr->node ) ) ) {
|
|
status = connect ( this->beaconSock, &pAddr->addr.sa, sizeof ( pAddr->addr.sa ) );
|
|
if (status<0) {
|
|
ipAddrToDottedIP ( & pAddr->addr.ia, buf, sizeof ( buf ) );
|
|
errlogPrintf ( "%s: CA beacon routing (connect to \"%s\") error was \"%s\"\n",
|
|
__FILE__, buf, SOCKERRSTR(SOCKERRNO));
|
|
}
|
|
else {
|
|
osiSockAddr sockAddr;
|
|
osiSocklen_t size = ( osiSocklen_t ) sizeof ( sockAddr.sa );
|
|
status = getsockname ( this->beaconSock, &sockAddr.sa, &size );
|
|
if ( status < 0 ) {
|
|
errlogPrintf ( "%s: CA beacon routing (getsockname) error was \"%s\"\n",
|
|
__FILE__, SOCKERRSTR(SOCKERRNO));
|
|
}
|
|
else if ( sockAddr.sa.sa_family == AF_INET ) {
|
|
addrField = sockAddr.ia.sin_addr.s_addr;
|
|
|
|
status = send ( this->beaconSock, &msg, length, 0 );
|
|
if ( status < 0 ) {
|
|
ipAddrToA ( &pAddr->addr.ia, buf, sizeof(buf) );
|
|
errlogPrintf ( "%s: CA beacon (send to \"%s\") error was \"%s\"\n",
|
|
__FILE__, buf, SOCKERRSTR(SOCKERRNO));
|
|
}
|
|
else {
|
|
unsigned statusAsLength = static_cast < unsigned > ( status );
|
|
assert ( statusAsLength == length );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::optimumInBufferSize()
|
|
//
|
|
bufSizeT casDGIntfIO::optimumInBufferSize ()
|
|
{
|
|
|
|
#if 1
|
|
//
|
|
// must update client before the message size can be
|
|
// increased here
|
|
//
|
|
return ETHERNET_MAX_UDP;
|
|
#else
|
|
int n;
|
|
int size;
|
|
int status;
|
|
|
|
/* fetch the TCP send buffer size */
|
|
n = sizeof(size);
|
|
status = getsockopt(
|
|
this->sock,
|
|
SOL_SOCKET,
|
|
SO_RCVBUF,
|
|
(char *)&size,
|
|
&n);
|
|
if(status < 0 || n != sizeof(size)){
|
|
size = ETEHRNET_MAX_UDP;
|
|
}
|
|
|
|
if (size<=0) {
|
|
size = ETHERNET_MAX_UDP;
|
|
}
|
|
return (bufSizeT) size;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::optimumOutBufferSize()
|
|
//
|
|
bufSizeT casDGIntfIO::optimumOutBufferSize ()
|
|
{
|
|
|
|
#if 1
|
|
//
|
|
// must update client before the message size can be
|
|
// increased here
|
|
//
|
|
return MAX_UDP_SEND;
|
|
#else
|
|
int n;
|
|
int size;
|
|
int status;
|
|
|
|
|
|
/* fetch the TCP send buffer size */
|
|
n = sizeof(size);
|
|
status = getsockopt(
|
|
this->sock,
|
|
SOL_SOCKET,
|
|
SO_SNDBUF,
|
|
(char *)&size,
|
|
&n);
|
|
if(status < 0 || n != sizeof(size)){
|
|
size = MAX_UDP_SEND;
|
|
}
|
|
|
|
if (size<=0) {
|
|
size = MAX_UDP_SEND;
|
|
}
|
|
return (bufSizeT) size;
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// casDGIntfIO::makeSockDG ()
|
|
//
|
|
SOCKET casDGIntfIO::makeSockDG ()
|
|
{
|
|
int yes = TRUE;
|
|
int status;
|
|
SOCKET newSock;
|
|
|
|
newSock = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (newSock == INVALID_SOCKET) {
|
|
errMessage(S_cas_noMemory, "CAS: unable to create cast socket\n");
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
status = setsockopt(
|
|
newSock,
|
|
SOL_SOCKET,
|
|
SO_BROADCAST,
|
|
(char *)&yes,
|
|
sizeof(yes));
|
|
if (status<0) {
|
|
socket_close (newSock);
|
|
errMessage(S_cas_internal,
|
|
"CAS: unable to set up cast socket\n");
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
//
|
|
// some concern that vxWorks will run out of mBuf's
|
|
// if this change is made
|
|
//
|
|
// joh 11-10-98
|
|
//
|
|
#if 0
|
|
{
|
|
//
|
|
//
|
|
// this allows for faster connects by queuing
|
|
// additional incoming UDP search frames
|
|
//
|
|
// this allocates a 32k buffer
|
|
// (uses a power of two)
|
|
//
|
|
int size = 1u<<15u;
|
|
status = setsockopt(
|
|
newSock,
|
|
SOL_SOCKET,
|
|
SO_RCVBUF,
|
|
(char *)&size,
|
|
sizeof(size));
|
|
if (status<0) {
|
|
socket_close (newSock);
|
|
errMessage(S_cas_internal,
|
|
"CAS: unable to set cast socket size\n");
|
|
return INVALID_SOCKET;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// release the port in case we exit early. Also if
|
|
// on a kernel with MULTICAST mods then we can have
|
|
// two UDP servers on the same port number (requires
|
|
// setting SO_REUSEADDR prior to the bind step below).
|
|
//
|
|
status = setsockopt(
|
|
newSock,
|
|
SOL_SOCKET,
|
|
SO_REUSEADDR,
|
|
(char *) &yes,
|
|
sizeof (yes));
|
|
if (status<0) {
|
|
socket_close (newSock);
|
|
errMessage(S_cas_internal,
|
|
"CAS: unable to set SO_REUSEADDR on UDP socket?\n");
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
return newSock;
|
|
}
|
|
|
|
//
|
|
// casDGIntfIO::getFD()
|
|
// (avoid problems with the GNU inliner)
|
|
//
|
|
int casDGIntfIO::getFD() const
|
|
{
|
|
return this->sock;
|
|
}
|
|
|