diff --git a/CMakeLists.txt b/CMakeLists.txt index 801d331..4d95993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,14 +2,13 @@ cmake_minimum_required(VERSION 3.15.0 FATAL_ERROR) project(punchnat CXX) -set(CMAKE_C_STANDARD 17) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set_property(GLOBAL PROPERTY USE_FOLDERS ON) -if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") +if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD" OR ${CMAKE_SYSTEM_NAME} MATCHES "OpenBSD") include_directories("/usr/local/include") include_directories("/usr/local/include/botan-2") endif() diff --git a/README.md b/README.md index 3883288..187d8cd 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ punchnat config1.conf config2.conf 如果不需要写入 Log 文件,那就删除 `log_path` 这一行。 +### 参数介绍 + + + ### STUN Servers 不支持 TCP 的普通 STUN 服务器(来自于[NatTypeTeste](https://github.com/HMBSbige/NatTypeTester)) - stun.syncthing.net @@ -70,6 +74,8 @@ punchnat config1.conf config2.conf - FreeBSD - Linux +预编译的二进制文件全部都是静态编译。Linux 版本基本上都是静态编译,但 libc 除外,因此准备了两个版本,一个用于 glibc (2.31),另一个用于 musl。 + --- ## 建立服务 @@ -157,27 +163,11 @@ make --- ## IPv4 映射 IPv6 -由于该项目内部使用的是 IPv6 单栈 + 开启 IPv4 映射地址(IPv4-mapped IPv6)来使用 IPv4 网络,因此请确保 v6only 选项的值为 0。 +由于 PunchNAT 内部使用的是 IPv6 单栈 + 开启 IPv4 映射地址(IPv4-mapped IPv6)来同时使用 IPv4 与 IPv6 网络,因此请确保 v6only 选项的值为 0。 **正常情况下不需要任何额外设置,FreeBSD 与 Linux 以及 Windows 都默认允许 IPv4 地址映射到 IPv6。** -如果不放心,那么可以这样做 -### FreeBSD -按照FreeBSD手册 [33.9.5. IPv6 and IPv4 Address Mapping](https://docs.freebsd.org/en/books/handbook/advanced-networking/#_ipv6_and_ipv4_address_mapping) 介绍,在 `/etc/rc.conf` 加一行即可 -``` -ipv6_ipv4mapping="YES" -``` -如果还是不放心,那就运行命令 -``` -sysctl net.inet6.ip6.v6only=0 -``` - -### Linux -可运行命令 -``` -sysctl -w net.ipv6.bindv6only=0 -``` -正常情况下不需要这样做,它的默认值就是 0。 +如果系统不支持 IPv6,或者禁用了 IPv6,请在配置文件中设置 ipv4_only=true,这样 PunchNAT 会退回到使用 IPv4 单栈模式。 ## 其它注意事项 ### NetBSD @@ -187,13 +177,13 @@ sysctl -w net.inet6.ip6.v6only=0 ``` 设置后,单栈+映射地址模式可以侦听双栈。 -但由于未知的原因,它无法主动连接 IPv4 映射地址,因此 `destination_address` 只能使用 IPv6 地址。 +但由于未知的原因,可能无法主动连接 IPv4 映射地址。 ### OpenBSD -因为 OpenBSD 彻底屏蔽了 IPv4 映射地址,所以在 OpenBSD 平台只能使用 IPv6 单栈模式。 +因为 OpenBSD 彻底屏蔽了 IPv4 映射地址,所以在 OpenBSD 平台使用双栈的话,需要将配置文件保存成两个,其中一个启用 ipv4_only=1,然后在使用 PunchNAT 时同时载入两个配置文件。 ## 关于代码 -### 为什么要用两个 asio::io_context -这里用了两个 asio::io_context,其中一个是用于处理 UDP 数据的异步循环,另一个用于处理内部逻辑以及 TCP 数据的收发。 +### 版面 +代码写得很随意,想到哪写到哪,因此版面混乱。 -之所以要这样做,完全是为了迁就 BSD 系统。如果只用一个 io_context 去做所有的事,由于两次接收之间的延迟过高,在 BSD 平台会导致 UDP 丢包率过高。 \ No newline at end of file +至于阅读者的感受嘛…… 那肯定会不爽。 \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 825fee7..e0f9e04 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,7 +14,7 @@ int main(int argc, char *argv[]) if (argc <= 1) { char app_name[] = "punchnat"; - printf("%s version 20230415\n", app_name); + printf("%s version 20230423\n", app_name); printf("Usage: %s config1.conf\n", app_name); printf(" %s config1.conf config2.conf...\n", app_name); return 0; diff --git a/src/networks/connections.cpp b/src/networks/connections.cpp index 2e70993..1435aee 100644 --- a/src/networks/connections.cpp +++ b/src/networks/connections.cpp @@ -27,12 +27,16 @@ void empty_tcp_disconnect(tcp_session *tmp) { } -std::unique_ptr send_stun_3489_request(udp_server &sender, const std::string &stun_host) +std::unique_ptr send_stun_3489_request(udp_server &sender, const std::string &stun_host, bool v4_only) { + auto udp_version = v4_only ? udp::v4() : udp::v6(); + udp::resolver::resolver_base::flags input_flags = udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching; + if (v4_only) + input_flags = udp::resolver::numeric_service; + asio::error_code ec; udp::resolver &udp_resolver = sender.get_resolver(); - udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp::v6(), stun_host, "3478", - udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp_version, stun_host, "3478", input_flags, ec); if (ec) return nullptr; @@ -50,12 +54,16 @@ std::unique_ptr send_stun_3489_request(udp_server &sender, return header; } -std::unique_ptr send_stun_8489_request(udp_server &sender, const std::string &stun_host) +std::unique_ptr send_stun_8489_request(udp_server &sender, const std::string &stun_host, bool v4_only) { + auto udp_version = v4_only ? udp::v4() : udp::v6(); + udp::resolver::resolver_base::flags input_flags = udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching; + if (v4_only) + input_flags = udp::resolver::numeric_service; + asio::error_code ec; udp::resolver &udp_resolver = sender.get_resolver(); - udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp::v6(), stun_host, "3478", - udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp_version, stun_host, "3478", input_flags, ec); if (ec) return nullptr; @@ -74,12 +82,16 @@ std::unique_ptr send_stun_8489_request(udp_server &sender, return header; } -void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, rfc8489::stun_header *header) +void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, rfc8489::stun_header *header, bool v4_only) { + auto udp_version = v4_only ? udp::v4() : udp::v6(); + udp::resolver::resolver_base::flags input_flags = udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching; + if (v4_only) + input_flags = udp::resolver::numeric_service; + asio::error_code ec; udp::resolver &udp_resolver = sender.get_resolver(); - udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp::v6(), stun_host, "3478", - udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); + udp::resolver::results_type remote_addresses = udp_resolver.resolve(udp_version, stun_host, "3478", input_flags, ec); if (ec) return; @@ -265,7 +277,8 @@ void tcp_server::acceptor_initialise(tcp::endpoint ep) { asio::ip::v6_only v6_option(false); tcp_acceptor.open(ep.protocol()); - tcp_acceptor.set_option(v6_option); + if (ep.address().is_v6()) + tcp_acceptor.set_option(v6_option); tcp_acceptor.set_option(tcp::no_delay(true)); tcp_acceptor.bind(ep); tcp_acceptor.listen(tcp_acceptor.max_connections); @@ -320,8 +333,12 @@ bool tcp_client::set_remote_hostname(const std::string &remote_address, asio::ip bool tcp_client::set_remote_hostname(const std::string &remote_address, const std::string &port_num, asio::error_code &ec) { - remote_endpoints = resolver.resolve(tcp::v6(), remote_address, port_num, - tcp::resolver::numeric_service | tcp::resolver::v4_mapped | tcp::resolver::all_matching, ec); + auto tcp_version = ipv4_only ? tcp::v4() : tcp::v6(); + tcp::resolver::resolver_base::flags input_flags = tcp::resolver::numeric_service | tcp::resolver::v4_mapped | tcp::resolver::all_matching; + if (ipv4_only) + input_flags = tcp::resolver::numeric_service; + + remote_endpoints = resolver.resolve(tcp_version, remote_address, port_num, input_flags, ec); return remote_endpoints.size() > 0; } @@ -356,7 +373,8 @@ void udp_server::initialise(udp::endpoint ep) { asio::ip::v6_only v6_option(false); connection_socket.open(ep.protocol()); - connection_socket.set_option(v6_option); + if (ep.address().is_v6()) + connection_socket.set_option(v6_option); connection_socket.bind(ep); } @@ -427,10 +445,12 @@ udp::resolver::results_type udp_client::get_remote_hostname(const std::string &r udp::resolver::results_type udp_client::get_remote_hostname(const std::string &remote_address, const std::string &port_num, asio::error_code &ec) { - udp::resolver::results_type remote_addresses = resolver.resolve(udp::v6(), remote_address, port_num, - udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); - - return remote_addresses; + if (ipv4_only) + return resolver.resolve(udp::v4(), remote_address, port_num, + udp::resolver::numeric_service | udp::resolver::address_configured, ec); + else + return resolver.resolve(udp::v6(), remote_address, port_num, + udp::resolver::numeric_service | udp::resolver::v4_mapped | udp::resolver::all_matching, ec); } void udp_client::disconnect() @@ -523,7 +543,8 @@ void udp_client::initialise() { asio::ip::v6_only v6_option(false); connection_socket.open(udp::v6()); - connection_socket.set_option(v6_option); + if (!ipv4_only) + connection_socket.set_option(v6_option); } void udp_client::start_receive() diff --git a/src/networks/connections.hpp b/src/networks/connections.hpp index c946ac6..bd2626d 100644 --- a/src/networks/connections.hpp +++ b/src/networks/connections.hpp @@ -122,8 +122,8 @@ class tcp_client { public: tcp_client() = delete; - tcp_client(asio::io_context &io_context) - : internal_io_context(io_context), resolver(io_context) + tcp_client(asio::io_context &io_context, bool v4_only = false) + : internal_io_context(io_context), resolver(io_context), ipv4_only(v4_only) { } @@ -137,6 +137,7 @@ class tcp_client asio::io_context &internal_io_context; tcp::resolver resolver; asio::ip::basic_resolver_results remote_endpoints; + const bool ipv4_only; }; @@ -177,10 +178,10 @@ class udp_client { public: udp_client() = delete; - udp_client(asio::io_context &io_context, asio::strand &asio_strand, udp_callback_t callback_func) + udp_client(asio::io_context &io_context, asio::strand &asio_strand, udp_callback_t callback_func, bool v4_only = false) : connection_socket(io_context), resolver(io_context), callback(callback_func), task_assigner(asio_strand), last_receive_time(right_now()), last_send_time(right_now()), - paused(false), stopped(false) + paused(false), stopped(false), ipv4_only(v4_only) { initialise(); } @@ -225,12 +226,13 @@ class udp_client std::atomic last_send_time; std::atomic paused; std::atomic stopped; + const bool ipv4_only; }; -std::unique_ptr send_stun_3489_request(udp_server &sender, const std::string &stun_host); -std::unique_ptr send_stun_8489_request(udp_server &sender, const std::string &stun_host); -void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, rfc8489::stun_header *header); +std::unique_ptr send_stun_3489_request(udp_server &sender, const std::string &stun_host, bool v4_only = false); +std::unique_ptr send_stun_8489_request(udp_server &sender, const std::string &stun_host, bool v4_only = false); +void resend_stun_8489_request(udp_server &sender, const std::string &stun_host, rfc8489::stun_header *header, bool v4_only = false); std::unique_ptr send_stun_8489_request(tcp_session &sender, const std::string &stun_host); void resend_stun_8489_request(tcp_session &sender, const std::string &stun_host, rfc8489::stun_header *header); diff --git a/src/networks/tcp_mode.cpp b/src/networks/tcp_mode.cpp index 3441d92..c8b021c 100644 --- a/src/networks/tcp_mode.cpp +++ b/src/networks/tcp_mode.cpp @@ -26,7 +26,12 @@ bool tcp_mode::start() if (port_number == 0) return false; - tcp::endpoint listen_on_ep(tcp::v6(), port_number); + tcp::endpoint listen_on_ep; + if (current_settings.ipv4_only) + listen_on_ep = tcp::endpoint(tcp::v4(), port_number); + else + listen_on_ep = tcp::endpoint(tcp::v6(), port_number); + if (!current_settings.listen_on.empty()) { asio::error_code ec; @@ -39,7 +44,7 @@ bool tcp_mode::start() return false; } - if (local_address.is_v4()) + if (local_address.is_v4() && !current_settings.ipv4_only) listen_on_ep.address(asio::ip::make_address_v6(asio::ip::v4_mapped, local_address.to_v4())); else listen_on_ep.address(local_address); @@ -70,7 +75,7 @@ void tcp_mode::tcp_server_accept_incoming(std::unique_ptr &&incomin if (!incoming_session->is_open()) return; - tcp_client target_connector(io_context); + tcp_client target_connector(io_context, current_settings.ipv4_only); std::string &destination_address = current_settings.destination_address; uint16_t destination_port = current_settings.destination_port; asio::error_code ec; diff --git a/src/networks/udp_mode.cpp b/src/networks/udp_mode.cpp index d36763e..5da73c6 100644 --- a/src/networks/udp_mode.cpp +++ b/src/networks/udp_mode.cpp @@ -23,7 +23,12 @@ bool udp_mode::start() if (port_number == 0) return false; - udp::endpoint listen_on_ep(udp::v6(), port_number); + udp::endpoint listen_on_ep; + if (current_settings.ipv4_only) + listen_on_ep = udp::endpoint(udp::v4(), port_number); + else + listen_on_ep = udp::endpoint(udp::v6(), port_number); + if (!current_settings.listen_on.empty()) { asio::error_code ec; @@ -36,7 +41,7 @@ bool udp_mode::start() return false; } - if (local_address.is_v4()) + if (local_address.is_v4() && !current_settings.ipv4_only) listen_on_ep.address(asio::ip::make_address_v6(asio::ip::v4_mapped, local_address.to_v4())); else listen_on_ep.address(local_address); @@ -53,7 +58,7 @@ bool udp_mode::start() if (!current_settings.stun_server.empty()) { - stun_header = send_stun_8489_request(*udp_access_point, current_settings.stun_server); + stun_header = send_stun_8489_request(*udp_access_point, current_settings.stun_server, current_settings.ipv4_only); timer_stun.expires_after(std::chrono::seconds(1)); timer_stun.async_wait([this](const asio::error_code &e) { send_stun_request(e); }); } @@ -106,7 +111,7 @@ void udp_mode::udp_server_incoming(std::unique_ptr data, size_t data_ return; auto udp_func = std::bind(&udp_mode::udp_client_incoming_to_udp, this, _1, _2, _3, _4); - auto udp_forwarder = std::make_unique(network_io, asio_strand, udp_func); + auto udp_forwarder = std::make_unique(network_io, asio_strand, udp_func, current_settings.ipv4_only); if (udp_forwarder == nullptr) return; @@ -232,7 +237,7 @@ void udp_mode::send_stun_request(const asio::error_code &e) return; if (!current_settings.stun_server.empty()) - resend_stun_8489_request(*udp_access_point, current_settings.stun_server, stun_header.get()); + resend_stun_8489_request(*udp_access_point, current_settings.stun_server, stun_header.get(), current_settings.ipv4_only); timer_stun.expires_after(STUN_RESEND); timer_stun.async_wait([this](const asio::error_code &e) { send_stun_request(e); }); diff --git a/src/shares/share_defines.cpp b/src/shares/share_defines.cpp index 5a562aa..0b567d1 100644 --- a/src/shares/share_defines.cpp +++ b/src/shares/share_defines.cpp @@ -76,6 +76,13 @@ user_settings parse_from_args(const std::vector &args, std::vector< current_user_settings.udp_timeout = static_cast(time_interval); break; + case strhash("ipv4_only"): + { + bool yes = value == "yes" || value == "true" || value == "1"; + current_user_settings.ipv4_only = yes; + break; + } + default: error_msg.emplace_back("unknow option: " + arg); } @@ -145,7 +152,8 @@ void print_ip_to_file(const std::string& message, const std::filesystem::path& l static std::mutex mtx; std::unique_lock locker{ mtx }; output_file.open(log_file, std::ios::out | std::ios::trunc); - output_file << message; + if (output_file.is_open() && output_file.good()) + output_file << message; output_file.close(); } @@ -155,6 +163,7 @@ void print_message_to_file(const std::string& message, const std::filesystem::pa static std::mutex mtx; std::unique_lock locker{ mtx }; output_file.open(log_file, std::ios::out | std::ios::app); - output_file << message; + if (output_file.is_open() && output_file.good()) + output_file << message; output_file.close(); } diff --git a/src/shares/share_defines.hpp b/src/shares/share_defines.hpp index e0ffeac..f06b5ec 100644 --- a/src/shares/share_defines.hpp +++ b/src/shares/share_defines.hpp @@ -14,9 +14,9 @@ template T generate_random_number() { - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_int_distribution uniform_dist(std::numeric_limits::min(), std::numeric_limits::max()); + thread_local std::random_device rd; + thread_local std::mt19937 mt(rd()); + thread_local std::uniform_int_distribution uniform_dist(std::numeric_limits::min(), std::numeric_limits::max()); return uniform_dist(mt); } @@ -25,6 +25,7 @@ struct user_settings uint16_t listen_port = 0; uint16_t destination_port = 0; uint16_t udp_timeout = 0; + bool ipv4_only = false; std::string listen_on; std::string destination_address; std::string stun_server;