/******************************************************************************/
/* */
/* X r d N e t S o c k e t . c c */
/* */
/* (C) 2004 by the Board of Trustees of the Leland Stanford, Jr., University */
/* All Rights Reserved */
/* Produced by Andrew Hanushevsky for Stanford University under contract */
/* DE-AC02-76-SFO0515 with the Deprtment of Energy */
/* */
/* This file is part of the XRootD software suite. */
/* */
/* XRootD is free software: you can redistribute it and/or modify it under */
/* the terms of the GNU Lesser General Public License as published by the */
/* Free Software Foundation, either version 3 of the License, or (at your */
/* option) any later version. */
/* */
/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
/* License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
/* COPYING (GPL license). If not, see . */
/* */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without */
/* specific prior written permission of the institution or contributor. */
/******************************************************************************/
#ifndef WIN32
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#else
#include
#include
#include
#include
#include
#include
#include
#include "XrdSys/XrdWin32.hh"
#endif
#include "XrdNet/XrdNetConnect.hh"
#include "XrdNet/XrdNetOpts.hh"
#include "XrdNet/XrdNetSocket.hh"
#include "XrdNet/XrdNetUtils.hh"
#include "XrdOuc/XrdOucUtils.hh"
#include "XrdSys/XrdSysError.hh"
#include "XrdSys/XrdSysFD.hh"
#include "XrdSys/XrdSysPlatform.hh"
/******************************************************************************/
/* G l o b a l s */
/******************************************************************************/
namespace XrdNetSocketCFG
{
int ka_Idle = 0;
int ka_Itvl = 0;
int ka_Icnt = 0;
};
/******************************************************************************/
/* l o c a l d e f i n e s */
/******************************************************************************/
#define Err(p,a,b,c) (ErrCode = (eroute ? eroute->Emsg(#p, a, b, c) : ErrCode),-1)
#define ErrM(p,a,b,c) (ErrCode = (eroute ? eroute->Emsg(#p, a, b, c) : ErrCode),-1)
/******************************************************************************/
/* C o n s t r u c t o r */
/******************************************************************************/
XrdNetSocket::XrdNetSocket(XrdSysError *erobj, int SockFileDesc)
{
ErrCode = 0;
eroute = erobj;
SockFD = SockFileDesc;
}
/******************************************************************************/
/* A c c e p t */
/******************************************************************************/
int XrdNetSocket::Accept(int timeout)
{
int retc, ClientSock;
ErrCode = 0;
// Check if a timeout was requested
//
if (timeout >= 0)
{struct pollfd sfd = {SockFD,
POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI|POLLHUP, 0};
do {retc = poll(&sfd, 1, timeout);}
while(retc < 0 && (errno == EAGAIN || errno == EINTR));
if (!sfd.revents) return -1;
}
do {ClientSock = XrdSysFD_Accept(SockFD, (struct sockaddr *)0, 0);}
while(ClientSock < 0 && errno == EINTR);
if (ClientSock < 0 && eroute) eroute->Emsg("Accept",errno,"accept connection");
// Return the socket number.
//
return ClientSock;
}
/******************************************************************************/
/* C l o s e */
/******************************************************************************/
void XrdNetSocket::Close()
{
// Close any open file descriptor.
//
if (SockFD >= 0) {close(SockFD); SockFD=-1;}
// Reset values and return.
//
ErrCode=0;
}
/******************************************************************************/
/* C r e a t e */
/******************************************************************************/
XrdNetSocket *XrdNetSocket::Create(XrdSysError *Say, const char *path,
const char *fn, mode_t mode, int opts)
{
XrdNetSocket *ASock;
int pflags = (opts & XRDNET_FIFO ? S_IFIFO : S_IFSOCK);
int sflags = (opts & XRDNET_UDPSOCKET) | XRDNET_SERVER;
int rc = 0;
mode_t myMode = (mode & (S_IRWXU | S_IRWXG));
const char *eMsg = 0;
char fnbuff[1024] = {0};
// Setup the path
//
if (!socketPath(Say, fnbuff, path, fn, mode|pflags))
return (XrdNetSocket *)0;
// Connect to the path
//
ASock = new XrdNetSocket(Say);
#ifndef WIN32
if (opts & XRDNET_FIFO)
{if ((ASock->SockFD = mkfifo(fnbuff, mode)) < 0 && errno != EEXIST)
{eMsg = "create fifo"; rc = errno;}
else if ((ASock->SockFD = XrdSysFD_Open(fnbuff, O_RDWR, myMode)) < 0)
{eMsg = "open fifo"; rc = errno;}
else if (opts & XRDNET_NOCLOSEX) XrdSysFD_Yield(ASock->SockFD);
} else if (ASock->Open(fnbuff, -1, sflags) < 0)
{eMsg = "create socket"; rc = ASock->LastError();}
#else
if (ASock->Open(fnbuff, -1, sflags) < 0)
{eMsg = "create socket"; rc = ASock->LastError();}
#endif
// Return the result
//
if (eMsg) {Say->Emsg("Create", rc, eMsg, fnbuff);
delete ASock; ASock = 0;
}
return ASock;
}
/******************************************************************************/
/* D e t a c h */
/******************************************************************************/
int XrdNetSocket::Detach()
{ int oldFD = SockFD;
SockFD = -1;
return oldFD;
}
/******************************************************************************/
/* g e t W i n d o w */
/******************************************************************************/
int XrdNetSocket::getWindow(int fd, int &Windowsz, XrdSysError *eDest)
{
socklen_t szb = (socklen_t)sizeof(Windowsz);
if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (Sokdata_t)&Windowsz, &szb))
{if (eDest) eDest->Emsg("setWindow", errno, "set socket RCVBUF");
return -1;
}
return 0;
}
/******************************************************************************/
/* O p e n */
/******************************************************************************/
int XrdNetSocket::Open(const char *inpath, int port, int flags, int windowsz)
{
const char *epath, *eText, *action = "configure socket";
char pbuff[128];
int myEC, backlog, SockProt;
int SockType = (flags & XRDNET_UDPSOCKET ? SOCK_DGRAM : SOCK_STREAM);
const int one = 1;
const SOCKLEN_t szone = (SOCKLEN_t)sizeof(one);
// Supply actual port number in error messages
//
if (inpath) epath = inpath;
else {sprintf(pbuff, "port %d", port);
epath = pbuff;
}
// Make sure this object is available for a new socket
//
if (SockFD >= 0) return Err(Open, EBUSY, "create socket for", epath);
// Save the request flags, sometimes we need to check them from the local copy
//
myEC = ErrCode = 0;
// Preset out address information
//
if ((eText = SockInfo.Set(inpath,(port < 0 ? XrdNetAddr::PortInSpec:port))))
{ErrCode = EHOSTUNREACH;
if (eroute)
{char buff[256];
snprintf(buff, sizeof(buff), "'; %s", eText);
eroute->Emsg("Open", "Unable to create socket for '", epath, buff);
}
return -1;
}
// Allocate a socket descriptor of the right type
//
SockProt = SockInfo.Protocol();
if ((SockFD = XrdSysFD_Socket(SockProt, SockType, 0)) < 0)
return Err(Open, errno, "create socket for", epath);
// Based on the type socket, set appropriate options. For server-side Unix
// sockets we must unlink the corresponding Unix path name or bind will fail.
// In some OS's, this creates a problem (e.g., Solaris) since the file inode is
// used to identify the socket and will likely change. This means that connects
// occuring before the bind will hang up to 3 minutes and client needs to retry.
// For non-Unix socketsr be prepared to timeout connects and try again.
//
if (SockProt == PF_UNIX)
{setOpts(SockFD, flags | XRDNET_UDPSOCKET, eroute);
if (flags & XRDNET_SERVER) unlink((const char *)inpath);
} else {
setOpts(SockFD, flags, eroute);
if (setsockopt(SockFD,SOL_SOCKET,SO_REUSEADDR, (Sokdata_t)&one, szone)
&& eroute) eroute->Emsg("Open",errno,"set socket REUSEADDR for",epath);
}
// Set the window size or udp buffer size, as needed (ignore errors)
//
if (windowsz) setWindow(SockFD, windowsz, eroute);
// Either do a bind or a connect.
//
if (flags & XRDNET_SERVER)
{action = "bind socket to";
if (bind(SockFD, SockInfo.SockAddr(), SockInfo.SockSize())) myEC = errno;
else if (SockType == SOCK_STREAM)
{action = "listen on stream";
if (!(backlog = flags & XRDNET_BKLG))
backlog = XRDNETSOCKET_MAXBKLG;
if (listen(SockFD, backlog)) myEC = errno;
}
if (SockProt == PF_UNIX) chmod(inpath, S_IRWXU);
} else {
if (SockType == SOCK_STREAM)
{int tmo = flags & XRDNET_TOUT;
action = "connect socket to";
if (tmo) myEC = XrdNetConnect::Connect(SockFD, SockInfo.SockAddr(),
SockInfo.SockSize(),tmo);
else if (connect(SockFD,SockInfo.SockAddr(),SockInfo.SockSize()))
myEC = errno;
}
}
// Check for any errors and return (Close() sets SockFD to -1).
//
if (myEC)
{Close();
ErrCode = myEC;
if (!(flags & XRDNET_NOEMSG) && eroute)
eroute->Emsg("Open", ErrCode, action, epath);
}
return SockFD;
}
/******************************************************************************/
/* P e e r n a m e */
/******************************************************************************/
const char *XrdNetSocket::Peername(const struct sockaddr **InetAddr,
int *InetSize)
{
const char *errtxt, *PeerName;
// Make sure we have something to look at
//
if (SockFD < 0)
{if (eroute) eroute->Emsg("Peername",
"Unable to obtain peer name; socket not open");
return (char *)0;
}
// Get the host name on the other side of this socket
//
if (!(PeerName = SockInfo.Name(0, &errtxt)))
{if (eroute)
eroute->Emsg("Peername", "Unable to obtain peer name; ",errtxt);
ErrCode = ESRCH;
}
// Return possible address, length and the name
//
if (InetAddr) *InetAddr = SockInfo.SockAddr();
if (InetSize) *InetSize = SockInfo.SockSize();
return PeerName;
}
/******************************************************************************/
/* s e t O p t s */
/******************************************************************************/
int XrdNetSocket::setOpts(int xfd, int opts, XrdSysError *eDest)
{
int rc = 0;
const int one = 1;
const int szint = sizeof(int);
const SOCKLEN_t szone = (SOCKLEN_t)sizeof(one);
static int tcpprotid = XrdNetUtils::ProtoID("tcp");
static struct linger liopts = {1, XRDNETSOCKET_LINGER};
const SOCKLEN_t szlio = (SOCKLEN_t)sizeof(liopts);
if (opts & XRDNET_NOCLOSEX && !XrdSysFD_Yield(xfd))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set fd close on exec");
}
if (opts & XRDNET_UDPSOCKET) return rc;
if (!(opts & XRDNET_NOLINGER)
&& setsockopt(xfd,SOL_SOCKET,SO_LINGER,(Sokdata_t)&liopts,szlio))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set socket LINGER");
}
if (opts & XRDNET_KEEPALIVE)
{if (setsockopt(xfd,SOL_SOCKET,SO_KEEPALIVE,(Sokdata_t)&one,szone))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set socket KEEPALIVE");
}
#ifdef __linux__
else if (opts & XRDNET_SERVER) // Following are inherited in Linux
{if (XrdNetSocketCFG::ka_Idle
&& setsockopt(xfd,SOL_TCP,TCP_KEEPIDLE,&XrdNetSocketCFG::ka_Idle,szint))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set socket KEEPIDLE");
}
if (XrdNetSocketCFG::ka_Itvl
&& setsockopt(xfd,SOL_TCP,TCP_KEEPINTVL,&XrdNetSocketCFG::ka_Itvl,szint))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set socket KEEPINTVL");
}
if (XrdNetSocketCFG::ka_Icnt
&& setsockopt(xfd,SOL_TCP,TCP_KEEPCNT, &XrdNetSocketCFG::ka_Icnt,szint))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set socket KEEPCNT");
}
}
#endif
}
if (!(opts & XRDNET_DELAY)
&& setsockopt(xfd, tcpprotid, TCP_NODELAY, (Sokdata_t)&one,szone))
{rc = 1;
if (eDest) eDest->Emsg("setOpts", errno, "set socket NODELAY");
}
return rc;
}
/******************************************************************************/
/* s e t W i n d o w */
/******************************************************************************/
int XrdNetSocket::setWindow(int xfd, int Windowsz, XrdSysError *eDest)
{
int rc = 0;
const SOCKLEN_t szwb = (SOCKLEN_t)sizeof(Windowsz);
if (setsockopt(xfd, SOL_SOCKET, SO_SNDBUF,
(Sokdata_t)&Windowsz, szwb))
{rc = 1;
if (eDest) eDest->Emsg("setWindow", errno, "set socket SNDBUF");
}
if (setsockopt(xfd, SOL_SOCKET, SO_RCVBUF,
(Sokdata_t)&Windowsz, szwb))
{rc = 1;
if (eDest) eDest->Emsg("setWindow", errno, "set socket RCVBUF");
}
return rc;
}
/******************************************************************************/
/* s o c k e t P a t h */
/******************************************************************************/
char *XrdNetSocket::socketPath(XrdSysError *Say, char *fnbuff,
const char *path, const char *fn, mode_t mode)
{
const int srchOK = S_IXUSR | S_IXGRP;
const int sfMask = (S_IFIFO | S_IFSOCK);
int rc, i, fnlen = strlen(fnbuff);
mode_t myMode = (mode & (S_IRWXU | S_IRWXG)) | srchOK;
struct stat buf;
char *sp = 0;
// Copy the const char path because makePath modifies it
//
i = strlen(path);
if (strlcpy(fnbuff, path, 1024) >= 1024 || (i + fnlen + 1) >= 1024)
{Say->Emsg("createPath", "Socket path", path, "too long");
return 0;
}
// Check if we should separate the filename from the path
//
if (!fn)
{if (fnbuff[i-1] == '/') fnbuff[i-1] = '\0';
if ((sp = rindex(fnbuff, '/'))) *sp = '\0';
}
// Create the directory if it is not already there
//
if ((rc = XrdOucUtils::makePath(fnbuff, myMode)))
{Say->Emsg("createPath", errno, "create path", path);
return 0;
}
// Construct full filename
//
if (sp) *sp = '/';
else {if (path[i-1] != '/') fnbuff[i++] = '/';
if (fn) strcpy(fnbuff+i, fn);
}
// Check is we have already created it and whether we can access
//
if (!stat(fnbuff,&buf))
{if ((buf.st_mode & S_IFMT) != (mode & sfMask))
{Say->Emsg("createPath","Path",fnbuff,
(mode & S_IFSOCK) ? "exists but is not a socket"
: "exists but is not a pipe");
return 0;
}
if (access(fnbuff, W_OK))
{Say->Emsg("cratePath", errno, "access path", fnbuff);
return 0;
}
} else chmod(fnbuff, mode); // This may fail on some platforms
// All set now
//
return fnbuff;
}