From f2d8a3ca1e9044de0fc69f68c1f975e294de1f8b Mon Sep 17 00:00:00 2001 From: enfein <83481737+enfein@users.noreply.github.com> Date: Thu, 26 Jan 2023 05:12:14 +0000 Subject: [PATCH] v1.11.0 release 1. Provide HTTP / HTTPS proxy (issue #47). 2. Add "mieru check update" and "mita check update" commands. --- Makefile | 4 +- README.md | 5 +- README.zh_CN.md | 7 +- .../package/mieru/amd64/debian/DEBIAN/control | 2 +- build/package/mieru/amd64/rpm/mieru.spec | 2 +- .../package/mieru/arm64/debian/DEBIAN/control | 2 +- build/package/mieru/arm64/rpm/mieru.spec | 2 +- .../package/mita/amd64/debian/DEBIAN/control | 2 +- build/package/mita/amd64/rpm/mita.spec | 2 +- .../package/mita/arm64/debian/DEBIAN/control | 2 +- build/package/mita/arm64/rpm/mita.spec | 2 +- configs/examples/client_config.json | 4 +- configs/templates/client_config.json | 4 +- docs/client-install.md | 13 +- docs/client-install.zh_CN.md | 13 +- docs/server-install.md | 8 +- docs/server-install.zh_CN.md | 8 +- pkg/appctl/appctlpb/clientcfg.pb.go | 40 ++++- pkg/appctl/appctlpb/servercfg.pb.go | 3 +- pkg/appctl/client.go | 93 ++++++----- pkg/appctl/client_test.go | 3 + pkg/appctl/proto/clientcfg.proto | 7 + pkg/appctl/proto/servercfg.proto | 1 + .../testdata/client_apply_config_2.json | 4 +- .../client_reject_same_port_http_rpc.json | 26 ++++ .../client_reject_same_port_http_socks5.json | 26 ++++ .../client_reject_same_port_rpc_socks5.json | 26 ++++ pkg/cli/client.go | 58 +++++-- pkg/cli/server.go | 20 ++- pkg/cli/shared.go | 37 +++++ pkg/http2socks/http2socks.go | 147 ++++++++++++++++++ pkg/log/entry_test.go | 8 +- pkg/netutil/conn.go | 20 ++- pkg/netutil/copy.go | 58 +++++++ pkg/socks5/{bidi.go => copy.go} | 41 ----- pkg/socks5/request.go | 2 +- pkg/socks5/socks5.go | 10 +- pkg/socks5/udp_test.go | 53 +++++++ pkg/socks5client/net.go | 55 ------- pkg/socks5client/parse.go | 28 +--- pkg/socks5client/parse_test.go | 12 +- pkg/socks5client/socks.go | 71 ++++----- pkg/socks5client/socks5.go | 99 ++++++++---- pkg/version/check_update.go | 74 +++++++++ pkg/{appctl/version.go => version/current.go} | 6 +- pkg/version/scheme.go | 98 ++++++++++++ pkg/version/scheme_test.go | 90 +++++++++++ test/cmd/sockshttpclient/sockshttpclient.go | 147 ++++++++++++------ test/cmd/socksudpclient/socksudpclient.go | 5 +- test/deploy/httptest/client_tcp.json | 4 +- test/deploy/httptest/client_udp.json | 4 +- test/deploy/httptest/test_tcp.sh | 17 +- test/deploy/httptest/test_udp.sh | 17 +- tools/bump_version.sh | 2 +- 54 files changed, 1135 insertions(+), 359 deletions(-) create mode 100644 pkg/appctl/testdata/client_reject_same_port_http_rpc.json create mode 100644 pkg/appctl/testdata/client_reject_same_port_http_socks5.json create mode 100644 pkg/appctl/testdata/client_reject_same_port_rpc_socks5.json create mode 100644 pkg/cli/shared.go create mode 100644 pkg/http2socks/http2socks.go create mode 100644 pkg/netutil/copy.go rename pkg/socks5/{bidi.go => copy.go} (74%) create mode 100644 pkg/socks5/udp_test.go delete mode 100644 pkg/socks5client/net.go create mode 100644 pkg/version/check_update.go rename pkg/{appctl/version.go => version/current.go} (89%) create mode 100644 pkg/version/scheme.go create mode 100644 pkg/version/scheme_test.go diff --git a/Makefile b/Makefile index d4eed733..771f79c1 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,10 @@ PROJECT_NAME=$(shell basename "${ROOT}") # - build/package/mita/arm64/rpm/mita.spec # - docs/server-install.md # - docs/server-install.zh_CN.md -# - pkg/appctl/version.go +# - pkg/version/current.go # # Use `tools/bump_version.sh` script to change all those files at one shot. -VERSION="1.10.0" +VERSION="1.11.0" # Build binaries and installation packages. .PHONY: build diff --git a/README.md b/README.md index 6428028d..754f13e9 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ For an explanation of the mieru protocol, see [mieru Proxy Protocol](https://git 5. When the server can not decrypt the data sent by the client, no content is returned. it is difficult for GFW to discover the mieru service through active probing. 6. mieru supports multiple users sharing a single proxy server. 7. mieru supports IPv4 and IPv6. -8. The client software supports Windows, Mac OS, Linux and Android systems. Android users should use SagerNet client version 0.8.1-rc02 or above, and install mieru plugin version 1.6.1 or above. -9. If you need advanced features like global proxy or customized routing rules, you can use mieru as the backend of a proxy platform such as clash. +8. mieru provides socks5, HTTP and HTTPS proxy. +9. The client software supports Windows, Mac OS, Linux and Android systems. Android users should use SagerNet client version 0.8.1-rc02 or above, and install mieru plugin version 1.6.1 or above. +10. If you need advanced features like global proxy or customized routing rules, you can use mieru as the backend of a proxy platform such as clash. ## User Guide diff --git a/README.zh_CN.md b/README.zh_CN.md index 6c1a32c8..53f42e79 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -24,9 +24,10 @@ mieru 的翻墙原理与 shadowsocks / v2ray 等软件类似,在客户端和 4. 在使用 UDP 传输协议时,mieru 不需要客户端和服务器进行握手,即可直接发送数据。 5. 当服务器无法解密客户端发送的数据时,不会返回任何内容。GFW 很难通过主动探测发现 mieru 服务。 6. mieru 支持多个用户共享代理服务器。 -7. 支持 IPv4 和 IPv6。 -8. 客户端软件支持 Windows, Mac OS, Linux 和 Android 系统。Android 用户请使用 0.8.1-rc02 版本或以上 SagerNet 客户端并安装 1.6.1 版本或以上 mieru 插件。 -9. 如果需要全局代理或自定义路由规则等高级功能,可以将 mieru 作为 clash 等代理平台的后端。 +7. mieru 支持 IPv4 和 IPv6。 +8. mieru 提供 socks5, HTTP 和 HTTPS 代理。 +9. 客户端软件支持 Windows, Mac OS, Linux 和 Android 系统。Android 用户请使用 0.8.1-rc02 版本或以上 SagerNet 客户端并安装 1.6.1 版本或以上 mieru 插件。 +10. 如果需要全局代理或自定义路由规则等高级功能,可以将 mieru 作为 clash 等代理平台的后端。 ## 使用教程 diff --git a/build/package/mieru/amd64/debian/DEBIAN/control b/build/package/mieru/amd64/debian/DEBIAN/control index ffeb46f2..eb49ef42 100755 --- a/build/package/mieru/amd64/debian/DEBIAN/control +++ b/build/package/mieru/amd64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mieru -Version: 1.10.0 +Version: 1.11.0 Section: net Priority: optional Architecture: amd64 diff --git a/build/package/mieru/amd64/rpm/mieru.spec b/build/package/mieru/amd64/rpm/mieru.spec index 0f8756c6..59e3413b 100644 --- a/build/package/mieru/amd64/rpm/mieru.spec +++ b/build/package/mieru/amd64/rpm/mieru.spec @@ -1,5 +1,5 @@ Name: mieru -Version: 1.10.0 +Version: 1.11.0 Release: 1%{?dist} Summary: Mieru proxy client License: GPLv3+ diff --git a/build/package/mieru/arm64/debian/DEBIAN/control b/build/package/mieru/arm64/debian/DEBIAN/control index 2515db2a..1e36f4b1 100755 --- a/build/package/mieru/arm64/debian/DEBIAN/control +++ b/build/package/mieru/arm64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mieru -Version: 1.10.0 +Version: 1.11.0 Section: net Priority: optional Architecture: arm64 diff --git a/build/package/mieru/arm64/rpm/mieru.spec b/build/package/mieru/arm64/rpm/mieru.spec index 0f8756c6..59e3413b 100644 --- a/build/package/mieru/arm64/rpm/mieru.spec +++ b/build/package/mieru/arm64/rpm/mieru.spec @@ -1,5 +1,5 @@ Name: mieru -Version: 1.10.0 +Version: 1.11.0 Release: 1%{?dist} Summary: Mieru proxy client License: GPLv3+ diff --git a/build/package/mita/amd64/debian/DEBIAN/control b/build/package/mita/amd64/debian/DEBIAN/control index 4331d60e..fa6e44fd 100755 --- a/build/package/mita/amd64/debian/DEBIAN/control +++ b/build/package/mita/amd64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mita -Version: 1.10.0 +Version: 1.11.0 Section: net Priority: optional Architecture: amd64 diff --git a/build/package/mita/amd64/rpm/mita.spec b/build/package/mita/amd64/rpm/mita.spec index 80f9187e..1451595f 100644 --- a/build/package/mita/amd64/rpm/mita.spec +++ b/build/package/mita/amd64/rpm/mita.spec @@ -1,5 +1,5 @@ Name: mita -Version: 1.10.0 +Version: 1.11.0 Release: 1%{?dist} Summary: Mieru proxy server License: GPLv3+ diff --git a/build/package/mita/arm64/debian/DEBIAN/control b/build/package/mita/arm64/debian/DEBIAN/control index a96698e9..1cdb924e 100755 --- a/build/package/mita/arm64/debian/DEBIAN/control +++ b/build/package/mita/arm64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mita -Version: 1.10.0 +Version: 1.11.0 Section: net Priority: optional Architecture: arm64 diff --git a/build/package/mita/arm64/rpm/mita.spec b/build/package/mita/arm64/rpm/mita.spec index 94b7dcee..1df99d8c 100644 --- a/build/package/mita/arm64/rpm/mita.spec +++ b/build/package/mita/arm64/rpm/mita.spec @@ -1,5 +1,5 @@ Name: mita -Version: 1.10.0 +Version: 1.11.0 Release: 1%{?dist} Summary: Mieru proxy server License: GPLv3+ diff --git a/configs/examples/client_config.json b/configs/examples/client_config.json index a34b04f3..00eca67c 100644 --- a/configs/examples/client_config.json +++ b/configs/examples/client_config.json @@ -25,5 +25,7 @@ "rpcPort": 8964, "socks5Port": 1080, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": 8080, + "httpProxyListenLAN": false } diff --git a/configs/templates/client_config.json b/configs/templates/client_config.json index 68fee310..2f9acd16 100644 --- a/configs/templates/client_config.json +++ b/configs/templates/client_config.json @@ -25,5 +25,7 @@ "rpcPort": -1, "socks5Port": -1, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": -1, + "httpProxyListenLAN": false } diff --git a/docs/client-install.md b/docs/client-install.md index 74c70983..f70b771b 100644 --- a/docs/client-install.md +++ b/docs/client-install.md @@ -44,7 +44,9 @@ to modify the proxy client settings. Here `` is a JSON formatted file. We "rpcPort": -1, "socks5Port": -1, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": -1, + "httpProxyListenLAN": false } ``` @@ -56,9 +58,10 @@ Please download or copy this template and use a text editor to modify the follow 4. If you have registered a domain name for the proxy server, please fill in the domain name in `profiles` -> `servers` -> `domainName`. Otherwise, do not modify this property. 5. Fill in `profiles` -> `servers` -> `portBindings` -> `port` with the TCP or UDP port number that mita is listening on. The port number must be the same as the one set in the proxy server. 6. Specify a value between 1280 and 1500 for the `profiles` -> `mtu` property. The default value is 1400. This value can be different from the setting in the proxy server. -7. Please specify a value from 1025 to 65535 for the `rpcPort` property. **Please make sure that the firewall allows communication using this port.** -8. Please specify a value between 1025 and 65535 for the `socks5Port` property. This port cannot be the same as `rpcPort`. **Make sure that the firewall allows communication on this port.** +7. Please specify a value between 1025 and 65535 for the `rpcPort` property. +8. Please specify a value between 1025 and 65535 for the `socks5Port` property. This port cannot be the same as `rpcPort`. 9. If the client needs to provide proxy services to other devices on the LAN, set the `socks5ListenLAN` property to `true`. +10. If you want to enable HTTP / HTTPS proxy, Please specify a value between 1025 and 65535 for the `httpProxyPort` property. This port cannot be the same as `rpcPort` or `socks5Port`. If the client needs to provide HTTP / HTTPS proxy services to other devices on the LAN, set the `httpProxyListenLAN` property to `true`. If you want to disable HTTP / HTTPS proxy, please delete `httpProxyPort` and `httpProxyListenLAN` property. If you have multiple proxy servers installed, or one server listening on multiple ports, you can add them all to the client settings. Each time a new connection is created, mieru will randomly select one of the servers and one of the ports. **If you are using multiple servers, make sure that each server has the mita proxy service started.** @@ -92,7 +95,9 @@ An example of the above setting is as follows. "rpcPort": 8964, "socks5Port": 1080, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": 8080, + "httpProxyListenLAN": false } ``` diff --git a/docs/client-install.zh_CN.md b/docs/client-install.zh_CN.md index d79449be..2657f99f 100644 --- a/docs/client-install.zh_CN.md +++ b/docs/client-install.zh_CN.md @@ -44,7 +44,9 @@ mieru apply config "rpcPort": -1, "socks5Port": -1, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": -1, + "httpProxyListenLAN": false } ``` @@ -56,9 +58,10 @@ mieru apply config 4. 如果你为代理服务器注册了域名,请在 `profiles` -> `servers` -> `domainName` 中填写域名。否则,请勿修改这个属性。 5. 在 `profiles` -> `servers` -> `portBindings` -> `port` 中填写 mita 监听的 TCP 或 UDP 端口号。这个端口号必须与代理服务器中的设置相同。 6. 请为 `profiles` -> `mtu` 属性中指定一个从 1280 到 1500 之间的值。默认值为 1400。这个值可以与代理服务器中的设置不同。 -7. 请为 `rpcPort` 属性指定一个从 1025 到 65535 之间的数值。**请确保防火墙允许使用该端口进行通信。** -8. 请为 `socks5Port` 属性指定一个从 1025 到 65535 之间的数值。该端口不能与 `rpcPort` 相同。**请确保防火墙允许使用该端口进行通信。** +7. 请为 `rpcPort` 属性指定一个从 1025 到 65535 之间的数值。 +8. 请为 `socks5Port` 属性指定一个从 1025 到 65535 之间的数值。该端口不能与 `rpcPort` 相同。 9. 如果客户端需要为局域网中的其他设备提供代理服务,请将 `socks5ListenLAN` 属性设置为 `true`。 +10. 如果要启动 HTTP / HTTPS 代理,请为 `httpProxyPort` 属性指定一个从 1025 到 65535 之间的数值。该端口不能与 `rpcPort` 和 `socks5Port` 相同。如果需要为局域网中的其他设备提供 HTTP / HTTPS 代理,请将 `httpProxyListenLAN` 属性设置为 `true`。如果不需要 HTTP / HTTPS 代理,请删除 `httpProxyPort` 和 `httpProxyListenLAN` 属性。 如果你安装了多台代理服务器,或者一台服务器监听多个端口,可以把它们都添加到客户端设置中。每次发起新的连接时,mieru 会随机选取其中的一台服务器和一个端口。**如果使用了多台服务器,请确保每一台服务器都启动了 mita 代理服务。** @@ -92,7 +95,9 @@ mieru apply config "rpcPort": 8964, "socks5Port": 1080, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": 8080, + "httpProxyListenLAN": false } ``` diff --git a/docs/server-install.md b/docs/server-install.md index 668a0217..d89484d8 100644 --- a/docs/server-install.md +++ b/docs/server-install.md @@ -10,10 +10,10 @@ Before installation and configuration, connect to the server via SSH and then ex ```sh # Debian / Ubuntu -curl -LSO https://github.com/enfein/mieru/releases/download/v1.10.0/mita_1.10.0_amd64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v1.11.0/mita_1.11.0_amd64.deb # Fedora / CentOS / Red Hat Enterprise Linux -curl -LSO https://github.com/enfein/mieru/releases/download/v1.10.0/mita-1.10.0-1.x86_64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v1.11.0/mita-1.11.0-1.x86_64.rpm ``` To download the installer for the ARM architecture, replace `amd64` with `arm64`, and `x86_64` with `aarch64` in the link. If the above link is blocked, please use your browser to download and install from the GitHub Releases page. @@ -22,10 +22,10 @@ To download the installer for the ARM architecture, replace `amd64` with `arm64` ```sh # Debian / Ubuntu -sudo dpkg -i mita_1.10.0_amd64.deb +sudo dpkg -i mita_1.11.0_amd64.deb # Fedora / CentOS / Red Hat Enterprise Linux -sudo rpm -Uvh --force mita-1.10.0-1.x86_64.rpm +sudo rpm -Uvh --force mita-1.11.0-1.x86_64.rpm ``` ## Grant permissions diff --git a/docs/server-install.zh_CN.md b/docs/server-install.zh_CN.md index e75784ee..9a454dbd 100644 --- a/docs/server-install.zh_CN.md +++ b/docs/server-install.zh_CN.md @@ -10,10 +10,10 @@ ```sh # Debian / Ubuntu -curl -LSO https://github.com/enfein/mieru/releases/download/v1.10.0/mita_1.10.0_amd64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v1.11.0/mita_1.11.0_amd64.deb # Fedora / CentOS / Red Hat Enterprise Linux -curl -LSO https://github.com/enfein/mieru/releases/download/v1.10.0/mita-1.10.0-1.x86_64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v1.11.0/mita-1.11.0-1.x86_64.rpm ``` 下载 ARM 架构的安装包,将链接中的 `amd64` 替换成 `arm64`,`x86_64` 替换成 `aarch64` 即可。如果上述链接被墙,请翻墙后使用浏览器从 GitHub Releases 页面下载安装。 @@ -22,10 +22,10 @@ curl -LSO https://github.com/enfein/mieru/releases/download/v1.10.0/mita-1.10.0- ```sh # Debian / Ubuntu -sudo dpkg -i mita_1.10.0_amd64.deb +sudo dpkg -i mita_1.11.0_amd64.deb # Fedora / CentOS / Red Hat Enterprise Linux -sudo rpm -Uvh --force mita-1.10.0-1.x86_64.rpm +sudo rpm -Uvh --force mita-1.11.0-1.x86_64.rpm ``` ## 赋予当前用户操作 mita 的权限,需要重启服务器使此设置生效 diff --git a/pkg/appctl/appctlpb/clientcfg.pb.go b/pkg/appctl/appctlpb/clientcfg.pb.go index b5a78ae0..d77aa459 100644 --- a/pkg/appctl/appctlpb/clientcfg.pb.go +++ b/pkg/appctl/appctlpb/clientcfg.pb.go @@ -161,12 +161,17 @@ type ClientConfig struct { // The management RPC port that client is listening in localhost. RpcPort *int32 `protobuf:"varint,3,opt,name=rpcPort,proto3,oneof" json:"rpcPort,omitempty"` // The socks5 port that mieru is listening. - Socks5Port *int32 `protobuf:"varint,4,opt,name=socks5Port,proto3,oneof" json:"socks5Port,omitempty"` + Socks5Port *int32 `protobuf:"varint,4,opt,name=socks5Port,proto3,oneof" json:"socks5Port,omitempty"` + // Client advanced settings. AdvancedSettings *ClientAdvancedSettings `protobuf:"bytes,5,opt,name=advancedSettings,proto3,oneof" json:"advancedSettings,omitempty"` // Client logging level. LoggingLevel *LoggingLevel `protobuf:"varint,6,opt,name=loggingLevel,proto3,enum=appctl.LoggingLevel,oneof" json:"loggingLevel,omitempty"` // If set, the socks5 port listens to LAN rather than localhost. Socks5ListenLAN *bool `protobuf:"varint,7,opt,name=socks5ListenLAN,proto3,oneof" json:"socks5ListenLAN,omitempty"` + // The port mieru is listening to provide HTTP / HTTPS proxy. + HttpProxyPort *int32 `protobuf:"varint,8,opt,name=httpProxyPort,proto3,oneof" json:"httpProxyPort,omitempty"` + // If set, the HTTP proxy port listens to LAN rather than localhost. + HttpProxyListenLAN *bool `protobuf:"varint,9,opt,name=httpProxyListenLAN,proto3,oneof" json:"httpProxyListenLAN,omitempty"` } func (x *ClientConfig) Reset() { @@ -250,6 +255,20 @@ func (x *ClientConfig) GetSocks5ListenLAN() bool { return false } +func (x *ClientConfig) GetHttpProxyPort() int32 { + if x != nil && x.HttpProxyPort != nil { + return *x.HttpProxyPort + } + return 0 +} + +func (x *ClientConfig) GetHttpProxyListenLAN() bool { + if x != nil && x.HttpProxyListenLAN != nil { + return *x.HttpProxyListenLAN + } + return false +} + var File_clientcfg_proto protoreflect.FileDescriptor var file_clientcfg_proto_rawDesc = []byte{ @@ -271,7 +290,7 @@ var file_clientcfg_proto_rawDesc = []byte{ 0x0c, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, 0x74, 0x75, 0x22, 0x18, 0x0a, 0x16, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x64, 0x76, 0x61, 0x6e, 0x63, 0x65, 0x64, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xd6, 0x03, 0x0a, 0x0c, 0x43, 0x6c, 0x69, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xdf, 0x04, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x70, 0x70, 0x63, 0x74, 0x6c, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x66, 0x69, @@ -294,6 +313,12 @@ var file_clientcfg_proto_rawDesc = []byte{ 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x35, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4c, 0x41, 0x4e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x48, 0x05, 0x52, 0x0f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x35, 0x4c, 0x69, + 0x73, 0x74, 0x65, 0x6e, 0x4c, 0x41, 0x4e, 0x88, 0x01, 0x01, 0x12, 0x29, 0x0a, 0x0d, 0x68, 0x74, + 0x74, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x05, 0x48, 0x06, 0x52, 0x0d, 0x68, 0x74, 0x74, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, 0x6f, + 0x72, 0x74, 0x88, 0x01, 0x01, 0x12, 0x33, 0x0a, 0x12, 0x68, 0x74, 0x74, 0x70, 0x50, 0x72, 0x6f, + 0x78, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4c, 0x41, 0x4e, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x08, 0x48, 0x07, 0x52, 0x12, 0x68, 0x74, 0x74, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4c, 0x41, 0x4e, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x72, 0x70, 0x63, 0x50, 0x6f, 0x72, 0x74, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x6f, 0x63, @@ -301,10 +326,13 @@ var file_clientcfg_proto_rawDesc = []byte{ 0x6e, 0x63, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x35, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4c, 0x41, - 0x4e, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x65, 0x6e, 0x66, 0x65, 0x69, 0x6e, 0x2f, 0x6d, 0x69, 0x65, 0x72, 0x75, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x61, 0x70, 0x70, 0x63, 0x74, 0x6c, 0x2f, 0x61, 0x70, 0x70, 0x63, 0x74, 0x6c, 0x70, 0x62, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4e, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x50, + 0x6f, 0x72, 0x74, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x50, 0x72, 0x6f, 0x78, + 0x79, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x4c, 0x41, 0x4e, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x65, 0x6e, 0x66, 0x65, 0x69, 0x6e, 0x2f, + 0x6d, 0x69, 0x65, 0x72, 0x75, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x70, 0x63, 0x74, 0x6c, + 0x2f, 0x61, 0x70, 0x70, 0x63, 0x74, 0x6c, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/pkg/appctl/appctlpb/servercfg.pb.go b/pkg/appctl/appctlpb/servercfg.pb.go index f5a7d8b7..02eaae35 100644 --- a/pkg/appctl/appctlpb/servercfg.pb.go +++ b/pkg/appctl/appctlpb/servercfg.pb.go @@ -92,7 +92,8 @@ type ServerConfig struct { // Server's port-protocol bindings. PortBindings []*PortBinding `protobuf:"bytes,1,rep,name=portBindings,proto3" json:"portBindings,omitempty"` // A list of registered users. - Users []*User `protobuf:"bytes,2,rep,name=users,proto3" json:"users,omitempty"` + Users []*User `protobuf:"bytes,2,rep,name=users,proto3" json:"users,omitempty"` + // Server advanced settings. AdvancedSettings *ServerAdvancedSettings `protobuf:"bytes,3,opt,name=advancedSettings,proto3,oneof" json:"advancedSettings,omitempty"` // Server logging level. LoggingLevel *LoggingLevel `protobuf:"varint,4,opt,name=loggingLevel,proto3,enum=appctl.LoggingLevel,oneof" json:"loggingLevel,omitempty"` diff --git a/pkg/appctl/client.go b/pkg/appctl/client.go index 1b9809e8..c0bf2dab 100644 --- a/pkg/appctl/client.go +++ b/pkg/appctl/client.go @@ -375,6 +375,7 @@ func ValidateClientConfigPatch(patch *pb.ClientConfig) error { // 2. the active profile is available // 3. RPC port is valid // 4. socks5 port is valid +// 5. RPC port, socks5 port, http proxy port are different func ValidateFullClientConfig(config *pb.ClientConfig) error { if err := ValidateClientConfigPatch(config); err != nil { return err @@ -395,13 +396,27 @@ func ValidateFullClientConfig(config *pb.ClientConfig) error { if !foundActiveProfile { return fmt.Errorf("active profile is not found in the profile list") } - // RPC port is allowed to set to 0, which means disable RPC. + // RPC port is allowed to be 0, which means disable RPC. if config.GetRpcPort() < 0 || config.GetRpcPort() > 65535 { return fmt.Errorf("RPC port number %d is invalid", config.GetRpcPort()) } if config.GetSocks5Port() < 1 || config.GetSocks5Port() > 65535 { return fmt.Errorf("socks5 port number %d is invalid", config.GetSocks5Port()) } + if config.GetRpcPort() == config.GetSocks5Port() { + return fmt.Errorf("RPC port number %d is the same as socks5 port number", config.GetRpcPort()) + } + if config.HttpProxyPort != nil { + if config.GetHttpProxyPort() < 1 || config.GetHttpProxyPort() > 65535 { + return fmt.Errorf("HTTP proxy port number %d is invalid", config.GetHttpProxyPort()) + } + if config.GetHttpProxyPort() == config.GetRpcPort() { + return fmt.Errorf("HTTP proxy port number %d is the same as RPC port number", config.GetHttpProxyPort()) + } + if config.GetHttpProxyPort() == config.GetSocks5Port() { + return fmt.Errorf("HTTP proxy port number %d is the same as socks5 port number", config.GetHttpProxyPort()) + } + } return nil } @@ -471,6 +486,7 @@ func clientConfigFilePath() (string, ConfigFileType, error) { // If a profile is specified in source, it is added to destination, // or replacing existing profile in destination. func mergeClientConfigByProfile(dst, src *pb.ClientConfig) { + // Merge src profiles into dst profiles. dstMapping := map[string]*pb.ClientProfile{} srcMapping := map[string]*pb.ClientProfile{} for _, profile := range dst.GetProfiles() { @@ -479,70 +495,77 @@ func mergeClientConfigByProfile(dst, src *pb.ClientConfig) { for _, profile := range src.GetProfiles() { srcMapping[profile.GetProfileName()] = profile } + mergedProfileMapping := map[string]*pb.ClientProfile{} + for name, profile := range dstMapping { + mergedProfileMapping[name] = profile + } + for name, profile := range srcMapping { + mergedProfileMapping[name] = profile + } + names := make([]string, 0, len(mergedProfileMapping)) + for name := range mergedProfileMapping { + names = append(names, name) + } + sort.Strings(names) + mergedProfiles := make([]*pb.ClientProfile, 0, len(mergedProfileMapping)) + for _, name := range names { + mergedProfiles = append(mergedProfiles, mergedProfileMapping[name]) + } + // Required fields. var activeProfile string if src.ActiveProfile != nil { activeProfile = src.GetActiveProfile() } else { activeProfile = dst.GetActiveProfile() } - var rpcPort int32 - if src.RpcPort != nil { - rpcPort = src.GetRpcPort() - } else { - rpcPort = dst.GetRpcPort() - } var sock5Port int32 if src.Socks5Port != nil { sock5Port = src.GetSocks5Port() } else { sock5Port = dst.GetSocks5Port() } - var advancedSettings *pb.ClientAdvancedSettings - if src.AdvancedSettings != nil { - advancedSettings = src.GetAdvancedSettings() - } else { - advancedSettings = dst.GetAdvancedSettings() - } var loggingLevel pb.LoggingLevel if src.LoggingLevel != nil { loggingLevel = src.GetLoggingLevel() } else { loggingLevel = dst.GetLoggingLevel() } - var socks5ListenLAN bool - if src.Socks5ListenLAN != nil { - socks5ListenLAN = src.GetSocks5ListenLAN() - } else { - socks5ListenLAN = dst.GetSocks5ListenLAN() - } - // Merge src into dst. - mergedProfileMapping := map[string]*pb.ClientProfile{} - for name, profile := range dstMapping { - mergedProfileMapping[name] = profile + // Optional fields. + var rpcPort *int32 = dst.RpcPort + if src.RpcPort != nil { + rpcPort = src.RpcPort } - for name, profile := range srcMapping { - mergedProfileMapping[name] = profile + var advancedSettings *pb.ClientAdvancedSettings = dst.AdvancedSettings + if src.AdvancedSettings != nil { + advancedSettings = src.AdvancedSettings } - names := make([]string, 0, len(mergedProfileMapping)) - for name := range mergedProfileMapping { - names = append(names, name) + var socks5ListenLAN *bool = dst.Socks5ListenLAN + if src.Socks5ListenLAN != nil { + socks5ListenLAN = src.Socks5ListenLAN } - sort.Strings(names) - mergedProfiles := make([]*pb.ClientProfile, 0, len(mergedProfileMapping)) - for _, name := range names { - mergedProfiles = append(mergedProfiles, mergedProfileMapping[name]) + var httpProxyPort *int32 = dst.HttpProxyPort + if src.HttpProxyPort != nil { + httpProxyPort = src.HttpProxyPort + } + var httpProxyListenLAN *bool = dst.HttpProxyListenLAN + if src.HttpProxyListenLAN != nil { + httpProxyListenLAN = src.HttpProxyListenLAN } proto.Reset(dst) + dst.ActiveProfile = proto.String(activeProfile) dst.Profiles = mergedProfiles - dst.RpcPort = proto.Int32(rpcPort) dst.Socks5Port = proto.Int32(sock5Port) - dst.AdvancedSettings = advancedSettings dst.LoggingLevel = &loggingLevel - dst.Socks5ListenLAN = proto.Bool(socks5ListenLAN) + + dst.RpcPort = rpcPort + dst.AdvancedSettings = advancedSettings + dst.Socks5ListenLAN = socks5ListenLAN + dst.HttpProxyPort = httpProxyPort + dst.HttpProxyListenLAN = httpProxyListenLAN } // deleteClientConfigFile deletes the client config file. diff --git a/pkg/appctl/client_test.go b/pkg/appctl/client_test.go index e8ab69d0..0b489b0d 100644 --- a/pkg/appctl/client_test.go +++ b/pkg/appctl/client_test.go @@ -78,6 +78,9 @@ func TestClientApplyReject(t *testing.T) { "testdata/client_reject_no_server_addr.json", "testdata/client_reject_no_socks5_port.json", "testdata/client_reject_no_user_name.json", + "testdata/client_reject_same_port_http_rpc.json", + "testdata/client_reject_same_port_http_socks5.json", + "testdata/client_reject_same_port_rpc_socks5.json", "testdata/client_reject_wrong_ipv4_address.json", "testdata/client_reject_wrong_ipv6_address.json", } diff --git a/pkg/appctl/proto/clientcfg.proto b/pkg/appctl/proto/clientcfg.proto index 120e6a99..7a5295cf 100644 --- a/pkg/appctl/proto/clientcfg.proto +++ b/pkg/appctl/proto/clientcfg.proto @@ -53,6 +53,7 @@ message ClientConfig { // The socks5 port that mieru is listening. optional int32 socks5Port = 4; + // Client advanced settings. optional ClientAdvancedSettings advancedSettings = 5; // Client logging level. @@ -60,4 +61,10 @@ message ClientConfig { // If set, the socks5 port listens to LAN rather than localhost. optional bool socks5ListenLAN = 7; + + // The port mieru is listening to provide HTTP / HTTPS proxy. + optional int32 httpProxyPort = 8; + + // If set, the HTTP proxy port listens to LAN rather than localhost. + optional bool httpProxyListenLAN = 9; } diff --git a/pkg/appctl/proto/servercfg.proto b/pkg/appctl/proto/servercfg.proto index 56d24697..f2039b66 100644 --- a/pkg/appctl/proto/servercfg.proto +++ b/pkg/appctl/proto/servercfg.proto @@ -37,6 +37,7 @@ message ServerConfig { // A list of registered users. repeated User users = 2; + // Server advanced settings. optional ServerAdvancedSettings advancedSettings = 3; // Server logging level. diff --git a/pkg/appctl/testdata/client_apply_config_2.json b/pkg/appctl/testdata/client_apply_config_2.json index 56a39f45..1115afd7 100644 --- a/pkg/appctl/testdata/client_apply_config_2.json +++ b/pkg/appctl/testdata/client_apply_config_2.json @@ -60,5 +60,7 @@ "rpcPort": 1999, "socks5Port": 1090, "loggingLevel": "INFO", - "socks5ListenLAN": false + "socks5ListenLAN": false, + "httpProxyPort": 8080, + "httpProxyListenLAN": true } diff --git a/pkg/appctl/testdata/client_reject_same_port_http_rpc.json b/pkg/appctl/testdata/client_reject_same_port_http_rpc.json new file mode 100644 index 00000000..4156d9e6 --- /dev/null +++ b/pkg/appctl/testdata/client_reject_same_port_http_rpc.json @@ -0,0 +1,26 @@ +{ + "profiles": [ + { + "profileName": "default", + "user": { + "name": "user1", + "password": "fa7206ed2a94" + }, + "servers": [ + { + "ipAddress": "1.1.1.1", + "portBindings": [ + { + "port": 4000, + "protocol": "UDP" + } + ] + } + ] + } + ], + "activeProfile": "default", + "rpcPort": 8080, + "socks5Port": 1080, + "httpProxyPort": 8080 +} diff --git a/pkg/appctl/testdata/client_reject_same_port_http_socks5.json b/pkg/appctl/testdata/client_reject_same_port_http_socks5.json new file mode 100644 index 00000000..08588fcd --- /dev/null +++ b/pkg/appctl/testdata/client_reject_same_port_http_socks5.json @@ -0,0 +1,26 @@ +{ + "profiles": [ + { + "profileName": "default", + "user": { + "name": "user1", + "password": "fa7206ed2a94" + }, + "servers": [ + { + "ipAddress": "1.1.1.1", + "portBindings": [ + { + "port": 4000, + "protocol": "UDP" + } + ] + } + ] + } + ], + "activeProfile": "default", + "rpcPort": 1080, + "socks5Port": 8080, + "httpProxyPort": 8080 +} diff --git a/pkg/appctl/testdata/client_reject_same_port_rpc_socks5.json b/pkg/appctl/testdata/client_reject_same_port_rpc_socks5.json new file mode 100644 index 00000000..34a7ab05 --- /dev/null +++ b/pkg/appctl/testdata/client_reject_same_port_rpc_socks5.json @@ -0,0 +1,26 @@ +{ + "profiles": [ + { + "profileName": "default", + "user": { + "name": "user1", + "password": "fa7206ed2a94" + }, + "servers": [ + { + "ipAddress": "1.1.1.1", + "portBindings": [ + { + "port": 4000, + "protocol": "UDP" + } + ] + } + ] + } + ], + "activeProfile": "default", + "rpcPort": 1080, + "socks5Port": 1080, + "httpProxyPort": 8080 +} diff --git a/pkg/cli/client.go b/pkg/cli/client.go index d6bceca8..5ace70e6 100644 --- a/pkg/cli/client.go +++ b/pkg/cli/client.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "net" + "net/http" "os/exec" "runtime/pprof" "strconv" @@ -30,6 +31,7 @@ import ( "github.com/enfein/mieru/pkg/appctl" "github.com/enfein/mieru/pkg/appctl/appctlpb" "github.com/enfein/mieru/pkg/cipher" + "github.com/enfein/mieru/pkg/http2socks" "github.com/enfein/mieru/pkg/log" "github.com/enfein/mieru/pkg/metrics" "github.com/enfein/mieru/pkg/netutil" @@ -114,7 +116,14 @@ func RegisterClientCommands() { func(s []string) error { return unexpectedArgsError(s, 2) }, - clientVersionFunc, + versionFunc, + ) + RegisterCallback( + []string{"", "check", "update"}, + func(s []string) error { + return unexpectedArgsError(s, 3) + }, + checkUpdateFunc, ) RegisterCallback( []string{"", "get", "thread-dump"}, @@ -166,6 +175,7 @@ var clientHelpFunc = func(s []string) error { describeConfigCmd := fmt.Sprintf(format, "describe config", "Show current client configuration") deleteProfileCmd := fmt.Sprintf(format, "delete profile ", "Delete a client configuration profile") versionCmd := fmt.Sprintf(format, "version", "Show mieru client version") + checkUpdateCmd := fmt.Sprintf(format, "check update", "Check mieru client update") log.Infof("Usage: %s []", binaryName) log.Infof("") log.Infof("Commands:") @@ -177,6 +187,7 @@ var clientHelpFunc = func(s []string) error { log.Infof("%s", describeConfigCmd) log.Infof("%s", deleteProfileCmd) log.Infof("%s", versionCmd) + log.Infof("%s", checkUpdateCmd) return nil } @@ -271,7 +282,6 @@ var clientRunFunc = func(s []string) error { } var wg sync.WaitGroup - wg.Add(1) // RPC port is allowed to set to 0. In that case, don't run RPC server. // When RPC server is not running, mieru commands can't be used to control the proxy client. @@ -358,13 +368,14 @@ var clientRunFunc = func(s []string) error { appctl.SetClientSocks5ServerRef(socks5Server) // Run the local socks5 server in the background. - go func() { - var socks5Addr string - if config.GetSocks5ListenLAN() { - socks5Addr = netutil.MaybeDecorateIPv6(netutil.AllIPAddr()) + ":" + strconv.Itoa(int(config.GetSocks5Port())) - } else { - socks5Addr = netutil.MaybeDecorateIPv6(netutil.LocalIPAddr()) + ":" + strconv.Itoa(int(config.GetSocks5Port())) - } + var socks5Addr string + if config.GetSocks5ListenLAN() { + socks5Addr = netutil.MaybeDecorateIPv6(netutil.AllIPAddr()) + ":" + strconv.Itoa(int(config.GetSocks5Port())) + } else { + socks5Addr = netutil.MaybeDecorateIPv6(netutil.LocalIPAddr()) + ":" + strconv.Itoa(int(config.GetSocks5Port())) + } + wg.Add(1) + go func(socks5Addr string) { l, err := net.Listen("tcp", socks5Addr) if err != nil { log.Fatalf("listen on socks5 address tcp %q failed: %v", socks5Addr, err) @@ -376,7 +387,29 @@ var clientRunFunc = func(s []string) error { } log.Infof("mieru client socks5 server is stopped") wg.Done() - }() + }(socks5Addr) + + // If HTTP proxy is enabled, run the local HTTP server in the background. + if config.GetHttpProxyPort() != 0 { + wg.Add(1) + go func(socks5Addr string) { + var httpServerAddr string + if config.GetHttpProxyListenLAN() { + httpServerAddr = netutil.MaybeDecorateIPv6(netutil.AllIPAddr()) + ":" + strconv.Itoa(int(config.GetHttpProxyPort())) + } else { + httpServerAddr = netutil.MaybeDecorateIPv6(netutil.LocalIPAddr()) + ":" + strconv.Itoa(int(config.GetHttpProxyPort())) + } + httpServer := http2socks.NewHTTPServer(httpServerAddr, &http2socks.Proxy{ + ProxyURI: "socks5://" + socks5Addr + "?timeout=10s", + }) + log.Infof("mieru client HTTP proxy server is running") + wg.Done() + if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatalf("run HTTP proxy server failed: %v", err) + } + }(socks5Addr) + } + <-appctl.ClientSocks5ServerStarted metrics.EnableLogging() @@ -458,11 +491,6 @@ var clientDeleteProfileFunc = func(s []string) error { return appctl.DeleteClientConfigProfile(s[3]) } -var clientVersionFunc = func(s []string) error { - log.Infof(appctl.AppVersion) - return nil -} - var clientGetThreadDumpFunc = func(s []string) error { if err := appctl.IsClientDaemonRunning(context.Background()); err != nil { log.Infof(stderror.ClientNotRunning) diff --git a/pkg/cli/server.go b/pkg/cli/server.go index 0d690192..d992e736 100644 --- a/pkg/cli/server.go +++ b/pkg/cli/server.go @@ -30,6 +30,7 @@ import ( "github.com/enfein/mieru/pkg/appctl" "github.com/enfein/mieru/pkg/appctl/appctlpb" "github.com/enfein/mieru/pkg/cipher" + "github.com/enfein/mieru/pkg/http2socks" "github.com/enfein/mieru/pkg/log" "github.com/enfein/mieru/pkg/metrics" "github.com/enfein/mieru/pkg/netutil" @@ -113,7 +114,14 @@ func RegisterServerCommands() { func(s []string) error { return unexpectedArgsError(s, 2) }, - serverVersionFunc, + versionFunc, + ) + RegisterCallback( + []string{"", "check", "update"}, + func(s []string) error { + return unexpectedArgsError(s, 3) + }, + checkUpdateFunc, ) RegisterCallback( []string{"", "get", "thread-dump"}, @@ -165,6 +173,7 @@ var serverHelpFunc = func(s []string) error { describeConfigCmd := fmt.Sprintf(format, "describe config", "Show current server configuration") deleteUserCmd := fmt.Sprintf(format, "delete user ", "Delete users from server configuration") versionCmd := fmt.Sprintf(format, "version", "Show mieru server version") + checkUpdateCmd := fmt.Sprintf(format, "check update", "Check mieru server update") log.Infof("Usage: %s []", binaryName) log.Infof("") log.Infof("Commands:") @@ -176,6 +185,7 @@ var serverHelpFunc = func(s []string) error { log.Infof("%s", describeConfigCmd) log.Infof("%s", deleteUserCmd) log.Infof("%s", versionCmd) + log.Infof("%s", checkUpdateCmd) return nil } @@ -277,6 +287,9 @@ var serverRunFunc = func(s []string) error { if clientDecryptionMetricGroup := metrics.GetMetricGroupByName(cipher.ClientDecryptionMetricGroupName); clientDecryptionMetricGroup != nil { clientDecryptionMetricGroup.DisableLogging() } + if httpMetricGroup := metrics.GetMetricGroupByName(http2socks.HTTPMetricGroupName); httpMetricGroup != nil { + httpMetricGroup.DisableLogging() + } // Start proxy if server config is valid. if err = appctl.ValidateFullServerConfig(config); err == nil { @@ -511,11 +524,6 @@ var serverDeleteUserFunc = func(s []string) error { return nil } -var serverVersionFunc = func(s []string) error { - log.Infof(appctl.AppVersion) - return nil -} - var serverGetThreadDumpFunc = func(s []string) error { appStatus, err := appctl.GetServerStatusWithRPC(context.Background()) if err != nil { diff --git a/pkg/cli/shared.go b/pkg/cli/shared.go new file mode 100644 index 00000000..4dfe6f15 --- /dev/null +++ b/pkg/cli/shared.go @@ -0,0 +1,37 @@ +// Copyright (C) 2023 mieru authors +// +// 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 . + +package cli + +import ( + "fmt" + + "github.com/enfein/mieru/pkg/log" + "github.com/enfein/mieru/pkg/version" +) + +var versionFunc = func(s []string) error { + log.Infof(version.AppVersion) + return nil +} + +var checkUpdateFunc = func(s []string) error { + _, msg, err := version.CheckUpdate() + if err != nil { + return fmt.Errorf("check update failed: %w", err) + } + log.Infof("%s", msg) + return nil +} diff --git a/pkg/http2socks/http2socks.go b/pkg/http2socks/http2socks.go new file mode 100644 index 00000000..df143cda --- /dev/null +++ b/pkg/http2socks/http2socks.go @@ -0,0 +1,147 @@ +// Copyright (C) 2023 mieru authors +// +// 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 . + +package http2socks + +import ( + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/enfein/mieru/pkg/log" + "github.com/enfein/mieru/pkg/metrics" + "github.com/enfein/mieru/pkg/netutil" + "github.com/enfein/mieru/pkg/socks5client" +) + +var ( + HTTPMetricGroupName = "HTTP proxy" + + HTTPRequests = metrics.RegisterMetric(HTTPMetricGroupName, "Requests") + HTTPConnErrors = metrics.RegisterMetric(HTTPMetricGroupName, "ConnErrors") + HTTPSchemeErrors = metrics.RegisterMetric(HTTPMetricGroupName, "SchemeErrors") +) + +type Proxy struct { + ProxyURI string +} + +var ( + _ http.Handler = &Proxy{} +) + +// NewHTTPServer returns a new HTTP proxy server. +func NewHTTPServer(listenAddr string, proxy *Proxy) *http.Server { + if proxy == nil { + return nil + } + return &http.Server{ + Addr: listenAddr, + Handler: proxy, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } +} + +// ServeHTTP implements http.Handler interface with a socks5 backend. +func (p *Proxy) ServeHTTP(res http.ResponseWriter, req *http.Request) { + HTTPRequests.Add(1) + if log.IsLevelEnabled(log.TraceLevel) { + log.Tracef("received HTTP proxy request %s %s", req.Method, req.URL.String()) + } + + // Dialer to socks5 server. + dialFunc := socks5client.Dial(p.ProxyURI, socks5client.ConnectCmd) + + if req.Method == http.MethodConnect { + // HTTPS + // Hijack the HTTP connection. + hijacker, ok := res.(http.Hijacker) + if !ok { + HTTPConnErrors.Add(1) + log.Debugf("http.ResponseWriter doesn't implement http.Hijacker interface") + return + } + httpConn, _, err := hijacker.Hijack() + if err != nil { + HTTPConnErrors.Add(1) + log.Debugf("hijack HTTP connection failed: %v", err) + return + } + + // Determine the destination port number. + port := req.URL.Port() + if port == "" { + switch req.URL.Scheme { + case "http": + port = "80" + case "https": + port = "443" + default: + // Unable to determine the port number. + HTTPSchemeErrors.Add(1) + log.Debugf("unable to determine HTTP port number from %s", req.URL.Redacted()) + return + } + } + + // Dial to socks server. + socksConn, err := dialFunc("tcp", netutil.MaybeDecorateIPv6(req.URL.Hostname())+":"+port) + if err != nil { + HTTPConnErrors.Add(1) + log.Debugf("HTTP proxy dial to socks5 server failed: %v", err) + return + } + httpConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")) + netutil.BidiCopy(httpConn, socksConn, true) + } else { + // HTTP + tr := &http.Transport{ + Dial: dialFunc, + } + client := &http.Client{ + Transport: tr, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return nil + }, + } + outReq := *req + outReq.RequestURI = "" + resp, err := client.Do(&outReq) + if err != nil { + HTTPConnErrors.Add(1) + log.Debugf("send HTTP proxy request to socks5 server failed: %v", err) + return + } + defer resp.Body.Close() + io.Copy(res, resp.Body) + } +} + +// TransportProxyFunc returns the Proxy function used by http.Transport. +func TransportProxyFunc(proxy string) func(*http.Request) (*url.URL, error) { + if !strings.HasPrefix(proxy, "http://") && !strings.HasPrefix(proxy, "https://") && !strings.HasPrefix(proxy, "socks5://") { + return func(r *http.Request) (*url.URL, error) { + return nil, fmt.Errorf("unsupport proxy URL %s", proxy) + } + } + return func(r *http.Request) (*url.URL, error) { + return url.Parse(proxy) + } +} diff --git a/pkg/log/entry_test.go b/pkg/log/entry_test.go index 5f290edf..d80670ee 100644 --- a/pkg/log/entry_test.go +++ b/pkg/log/entry_test.go @@ -290,8 +290,8 @@ func TestEntryReportCallerRace(t *testing.T) { logger := New() entry := NewEntry(logger) - // logging before SetReportCaller has the highest chance of causing a race condition - // to be detected, but doing it twice just to increase the likelyhood of detecting the race + // Logging before SetReportCaller has the highest chance of causing a race condition + // to be detected, but doing it twice just to increase the likelihood of detecting the race. go func() { entry.Print("should not race") }() @@ -307,8 +307,8 @@ func TestEntryFormatterRace(t *testing.T) { logger := New() entry := NewEntry(logger) - // logging before SetReportCaller has the highest chance of causing a race condition - // to be detected, but doing it twice just to increase the likelyhood of detecting the race + // Logging before SetReportCaller has the highest chance of causing a race condition + // to be detected, but doing it twice just to increase the likelihood of detecting the race. go func() { entry.Print("should not race") }() diff --git a/pkg/netutil/conn.go b/pkg/netutil/conn.go index 5b2f7289..a667c033 100644 --- a/pkg/netutil/conn.go +++ b/pkg/netutil/conn.go @@ -15,7 +15,10 @@ package netutil -import "net" +import ( + "context" + "net" +) // WaitForClose blocks the go routine. It returns when the peer closes the connection. // In the meanwhile, everything send by the peer is discarded. @@ -28,3 +31,18 @@ func WaitForClose(conn net.Conn) { } } } + +// SendReceive sends a request to the connection and returns the response. +// The maxinum size of response is 4096 bytes. +func SendReceive(ctx context.Context, conn net.Conn, req []byte) (resp []byte, err error) { + _, err = conn.Write(req) + if err != nil { + return + } + + resp = make([]byte, 4096) + var n int + n, err = conn.Read(resp) + resp = resp[:n] + return +} diff --git a/pkg/netutil/copy.go b/pkg/netutil/copy.go new file mode 100644 index 00000000..de578243 --- /dev/null +++ b/pkg/netutil/copy.go @@ -0,0 +1,58 @@ +// Copyright (C) 2022 mieru authors +// +// 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 . + +package netutil + +import "io" + +type closeWriter interface { + CloseWrite() error +} + +// BidiCopy does bi-directional data copy. +func BidiCopy(conn1, conn2 io.ReadWriteCloser, isClient bool) error { + errCh := make(chan error, 2) + go func() { + _, err := io.Copy(conn1, conn2) + if isClient { + // Must call Close() to make sure counter is updated. + conn1.Close() + } else { + // Avoid counter updated twice due to twice Close(), use CloseWrite(). + // The connection still needs to close here to unblock the other side. + if tcpConn1, ok := conn1.(closeWriter); ok { + tcpConn1.CloseWrite() + } + } + errCh <- err + }() + go func() { + _, err := io.Copy(conn2, conn1) + if isClient { + conn2.Close() + } else { + if tcpConn2, ok := conn2.(closeWriter); ok { + tcpConn2.CloseWrite() + } + } + errCh <- err + }() + for i := 0; i < 2; i++ { + if err := <-errCh; err != nil { + return err + } + } + return nil +} diff --git a/pkg/socks5/bidi.go b/pkg/socks5/copy.go similarity index 74% rename from pkg/socks5/bidi.go rename to pkg/socks5/copy.go index 6fa84805..214b7a88 100644 --- a/pkg/socks5/bidi.go +++ b/pkg/socks5/copy.go @@ -17,53 +17,12 @@ package socks5 import ( "fmt" - "io" "net" "sync/atomic" "github.com/enfein/mieru/pkg/log" ) -type closeWriter interface { - CloseWrite() error -} - -// BidiCopy does bi-directional data copy. -func BidiCopy(conn1, conn2 io.ReadWriteCloser, isClient bool) error { - errCh := make(chan error, 2) - go func() { - _, err := io.Copy(conn1, conn2) - if isClient { - // Must call Close() to make sure counter is updated. - conn1.Close() - } else { - // Avoid counter updated twice due to twice Close(), use CloseWrite(). - // The connection still needs to close here to unblock the other side. - if tcpConn1, ok := conn1.(closeWriter); ok { - tcpConn1.CloseWrite() - } - } - errCh <- err - }() - go func() { - _, err := io.Copy(conn2, conn1) - if isClient { - conn2.Close() - } else { - if tcpConn2, ok := conn2.(closeWriter); ok { - tcpConn2.CloseWrite() - } - } - errCh <- err - }() - for i := 0; i < 2; i++ { - if err := <-errCh; err != nil { - return err - } - } - return nil -} - // BidiCopyUDP does bi-directional data copy between a proxy client UDP endpoint // and the proxy tunnel. func BidiCopyUDP(udpConn *net.UDPConn, tunnelConn *UDPAssociateTunnelConn) error { diff --git a/pkg/socks5/request.go b/pkg/socks5/request.go index 84a26904..fa4f92cb 100644 --- a/pkg/socks5/request.go +++ b/pkg/socks5/request.go @@ -179,7 +179,7 @@ func (s *Server) handleConnect(ctx context.Context, conn io.ReadWriteCloser, req return fmt.Errorf("failed to send reply: %w", err) } - return BidiCopy(conn, target, false) + return netutil.BidiCopy(conn, target, false) } // handleBind is used to handle a bind command. diff --git a/pkg/socks5/socks5.go b/pkg/socks5/socks5.go index 0670f874..376d458a 100644 --- a/pkg/socks5/socks5.go +++ b/pkg/socks5/socks5.go @@ -30,16 +30,16 @@ var ( UDPAssociateErrors = metrics.RegisterMetric("socks5", "UDPAssociateErrors") // Incoming UDP association bytes. - UDPAssociateInBytes = metrics.RegisterMetric("socks5", "UDPAssociateInBytes") + UDPAssociateInBytes = metrics.RegisterMetric("socks5 UDP associate", "InBytes") // Outgoing UDP association bytes. - UDPAssociateOutBytes = metrics.RegisterMetric("socks5", "UDPAssociateOutBytes") + UDPAssociateOutBytes = metrics.RegisterMetric("socks5 UDP associate", "OutBytes") // Incoming UDP association packets. - UDPAssociateInPkts = metrics.RegisterMetric("socks5", "UDPAssociateInPkts") + UDPAssociateInPkts = metrics.RegisterMetric("socks5 UDP associate", "InPkts") // Outgoing UDP association packets. - UDPAssociateOutPkts = metrics.RegisterMetric("socks5", "UDPAssociateOutPkts") + UDPAssociateOutPkts = metrics.RegisterMetric("socks5 UDP associate", "OutPkts") ) // ProxyConfig is used to configure mieru proxy options. @@ -270,7 +270,7 @@ func (s *Server) clientServeConn(conn net.Conn) error { }() return BidiCopyUDP(udpAssociateConn, WrapUDPAssociateTunnel(proxyConn)) } - return BidiCopy(conn, proxyConn, true) + return netutil.BidiCopy(conn, proxyConn, true) } func (s *Server) serverServeConn(conn net.Conn) error { diff --git a/pkg/socks5/udp_test.go b/pkg/socks5/udp_test.go new file mode 100644 index 00000000..36788a72 --- /dev/null +++ b/pkg/socks5/udp_test.go @@ -0,0 +1,53 @@ +// Copyright (C) 2023 mieru authors +// +// 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 . + +package socks5 + +import ( + "testing" + + "github.com/enfein/mieru/pkg/testtool" +) + +func TestUDPAssociateTunnelConn(t *testing.T) { + in, out := testtool.BufPipe() + inConn := WrapUDPAssociateTunnel(in) + outConn := WrapUDPAssociateTunnel(out) + + data := []byte{8, 9, 6, 4} + n, err := inConn.Write(data) + if err != nil { + t.Fatalf("Write() failed: %v", err) + } + if n != 4 { + t.Fatalf("Write() returns %d, want %d", n, 4) + } + + buf := make([]byte, 16) + n, err = outConn.Read(buf) + if err != nil { + t.Fatalf("Read() failed: %v", err) + } + if n != 4 { + t.Fatalf("Read() returns %d, want %d", n, 4) + } + + if err = inConn.Close(); err != nil { + t.Errorf("Close() failed: %v", err) + } + if err = outConn.Close(); err != nil { + t.Errorf("Close() failed: %v", err) + } +} diff --git a/pkg/socks5client/net.go b/pkg/socks5client/net.go deleted file mode 100644 index adfcc3fb..00000000 --- a/pkg/socks5client/net.go +++ /dev/null @@ -1,55 +0,0 @@ -package socks5client - -import ( - "bytes" - "net" - "strconv" - "time" -) - -type requestBuilder struct { - bytes.Buffer -} - -func (b *requestBuilder) add(data ...byte) { - _, _ = b.Write(data) -} - -func (c *config) sendReceive(conn net.Conn, req []byte) (resp []byte, err error) { - if c.Timeout > 0 { - if err := conn.SetWriteDeadline(time.Now().Add(c.Timeout)); err != nil { - return nil, err - } - } - _, err = conn.Write(req) - if err != nil { - return - } - resp, err = c.readAll(conn) - return -} - -func (c *config) readAll(conn net.Conn) (resp []byte, err error) { - resp = make([]byte, 1024) - if c.Timeout > 0 { - if err := conn.SetReadDeadline(time.Now().Add(c.Timeout)); err != nil { - return nil, err - } - } - n, err := conn.Read(resp) - resp = resp[:n] - return -} - -func splitHostPort(addr string) (host string, port uint16, err error) { - host, portStr, err := net.SplitHostPort(addr) - if err != nil { - return "", 0, err - } - portInt, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - return "", 0, err - } - port = uint16(portInt) - return -} diff --git a/pkg/socks5client/parse.go b/pkg/socks5client/parse.go index 56bc843e..429f35d7 100644 --- a/pkg/socks5client/parse.go +++ b/pkg/socks5client/parse.go @@ -6,35 +6,17 @@ import ( "time" ) -type ( - config struct { - Proto int - Host string - Auth *auth - Timeout time.Duration - CmdType byte - } - auth struct { - Username string - Password string - } -) - -func parse(proxyURI string) (*config, error) { +func parse(proxyURI string) (*Config, error) { uri, err := url.Parse(proxyURI) if err != nil { return nil, err } - cfg := &config{} + cfg := &Config{} switch uri.Scheme { - case "socks4": - cfg.Proto = SOCKS4 - case "socks4a": - cfg.Proto = SOCKS4A case "socks5": - cfg.Proto = SOCKS5 + // This is the only supported scheme. default: - return nil, fmt.Errorf("unknown SOCKS protocol %s", uri.Scheme) + return nil, fmt.Errorf("unsupported SOCKS protocol %s", uri.Scheme) } cfg.Host = uri.Host user := uri.User.Username() @@ -43,7 +25,7 @@ func parse(proxyURI string) (*config, error) { if user == "" || password == "" || len(user) > 255 || len(password) > 255 { return nil, fmt.Errorf("invalid user name or password") } - cfg.Auth = &auth{ + cfg.Auth = &Auth{ Username: user, Password: password, } diff --git a/pkg/socks5client/parse_test.go b/pkg/socks5client/parse_test.go index b461a756..218ff1f1 100644 --- a/pkg/socks5client/parse_test.go +++ b/pkg/socks5client/parse_test.go @@ -11,14 +11,13 @@ func TestParse(t *testing.T) { testcases := []struct { name string uri string - cfg config + cfg Config }{ { name: "full config", uri: "socks5://u1:p1@127.0.0.1:8080?timeout=2s", - cfg: config{ - Proto: SOCKS5, - Auth: &auth{ + cfg: Config{ + Auth: &Auth{ Username: "u1", Password: "p1", }, @@ -29,9 +28,8 @@ func TestParse(t *testing.T) { { name: "simple socks5", uri: "socks5://127.0.0.1:8080", - cfg: config{ - Proto: SOCKS5, - Host: "127.0.0.1:8080", + cfg: Config{ + Host: "127.0.0.1:8080", }, }, } diff --git a/pkg/socks5client/socks.go b/pkg/socks5client/socks.go index 30a7eb53..bcb20c7b 100644 --- a/pkg/socks5client/socks.go +++ b/pkg/socks5client/socks.go @@ -8,13 +8,7 @@ package socks5client import ( "fmt" "net" -) - -// Socks protocol versions. -const ( - SOCKS4 = iota - SOCKS4A - SOCKS5 + "time" ) // Socks5 command types. @@ -31,9 +25,25 @@ const ( IPv6 byte = 4 ) +type ( + // Config contains socks5 client configuration. + Config struct { + Host string + Auth *Auth + Timeout time.Duration + CmdType byte + } + + // Auth contains socks5 client username and password. + Auth struct { + Username string + Password string + } +) + // Dial returns the dial function to be used in http.Transport object. // Argument proxyURI should be in the format: "socks5://user:password@127.0.0.1:1080?timeout=5s". -// The protocol could be socks5, socks4 and socks4a. +// Only socks5 protocol is supported. func Dial(proxyURI string, cmdType byte) func(string, string) (net.Conn, error) { if cmdType != ConnectCmd && cmdType != BindCmd && cmdType != UDPAssociateCmd { return dialError(fmt.Errorf("command type %d is invalid", cmdType)) @@ -43,14 +53,25 @@ func Dial(proxyURI string, cmdType byte) func(string, string) (net.Conn, error) return dialError(err) } cfg.CmdType = cmdType - return cfg.dialFunc() + return func(_, targetAddr string) (conn net.Conn, err error) { + return cfg.dialSocks5(targetAddr) + } } -func DialSocksProxy(socksType int, proxy string, cmdType byte) func(string, string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) { - if cmdType != ConnectCmd && cmdType != BindCmd && cmdType != UDPAssociateCmd { - return dialErrorLong(fmt.Errorf("command type %d is invalid", cmdType)) +// DialSocks5Proxy returns two connections that can be used to send TCP and UDP traffic. +func DialSocks5Proxy(cfg *Config) func(string, string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) { + if cfg == nil { + return dialErrorLong(fmt.Errorf("socks5 client configuration is nil")) + } + if cfg.Host == "" { + return dialErrorLong(fmt.Errorf("socks5 client configuration has no proxy host")) + } + if cfg.CmdType != ConnectCmd && cfg.CmdType != BindCmd && cfg.CmdType != UDPAssociateCmd { + return dialErrorLong(fmt.Errorf("socks5 client configuration command type %v is invalid", cfg.CmdType)) + } + return func(_, targetAddr string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) { + return cfg.dialSocks5Long(targetAddr) } - return (&config{Proto: socksType, Host: proxy, CmdType: cmdType}).dialFuncLong() } // SendUDP sends a UDP associate message and returns the response. @@ -93,30 +114,6 @@ func SendUDP(conn *net.UDPConn, proxyAddr, dstAddr *net.UDPAddr, payload []byte) } } -func (c *config) dialFunc() func(string, string) (net.Conn, error) { - switch c.Proto { - case SOCKS5: - return func(_, targetAddr string) (conn net.Conn, err error) { - return c.dialSocks5(targetAddr) - } - case SOCKS4, SOCKS4A: - return dialError(fmt.Errorf("unsupported SOCKS protocol %v", c.Proto)) - } - return dialError(fmt.Errorf("unknown SOCKS protocol %v", c.Proto)) -} - -func (c *config) dialFuncLong() func(string, string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) { - switch c.Proto { - case SOCKS5: - return func(_, targetAddr string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) { - return c.dialSocks5Long(targetAddr) - } - case SOCKS4, SOCKS4A: - return dialErrorLong(fmt.Errorf("unsupported SOCKS protocol %v", c.Proto)) - } - return dialErrorLong(fmt.Errorf("unknown SOCKS protocol %v", c.Proto)) -} - func dialError(err error) func(string, string) (net.Conn, error) { return func(_, _ string) (net.Conn, error) { return nil, err diff --git a/pkg/socks5client/socks5.go b/pkg/socks5client/socks5.go index 008443ba..00096f68 100644 --- a/pkg/socks5client/socks5.go +++ b/pkg/socks5client/socks5.go @@ -1,18 +1,29 @@ package socks5client import ( + "bytes" + "context" "fmt" "net" + "strconv" + + "github.com/enfein/mieru/pkg/netutil" ) -func (cfg *config) dialSocks5(targetAddr string) (conn net.Conn, err error) { +func (cfg *Config) dialSocks5(targetAddr string) (conn net.Conn, err error) { conn, _, _, err = cfg.dialSocks5Long(targetAddr) return } -func (cfg *config) dialSocks5Long(targetAddr string) (conn net.Conn, udpConn *net.UDPConn, proxyUDPAddr *net.UDPAddr, err error) { - proxy := cfg.Host - conn, err = net.DialTimeout("tcp", proxy, cfg.Timeout) +func (cfg *Config) dialSocks5Long(targetAddr string) (conn net.Conn, udpConn *net.UDPConn, proxyUDPAddr *net.UDPAddr, err error) { + ctx := context.Background() + var cancelFunc context.CancelFunc + if cfg.Timeout != 0 { + ctx, cancelFunc = context.WithTimeout(ctx, cfg.Timeout) + defer cancelFunc() + } + dialer := &net.Dialer{} + conn, err = dialer.DialContext(ctx, "tcp", cfg.Host) if err != nil { return nil, nil, nil, err } @@ -22,22 +33,22 @@ func (cfg *config) dialSocks5Long(targetAddr string) (conn net.Conn, udpConn *ne } }() - var req requestBuilder + // Prepare the first request. + var req bytes.Buffer version := byte(5) // socks version 5 method := byte(0) // method 0: no authentication (only anonymous access supported for now) if cfg.Auth != nil { method = 2 // method 2: username/password } - - // version identifier/method selection request - req.add( + req.Write([]byte{ version, // socks version 1, // number of methods method, - ) + }) + // Process the first response. var resp []byte - resp, err = cfg.sendReceive(conn, req.Bytes()) + resp, err = netutil.SendReceive(ctx, conn, req.Bytes()) if err != nil { return nil, nil, nil, err } else if len(resp) != 2 { @@ -50,14 +61,15 @@ func (cfg *config) dialSocks5Long(targetAddr string) (conn net.Conn, udpConn *ne if cfg.Auth != nil { version := byte(1) // user/password version 1 req.Reset() - req.add( + req.Write([]byte{ version, // user/password version byte(len(cfg.Auth.Username)), // length of username - ) - req.add([]byte(cfg.Auth.Username)...) - req.add(byte(len(cfg.Auth.Password))) - req.add([]byte(cfg.Auth.Password)...) - resp, err = cfg.sendReceive(conn, req.Bytes()) + }) + req.Write([]byte(cfg.Auth.Username)) + req.Write([]byte{byte(len(cfg.Auth.Password))}) + req.Write([]byte(cfg.Auth.Password)) + + resp, err = netutil.SendReceive(ctx, conn, req.Bytes()) if err != nil { return nil, nil, nil, err } else if len(resp) != 2 { @@ -69,27 +81,48 @@ func (cfg *config) dialSocks5Long(targetAddr string) (conn net.Conn, udpConn *ne } } - // detail request - var host string - var port uint16 - host, port, err = splitHostPort(targetAddr) + // Prepare the second request. + host, portStr, err := net.SplitHostPort(targetAddr) + if err != nil { + return nil, nil, nil, err + } + portInt, err := strconv.ParseUint(portStr, 10, 16) if err != nil { return nil, nil, nil, err } + port := uint16(portInt) + req.Reset() - req.add( - 5, // version number - cfg.CmdType, // command - 0, // reserved, must be zero - 3, // address type, 3 means domain name - byte(len(host)), // address length - ) - req.add([]byte(host)...) - req.add( - byte(port>>8), // higher byte of destination port - byte(port), // lower byte of destination port (big endian) - ) - resp, err = cfg.sendReceive(conn, req.Bytes()) + req.Write([]byte{ + 5, // version number + cfg.CmdType, // command + 0, // reserved, must be zero + }) + + hostIP := net.ParseIP(host) + if hostIP == nil { + // Domain name. + req.Write([]byte{3, byte(len(host))}) + req.Write([]byte(host)) + } else { + hostIPv4 := hostIP.To4() + if hostIPv4 != nil { + // IPv4 + req.Write([]byte{1}) + req.Write(hostIPv4) + } else { + // IPv6 + req.Write([]byte{4}) + req.Write(hostIP) + } + } + req.Write([]byte{ + byte(port >> 8), // higher byte of destination port + byte(port), // lower byte of destination port (big endian) + }) + + // Process the second response. + resp, err = netutil.SendReceive(ctx, conn, req.Bytes()) if err != nil { return } else if len(resp) < 10 { diff --git a/pkg/version/check_update.go b/pkg/version/check_update.go new file mode 100644 index 00000000..1200ad90 --- /dev/null +++ b/pkg/version/check_update.go @@ -0,0 +1,74 @@ +// Copyright (C) 2023 mieru authors +// +// 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 . + +package version + +import ( + "encoding/json" + "fmt" + "io" + "net/http" +) + +// CheckUpdate fetches the latest mieru / mita version using GitHub API +// and check if a new release is available. +func CheckUpdate() (hasUpdate bool, msg string, err error) { + var remoteTag string + remoteTag, err = queryLatestVersion() + if err != nil { + return false, "", fmt.Errorf("queryLatestVersion() failed: %w", err) + } + + var currentVersion Version + var remoteVersion Version + currentVersion, err = Parse(AppVersion) + if err != nil { + return false, "", fmt.Errorf("Parse() failed: %w", err) + } + remoteVersion, err = ParseTag(remoteTag) + if err != nil { + return false, "", fmt.Errorf("ParseTag() failed: %w", err) + } + + if currentVersion.LessThan(remoteVersion) { + return true, fmt.Sprintf("update is available at %s", "https://github.com/enfein/mieru/releases/tag/"+remoteTag), nil + } else { + return false, "already up to date", nil + } +} + +func queryLatestVersion() (string, error) { + resp, err := http.Get("https://api.github.com/repos/enfein/mieru/releases/latest") + if err != nil { + return "", fmt.Errorf("http.Get() failed: %v [GitHub may be blocked in your network]", err) + } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP returned expected status code %d", resp.StatusCode) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("io.ReadAll() failed: %v", err) + } + var release latestRelease + if err := json.Unmarshal(body, &release); err != nil { + return "", fmt.Errorf("json.Unmarshal() failed: %v", err) + } + return release.TagName, nil +} + +type latestRelease struct { + TagName string `json:"tag_name"` +} diff --git a/pkg/appctl/version.go b/pkg/version/current.go similarity index 89% rename from pkg/appctl/version.go rename to pkg/version/current.go index 2d86a187..f1ddf659 100644 --- a/pkg/appctl/version.go +++ b/pkg/version/current.go @@ -1,4 +1,4 @@ -// Copyright (C) 2022 mieru authors +// Copyright (C) 2023 mieru authors // // 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 @@ -13,8 +13,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package appctl +package version const ( - AppVersion = "1.10.0" + AppVersion = "1.11.0" ) diff --git a/pkg/version/scheme.go b/pkg/version/scheme.go new file mode 100644 index 00000000..cdce3284 --- /dev/null +++ b/pkg/version/scheme.go @@ -0,0 +1,98 @@ +// Copyright (C) 2023 mieru authors +// +// 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 . + +package version + +import ( + "fmt" + "regexp" + "strconv" +) + +var ( + versionFmt = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)$`) + tagFmt = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)$`) +) + +// Version defines the components of version number. +type Version struct { + Major int + Minor int + Patch int +} + +// String returns the string representation of version number, e.g. "1.0.0". +func (v Version) String() string { + return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) +} + +// ToTag returns the associated tag name of the version, e.g. "v1.0.0". +func (v Version) ToTag() string { + return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) +} + +// LessThan returns true if the current version is less than the compared version. +func (v Version) LessThan(another Version) bool { + if v.Major > another.Major { + return false + } + if v.Major < another.Major { + return true + } + + if v.Minor > another.Minor { + return false + } + if v.Minor < another.Minor { + return true + } + + if v.Patch >= another.Patch { + return false + } + return true +} + +// Parse constructs the version from a string. +func Parse(s string) (Version, error) { + matches := versionFmt.FindStringSubmatch(s) + if len(matches) != 4 { + return Version{}, fmt.Errorf("failed to parse version %s", s) + } + major, _ := strconv.Atoi(matches[1]) + minor, _ := strconv.Atoi(matches[2]) + patch, _ := strconv.Atoi(matches[3]) + return Version{ + Major: major, + Minor: minor, + Patch: patch, + }, nil +} + +// ParseTag constructs the version from a tag name. +func ParseTag(tag string) (Version, error) { + matches := tagFmt.FindStringSubmatch(tag) + if len(matches) != 4 { + return Version{}, fmt.Errorf("failed to parse tag %s", tag) + } + major, _ := strconv.Atoi(matches[1]) + minor, _ := strconv.Atoi(matches[2]) + patch, _ := strconv.Atoi(matches[3]) + return Version{ + Major: major, + Minor: minor, + Patch: patch, + }, nil +} diff --git a/pkg/version/scheme_test.go b/pkg/version/scheme_test.go new file mode 100644 index 00000000..a20e9249 --- /dev/null +++ b/pkg/version/scheme_test.go @@ -0,0 +1,90 @@ +// Copyright (C) 2023 mieru authors +// +// 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 . + +package version + +import "testing" + +func TestAppVersion(t *testing.T) { + v, err := Parse(AppVersion) + if err != nil { + t.Fatalf("Parse() failed: %v", err) + } + str := v.String() + if str != AppVersion { + t.Errorf("version string = %s, want %s", str, AppVersion) + } + + tag := "v" + AppVersion + v, err = ParseTag(tag) + if err != nil { + t.Fatalf("ParseTag() failed: %v", err) + } + tagStr := v.ToTag() + if tagStr != tag { + t.Errorf("tag = %s, want %s", tagStr, tag) + } +} + +func TestLessThan(t *testing.T) { + testcases := []struct { + my Version + another Version + isLess bool + }{ + { + Version{1, 2, 3}, + Version{1, 2, 3}, + false, + }, + { + Version{1, 2, 3}, + Version{2, 3, 4}, + true, + }, + { + Version{2, 3, 4}, + Version{1, 2, 3}, + false, + }, + { + Version{1, 2, 3}, + Version{1, 3, 2}, + true, + }, + { + Version{1, 3, 2}, + Version{1, 2, 3}, + false, + }, + { + Version{1, 2, 3}, + Version{1, 2, 4}, + true, + }, + { + Version{1, 2, 4}, + Version{1, 2, 3}, + false, + }, + } + + for _, tc := range testcases { + res := tc.my.LessThan(tc.another) + if res != tc.isLess { + t.Errorf("%s less than %s got %v, want %v", tc.my.String(), tc.another.String(), res, tc.isLess) + } + } +} diff --git a/test/cmd/sockshttpclient/sockshttpclient.go b/test/cmd/sockshttpclient/sockshttpclient.go index 2efeb943..c75cb7fa 100644 --- a/test/cmd/sockshttpclient/sockshttpclient.go +++ b/test/cmd/sockshttpclient/sockshttpclient.go @@ -31,6 +31,7 @@ import ( "sync/atomic" "time" + "github.com/enfein/mieru/pkg/http2socks" "github.com/enfein/mieru/pkg/log" "github.com/enfein/mieru/pkg/socks5client" ) @@ -38,14 +39,20 @@ import ( const ( NewConnTest = "new_conn" ReuseConnTest = "reuse_conn" + + Socks5ProxyMode = "socks5" + HTTPProxyMode = "http" + NoProxyMode = "no" ) var ( + proxyMode = flag.String("proxy_mode", Socks5ProxyMode, "Proxy mode. Options: http, socks5, no.") dstHost = flag.String("dst_host", "", "The host IP or domain name that HTTP server is running.") dstPort = flag.Int("dst_port", 0, "The TCP port that HTTP server is listening.") localProxyHost = flag.String("local_proxy_host", "", "The host IP or domain name that local socks proxy is running.") localProxyPort = flag.Int("local_proxy_port", 0, "The TCP port that local socks proxy is listening.") - noProxy = flag.Bool("no_proxy", false, "Don't use proxy. Directly connect to the destination.") + localHTTPHost = flag.String("local_http_host", "", "The host IP or domain name that local HTTP proxy is running.") + localHTTPPort = flag.Int("local_http_port", 0, "The TCP port that local HTTP proxy is listening.") testCase = flag.String("test_case", "new_conn", fmt.Sprintf("Supported: %q, %q.", NewConnTest, ReuseConnTest)) intervalMs = flag.Int("interval_ms", 0, "Sleep in milliseconds between two requests.") numRequest = flag.Int("num_request", 0, "Number of HTTP requests send to server before exit. This option is not compatible with -test_time_sec.") @@ -56,26 +63,6 @@ var ( startTime time.Time ) -type countedConn struct { - net.Conn -} - -func (c countedConn) Read(b []byte) (n int, err error) { - n, err = c.Conn.Read(b) - atomic.AddInt64(&totalBytes, int64(n)) - return -} - -func (c countedConn) Write(b []byte) (n int, err error) { - n, err = c.Conn.Write(b) - atomic.AddInt64(&totalBytes, int64(n)) - return -} - -func wrapConn(conn net.Conn) net.Conn { - return countedConn{Conn: conn} -} - func init() { log.SetFormatter(&log.DaemonFormatter{}) } @@ -85,9 +72,15 @@ func main() { if *dstHost == "" || *dstPort == 0 { log.Fatalf("HTTP server host or port is not set") } - if *localProxyHost == "" || *localProxyPort == 0 { + if *proxyMode != Socks5ProxyMode && *proxyMode != HTTPProxyMode && *proxyMode != NoProxyMode { + log.Fatalf("Proxy mode %q is invalid", *proxyMode) + } + if *proxyMode == Socks5ProxyMode && (*localProxyHost == "" || *localProxyPort == 0) { log.Fatalf("Local socks proxy host or port is not set") } + if *proxyMode == HTTPProxyMode && (*localHTTPHost == "" || *localHTTPPort == 0) { + log.Fatalf("Local HTTP proxy host or port is not set") + } if *testCase != NewConnTest && *testCase != ReuseConnTest { log.Fatalf("Test case %q is unknown", *testCase) } @@ -105,7 +98,7 @@ func main() { if *testCase == NewConnTest { if *numRequest > 0 { for i := 1; i <= *numRequest; i++ { - CreateNewConnAndDoRequest(i, *noProxy) + CreateNewConnAndDoRequest(i, *proxyMode) if *printSpeed > 0 && i%*printSpeed == 0 { printNetworkSpeed(i) } @@ -123,7 +116,7 @@ func main() { case <-done: return default: - CreateNewConnAndDoRequest(i, *noProxy) + CreateNewConnAndDoRequest(i, *proxyMode) if *printSpeed > 0 && i%*printSpeed == 0 { printNetworkSpeed(i) } @@ -134,13 +127,27 @@ func main() { } } else if *testCase == ReuseConnTest { var conn net.Conn + var client *http.Client var err error for { - if *noProxy { - conn, err = net.Dial("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) - } else { - socksDialer := socks5client.DialSocksProxy(socks5client.SOCKS5, *localProxyHost+":"+strconv.Itoa(*localProxyPort), socks5client.ConnectCmd) + if *proxyMode == Socks5ProxyMode { + socksDialer := socks5client.DialSocks5Proxy(&socks5client.Config{ + Host: *localProxyHost + ":" + strconv.Itoa(*localProxyPort), + CmdType: socks5client.ConnectCmd, + }) conn, _, _, err = socksDialer("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) + } else if *proxyMode == HTTPProxyMode { + tr := &http.Transport{ + Proxy: http2socks.TransportProxyFunc("http://" + *localHTTPHost + ":" + strconv.Itoa(*localHTTPPort)), + } + client = &http.Client{ + Transport: tr, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return nil + }, + } + } else if *proxyMode == NoProxyMode { + conn, err = net.Dial("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) } if err == nil { break @@ -149,10 +156,18 @@ func main() { log.Fatalf("dial failed: %v", err) } } - conn = wrapConn(conn) + if client != nil { + defer client.CloseIdleConnections() + } else { + defer conn.Close() + } if *numRequest > 0 { for i := 1; i <= *numRequest; i++ { - DoRequestWithExistingConn(conn, i) + if client != nil { + DoRequestWithExistingHTTPClient(client, i) + } else { + DoRequestWithExistingConn(conn, i) + } if *printSpeed > 0 && i%*printSpeed == 0 { printNetworkSpeed(i) } @@ -170,7 +185,11 @@ func main() { case <-done: return default: - DoRequestWithExistingConn(conn, i) + if client != nil { + DoRequestWithExistingHTTPClient(client, i) + } else { + DoRequestWithExistingConn(conn, i) + } if *printSpeed > 0 && i%*printSpeed == 0 { printNetworkSpeed(i) } @@ -179,19 +198,32 @@ func main() { } } } - conn.Close() } } -func CreateNewConnAndDoRequest(seq int, noProxy bool) { +func CreateNewConnAndDoRequest(seq int, proxyMode string) { var conn net.Conn + var client *http.Client var err error for { - if noProxy { - conn, err = net.Dial("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) - } else { - socksDialer := socks5client.DialSocksProxy(socks5client.SOCKS5, *localProxyHost+":"+strconv.Itoa(*localProxyPort), socks5client.ConnectCmd) + if proxyMode == Socks5ProxyMode { + socksDialer := socks5client.DialSocks5Proxy(&socks5client.Config{ + Host: *localProxyHost + ":" + strconv.Itoa(*localProxyPort), + CmdType: socks5client.ConnectCmd, + }) conn, _, _, err = socksDialer("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) + } else if proxyMode == HTTPProxyMode { + tr := &http.Transport{ + Proxy: http2socks.TransportProxyFunc("http://" + *localHTTPHost + ":" + strconv.Itoa(*localHTTPPort)), + } + client = &http.Client{ + Transport: tr, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return nil + }, + } + } else if proxyMode == NoProxyMode { + conn, err = net.Dial("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) } if err == nil { break @@ -200,8 +232,13 @@ func CreateNewConnAndDoRequest(seq int, noProxy bool) { log.Fatalf("dial failed: %v", err) } } - defer conn.Close() - DoRequestWithExistingConn(wrapConn(conn), seq) + if client != nil { + defer client.CloseIdleConnections() + DoRequestWithExistingHTTPClient(client, seq) + } else { + defer conn.Close() + DoRequestWithExistingConn(conn, seq) + } } func DoRequestWithExistingConn(conn net.Conn, seq int) { @@ -220,16 +257,33 @@ func DoRequestWithExistingConn(conn net.Conn, seq int) { if err != nil { log.Fatalf("error connect to server %s:%d %v", *dstHost, *dstPort, err) } - if resp.StatusCode != http.StatusOK { - log.Fatalf("server responded status %s", resp.Status) + checkResponse(resp, seq) +} + +func DoRequestWithExistingHTTPClient(client *http.Client, seq int) { + req, err := http.NewRequest(http.MethodGet, "", nil) + if err != nil { + log.Fatalf("error create HTTP request: %v", err) + } + req.URL.Scheme = "http" + req.URL.Host = *dstHost + ":" + strconv.Itoa(*dstPort) + resp, err := client.Do(req) + if err != nil { + log.Fatalf("do HTTP request failed: %v", err) } + checkResponse(resp, seq) +} - providedCheckSum := resp.Header.Get("X-SHA1") - if len(providedCheckSum) == 0 { - log.Fatalf("server bug: SHA-1 check is not set") +func checkResponse(resp *http.Response, seq int) { + if resp == nil { + log.Fatalf("HTTP response is nil") + } + if resp.StatusCode != http.StatusOK { + log.Fatalf("server responded status %s", resp.Status) } body, err := io.ReadAll(resp.Body) + atomic.AddInt64(&totalBytes, int64(len(body))) defer resp.Body.Close() if err != nil { log.Fatalf("failed to read HTTP response: %v", err) @@ -240,14 +294,15 @@ func DoRequestWithExistingConn(conn net.Conn, seq int) { computedCheckSum := hex.EncodeToString(computedCheckSumArr[:]) log.Debugf("HTTP client computed SHA-1 checksum: %s", computedCheckSum) - if providedCheckSum != computedCheckSum { + providedCheckSum := resp.Header.Get("X-SHA1") + if len(providedCheckSum) != 0 && providedCheckSum != computedCheckSum { log.Fatalf("SHA-1 checksum not match. Provided by server: %s, computed with response data: %s", providedCheckSum, computedCheckSum) } } func printNetworkSpeed(seq int) { - sec := int(time.Now().Sub(startTime) / time.Second) + sec := int(time.Since(startTime) / time.Second) if sec <= 0 { sec = 1 } diff --git a/test/cmd/socksudpclient/socksudpclient.go b/test/cmd/socksudpclient/socksudpclient.go index 2a694cb1..ac01160d 100644 --- a/test/cmd/socksudpclient/socksudpclient.go +++ b/test/cmd/socksudpclient/socksudpclient.go @@ -81,7 +81,10 @@ func main() { } func CreateNewConnAndDoRequest(nRequest int, dstAddr *net.UDPAddr) { - socksDialer := socks5client.DialSocksProxy(socks5client.SOCKS5, *localProxyHost+":"+strconv.Itoa(*localProxyPort), socks5client.UDPAssociateCmd) + socksDialer := socks5client.DialSocks5Proxy(&socks5client.Config{ + Host: *localProxyHost + ":" + strconv.Itoa(*localProxyPort), + CmdType: socks5client.UDPAssociateCmd, + }) ctrlConn, udpConn, proxyAddr, err := socksDialer("tcp", *dstHost+":"+strconv.Itoa(*dstPort)) if err != nil { log.Fatalf("dial to socks: %v", err) diff --git a/test/deploy/httptest/client_tcp.json b/test/deploy/httptest/client_tcp.json index 1be8b35f..57d13791 100644 --- a/test/deploy/httptest/client_tcp.json +++ b/test/deploy/httptest/client_tcp.json @@ -35,5 +35,7 @@ "rpcPort": 8989, "socks5Port": 1080, "loggingLevel": "INFO", - "socks5ListenLAN": true + "socks5ListenLAN": true, + "httpProxyPort": 8808, + "httpProxyListenLAN": true } diff --git a/test/deploy/httptest/client_udp.json b/test/deploy/httptest/client_udp.json index 4189cade..cc83394c 100644 --- a/test/deploy/httptest/client_udp.json +++ b/test/deploy/httptest/client_udp.json @@ -36,5 +36,7 @@ "rpcPort": 8989, "socks5Port": 1080, "loggingLevel": "INFO", - "socks5ListenLAN": true + "socks5ListenLAN": true, + "httpProxyPort": 8808, + "httpProxyListenLAN": true } diff --git a/test/deploy/httptest/test_tcp.sh b/test/deploy/httptest/test_tcp.sh index 0805d308..06853143 100755 --- a/test/deploy/httptest/test_tcp.sh +++ b/test/deploy/httptest/test_tcp.sh @@ -57,6 +57,7 @@ fi # Start testing. sleep 2 +echo ">>> socks5 - new connections - TCP <<<" ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ -local_proxy_host=127.0.0.1 -local_proxy_port=1080 \ -test_case=new_conn -num_request=3000 @@ -69,9 +70,23 @@ if [ "$?" -ne "0" ]; then fi sleep 1 +echo ">>> http - new connections - TCP <<<" +./sockshttpclient -proxy_mode=http -dst_host=127.0.0.1 -dst_port=8080 \ + -local_http_host=127.0.0.1 -local_http_port=8808 \ + -test_case=new_conn -num_request=3000 +if [ "$?" -ne "0" ]; then + print_mieru_client_log + print_mieru_client_thread_dump + print_mieru_server_thread_dump + echo "Test new_conn failed." + exit 1 +fi + +sleep 1 +echo ">>> socks5 - reuse one connection - TCP <<<" ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ -local_proxy_host=127.0.0.1 -local_proxy_port=1080 \ - -test_case=reuse_conn -test_time_sec=60 + -test_case=reuse_conn -test_time_sec=30 if [ "$?" -ne "0" ]; then print_mieru_client_log print_mieru_client_thread_dump diff --git a/test/deploy/httptest/test_udp.sh b/test/deploy/httptest/test_udp.sh index 3459f20d..f560b8b0 100755 --- a/test/deploy/httptest/test_udp.sh +++ b/test/deploy/httptest/test_udp.sh @@ -57,6 +57,7 @@ fi # Start testing. sleep 2 +echo ">>> socks5 - new connections - UDP <<<" ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ -local_proxy_host=127.0.0.1 -local_proxy_port=1080 \ -test_case=new_conn -num_request=3000 @@ -69,9 +70,23 @@ if [ "$?" -ne "0" ]; then fi sleep 1 +echo ">>> http - new connections - UDP <<<" +./sockshttpclient -proxy_mode=http -dst_host=127.0.0.1 -dst_port=8080 \ + -local_http_host=127.0.0.1 -local_http_port=8808 \ + -test_case=new_conn -num_request=3000 +if [ "$?" -ne "0" ]; then + print_mieru_client_log + print_mieru_client_thread_dump + print_mieru_server_thread_dump + echo "Test new_conn failed." + exit 1 +fi + +sleep 1 +echo ">>> socks5 - reuse one connection - UDP <<<" ./sockshttpclient -dst_host=127.0.0.1 -dst_port=8080 \ -local_proxy_host=127.0.0.1 -local_proxy_port=1080 \ - -test_case=reuse_conn -test_time_sec=60 + -test_case=reuse_conn -test_time_sec=30 if [ "$?" -ne "0" ]; then print_mieru_client_log print_mieru_client_thread_dump diff --git a/tools/bump_version.sh b/tools/bump_version.sh index 895b3ff1..771cdfb9 100755 --- a/tools/bump_version.sh +++ b/tools/bump_version.sh @@ -41,4 +41,4 @@ sed -i "s/$1/$2/g" build/package/mita/arm64/rpm/mita.spec sed -i "s/$1/$2/g" docs/server-install.md sed -i "s/$1/$2/g" docs/server-install.zh_CN.md sed -i "s/$1/$2/g" Makefile -sed -i "s/$1/$2/g" pkg/appctl/version.go +sed -i "s/$1/$2/g" pkg/version/current.go