From 14a74d2837bfb831bd113b4e9822adcce5c5111c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randolf=20Richardson=20=E5=BC=B5=E6=96=87=E9=81=93?= Date: Sat, 10 Aug 2024 18:23:43 -0700 Subject: [PATCH 1/3] Create hans.conf --- etc/hans.conf | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 etc/hans.conf diff --git a/etc/hans.conf b/etc/hans.conf new file mode 100644 index 0000000..31dc9be --- /dev/null +++ b/etc/hans.conf @@ -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 +# +# (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 +# +# (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 +# +Passphrase radio-active_cats_have_18_half-lives + +# Make HANS run under the specified user (downgrade from root). +# +# Username +# +#Username nobody + +# Request a specific IP address be assigned by the HANS' DHCP +# server. +# +# RequestIP +# +#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 +# +#Device tun0 + +# Set the maximum size of echo packets. +# +# Default: 1500 +# +# MTU +# +#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 +# +#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 From d1764a587e8579604f358bf59ff5ed3685201131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randolf=20Richardson=20=E5=BC=B5=E6=96=87=E9=81=93?= Date: Sat, 10 Aug 2024 18:25:01 -0700 Subject: [PATCH 2/3] Update main.cpp Added support for /etc/hans.conf and also the -F command-line argument to support additional .conf files on-the-fly. --- src/main.cpp | 189 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 153 insertions(+), 36 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ff22550..949d1b8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -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"); @@ -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': @@ -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; @@ -261,4 +377,5 @@ int main(int argc, char *argv[]) } return 0; + } From 327de1b29218c44b5d878392d90b40caaaac9bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randolf=20Richardson=20=E5=BC=B5=E6=96=87=E9=81=93?= Date: Sat, 10 Aug 2024 18:27:24 -0700 Subject: [PATCH 3/3] Update CHANGES --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 5ffd93d..da52517 100644 --- a/CHANGES +++ b/CHANGES @@ -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)