mirror of
https://github.com/norohind/hans.git
synced 2025-04-13 13:17:23 +03:00
355 lines
10 KiB
C++
355 lines
10 KiB
C++
/*
|
|
* Hans - IP over ICMP
|
|
* Copyright (C) 2009 Friedrich Schöller <hans@schoeller.se>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "server.h"
|
|
#include "client.h"
|
|
#include "config.h"
|
|
#include "utility.h"
|
|
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
#include <syslog.h>
|
|
|
|
using namespace std;
|
|
|
|
#define FIRST_ASSIGNED_IP_OFFSET 100
|
|
|
|
const Worker::TunnelHeader::Magic Server::magic("hans");
|
|
|
|
Server::Server(int tunnelMtu, const char *deviceName, const char *passphrase, uint32_t network, bool answerEcho, uid_t uid, gid_t gid, int pollTimeout)
|
|
: Worker(tunnelMtu, deviceName, answerEcho, uid, gid), auth(passphrase)
|
|
{
|
|
this->network = network & 0xffffff00;
|
|
this->pollTimeout = pollTimeout;
|
|
this->latestAssignedIpOffset = FIRST_ASSIGNED_IP_OFFSET - 1;
|
|
|
|
tun->setIp(this->network + 1, this->network + 2, true);
|
|
|
|
dropPrivileges();
|
|
}
|
|
|
|
Server::~Server()
|
|
{
|
|
|
|
}
|
|
|
|
void Server::handleUnknownClient(const TunnelHeader &header, int dataLength, uint32_t realIp, uint16_t echoId, uint16_t echoSeq)
|
|
{
|
|
ClientData client;
|
|
client.realIp = realIp;
|
|
client.maxPolls = 1;
|
|
|
|
pollReceived(&client, echoId, echoSeq);
|
|
|
|
if (header.type != TunnelHeader::TYPE_CONNECTION_REQUEST || dataLength != sizeof(ClientConnectData))
|
|
{
|
|
syslog(LOG_DEBUG, "invalid request %s", Utility::formatIp(realIp).c_str());
|
|
sendReset(&client);
|
|
return;
|
|
}
|
|
|
|
ClientConnectData *connectData = (ClientConnectData *)echoReceivePayloadBuffer();
|
|
|
|
client.maxPolls = connectData->maxPolls;
|
|
client.state = ClientData::STATE_NEW;
|
|
client.tunnelIp = reserveTunnelIp(connectData->desiredIp);
|
|
|
|
syslog(LOG_DEBUG, "new client: %s (%s)\n", Utility::formatIp(client.realIp).c_str(), Utility::formatIp(client.tunnelIp).c_str());
|
|
|
|
if (client.tunnelIp != 0)
|
|
{
|
|
client.challenge = auth.generateChallenge(CHALLENGE_SIZE);
|
|
sendChallenge(&client);
|
|
|
|
// add client to list
|
|
clientList.push_back(client);
|
|
clientRealIpMap[realIp] = clientList.size() - 1;
|
|
clientTunnelIpMap[client.tunnelIp] = clientList.size() - 1;
|
|
}
|
|
else
|
|
{
|
|
syslog(LOG_WARNING, "server full");
|
|
sendEchoToClient(&client, TunnelHeader::TYPE_SERVER_FULL, 0);
|
|
}
|
|
}
|
|
|
|
void Server::sendChallenge(ClientData *client)
|
|
{
|
|
syslog(LOG_DEBUG, "sending challenge to: %s\n", Utility::formatIp(client->realIp).c_str());
|
|
|
|
memcpy(echoSendPayloadBuffer(), &client->challenge[0], client->challenge.size());
|
|
sendEchoToClient(client, TunnelHeader::TYPE_CHALLENGE, client->challenge.size());
|
|
|
|
client->state = ClientData::STATE_CHALLENGE_SENT;
|
|
}
|
|
|
|
void Server::removeClient(ClientData *client)
|
|
{
|
|
syslog(LOG_DEBUG, "removing client: %s (%s)\n", Utility::formatIp(client->realIp).c_str(), Utility::formatIp(client->tunnelIp).c_str());
|
|
|
|
releaseTunnelIp(client->tunnelIp);
|
|
|
|
int nr = clientRealIpMap[client->realIp];
|
|
|
|
clientRealIpMap.erase(client->realIp);
|
|
clientTunnelIpMap.erase(client->tunnelIp);
|
|
|
|
clientList.erase(clientList.begin() + nr);
|
|
}
|
|
|
|
void Server::checkChallenge(ClientData *client, int length)
|
|
{
|
|
Auth::Response rightResponse = auth.getResponse(client->challenge);
|
|
|
|
if (length != sizeof(Auth::Response) || memcmp(&rightResponse, echoReceivePayloadBuffer(), length) != 0)
|
|
{
|
|
syslog(LOG_DEBUG, "wrong challenge response\n");
|
|
|
|
sendEchoToClient(client, TunnelHeader::TYPE_CHALLENGE_ERROR, 0);
|
|
|
|
removeClient(client);
|
|
return;
|
|
}
|
|
|
|
uint32_t *ip = (uint32_t *)echoSendPayloadBuffer();
|
|
*ip = htonl(client->tunnelIp);
|
|
|
|
sendEchoToClient(client, TunnelHeader::TYPE_CONNECTION_ACCEPT, sizeof(uint32_t));
|
|
|
|
client->state = ClientData::STATE_ESTABLISHED;
|
|
|
|
syslog(LOG_INFO, "connection established: %s", Utility::formatIp(client->realIp).c_str());
|
|
}
|
|
|
|
void Server::sendReset(ClientData *client)
|
|
{
|
|
syslog(LOG_DEBUG, "sending reset: %s", Utility::formatIp(client->realIp).c_str());
|
|
sendEchoToClient(client, TunnelHeader::TYPE_RESET_CONNECTION, 0);
|
|
}
|
|
|
|
bool Server::handleEchoData(const TunnelHeader &header, int dataLength, uint32_t realIp, bool reply, uint16_t id, uint16_t seq)
|
|
{
|
|
if (reply)
|
|
return false;
|
|
|
|
if (header.magic != Client::magic)
|
|
return false;
|
|
|
|
ClientData *client = getClientByRealIp(realIp);
|
|
if (client == NULL)
|
|
{
|
|
handleUnknownClient(header, dataLength, realIp, id, seq);
|
|
return true;
|
|
}
|
|
|
|
pollReceived(client, id, seq);
|
|
|
|
switch (header.type)
|
|
{
|
|
case TunnelHeader::TYPE_CONNECTION_REQUEST:
|
|
if (client->state == ClientData::STATE_CHALLENGE_SENT)
|
|
{
|
|
sendChallenge(client);
|
|
return true;
|
|
}
|
|
|
|
while (client->pollIds.size() > 1)
|
|
client->pollIds.pop();
|
|
|
|
syslog(LOG_DEBUG, "reconnecting %s", Utility::formatIp(realIp).c_str());
|
|
sendReset(client);
|
|
removeClient(client);
|
|
return true;
|
|
case TunnelHeader::TYPE_CHALLENGE_RESPONSE:
|
|
if (client->state == ClientData::STATE_CHALLENGE_SENT)
|
|
{
|
|
checkChallenge(client, dataLength);
|
|
return true;
|
|
}
|
|
break;
|
|
case TunnelHeader::TYPE_DATA:
|
|
if (client->state == ClientData::STATE_ESTABLISHED)
|
|
{
|
|
if (dataLength == 0)
|
|
{
|
|
syslog(LOG_WARNING, "received empty data packet");
|
|
return true;
|
|
}
|
|
|
|
sendToTun(dataLength);
|
|
return true;
|
|
}
|
|
break;
|
|
case TunnelHeader::TYPE_POLL:
|
|
return true;
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "invalid packet from: %s, type: %d, state: %d", Utility::formatIp(realIp).c_str(), header.type, client->state);
|
|
|
|
return true;
|
|
}
|
|
|
|
Server::ClientData *Server::getClientByTunnelIp(uint32_t ip)
|
|
{
|
|
ClientIpMap::iterator clientMapIterator = clientTunnelIpMap.find(ip);
|
|
if (clientMapIterator == clientTunnelIpMap.end())
|
|
return NULL;
|
|
|
|
return &clientList[clientMapIterator->second];
|
|
}
|
|
|
|
Server::ClientData *Server::getClientByRealIp(uint32_t ip)
|
|
{
|
|
ClientIpMap::iterator clientMapIterator = clientRealIpMap.find(ip);
|
|
if (clientMapIterator == clientRealIpMap.end())
|
|
return NULL;
|
|
|
|
return &clientList[clientMapIterator->second];
|
|
}
|
|
|
|
void Server::handleTunData(int dataLength, uint32_t sourceIp, uint32_t destIp)
|
|
{
|
|
if (destIp == network + 255) // ignore broadcasts
|
|
return;
|
|
|
|
ClientData *client = getClientByTunnelIp(destIp);
|
|
|
|
if (client == NULL)
|
|
{
|
|
syslog(LOG_DEBUG, "unknown client: %s\n", Utility::formatIp(destIp).c_str());
|
|
return;
|
|
}
|
|
|
|
sendEchoToClient(client, TunnelHeader::TYPE_DATA, dataLength);
|
|
}
|
|
|
|
void Server::pollReceived(ClientData *client, uint16_t echoId, uint16_t echoSeq)
|
|
{
|
|
unsigned int maxSavedPolls = client->maxPolls != 0 ? client->maxPolls : 1;
|
|
|
|
client->pollIds.push(ClientData::EchoId(echoId, echoSeq));
|
|
if (client->pollIds.size() > maxSavedPolls)
|
|
client->pollIds.pop();
|
|
DEBUG_ONLY(printf("poll -> %d\n", client->pollIds.size()));
|
|
|
|
if (client->pendingPackets.size() > 0)
|
|
{
|
|
Packet &packet = client->pendingPackets.front();
|
|
memcpy(echoSendPayloadBuffer(), &packet.data[0], packet.data.size());
|
|
client->pendingPackets.pop();
|
|
|
|
DEBUG_ONLY(printf("pending packet: %d bytes\n", packet.data.size()));
|
|
sendEchoToClient(client, packet.type, packet.data.size());
|
|
}
|
|
|
|
client->lastActivity = now;
|
|
}
|
|
|
|
void Server::sendEchoToClient(ClientData *client, int type, int dataLength)
|
|
{
|
|
if (client->maxPolls == 0)
|
|
{
|
|
sendEcho(magic, type, dataLength, client->realIp, true, client->pollIds.front().id, client->pollIds.front().seq);
|
|
return;
|
|
}
|
|
|
|
if (client->pollIds.size() != 0)
|
|
{
|
|
ClientData::EchoId echoId = client->pollIds.front();
|
|
client->pollIds.pop();
|
|
|
|
DEBUG_ONLY(printf("sending -> %d\n", client->pollIds.size()));
|
|
sendEcho(magic, type, dataLength, client->realIp, true, echoId.id, echoId.seq);
|
|
return;
|
|
}
|
|
|
|
if (client->pendingPackets.size() == MAX_BUFFERED_PACKETS)
|
|
{
|
|
client->pendingPackets.pop();
|
|
syslog(LOG_WARNING, "packet dropped to %s", Utility::formatIp(client->tunnelIp).c_str());
|
|
}
|
|
|
|
DEBUG_ONLY(printf("packet queued: %d bytes\n", dataLength));
|
|
|
|
client->pendingPackets.push(Packet());
|
|
Packet &packet = client->pendingPackets.back();
|
|
packet.type = type;
|
|
packet.data.resize(dataLength);
|
|
memcpy(&packet.data[0], echoReceivePayloadBuffer(), dataLength);
|
|
}
|
|
|
|
void Server::releaseTunnelIp(uint32_t tunnelIp)
|
|
{
|
|
usedIps.erase(tunnelIp);
|
|
}
|
|
|
|
void Server::handleTimeout()
|
|
{
|
|
for (int i = 0; i < clientList.size(); i++)
|
|
{
|
|
ClientData *client = &clientList[i];
|
|
|
|
if (client->lastActivity + KEEP_ALIVE_INTERVAL * 2 < now)
|
|
{
|
|
syslog(LOG_DEBUG, "client timeout: %s\n", Utility::formatIp(client->realIp).c_str());
|
|
removeClient(client);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
setTimeout(KEEP_ALIVE_INTERVAL);
|
|
}
|
|
|
|
uint32_t Server::reserveTunnelIp(uint32_t desiredIp)
|
|
{
|
|
if (desiredIp > network + 1 && desiredIp < network + 255 && !usedIps.count(desiredIp))
|
|
{
|
|
usedIps.insert(desiredIp);
|
|
return desiredIp;
|
|
}
|
|
|
|
bool ipAvailable = false;
|
|
|
|
for (int i = 0; i < 255 - FIRST_ASSIGNED_IP_OFFSET; i++)
|
|
{
|
|
latestAssignedIpOffset++;
|
|
if (latestAssignedIpOffset == 255)
|
|
latestAssignedIpOffset = FIRST_ASSIGNED_IP_OFFSET;
|
|
|
|
if (!usedIps.count(network + latestAssignedIpOffset))
|
|
{
|
|
ipAvailable = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ipAvailable)
|
|
return 0;
|
|
|
|
usedIps.insert(network + latestAssignedIpOffset);
|
|
return network + latestAssignedIpOffset;
|
|
}
|
|
|
|
void Server::run()
|
|
{
|
|
setTimeout(KEEP_ALIVE_INTERVAL);
|
|
|
|
Worker::run();
|
|
}
|