mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
402 lines
9.3 KiB
C++
402 lines
9.3 KiB
C++
/*************************************************************************
|
|
* UrBackup - Client/Server backup system
|
|
* Copyright (C) 2011-2016 Martin Raiber
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
**************************************************************************/
|
|
|
|
#include "vld.h"
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#endif
|
|
#include "ServiceAcceptor.h"
|
|
#include "stringtools.h"
|
|
#include "ServiceWorker.h"
|
|
#include <memory.h>
|
|
|
|
#include "Interface/Mutex.h"
|
|
#include "Interface/Condition.h"
|
|
#include "Interface/Service.h"
|
|
|
|
namespace
|
|
{
|
|
bool prepareSocket(SOCKET s)
|
|
{
|
|
#if !defined(_WIN32) && !defined(SOCK_CLOEXEC)
|
|
fcntl(s, F_SETFD, fcntl(s, F_GETFD, 0) | FD_CLOEXEC);
|
|
#endif
|
|
|
|
int optval = 1;
|
|
int rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(int));
|
|
if (rc == SOCKET_ERROR)
|
|
{
|
|
Server->Log("Failed setting SO_REUSEADDR", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
optval = 1;
|
|
setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (void*)&optval, sizeof(optval));
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
|
|
CServiceAcceptor::CServiceAcceptor(IService * pService, std::string pName, unsigned short port, int pMaxClientsPerThread, IServer::BindTarget bindTarget)
|
|
: maxClientsPerThread(pMaxClientsPerThread), s(SOCKET_ERROR), s_v6(SOCKET_ERROR)
|
|
{
|
|
name=pName;
|
|
service=pService;
|
|
exitpipe=Server->createMemoryPipe();
|
|
do_exit=false;
|
|
has_error=false;
|
|
#ifndef _WIN32
|
|
pipe(xpipe);
|
|
#endif
|
|
|
|
int rc;
|
|
#ifdef _WIN32
|
|
WSADATA wsadata;
|
|
rc = WSAStartup(MAKEWORD(2,2), &wsadata);
|
|
if(rc == SOCKET_ERROR) return;
|
|
#endif
|
|
|
|
bool has_ipv4 = Server->getServerParameter("disable_ipv4").empty();
|
|
bool has_ipv6 = Server->getServerParameter("disable_ipv6").empty();
|
|
|
|
if (!has_ipv4 && !has_ipv6)
|
|
{
|
|
Server->Log(name + ": IPv4 and IPv6 disabled", LL_ERROR);
|
|
has_error = true;
|
|
return;
|
|
}
|
|
|
|
if (has_ipv4)
|
|
{
|
|
if (!init_socket_v4(port, bindTarget))
|
|
{
|
|
if (Server->getServerParameter("ipv4_optional").empty()
|
|
|| !has_ipv6)
|
|
{
|
|
has_error = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (has_ipv6)
|
|
{
|
|
if (!init_socket_v6(port, bindTarget))
|
|
{
|
|
if (!Server->getServerParameter("ipv6_required").empty()
|
|
|| !has_ipv4)
|
|
{
|
|
has_error = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Server->Log(name+": Server started up successfully!",LL_INFO);
|
|
}
|
|
|
|
CServiceAcceptor::~CServiceAcceptor()
|
|
{
|
|
do_exit = true;
|
|
|
|
#ifndef _WIN32
|
|
char ch = 0;
|
|
write(xpipe[1], &ch, 1);
|
|
#endif
|
|
|
|
if (s != SOCKET_ERROR)
|
|
closesocket(s);
|
|
if (s_v6 != SOCKET_ERROR)
|
|
closesocket(s_v6);
|
|
|
|
if(has_error)
|
|
return;
|
|
|
|
for(size_t i=0;i<workers.size();++i)
|
|
{
|
|
workers[i]->stop();
|
|
}
|
|
size_t c=0;
|
|
Server->Log("c=0/"+convert(workers.size()+1));
|
|
while(c<workers.size()+1)
|
|
{
|
|
std::string r;
|
|
exitpipe->Read(&r);
|
|
if(r=="ok")
|
|
{
|
|
++c;
|
|
Server->Log("c="+convert(c)+"/"+convert(workers.size()+1));
|
|
}
|
|
}
|
|
Server->destroy(exitpipe);
|
|
for(size_t i=0;i<workers.size();++i)
|
|
{
|
|
delete workers[i];
|
|
}
|
|
|
|
delete service;
|
|
}
|
|
|
|
void CServiceAcceptor::operator()(void)
|
|
{
|
|
if(has_error)
|
|
return;
|
|
|
|
while(!do_exit)
|
|
{
|
|
#ifdef _WIN32
|
|
fd_set fdset;
|
|
FD_ZERO(&fdset);
|
|
if(s!=SOCKET_ERROR)
|
|
FD_SET(s, &fdset);
|
|
if (s_v6 != SOCKET_ERROR)
|
|
FD_SET(s_v6, &fdset);
|
|
SOCKET maxs = (std::max)(s, s_v6) + 1;
|
|
|
|
timeval lon;
|
|
lon.tv_sec=100;
|
|
lon.tv_usec=0;
|
|
|
|
_i32 rc=select((int)maxs, &fdset, 0, 0, &lon);
|
|
size_t num_fds = 2;
|
|
#else
|
|
pollfd conn[3];
|
|
conn[0].fd= s !=SOCKET_ERROR ? s: s_v6;
|
|
conn[0].events=POLLIN;
|
|
conn[0].revents=0;
|
|
conn[1].fd=xpipe[0];
|
|
conn[1].events=POLLIN;
|
|
conn[1].revents=0;
|
|
conn[2].revents = 0;
|
|
|
|
nfds_t num_fds = 2;
|
|
|
|
if (s_v6 != SOCKET_ERROR
|
|
&& s != SOCKET_ERROR)
|
|
{
|
|
num_fds = 3;
|
|
conn[2].fd = s_v6;
|
|
conn[2].events = POLLIN;
|
|
}
|
|
|
|
int rc = poll(conn, num_fds, 100*1000);
|
|
#endif
|
|
if(rc>=0)
|
|
{
|
|
for (size_t n = 0; n < num_fds; ++n)
|
|
{
|
|
#ifdef _WIN32
|
|
SOCKET accept_socket = n == 0 ? s : s_v6;
|
|
if (accept_socket == SOCKET_ERROR)
|
|
continue;
|
|
if (!FD_ISSET(accept_socket, &fdset))
|
|
continue;
|
|
#else
|
|
if (n == 1)
|
|
continue;
|
|
if (conn[n].revents == 0)
|
|
continue;
|
|
SOCKET accept_socket = conn[n].fd;
|
|
#endif
|
|
if (do_exit)
|
|
break;
|
|
|
|
union
|
|
{
|
|
sockaddr_in naddr_v4;
|
|
sockaddr_in6 naddr_v6;
|
|
} naddr;
|
|
socklen_t addrsize;
|
|
if (accept_socket == s_v6)
|
|
addrsize = sizeof(naddr.naddr_v6);
|
|
else
|
|
addrsize = sizeof(naddr.naddr_v4);
|
|
|
|
sockaddr* paddr = (sockaddr*)&naddr;
|
|
SOCKET ns= ACCEPT_CLOEXEC(accept_socket, paddr, &addrsize);
|
|
if(ns!=SOCKET_ERROR)
|
|
{
|
|
#ifdef __APPLE__
|
|
int optval = 1;
|
|
setsockopt(ns, SOL_SOCKET, SO_NOSIGPIPE, (void*)&optval, sizeof(optval));
|
|
#endif
|
|
//Server->Log(name+": New Connection incomming "+convert(Server->getTimeMS())+" s: "+convert((int)ns), LL_DEBUG);
|
|
|
|
#ifdef _WIN32
|
|
int window_size=Server->getSendWindowSize();
|
|
if(window_size>0)
|
|
setsockopt(ns, SOL_SOCKET, SO_SNDBUF, (char *) &window_size, sizeof(window_size));
|
|
|
|
window_size = Server->getRecvWindowSize();
|
|
if(window_size>0)
|
|
setsockopt(ns, SOL_SOCKET, SO_RCVBUF, (char *) &window_size, sizeof(window_size));
|
|
#endif
|
|
char buf[100];
|
|
const char* endpoint = inet_ntop(paddr->sa_family,
|
|
accept_socket == s_v6 ? (void*)&naddr.naddr_v6.sin6_addr : (void*)&naddr.naddr_v4.sin_addr,
|
|
buf, sizeof(buf));
|
|
|
|
AddToWorker(ns, endpoint!=NULL ? endpoint : "");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Server->Log("Select error in CServiceAcceptor Thread.", LL_ERROR);
|
|
break;
|
|
}
|
|
}
|
|
Server->Log("ServiceAcceptor finished", LL_DEBUG);
|
|
exitpipe->Write("ok");
|
|
}
|
|
|
|
void CServiceAcceptor::AddToWorker(SOCKET pSocket, const std::string& endpoint)
|
|
{
|
|
for(size_t i=0;i<workers.size();++i)
|
|
{
|
|
if( workers[i]->getAvailableSlots()>0 )
|
|
{
|
|
workers[i]->AddClient(pSocket, endpoint);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Server->Log(name+": No available slots... starting new Worker", LL_DEBUG);
|
|
|
|
CServiceWorker *nw=new CServiceWorker(service, name, exitpipe, maxClientsPerThread);
|
|
workers.push_back(nw);
|
|
|
|
Server->createThread(nw, name+": worker");
|
|
|
|
nw->AddClient( pSocket, endpoint );
|
|
}
|
|
|
|
bool CServiceAcceptor::init_socket_v4(unsigned short port, IServer::BindTarget bindTarget)
|
|
{
|
|
int type = SOCK_STREAM;
|
|
#if !defined(_WIN32) && defined(SOCK_CLOEXEC)
|
|
type |= SOCK_CLOEXEC;
|
|
#endif
|
|
s = socket(AF_INET, type, 0);
|
|
if (s == SOCKET_ERROR)
|
|
{
|
|
#if !defined(_WIN32) && defined(SOCK_CLOEXEC)
|
|
if (errno == EINVAL)
|
|
{
|
|
type &= ~SOCK_CLOEXEC;
|
|
s = socket(AF_INET, type, 0);
|
|
}
|
|
#endif
|
|
if (s == SOCKET_ERROR)
|
|
{
|
|
Server->Log(name + ": Creating SOCKET failed", LL_ERROR);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!prepareSocket(s))
|
|
{
|
|
closesocket(s);
|
|
s = SOCKET_ERROR;
|
|
return false;
|
|
}
|
|
|
|
sockaddr_in addr;
|
|
|
|
memset(&addr, 0, sizeof(sockaddr_in));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(port);
|
|
switch (bindTarget)
|
|
{
|
|
case IServer::BindTarget_All:
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
break;
|
|
case IServer::BindTarget_Localhost:
|
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
break;
|
|
}
|
|
|
|
int rc = bind(s, (sockaddr*)& addr, sizeof(addr));
|
|
if (rc == SOCKET_ERROR)
|
|
{
|
|
Server->Log(name + ": Failed binding socket to port " + convert(port) + ". Another instance of this application may already be active and bound to this port.", LL_ERROR);
|
|
closesocket(s);
|
|
s = SOCKET_ERROR;
|
|
return false;
|
|
}
|
|
|
|
listen(s, 10000);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CServiceAcceptor::init_socket_v6(unsigned short port, IServer::BindTarget bindTarget)
|
|
{
|
|
int type = SOCK_STREAM;
|
|
#if !defined(_WIN32) && defined(SOCK_CLOEXEC)
|
|
type |= SOCK_CLOEXEC;
|
|
#endif
|
|
|
|
s_v6 = socket(AF_INET6, type, 0);
|
|
if (s_v6 < 1)
|
|
{
|
|
Server->Log(name + ": Creating v6 SOCKET failed", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
if (!prepareSocket(s_v6))
|
|
{
|
|
closesocket(s_v6);
|
|
s_v6 = SOCKET_ERROR;
|
|
return false;
|
|
}
|
|
|
|
int optval = 1;
|
|
setsockopt(s_v6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)& optval, sizeof(optval));
|
|
|
|
sockaddr_in6 addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin6_family = AF_INET6;
|
|
addr.sin6_port = htons(port);
|
|
switch (bindTarget)
|
|
{
|
|
case IServer::BindTarget_All:
|
|
addr.sin6_addr = in6addr_any;
|
|
break;
|
|
case IServer::BindTarget_Localhost:
|
|
addr.sin6_addr = in6addr_loopback;
|
|
break;
|
|
}
|
|
|
|
int rc = bind(s_v6, (sockaddr*)& addr, sizeof(addr));
|
|
if (rc == SOCKET_ERROR)
|
|
{
|
|
Server->Log(name + ": Failed binding ipv6 socket to port " + convert(port) + ". Another instance of this application may already be active and bound to this port.", LL_ERROR);
|
|
closesocket(s_v6);
|
|
s_v6 = SOCKET_ERROR;
|
|
return false;
|
|
}
|
|
|
|
listen(s_v6, 10000);
|
|
|
|
return true;
|
|
}
|
|
|