From 80c2a968d1891d9234e2ce13918ca248fc873355 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Mon, 16 Oct 2023 04:12:41 +0800 Subject: [PATCH] Add support for WireGuard nodes in Clash, Surge and Loon configs --- src/generator/config/subexport.cpp | 74 ++++++++++++++++- src/parser/config/proxy.h | 18 ++++- src/parser/subparser.cpp | 126 ++++++++++++++++++++++++++++- src/utils/regexp.cpp | 52 ++++++++---- src/utils/regexp.h | 1 + src/utils/string.cpp | 2 +- src/utils/string.h | 2 +- 7 files changed, 255 insertions(+), 20 deletions(-) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index bb17bf6..57c197f 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -444,6 +444,20 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) singleproxy["password"].SetTag("str"); break; + case ProxyType::WireGuard: + singleproxy["type"] = "wireguard"; + singleproxy["public-key"] = x.PublicKey; + singleproxy["private-key"] = x.PrivateKey; + singleproxy["ip"] = x.SelfIP; + if(!x.SelfIPv6.empty()) + singleproxy["ipv6"] = x.SelfIPv6; + if(!x.PreSharedKey.empty()) + singleproxy["preshared-key"] = x.PreSharedKey; + if(!x.DnsServers.empty()) + singleproxy["dns"] = x.DnsServers; + if(x.Mtu > 0) + singleproxy["mtu"] = x.Mtu; + break; default: continue; } @@ -595,6 +609,24 @@ std::string proxyToClash(std::vector &nodes, const std::string &base_conf return output_content; } +// peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) +std::string generatePeer(Proxy &node, bool client_id_as_reserved = false) +{ + std::string result; + result += "public-key = " + node.PublicKey; + result += ", endpoint = " + node.Hostname + ":" + std::to_string(node.Port); + if(!node.AllowedIPs.empty()) + result += ", allowed-ips = \"" + node.AllowedIPs + "\""; + if(!node.ClientId.empty()) + { + if(client_id_as_reserved) + result += ", reserved = [" + node.ClientId + "]"; + else + result += ", client-id = " + node.ClientId; + } + return result; +} + std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, int surge_ver, extra_settings &ext) { INIReader ini; @@ -644,7 +676,7 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf scv.define(x.AllowInsecure); tls13.define(x.TLS13); - std::string proxy; + std::string proxy, section, real_section; string_array args, headers; switch(x.Type) @@ -770,6 +802,28 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf if(x.SnellVersion != 0) proxy += ", version=" + std::to_string(x.SnellVersion); break; + case ProxyType::WireGuard: + if(surge_ver < 4 && surge_ver != -3) + continue; + section = randomStr(5); + real_section = "WireGuard " + section; + proxy = "wireguard, section-name=" + section; + if(!x.TestUrl.empty()) + proxy += ", test-url=" + x.TestUrl; + ini.set(real_section, "private-key", x.PrivateKey); + ini.set(real_section, "self-ip", x.SelfIP); + if(!x.SelfIPv6.empty()) + ini.set(real_section, "self-ip-v6", x.SelfIPv6); + if(!x.PreSharedKey.empty()) + ini.set(real_section, "preshared-key", x.PreSharedKey); + if(!x.DnsServers.empty()) + ini.set(real_section, "dns-server", join(x.DnsServers, ",")); + if(x.Mtu > 0) + ini.set(real_section, "mtu", std::to_string(x.Mtu)); + if(x.KeepAlive > 0) + ini.set(real_section, "keepalive", std::to_string(x.KeepAlive)); + ini.set(real_section, "peer", "(" + generatePeer(x) + ")"); + break; default: continue; } @@ -1866,6 +1920,24 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, proxy += ",skip-cert-verify=" + std::string(scv.get() ? "true" : "false"); } break; + case ProxyType::WireGuard: + proxy = "wireguard, interface-ip=" + x.SelfIP; + if(!x.SelfIPv6.empty()) + proxy += ", interface-ipv6=" + x.SelfIPv6; + proxy += ", private-key=" + x.PrivateKey; + for(const auto &y : x.DnsServers) + { + if(isIPv4(y)) + proxy += ", dns=" + y; + else if(isIPv6(y)) + proxy += ", dnsv6=" + y; + } + if(x.Mtu > 0) + proxy += ", mtu=" + std::to_string(x.Mtu); + if(x.KeepAlive > 0) + proxy += ", keepalive=" + std::to_string(x.KeepAlive); + proxy += ", peers=[{" + generatePeer(x, true) + "}]"; + break; default: continue; } diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h index 5edc1de..94f6886 100644 --- a/src/parser/config/proxy.h +++ b/src/parser/config/proxy.h @@ -2,10 +2,12 @@ #define PROXY_H_INCLUDED #include +#include #include "../../utils/tribool.h" using String = std::string; +using StringArray = std::vector; enum ProxyType { @@ -17,7 +19,8 @@ enum ProxyType Snell, HTTP, HTTPS, - SOCKS5 + SOCKS5, + WireGuard }; inline String getProxyTypeName(int type) @@ -84,6 +87,18 @@ struct Proxy uint16_t SnellVersion = 0; String ServerName; + + String SelfIP; + String SelfIPv6; + String PublicKey; + String PrivateKey; + String PreSharedKey; + StringArray DnsServers; + uint16_t Mtu = 0; + String AllowedIPs = "0.0.0.0/0, ::/0"; + uint16_t KeepAlive = 0; + String TestUrl; + String ClientId; }; #define SS_DEFAULT_GROUP "SSProvider" @@ -93,5 +108,6 @@ struct Proxy #define HTTP_DEFAULT_GROUP "HTTPProvider" #define TROJAN_DEFAULT_GROUP "TrojanProvider" #define SNELL_DEFAULT_GROUP "SnellProvider" +#define WG_DEFAULT_GROUP "WireGuardProvider" #endif // PROXY_H_INCLUDED diff --git a/src/parser/subparser.cpp b/src/parser/subparser.cpp index 1434e6a..35cbf7c 100644 --- a/src/parser/subparser.cpp +++ b/src/parser/subparser.cpp @@ -115,6 +115,20 @@ void snellConstruct(Proxy &node, const std::string &group, const std::string &re node.SnellVersion = version; } +void wireguardConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &selfIp, const std::string &selfIpv6, const std::string &privKey, const std::string &pubKey, const std::string &psk, const string_array &dns, const std::string &mtu, const std::string &keepalive, const std::string &testUrl, const std::string &clientId, const tribool &udp) { + commonConstruct(node, ProxyType::WireGuard, group, remarks, server, port, udp, tribool(), tribool(), tribool()); + node.SelfIP = selfIp; + node.SelfIPv6 = selfIpv6; + node.PrivateKey = privKey; + node.PublicKey = pubKey; + node.PreSharedKey = psk; + node.DnsServers = dns; + node.Mtu = to_int(mtu); + node.KeepAlive = to_int(keepalive); + node.TestUrl = testUrl; + node.ClientId = clientId; +} + void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls, sni; @@ -953,6 +967,8 @@ void explodeClash(Node yamlnode, std::vector &nodes) std::string plugin, pluginopts, pluginopts_mode, pluginopts_host, pluginopts_mux; //ss std::string protocol, protoparam, obfs, obfsparam; //ssr std::string user; //socks + std::string ip, ipv6, private_key, public_key, mtu; //wireguard + string_array dns_server; tribool udp, tfo, scv; Node singleproxy; uint32_t index = nodes.size(); @@ -1151,6 +1167,18 @@ void explodeClash(Node yamlnode, std::vector &nodes) snellConstruct(node, group, ps, server, port, password, obfs, host, to_int(aid, 0), udp, tfo, scv); break; + case "wireguard"_hash: + group = WG_DEFAULT_GROUP; + singleproxy["public-key"] >>= public_key; + singleproxy["private-key"] >>= private_key; + singleproxy["dns"] >>= dns_server; + singleproxy["mtu"] >>= mtu; + singleproxy["preshared-key"] >>= password; + singleproxy["ip"] >>= ip; + singleproxy["ipv6"] >>= ipv6; + + wireguardConstruct(node, group, ps, server, port, ip, ipv6, private_key, public_key, password, dns_server, mtu, "0", "", "", udp); + break; default: continue; } @@ -1288,6 +1316,41 @@ void explodeKitsunebi(std::string kit, Proxy &node) vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls, ""); } +// peer = (public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, allowed-ips = "0.0.0.0/0, ::/0", endpoint = engage.cloudflareclient.com:2408, client-id = 139/184/125),(public-key = bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=, endpoint = engage.cloudflareclient.com:2408) +void parsePeers(Proxy &node, const std::string &data) +{ + auto peers = regGetAllMatch(data, R"(\((.*?)\))", true); + if(peers.empty()) + return; + auto peer = peers[0]; + auto peerdata = regGetAllMatch(peer, R"(([a-z-]+) ?= ?([^" ),]+|".*?"),? ?)", true); + if(peerdata.size() % 2 != 0) + return; + for(size_t i = 0; i < peerdata.size(); i += 2) + { + auto key = peerdata[i]; + auto val = peerdata[i + 1]; + switch(hash_(key)) + { + case "public-key"_hash: + node.PublicKey = val; + break; + case "endpoint"_hash: + node.Hostname = val.substr(0, val.rfind(':')); + node.Port = to_int(val.substr(val.rfind(':') + 1)); + break; + case "client-id"_hash: + node.ClientId = val; + break; + case "allowed-ips"_hash: + node.AllowedIPs = trimOf(val, '"'); + break; + default: + break; + } + } +} + bool explodeSurge(std::string surge, std::vector &nodes) { std::multimap proxies; @@ -1303,7 +1366,6 @@ bool explodeSurge(std::string surge, std::vector &nodes) ini.keep_empty_section = false; ini.allow_dup_section_titles = true; ini.set_isolated_items_section("Proxy"); - ini.include_section("Proxy"); ini.add_direct_save_section("Proxy"); if(surge.find("[Proxy]") != surge.npos) surge = regReplace(surge, R"(^[\S\s]*?\[)", "[", false); @@ -1322,6 +1384,9 @@ bool explodeSurge(std::string surge, std::vector &nodes) std::string plugin, pluginopts, pluginopts_mode, pluginopts_host, mod_url, mod_md5; //ss std::string id, net, tls, host, edge, path; //v2 std::string protocol, protoparam; //ssr + std::string section, ip, ipv6, private_key, public_key, mtu, test_url, client_id, peer, keepalive; //wireguard + string_array dns_servers; + string_multimap wireguard_config; std::string version, aead = "1"; std::string itemName, itemVal, config; std::vector configs, vArray, headers, header; @@ -1660,6 +1725,65 @@ bool explodeSurge(std::string surge, std::vector &nodes) snellConstruct(node, SNELL_DEFAULT_GROUP, remarks, server, port, password, plugin, host, to_int(version, 0), udp, tfo, scv); break; + case "wireguard"_hash: + for (i = 1; i < configs.size(); i++) + { + vArray = split(trim(configs[i]), "="); + if(vArray.size() != 2) + continue; + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch(hash_(itemName)) + { + case "section-name"_hash: + section = itemVal; + break; + case "test-url"_hash: + test_url = itemVal; + break; + } + } + if(section.empty()) + continue; + ini.get_items("WireGuard " + section, wireguard_config); + if(wireguard_config.empty()) + continue; + + for (auto &c : wireguard_config) + { + itemName = trim(c.first); + itemVal = trim(c.second); + switch(hash_(itemName)) + { + case "self-ip"_hash: + ip = itemVal; + break; + case "self-ip-v6"_hash: + ipv6 = itemVal; + break; + case "private-key"_hash: + private_key = itemVal; + break; + case "dns-server"_hash: + vArray = split(itemVal, ","); + for (auto &y : vArray) + dns_servers.emplace_back(trim(y)); + break; + case "mtu"_hash: + mtu = itemVal; + break; + case "peer"_hash: + peer = itemVal; + break; + case "keepalive"_hash: + keepalive = itemVal; + break; + } + } + + wireguardConstruct(node, WG_DEFAULT_GROUP, remarks, "", "0", ip, ipv6, private_key, "", "", dns_servers, mtu, keepalive, test_url, "", udp); + parsePeers(node, peer); + break; default: switch(hash_(remarks)) { diff --git a/src/utils/regexp.cpp b/src/utils/regexp.cpp index 8e809ae..e0ddaa2 100644 --- a/src/utils/regexp.cpp +++ b/src/utils/regexp.cpp @@ -10,6 +10,8 @@ using jp = jpcre2::select; //#endif // USE_STD_REGEX +#include "regexp.h" + /* #ifdef USE_STD_REGEX bool regValid(const std::string ®) @@ -164,34 +166,54 @@ bool regValid(const std::string ®) } int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...) +{ + auto result = regGetAllMatch(src, match, false); + if(result.empty()) + return -1; + va_list vl; + va_start(vl, group_count); + size_t index = 0; + while(group_count) + { + std::string* arg = va_arg(vl, std::string*); + if(arg != nullptr) + *arg = std::move(result[index]); + index++; + group_count--; + if(result.size() <= index) + break; + } + va_end(vl); + return 0; +} + +std::vector regGetAllMatch(const std::string &src, const std::string &match, bool group_only) { jp::Regex reg; reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); jp::VecNum vec_num; jp::RegexMatch rm; size_t count = rm.setRegexObject(®).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("g").match(); + std::vector result; if(!count) - return -1; - va_list vl; - va_start(vl, group_count); - size_t index = 0, match_index = 0; - while(group_count) + return result; + size_t begin = 0; + if(group_only) + begin = 1; + size_t index = begin, match_index = 0; + while(true) { - std::string* arg = va_arg(vl, std::string*); - if(arg != NULL) - *arg = std::move(vec_num[match_index][index]); - index++; - group_count--; + if(vec_num.size() <= match_index) + break; if(vec_num[match_index].size() <= index) { match_index++; - index = 0; + index = begin; } - if(vec_num.size() <= match_index) - break; + result.push_back(std::move(vec_num[match_index][index])); + index++; } - va_end(vl); - return 0; + return result; } //#endif // USE_STD_REGEX diff --git a/src/utils/regexp.h b/src/utils/regexp.h index 994709a..06558a5 100644 --- a/src/utils/regexp.h +++ b/src/utils/regexp.h @@ -8,6 +8,7 @@ bool regFind(const std::string &src, const std::string &match); std::string regReplace(const std::string &src, const std::string &match, const std::string &rep, bool global = true, bool multiline = true); bool regMatch(const std::string &src, const std::string &match); int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...); +std::vector regGetAllMatch(const std::string &src, const std::string &match, bool group_only = false); std::string regTrim(const std::string &src); #endif // REGEXP_H_INCLUDED diff --git a/src/utils/string.cpp b/src/utils/string.cpp index 0730f84..7e2789e 100644 --- a/src/utils/string.cpp +++ b/src/utils/string.cpp @@ -354,7 +354,7 @@ bool isStrUTF8(const std::string &data) return true; } -std::string randomStr(const int len) +std::string randomStr(int len) { std::string retData; srand(time(NULL)); diff --git a/src/utils/string.h b/src/utils/string.h index 7c16bec..1a42487 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -33,7 +33,7 @@ std::string trim(const std::string& str, bool before = true, bool after = true); std::string trimQuote(const std::string &str, bool before = true, bool after = true); void trimSelfOf(std::string &str, char target, bool before = true, bool after = true); std::string trimWhitespace(const std::string &str, bool before = false, bool after = true); -std::string randomStr(const int len); +std::string randomStr(int len); bool isStrUTF8(const std::string &data); void removeUTF8BOM(std::string &data);