Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Release 1.2 (August 2024)
-------------------------
* Added support for configuration files, and added /etc/hans.conf as an example [Randolf Richardson]
* Documented /etc/hans.conf (in configuration file comments) [Randolf Richardson]

Release 1.1 (November 2022)
---------------------------
* Switch to utun devices on macOS (thanks to unkernet)
Expand Down
115 changes: 115 additions & 0 deletions etc/hans.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# HANS - IP over ICMP by Friedrich Schöller
# https://code.gerade.org/hans/
#
# Server daemon configuration file implementation and
# documentation by Randolf Richardson (August 10, 2024)
# https://www.randolf.ca/
#
# Source code repositories:
# https://www.sourceforge.net/projects/hanstunnel/files/source/
# https://www.github.com/friedrich/hans
#
# Note: Directives and their values are case-sensitive.
#

# For clients, specify the IP address or host of the server that
# you're connecting to:
#
# Client <ip-address-or-hostname>
#
# (Use either the "Client" or "Server" directive to configure
# HANS accordingly.)
#
Client hans-server.example.com

# For servers, specify the IP address of the internal /24
# network to use (this network will be extended to clients, who
# will each be assigned an IP address automatically via DHCP):
#
# Server <ip-address-or-hostname>
#
# (Use either the "Client" or "Server" directive to configure
# HANS accordingly.)
#
#Server 10.4.0.1

# Using a passphrase is essential as a means to preventing
# unauthorized clients from connecting and accessing your
# network (you should also limit which clients can connect to
# your server by using firewall rules to block all except for
# those public IP addresses that your users are known to
# connect from).
#
# Passphrase <password>
#
Passphrase radio-active_cats_have_18_half-lives

# Make HANS run under the specified user (downgrade from root).
#
# Username <username>
#
#Username nobody

# Request a specific IP address be assigned by the HANS' DHCP
# server.
#
# RequestIP <ip-address>
#
#RequestIP 10.4.0.42

# Respond to ordinary ICMP ping requests in server mode.
#
# RespondToPing < Yes | On | 1 | No | Off | 0 >
#
#RespondToPing No

# Specify which network interface device to use.
#
# Device <tun-device-name>
#
#Device tun0

# Set the maximum size of echo packets.
#
# Default: 1500
#
# MTU <integer>
#
#MTU 1500

# Number of echo requests clients send in advance for the server
# to reply to. 0 disabled polling, which is believed to be the
# best choice in networks that allow unlimited echo replies.
#
# Default: 10
#
# MaxPolls <integer>
#
#MaxPolls 10

# Change ICMP echo ID on every request. While this may help
# with buggy network routers, performance may be impacted.
#
# ChangeEchoID < Yes | On | 1 | No | Off | 0 >
#
#ChangeEchoID Off

# Change the ICMP packet Sequence number on every ICMP echo
# request. While this may help with buggy network routers,
# performance may be impacted.
#
# ChangeEchoSequence < Yes | On | 1 | No | Off | 0 >
#
#ChangeEchoSequence Off

# Operate in foreground mode.
#
# Verbose < Yes | On | 1 | No | Off | 0 >
#
#Foreground Off

# Control verbose output.
#
# Verbose < Yes | On | 1 | No | Off | 0 >
#
#Verbose Off
189 changes: 153 additions & 36 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <iostream>
#include <arpa/inet.h>
#include <fstream>
#include <netinet/in.h>
#include <sys/types.h>
#include <stdlib.h>
Expand All @@ -45,6 +46,24 @@ using std::string;

static Worker *worker = NULL;

string serverName;
string userName;
string passphrase;
string device;
bool isServer = false;
bool isClient = false;
bool foreground = false;
int mtu = 1500;
int maxPolls = 10;
uint32_t network = INADDR_NONE;
uint32_t clientIp = INADDR_NONE;
bool answerPing = false;
uid_t uid = 0;
gid_t gid = 0;
bool changeEchoId = false;
bool changeEchoSeq = false;
bool verbose = false;

static void sig_term_handler(int)
{
syslog(LOG_INFO, "SIGTERM received");
Expand All @@ -62,60 +81,154 @@ static void sig_int_handler(int)
static void usage()
{
std::cerr <<
"Hans - IP over ICMP version 1.1\n\n"
"Hans - IP over ICMP version 1.2\n\n"
"RUN AS CLIENT\n"
" hans -c server [-fv] [-p passphrase] [-u user] [-d tun_device]\n"
" [-m reference_mtu] [-w polls]\n\n"
"RUN AS SERVER (linux only)\n"
" [-m reference_mtu] [-w polls] [-F hans.conf]\n\n"
"RUN AS SERVER (Linux only)\n"
" hans -s network [-fvr] [-p passphrase] [-u user] [-d tun_device]\n"
" [-m reference_mtu] [-a ip]\n\n"
" [-m reference_mtu] [-a ip] [-F hans.conf]\n\n"
"ARGUMENTS\n"
" -c server Run as client. Connect to given server address.\n"
" -s network Run as server. Use given network address on virtual interfaces.\n"
" -c server Run as client. Connect to given server address.\n"
" -s network Run as server. Use given network address on virtual interfaces.\n"
" -p passphrase Set passphrase.\n"
" -u username Change user under which the program runs.\n"
" -a ip Request assignment of given tunnel ip address from the server.\n"
" -a ip Request assignment of given tunnel IP address from the server.\n"
" -r Respond to ordinary pings in server mode.\n"
" -d device Use given tun device.\n"
" -m mtu Set maximum echo packet size. This should correspond to the MTU\n"
" -m mtu Set maximum echo packet size. This should correspond to the MTU\n"
" of the network between client and server, which is usually 1500\n"
" over Ethernet. Has to be the same on client and server. Defaults\n"
" to 1500.\n"
" over Ethernet. Has to be the same on client and server.\n"
" Defaults to 1500.\n"
" -w polls Number of echo requests the client sends in advance for the\n"
" server to reply to. 0 disables polling, which is the best choice\n"
" if the network allows unlimited echo replies. Defaults to 10.\n"
" -i Change echo id on every echo request. May help with buggy\n"
" routers. May impact performance with others.\n"
" -q Change echo sequence number on every echo request. May help with\n"
" buggy routers. May impact performance with others.\n"
" server to reply to. 0 disables polling, which is the best\n"
" choice if the network allows unlimited echo replies. Defaults\n"
" to 10.\n"
" -i Change echo ID on every echo request. May help with buggy\n"
" routers. May impact performance with others.\n"
" -q Change echo sequence number on every echo request. May help\n"
" with buggy routers; may impact performance with others.\n"
" -f Run in foreground.\n"
" -v Print debug information.\n";
" -v Print debug information.\n"
" -F confFile Additional configuration file to load during the processing\n"
" of all command-line arguments.\n";
}

int main(int argc, char *argv[])
bool readConf(const string confFile, const bool okIfNotExist = false)
{
string serverName;
string userName;
string passphrase;
string device;
bool isServer = false;
bool isClient = false;
bool foreground = false;
int mtu = 1500;
int maxPolls = 10;
uint32_t network = INADDR_NONE;
uint32_t clientIp = INADDR_NONE;
bool answerPing = false;
uid_t uid = 0;
gid_t gid = 0;
bool changeEchoId = false;
bool changeEchoSeq = false;
bool verbose = false;
// Open configuration file
std::ifstream ifs(confFile.c_str()); // Open with RAII, which closes automatically
if (!ifs.is_open()) {
if (okIfNotExist) return true; // Conditionally don't report this error
std::cerr << "cannot open file " << confFile << std::endl;
return false;
}

// Process all lines in the configuration file
string line;
int line_count = 0; // We use this to include the line number if there's an error
while (std::getline(ifs, line)) {
line_count++;
line.erase(0, line.find_first_not_of(" \t")); // Trim leading whitespace
if (line.empty()) continue; // Ignore empty lines
if (line[0] == '#') continue; // Ignore comments

// Split directive from value
int offset = line.find_first_of(" \t");
if (offset == string::npos) {
std::cerr << "unrecognized directive in " << confFile << "[" << line_count << "]: " << line << std::endl;
return false;
}
string directive = line.substr(0, offset);
string value = line.substr(offset);
value.erase(0, value.find_first_not_of(" \t")); // Trim leading whitespace
value.erase(value.find_last_not_of(" \t") + 1); // Trim trailing whitspace
// std::cout << "directive=[" << directive << "] value=[" << value << "]" << std::endl; // Debug

// Process directives
if (directive == "Foreground") { // -f
if (value == "1" || value == "Yes" || value == "On") { foreground = true;
} else if (value == "0" || value == "No" || value == "Off") { foreground = false;
} else {
std::cerr << "Invalid parameter in " << confFile << "[" << line_count << "]: " << line << std::endl
<< " must be one of: 0, No, Off, 1, Yes, On" << std::endl;
return false;
}
} else if (directive == "Username") { // -u
userName = value;
} else if (directive == "Device") { // -d
device = value;
} else if (directive == "Passphrase") { // -p
passphrase = value;
} else if (directive == "Client") { // -c
isClient = true;
serverName = value;
} else if (directive == "Server") { // -s
isServer = true;
network = ntohl(inet_addr(value.c_str()));
if (network == INADDR_NONE) {
std::cerr << "invalid network in " << confFile << "[" << line_count << "]: " << line << std::endl;
return false;
}
} else if (directive == "MTU") { // -m
mtu = atoi(value.c_str());
} else if (directive == "MaxPolls") { // -w
maxPolls = atoi(value.c_str());
} else if (directive == "RespondToPing") { // -r
if (value == "1" || value == "Yes" || value == "On") { answerPing = true;
} else if (value == "0" || value == "No" || value == "Off") { answerPing = false;
} else {
std::cerr << "Invalid parameter in " << confFile << "[" << line_count << "]: " << line << std::endl
<< " must be one of: 0, No, Off, 1, Yes, On" << std::endl;
return false;
}
} else if (directive == "ChangeEchoSequence") { // -q
if (value == "1" || value == "Yes" || value == "On") { changeEchoSeq = true;
} else if (value == "0" || value == "No" || value == "Off") { changeEchoSeq = false;
} else {
std::cerr << "Invalid parameter in " << confFile << "[" << line_count << "]: " << line << std::endl
<< " must be one of: 0, No, Off, 1, Yes, On" << std::endl;
return false;
}
} else if (directive == "ChangeEchoID") { // -i
if (value == "1" || value == "Yes" || value == "On") { changeEchoId = true;
} else if (value == "0" || value == "No" || value == "Off") { changeEchoId = false;
} else {
std::cerr << "Invalid parameter in " << confFile << "[" << line_count << "]: " << line << std::endl
<< " must be one of: 0, No, Off, 1, Yes, On" << std::endl;
return false;
}
} else if (directive == "Verbose") { // -v
if (value == "1" || value == "Yes" || value == "On") { verbose = true;
} else if (value == "0" || value == "No" || value == "Off") { verbose = false;
} else {
std::cerr << "Invalid parameter in " << confFile << "[" << line_count << "]: " << line << std::endl
<< " must be one of: 0, No, Off, 1, Yes, On" << std::endl;
return false;
}
} else if (directive == "RequestIP") { // -a
clientIp = ntohl(inet_addr(value.c_str()));
} else {
std::cerr << "unrecognized directive in " << confFile << "[" << line_count << "]: " << line << std::endl;
return false;
}

}

return true;
}

int main(int argc, char *argv[])
{
openlog(argv[0], LOG_PERROR, LOG_DAEMON);

// Read default configuration file /etc/hans.conf before processing
// command-line arguments (which effectively override those values).
//
if (!readConf("/etc/hans.conf", true)) return 1;

int c;
while ((c = getopt(argc, argv, "fru:d:p:s:c:m:w:qiva:")) != -1)
while ((c = getopt(argc, argv, "fru:d:p:s:c:m:w:qiva:F:")) != -1)
{
switch(c) {
case 'f':
Expand Down Expand Up @@ -162,6 +275,9 @@ int main(int argc, char *argv[])
case 'a':
clientIp = ntohl(inet_addr(optarg));
break;
case 'F':
if (!readConf(optarg)) return 1;
break;
default:
usage();
return 1;
Expand Down Expand Up @@ -261,4 +377,5 @@ int main(int argc, char *argv[])
}

return 0;

}