From 52c360eb49821d106b212d91e975c276d03a42ae Mon Sep 17 00:00:00 2001 From: zeaslity Date: Fri, 1 Mar 2024 09:31:37 +0800 Subject: [PATCH] [ Cmii ] [ Octopus ] - add all image tag back up; add socks5 proxy --- cmii_operator/CmiiK8sConfig.go | 84 +--- cmii_operator/CmiiK8sOperator.go | 68 +++ cmii_operator/CmiiK8sOperator_test.go | 20 +- cmii_operator/CmiiStatus.go | 34 ++ cmii_operator/K8sOperator.go | 17 + cmii_operator/log/cmii-update-log.txt | 1 + socks5_txthinking/.gitignore | 28 ++ socks5_txthinking/LICENSE | 21 + socks5_txthinking/README.md | 103 ++++ socks5_txthinking/README_ZH.md | 102 ++++ socks5_txthinking/bind.go | 11 + socks5_txthinking/client.go | 254 ++++++++++ socks5_txthinking/client_side.go | 172 +++++++ socks5_txthinking/connect.go | 53 +++ socks5_txthinking/example_test.go | 87 ++++ socks5_txthinking/go.mod | 8 + socks5_txthinking/go.sum | 41 ++ socks5_txthinking/init.go | 52 +++ socks5_txthinking/main.go | 5 + socks5_txthinking/old_tcp_tailscale/logger.go | 211 +++++++++ socks5_txthinking/old_tcp_tailscale/main.go | 42 ++ socks5_txthinking/old_tcp_tailscale/socks5.go | 417 +++++++++++++++++ socks5_txthinking/runnergrounp.go | 96 ++++ socks5_txthinking/server.go | 438 ++++++++++++++++++ socks5_txthinking/server_side.go | 282 +++++++++++ socks5_txthinking/socks5.go | 119 +++++ socks5_txthinking/udp.go | 59 +++ socks5_txthinking/util.go | 135 ++++++ socks5_txthinking/util_test.go | 9 + 29 files changed, 2886 insertions(+), 83 deletions(-) create mode 100644 socks5_txthinking/.gitignore create mode 100644 socks5_txthinking/LICENSE create mode 100644 socks5_txthinking/README.md create mode 100644 socks5_txthinking/README_ZH.md create mode 100644 socks5_txthinking/bind.go create mode 100644 socks5_txthinking/client.go create mode 100644 socks5_txthinking/client_side.go create mode 100644 socks5_txthinking/connect.go create mode 100644 socks5_txthinking/example_test.go create mode 100644 socks5_txthinking/go.mod create mode 100644 socks5_txthinking/go.sum create mode 100644 socks5_txthinking/init.go create mode 100644 socks5_txthinking/main.go create mode 100644 socks5_txthinking/old_tcp_tailscale/logger.go create mode 100644 socks5_txthinking/old_tcp_tailscale/main.go create mode 100644 socks5_txthinking/old_tcp_tailscale/socks5.go create mode 100644 socks5_txthinking/runnergrounp.go create mode 100644 socks5_txthinking/server.go create mode 100644 socks5_txthinking/server_side.go create mode 100644 socks5_txthinking/socks5.go create mode 100644 socks5_txthinking/udp.go create mode 100644 socks5_txthinking/util.go create mode 100644 socks5_txthinking/util_test.go diff --git a/cmii_operator/CmiiK8sConfig.go b/cmii_operator/CmiiK8sConfig.go index 23260f0..7c31143 100644 --- a/cmii_operator/CmiiK8sConfig.go +++ b/cmii_operator/CmiiK8sConfig.go @@ -1,28 +1,5 @@ package cmii_operator -var CmiiFrontendAppName = []string{ - "cmii-uav-platform", - "cmii-uav-platform-oms", - "cmii-uav-platform-mws", - "cmii-uav-platform-cms-portal", - "cmii-uav-platform-armypeople", - "cmii-uav-platform-share", - "cmii-suav-platform-supervisionh5", - "cmii-suav-platform-supervision", - "cmii-uav-platform-ai-brain", - "cmii-uav-platform-base", - "cmii-uav-platform-detection", - "cmii-uav-platform-emergency-rescue", - "cmii-uav-platform-logistics", - "cmii-uav-platform-media", - "cmii-uav-platform-open", - "cmii-uav-platform-security", - "cmii-uav-platform-securityh5", - "cmii-uav-platform-seniclive", - "cmii-uav-platform-threedsimulation", - "cmii-uav-platform-visualization", -} - var CmiiBackendAppMap = map[string]string{ "cmii-admin-data": "5.2.0", "cmii-admin-gateway": "5.2.0", @@ -43,10 +20,6 @@ var CmiiBackendAppMap = map[string]string{ "cmii-uav-device": "5.2.0", "cmii-uav-emergency": "5.2.0", "cmii-uav-gateway": "5.2.0", - "cmii-uav-gis-server": "5.2.0", - "cmii-uav-grid-datasource": "5.2.0-24810", - "cmii-uav-grid-engine": "5.1.0", - "cmii-uav-grid-manage": "5.1.0", "cmii-uav-industrial-portfolio": "5.2.0-25268-10", "cmii-uav-integration": "5.2.0-25447", "cmii-uav-kpi-monitor": "5.2.0", @@ -101,56 +74,17 @@ var CmiiMiddlewareNameMap = map[string]string{ "helm-rabbitmq": "single", } -var CmiiBackendAppName = []string{ - "cmii-uav-gateway", - "cmii-uav-oauth", - "cmii-uav-user", - "cmii-uav-material-warehouse", - "cmii-uav-device", - "cmii-uav-mission", - "cmii-uav-mqtthandler", - "cmii-uav-surveillance", - "cmii-uav-waypoint", - "cmii-uav-airspace", - "cmii-uav-industrial-portfolio", - "cmii-uav-integration", - "cmii-admin-data", - "cmii-admin-gateway", - "cmii-admin-user", - "cmii-uav-cloud-live", - "cmii-uav-emergency", - "cmii-uav-cms", - "cmii-open-gateway", - "cmii-uav-cloud-live", - "cmii-uav-alarm", - "cmii-uav-brain", - "cmii-app-release", - "cmii-uav-notice", - "cmii-suav-supervision", - "cmii-uav-autowaypoint", - "cmii-uav-data-post-process", - "cmii-uav-depotautoreturn", - "cmii-uav-developer", - "cmii-uav-kpi-monitor", - "cmii-uav-logger", - "cmii-uav-process", - "cmii-uav-threedsimulation", - "cmii-uav-tower", +var CmiiSrsAppMap = map[string]string{ + "helm-live-op-v2": "deployment", + "helm-live-rtsp-op": "4.1.6", + "helm-live-srs-rtc": "statefulset", } -var CmiiStreamAppName = []string{ - "cmii-uav-cloud-live", - "helm-live-op-v2", - "helm-live-rtsp-op", - "helm-live-rtsp-zlm", - "helm-vms-deploy", -} - -var CmiiGISAppName = []string{ - "cmii-uav-gis-server", - "cmii-uav-grid-datasource", - "cmii-uav-grid-engine", - "cmii-uav-grid-manage", +var CmiiGISAppMap = map[string]string{ + "cmii-uav-gis-server": "5.4.0", + "cmii-uav-grid-datasource": "5.4.0", + "cmii-uav-grid-engine": "5.4.0", + "cmii-uav-grid-manage": "5.4.0", } var CmiiDevK8sConfig = `apiVersion: v1 clusters: diff --git a/cmii_operator/CmiiK8sOperator.go b/cmii_operator/CmiiK8sOperator.go index cbef604..e592914 100644 --- a/cmii_operator/CmiiK8sOperator.go +++ b/cmii_operator/CmiiK8sOperator.go @@ -426,6 +426,74 @@ func BackupAllCmiiDeploymentToMap(cmiiEnv string) (backendMap, frontendMap map[s return backendMap, frontendMap } +func BackUpAllCmiiAppImageNameFromEnv(cmiiEnv string) { + + CmiiOperator.changeOperatorEnv(cmiiEnv) + filePath := "C:\\Users\\wddsh\\Documents\\IdeaProjects\\ProjectOctopus\\cmii_operator\\log\\images-" + CmiiOperator.CurrentNamespace + "-" + utils.TimeSplitFormatString() + ".txt" + + only := make(map[string]string, 150) + // front + executor.BasicAppendContentToFile("---\n", filePath) + for key, value := range CmiiFrontendAppMap { + _, ok := only[key] + if !ok { + deploy := CmiiOperator.DeploymentOneInterface(cmiiEnv, key) + if deploy != nil { + only[key] = value + executor.BasicAppendContentToFile(deploy.Image+"\n", filePath) + } + } + } + executor.BasicAppendContentToFile("---\n", filePath) + for key, value := range CmiiBackendAppMap { + _, ok := only[key] + if !ok { + deploy := CmiiOperator.DeploymentOneInterface(cmiiEnv, key) + if deploy != nil { + only[key] = value + executor.BasicAppendContentToFile(deploy.Image+"\n", filePath) + } + } + } + // backend + executor.BasicAppendContentToFile("---\n", filePath) + // gis server + for key, value := range CmiiGISAppMap { + _, ok := only[key] + if !ok { + deploy := CmiiOperator.DeploymentOneInterface(cmiiEnv, key) + if deploy != nil { + only[key] = value + executor.BasicAppendContentToFile(deploy.Image+"\n", filePath) + } + } + } + // srs + executor.BasicAppendContentToFile("---\n", filePath) + for key, value := range CmiiSrsAppMap { + _, ok := only[key] + if !ok { + var app *CmiiDeploymentInterface + if strings.Contains(value, "deployment") { + app = CmiiOperator.DeploymentOneInterface(cmiiEnv, key) + if app != nil { + only[key] = value + executor.BasicAppendContentToFile(app.Image+"\n", filePath) + } + } else if strings.Contains(value, "state") { + app = CmiiOperator.StatefulSetOneInterface(cmiiEnv, key) + if app != nil { + only[key] = value + for _, image := range app.ContainerImageMap { + executor.BasicAppendContentToFile(image+"\n", filePath) + } + } + } + } + } + executor.BasicAppendContentToFile("---\n", filePath) +} + func FilterAllCmiiAppStrict(source []CmiiDeploymentInterface) (result []CmiiDeploymentInterface) { for _, c := range source { diff --git a/cmii_operator/CmiiK8sOperator_test.go b/cmii_operator/CmiiK8sOperator_test.go index fb5cfff..38d8711 100644 --- a/cmii_operator/CmiiK8sOperator_test.go +++ b/cmii_operator/CmiiK8sOperator_test.go @@ -50,12 +50,6 @@ func TestFindCmiiMiddlewarePodInterface(t *testing.T) { } } -func TestBackupAllDeploymentFromEnv(t *testing.T) { - - BackupAllDeploymentFromEnv(devFlight) - -} - func TestBackupAllCmiiDeploymentToMap(t *testing.T) { backendMap, frontendMap := BackupAllCmiiDeploymentToMap(demo) @@ -226,11 +220,21 @@ func TestUpdateCmiiImageTagFromNameTagMap(t *testing.T) { } +func TestBackupAllDeploymentFromEnv(t *testing.T) { + + BackupAllDeploymentFromEnv(demo) + +} + +func TestBackUpAllCmiiAppImageNameFromEnv(t *testing.T) { + BackUpAllCmiiAppImageNameFromEnv(demo) +} + func TestUpdateCmiiDeploymentImageTag(t *testing.T) { - cmiiEnv := devFlight + cmiiEnv := demo appName := "cmii-uav-device" - newTag := "5.4.0-26906-01" + newTag := "5.4.0-26905" tag := UpdateCmiiDeploymentImageTag(cmiiEnv, appName, newTag) assert.Equal(t, tag, true, "update image tag failed !") diff --git a/cmii_operator/CmiiStatus.go b/cmii_operator/CmiiStatus.go index b0456b1..60d8619 100644 --- a/cmii_operator/CmiiStatus.go +++ b/cmii_operator/CmiiStatus.go @@ -99,6 +99,40 @@ func (deploy CmiiDeploymentInterface) Convert(deployment v1.Deployment) CmiiDepl return deploy } +func (deploy CmiiDeploymentInterface) ConvertFromStatefulSet(statefulSet v1.StatefulSet) CmiiDeploymentInterface { + containers := statefulSet.Spec.Template.Spec.Containers + + containerImageMap := make(map[string]string, len(containers)) + if len(containers) > 1 { + log.WarnF("[CmiiDeploymentInterface ConvertFromStatefulSet] - statefulSet [%s] [%s] container greater than one !", statefulSet.Namespace, statefulSet.Name) + } + for _, container := range containers { + containerImageMap[container.Name] = container.Image + deploy.Image = container.Image + deploy.ContainerName = container.Name + deploy.ImageTag = strings.Split(container.Image, ":")[1] + + for _, envVar := range container.Env { + if strings.HasPrefix(envVar.Name, "GIT_BRANCH") { + deploy.GitBranch = envVar.Value + } + + if strings.HasPrefix(envVar.Name, "GIT_COMMIT") { + deploy.GitCommit = envVar.Value + } + } + } + + deploy.Name = statefulSet.Name + deploy.Namespace = statefulSet.Namespace + deploy.AvailableReplicas = statefulSet.Status.AvailableReplicas + deploy.Replicas = *statefulSet.Spec.Replicas + deploy.ContainerImageMap = containerImageMap + deploy.StatusOk = statefulSet.Status.AvailableReplicas == *statefulSet.Spec.Replicas + + return deploy +} + func (pod CmiiPodInterface) Convert(podDetail corev1.Pod) CmiiPodInterface { containers := podDetail.Spec.Containers diff --git a/cmii_operator/K8sOperator.go b/cmii_operator/K8sOperator.go index e82796c..191b207 100644 --- a/cmii_operator/K8sOperator.go +++ b/cmii_operator/K8sOperator.go @@ -330,6 +330,23 @@ func (op *CmiiK8sOperator) DeploymentOneInterface(cmiiEnv, appName string) (depl return &convert } +func (op *CmiiK8sOperator) StatefulSetOneInterface(cmiiEnv, appName string) (deploy *CmiiDeploymentInterface) { + + op.changeOperatorEnv(cmiiEnv) + client := op.CurrentClient + deploymentInterface := CmiiDeploymentInterface{} + + statefulSet, err := client.AppsV1().StatefulSets(op.CurrentNamespace).Get(context.TODO(), appName, metav1.GetOptions{}) + if err != nil { + log.ErrorF("[StatefulSetOneInterface] - stateful set [%s] [%s] not exists ! %s", cmiiEnv, appName, err.Error()) + return nil + } + + convert := deploymentInterface.ConvertFromStatefulSet(*statefulSet) + + return &convert +} + func (op *CmiiK8sOperator) DeploymentScale(cmiiEnv, appName string, scaleCount int32) bool { deployment := op.DeploymentOneInterface(cmiiEnv, appName) diff --git a/cmii_operator/log/cmii-update-log.txt b/cmii_operator/log/cmii-update-log.txt index d1a7336..41c62c8 100644 --- a/cmii_operator/log/cmii-update-log.txt +++ b/cmii_operator/log/cmii-update-log.txt @@ -27,3 +27,4 @@ 2024-02-23-09-31-21 uavcloud-demo cmii-uav-device 5.4.0 5.4.0-26906 2024-02-23-10-55-14 uavcloud-demo cmii-uav-device 5.4.0-26906 5.4.0-26906-01 2024-02-23-14-32-05 uavcloud-devflight cmii-uav-device 5.2.0-validation 5.4.0-26906-01 +2024-02-28-17-09-55 uavcloud-demo cmii-uav-device 5.4.0 5.4.0-26905 diff --git a/socks5_txthinking/.gitignore b/socks5_txthinking/.gitignore new file mode 100644 index 0000000..9b78a33 --- /dev/null +++ b/socks5_txthinking/.gitignore @@ -0,0 +1,28 @@ +# IDEs +.vscode +.idea + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/socks5_txthinking/LICENSE b/socks5_txthinking/LICENSE new file mode 100644 index 0000000..03aad45 --- /dev/null +++ b/socks5_txthinking/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015-present Cloud https://www.txthinking.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/socks5_txthinking/README.md b/socks5_txthinking/README.md new file mode 100644 index 0000000..1c38676 --- /dev/null +++ b/socks5_txthinking/README.md @@ -0,0 +1,103 @@ +## socks5 + +[中文](README_ZH.md) + +[![Go Report Card](https://goreportcard.com/badge/github.com/txthinking/socks5)](https://goreportcard.com/report/github.com/txthinking/socks5) +[![GoDoc](https://godoc.org/github.com/txthinking/socks5?status.svg)](https://godoc.org/github.com/txthinking/socks5) + +[🗣 News](https://t.me/s/txthinking_news) +[🩸 Youtube](https://www.youtube.com/txthinking) + +SOCKS Protocol Version 5 Library. + +Full TCP/UDP and IPv4/IPv6 support. +Goals: KISS, less is more, small API, code is like the original protocol. + +❤️ A project by [txthinking.com](https://www.txthinking.com) + +### Install + +``` +$ go get github.com/txthinking/socks5 +``` + +### Struct is like concept in protocol + +- Negotiation: + - `type NegotiationRequest struct` + - `func NewNegotiationRequest(methods []byte)`, in client + - `func (r *NegotiationRequest) WriteTo(w io.Writer)`, client writes to server + - `func NewNegotiationRequestFrom(r io.Reader)`, server reads from client + - `type NegotiationReply struct` + - `func NewNegotiationReply(method byte)`, in server + - `func (r *NegotiationReply) WriteTo(w io.Writer)`, server writes to client + - `func NewNegotiationReplyFrom(r io.Reader)`, client reads from server +- User and password negotiation: + - `type UserPassNegotiationRequest struct` + - `func NewUserPassNegotiationRequest(username []byte, password []byte)`, in client + - `func (r *UserPassNegotiationRequest) WriteTo(w io.Writer)`, client writes to server + - `func NewUserPassNegotiationRequestFrom(r io.Reader)`, server reads from client + - `type UserPassNegotiationReply struct` + - `func NewUserPassNegotiationReply(status byte)`, in server + - `func (r *UserPassNegotiationReply) WriteTo(w io.Writer)`, server writes to client + - `func NewUserPassNegotiationReplyFrom(r io.Reader)`, client reads from server +- Request: + - `type Request struct` + - `func NewRequest(cmd byte, atyp byte, dstaddr []byte, dstport []byte)`, in client + - `func (r *Request) WriteTo(w io.Writer)`, client writes to server + - `func NewRequestFrom(r io.Reader)`, server reads from client + - After server gets the client's \*Request, processes... +- Reply: + - `type Reply struct` + - `func NewReply(rep byte, atyp byte, bndaddr []byte, bndport []byte)`, in server + - `func (r *Reply) WriteTo(w io.Writer)`, server writes to client + - `func NewReplyFrom(r io.Reader)`, client reads from server +- Datagram: + - `type Datagram struct` + - `func NewDatagram(atyp byte, dstaddr []byte, dstport []byte, data []byte)` + - `func NewDatagramFromBytes(bb []byte)` + - `func (d *Datagram) Bytes()` + +### Advanced API + +> This can satisfy the classic scenario, and it is still recommended that you choose the above small API to customize +> for special scenarios. + +**Server**: support both TCP and UDP + +- `type Server struct` +- `type Handler interface` + - `TCPHandle(*Server, *net.TCPConn, *Request) error` + - `UDPHandle(*Server, *net.UDPAddr, *Datagram) error` + +Example: + +``` +server, _ := NewClassicServer(addr, ip, username, password, tcpTimeout, udpTimeout) +server.ListenAndServe(Handler) +``` + +**Client**: support both TCP and UDP and return net.Conn + +- `type Client struct` + +Example: + +``` +client, _ := socks5.NewClient(server, username, password, tcpTimeout, udpTimeout) +conn, _ := client.Dial(network, addr) +``` + +### Projects using this library + +- Brook: https://github.com/txthinking/brook +- Shiliew: https://www.txthinking.com/shiliew.html +- dismap: https://github.com/zhzyker/dismap +- emp3r0r: https://github.com/jm33-m0/emp3r0r +- hysteria: https://github.com/apernet/hysteria +- mtg: https://github.com/9seconds/mtg +- trojan-go: https://github.com/p4gefau1t/trojan-go + +## License + +Licensed under The MIT License diff --git a/socks5_txthinking/README_ZH.md b/socks5_txthinking/README_ZH.md new file mode 100644 index 0000000..c41b747 --- /dev/null +++ b/socks5_txthinking/README_ZH.md @@ -0,0 +1,102 @@ +## socks5 + +[English](README.md) + +[![Go Report Card](https://goreportcard.com/badge/github.com/txthinking/socks5)](https://goreportcard.com/report/github.com/txthinking/socks5) +[![GoDoc](https://godoc.org/github.com/txthinking/socks5?status.svg)](https://godoc.org/github.com/txthinking/socks5) + +[🗣 News](https://t.me/s/txthinking_news) +[🩸 Youtube](https://www.youtube.com/txthinking) + +SOCKS Protocol Version 5 Library. + +完整 TCP/UDP 和 IPv4/IPv6 支持. +目标: KISS, less is more, small API, code is like the original protocol. + +❤️ A project by [txthinking.com](https://www.txthinking.com) + +### 获取 + +``` +$ go get github.com/txthinking/socks5 +``` + +### Struct的概念 对标 原始协议里的概念 + +* Negotiation: + * `type NegotiationRequest struct` + * `func NewNegotiationRequest(methods []byte)`, in client + * `func (r *NegotiationRequest) WriteTo(w io.Writer)`, client writes to server + * `func NewNegotiationRequestFrom(r io.Reader)`, server reads from client + * `type NegotiationReply struct` + * `func NewNegotiationReply(method byte)`, in server + * `func (r *NegotiationReply) WriteTo(w io.Writer)`, server writes to client + * `func NewNegotiationReplyFrom(r io.Reader)`, client reads from server +* User and password negotiation: + * `type UserPassNegotiationRequest struct` + * `func NewUserPassNegotiationRequest(username []byte, password []byte)`, in client + * `func (r *UserPassNegotiationRequest) WriteTo(w io.Writer)`, client writes to server + * `func NewUserPassNegotiationRequestFrom(r io.Reader)`, server reads from client + * `type UserPassNegotiationReply struct` + * `func NewUserPassNegotiationReply(status byte)`, in server + * `func (r *UserPassNegotiationReply) WriteTo(w io.Writer)`, server writes to client + * `func NewUserPassNegotiationReplyFrom(r io.Reader)`, client reads from server +* Request: + * `type Request struct` + * `func NewRequest(cmd byte, atyp byte, dstaddr []byte, dstport []byte)`, in client + * `func (r *Request) WriteTo(w io.Writer)`, client writes to server + * `func NewRequestFrom(r io.Reader)`, server reads from client + * After server gets the client's *Request, processes... +* Reply: + * `type Reply struct` + * `func NewReply(rep byte, atyp byte, bndaddr []byte, bndport []byte)`, in server + * `func (r *Reply) WriteTo(w io.Writer)`, server writes to client + * `func NewReplyFrom(r io.Reader)`, client reads from server +* Datagram: + * `type Datagram struct` + * `func NewDatagram(atyp byte, dstaddr []byte, dstport []byte, data []byte)` + * `func NewDatagramFromBytes(bb []byte)` + * `func (d *Datagram) Bytes()` + +### 高级 API + +> 这可以满足经典场景,特殊场景推荐你选择上面的小API来自定义。 + +**Server**: 支持UDP和TCP + +* `type Server struct` +* `type Handler interface` + * `TCPHandle(*Server, *net.TCPConn, *Request) error` + * `UDPHandle(*Server, *net.UDPAddr, *Datagram) error` + +举例: + +``` +server, _ := NewClassicServer(addr, ip, username, password, tcpTimeout, udpTimeout) +server.ListenAndServe(Handler) +``` + +**Client**: 支持TCP和UDP, 返回net.Conn + +* `type Client struct` + +举例: + +``` +client, _ := socks5.NewClient(server, username, password, tcpTimeout, udpTimeout) +conn, _ := client.Dial(network, addr) +``` + +### 谁在使用此项目 + +- Brook: https://github.com/txthinking/brook +- Shiliew: https://www.txthinking.com/shiliew.html +- dismap: https://github.com/zhzyker/dismap +- emp3r0r: https://github.com/jm33-m0/emp3r0r +- hysteria: https://github.com/apernet/hysteria +- mtg: https://github.com/9seconds/mtg +- trojan-go: https://github.com/p4gefau1t/trojan-go + +## 开源协议 + +基于 MIT 协议开源 diff --git a/socks5_txthinking/bind.go b/socks5_txthinking/bind.go new file mode 100644 index 0000000..429949c --- /dev/null +++ b/socks5_txthinking/bind.go @@ -0,0 +1,11 @@ +package socks5 + +import ( + "errors" + "net" +) + +// TODO +func (r *Request) bind(c net.Conn) error { + return errors.New("Unsupport BIND now") +} diff --git a/socks5_txthinking/client.go b/socks5_txthinking/client.go new file mode 100644 index 0000000..d8a899b --- /dev/null +++ b/socks5_txthinking/client.go @@ -0,0 +1,254 @@ +package socks5 + +import ( + "errors" + "net" + "time" +) + +// Client is socks5 client wrapper +type Client struct { + Server string + UserName string + Password string + // On cmd UDP, let server control the tcp and udp connection relationship + TCPConn net.Conn + UDPConn net.Conn + RemoteAddress net.Addr + TCPTimeout int + UDPTimeout int + Dst string +} + +// This is just create a client, you need to use Dial to create conn +func NewClient(addr, username, password string, tcpTimeout, udpTimeout int) (*Client, error) { + c := &Client{ + Server: addr, + UserName: username, + Password: password, + TCPTimeout: tcpTimeout, + UDPTimeout: udpTimeout, + } + return c, nil +} + +func (c *Client) Dial(network, addr string) (net.Conn, error) { + return c.DialWithLocalAddr(network, "", addr, nil) +} + +// If you want to send address that expects to use to send UDP, just assign it to src, otherwise it will send zero address. +// Recommend specifying the src address in a non-NAT environment, and leave it blank in other cases. +func (c *Client) DialWithLocalAddr(network, src, dst string, remoteAddr net.Addr) (net.Conn, error) { + c = &Client{ + Server: c.Server, + UserName: c.UserName, + Password: c.Password, + TCPTimeout: c.TCPTimeout, + UDPTimeout: c.UDPTimeout, + Dst: dst, + RemoteAddress: remoteAddr, + } + var err error + if network == "tcp" { + var laddr net.Addr + if src != "" { + laddr, err = net.ResolveTCPAddr("tcp", src) + if err != nil { + return nil, err + } + } + if err := c.Negotiate(laddr); err != nil { + return nil, err + } + a, h, p, err := ParseAddress(dst) + if err != nil { + return nil, err + } + if a == ATYPDomain { + h = h[1:] + } + if _, err := c.Request(NewRequest(CmdConnect, a, h, p)); err != nil { + return nil, err + } + return c, nil + } + if network == "udp" { + var laddr net.Addr + if src != "" { + laddr, err = net.ResolveTCPAddr("tcp", src) + if err != nil { + return nil, err + } + } + if err := c.Negotiate(laddr); err != nil { + return nil, err + } + + a, h, p := ATYPIPv4, net.IPv4zero, []byte{0x00, 0x00} + if src != "" { + a, h, p, err = ParseAddress(src) + if err != nil { + return nil, err + } + if a == ATYPDomain { + h = h[1:] + } + } + rp, err := c.Request(NewRequest(CmdUDP, a, h, p)) + if err != nil { + return nil, err + } + c.UDPConn, err = DialUDP("udp", src, rp.Address()) + if err != nil { + return nil, err + } + if c.UDPTimeout != 0 { + if err := c.UDPConn.SetDeadline(time.Now().Add(time.Duration(c.UDPTimeout) * time.Second)); err != nil { + return nil, err + } + } + return c, nil + } + return nil, errors.New("unsupport network") +} + +func (c *Client) Read(b []byte) (int, error) { + if c.UDPConn == nil { + return c.TCPConn.Read(b) + } + n, err := c.UDPConn.Read(b) + if err != nil { + return 0, err + } + d, err := NewDatagramFromBytes(b[0:n]) + if err != nil { + return 0, err + } + n = copy(b, d.Data) + return n, nil +} + +func (c *Client) Write(b []byte) (int, error) { + if c.UDPConn == nil { + return c.TCPConn.Write(b) + } + a, h, p, err := ParseAddress(c.Dst) + if err != nil { + return 0, err + } + if a == ATYPDomain { + h = h[1:] + } + d := NewDatagram(a, h, p, b) + b1 := d.Bytes() + n, err := c.UDPConn.Write(b1) + if err != nil { + return 0, err + } + if len(b1) != n { + return 0, errors.New("not write full") + } + return len(b), nil +} + +func (c *Client) Close() error { + if c.UDPConn == nil { + return c.TCPConn.Close() + } + if c.TCPConn != nil { + c.TCPConn.Close() + } + return c.UDPConn.Close() +} + +func (c *Client) LocalAddr() net.Addr { + if c.UDPConn == nil { + return c.TCPConn.LocalAddr() + } + return c.UDPConn.LocalAddr() +} + +func (c *Client) RemoteAddr() net.Addr { + return c.RemoteAddress +} + +func (c *Client) SetDeadline(t time.Time) error { + if c.UDPConn == nil { + return c.TCPConn.SetDeadline(t) + } + return c.UDPConn.SetDeadline(t) +} + +func (c *Client) SetReadDeadline(t time.Time) error { + if c.UDPConn == nil { + return c.TCPConn.SetReadDeadline(t) + } + return c.UDPConn.SetReadDeadline(t) +} + +func (c *Client) SetWriteDeadline(t time.Time) error { + if c.UDPConn == nil { + return c.TCPConn.SetWriteDeadline(t) + } + return c.UDPConn.SetWriteDeadline(t) +} + +func (c *Client) Negotiate(laddr net.Addr) error { + src := "" + if laddr != nil { + src = laddr.String() + } + var err error + c.TCPConn, err = DialTCP("tcp", src, c.Server) + if err != nil { + return err + } + if c.TCPTimeout != 0 { + if err := c.TCPConn.SetDeadline(time.Now().Add(time.Duration(c.TCPTimeout) * time.Second)); err != nil { + return err + } + } + m := MethodNone + if c.UserName != "" && c.Password != "" { + m = MethodUsernamePassword + } + rq := NewNegotiationRequest([]byte{m}) + if _, err := rq.WriteTo(c.TCPConn); err != nil { + return err + } + rp, err := NewNegotiationReplyFrom(c.TCPConn) + if err != nil { + return err + } + if rp.Method != m { + return errors.New("Unsupport method") + } + if m == MethodUsernamePassword { + urq := NewUserPassNegotiationRequest([]byte(c.UserName), []byte(c.Password)) + if _, err := urq.WriteTo(c.TCPConn); err != nil { + return err + } + urp, err := NewUserPassNegotiationReplyFrom(c.TCPConn) + if err != nil { + return err + } + if urp.Status != UserPassStatusSuccess { + return ErrUserPassAuth + } + } + return nil +} + +func (c *Client) Request(r *Request) (*Reply, error) { + if _, err := r.WriteTo(c.TCPConn); err != nil { + return nil, err + } + rp, err := NewReplyFrom(c.TCPConn) + if err != nil { + return nil, err + } + if rp.Rep != RepSuccess { + return nil, errors.New("Host unreachable") + } + return rp, nil +} diff --git a/socks5_txthinking/client_side.go b/socks5_txthinking/client_side.go new file mode 100644 index 0000000..9d0f944 --- /dev/null +++ b/socks5_txthinking/client_side.go @@ -0,0 +1,172 @@ +package socks5 + +import ( + "errors" + "io" + "log" +) + +var ( + // ErrBadReply is the error when read reply + ErrBadReply = errors.New("Bad Reply") +) + +// NewNegotiationRequest return negotiation request packet can be writed into server +func NewNegotiationRequest(methods []byte) *NegotiationRequest { + return &NegotiationRequest{ + Ver: Ver, + NMethods: byte(len(methods)), + Methods: methods, + } +} + +// WriteTo write negotiation request packet into server +func (r *NegotiationRequest) WriteTo(w io.Writer) (int64, error) { + i, err := w.Write(append([]byte{r.Ver, r.NMethods}, r.Methods...)) + if err != nil { + return 0, err + } + if Debug { + log.Printf("Sent NegotiationRequest: %#v %#v %#v\n", r.Ver, r.NMethods, r.Methods) + } + return int64(i), nil +} + +// NewNegotiationReplyFrom read negotiation reply packet from server +func NewNegotiationReplyFrom(r io.Reader) (*NegotiationReply, error) { + bb := make([]byte, 2) + if _, err := io.ReadFull(r, bb); err != nil { + return nil, err + } + if bb[0] != Ver { + return nil, ErrVersion + } + if Debug { + log.Printf("Got NegotiationReply: %#v %#v\n", bb[0], bb[1]) + } + return &NegotiationReply{ + Ver: bb[0], + Method: bb[1], + }, nil +} + +// NewUserPassNegotiationRequest return user password negotiation request packet can be writed into server +func NewUserPassNegotiationRequest(username []byte, password []byte) *UserPassNegotiationRequest { + return &UserPassNegotiationRequest{ + Ver: UserPassVer, + Ulen: byte(len(username)), + Uname: username, + Plen: byte(len(password)), + Passwd: password, + } +} + +// WriteTo write user password negotiation request packet into server +func (r *UserPassNegotiationRequest) WriteTo(w io.Writer) (int64, error) { + i, err := w.Write(append(append(append([]byte{r.Ver, r.Ulen}, r.Uname...), r.Plen), r.Passwd...)) + if err != nil { + return 0, err + } + if Debug { + log.Printf("Sent UserNameNegotiationRequest: %#v %#v %#v %#v %#v\n", r.Ver, r.Ulen, r.Uname, r.Plen, r.Passwd) + } + return int64(i), nil +} + +// NewUserPassNegotiationReplyFrom read user password negotiation reply packet from server +func NewUserPassNegotiationReplyFrom(r io.Reader) (*UserPassNegotiationReply, error) { + bb := make([]byte, 2) + if _, err := io.ReadFull(r, bb); err != nil { + return nil, err + } + if bb[0] != UserPassVer { + return nil, ErrUserPassVersion + } + if Debug { + log.Printf("Got UserPassNegotiationReply: %#v %#v \n", bb[0], bb[1]) + } + return &UserPassNegotiationReply{ + Ver: bb[0], + Status: bb[1], + }, nil +} + +// NewRequest return request packet can be writed into server, dstaddr should not have domain length +func NewRequest(cmd byte, atyp byte, dstaddr []byte, dstport []byte) *Request { + if atyp == ATYPDomain { + dstaddr = append([]byte{byte(len(dstaddr))}, dstaddr...) + } + return &Request{ + Ver: Ver, + Cmd: cmd, + Rsv: 0x00, + Atyp: atyp, + DstAddr: dstaddr, + DstPort: dstport, + } +} + +// WriteTo write request packet into server +func (r *Request) WriteTo(w io.Writer) (int64, error) { + i, err := w.Write(append(append([]byte{r.Ver, r.Cmd, r.Rsv, r.Atyp}, r.DstAddr...), r.DstPort...)) + if err != nil { + return 0, err + } + if Debug { + log.Printf("Sent Request: %#v %#v %#v %#v %#v %#v\n", r.Ver, r.Cmd, r.Rsv, r.Atyp, r.DstAddr, r.DstPort) + } + return int64(i), nil +} + +// NewReplyFrom read reply packet from server +func NewReplyFrom(r io.Reader) (*Reply, error) { + bb := make([]byte, 4) + if _, err := io.ReadFull(r, bb); err != nil { + return nil, err + } + if bb[0] != Ver { + return nil, ErrVersion + } + var addr []byte + if bb[3] == ATYPIPv4 { + addr = make([]byte, 4) + if _, err := io.ReadFull(r, addr); err != nil { + return nil, err + } + } else if bb[3] == ATYPIPv6 { + addr = make([]byte, 16) + if _, err := io.ReadFull(r, addr); err != nil { + return nil, err + } + } else if bb[3] == ATYPDomain { + dal := make([]byte, 1) + if _, err := io.ReadFull(r, dal); err != nil { + return nil, err + } + if dal[0] == 0 { + return nil, ErrBadReply + } + addr = make([]byte, int(dal[0])) + if _, err := io.ReadFull(r, addr); err != nil { + return nil, err + } + addr = append(dal, addr...) + } else { + return nil, ErrBadReply + } + port := make([]byte, 2) + if _, err := io.ReadFull(r, port); err != nil { + return nil, err + } + if Debug { + log.Printf("Got Reply: %#v %#v %#v %#v %#v %#v\n", bb[0], bb[1], bb[2], bb[3], addr, port) + } + return &Reply{ + Ver: bb[0], + Rep: bb[1], + Rsv: bb[2], + Atyp: bb[3], + BndAddr: addr, + BndPort: port, + }, nil +} diff --git a/socks5_txthinking/connect.go b/socks5_txthinking/connect.go new file mode 100644 index 0000000..1f16ce3 --- /dev/null +++ b/socks5_txthinking/connect.go @@ -0,0 +1,53 @@ +package socks5 + +import ( + "io" + "log" + "net" +) + +// Connect remote conn which u want to connect with your dialer +// Error or OK both replied. +func (r *Request) Connect(w io.Writer) (net.Conn, error) { + if Debug { + log.Println("Call:", r.Address()) + } + rc, err := DialTCP("tcp", "", r.Address()) + if err != nil { + var p *Reply + if r.Atyp == ATYPIPv4 || r.Atyp == ATYPDomain { + p = NewReply(RepHostUnreachable, ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + } else { + p = NewReply(RepHostUnreachable, ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00}) + } + if _, err := p.WriteTo(w); err != nil { + return nil, err + } + return nil, err + } + + a, addr, port, err := ParseAddress(rc.LocalAddr().String()) + if err != nil { + rc.Close() + var p *Reply + if r.Atyp == ATYPIPv4 || r.Atyp == ATYPDomain { + p = NewReply(RepHostUnreachable, ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + } else { + p = NewReply(RepHostUnreachable, ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00}) + } + if _, err := p.WriteTo(w); err != nil { + return nil, err + } + return nil, err + } + if a == ATYPDomain { + addr = addr[1:] + } + p := NewReply(RepSuccess, a, addr, port) + if _, err := p.WriteTo(w); err != nil { + rc.Close() + return nil, err + } + + return rc, nil +} diff --git a/socks5_txthinking/example_test.go b/socks5_txthinking/example_test.go new file mode 100644 index 0000000..bf2da03 --- /dev/null +++ b/socks5_txthinking/example_test.go @@ -0,0 +1,87 @@ +package socks5 + +import ( + "encoding/hex" + "io/ioutil" + "log" + "net" + "net/http" + + "github.com/miekg/dns" +) + +func ExampleServer() { + s, err := NewClassicServer("127.0.0.1:1080", "127.0.0.1", "", "", 0, 60) + if err != nil { + log.Println(err) + return + } + // You can pass in custom Handler + s.ListenAndServe(nil) + // #Output: +} + +func ExampleClient_tcp() { + go ExampleServer() + c, err := NewClient("127.0.0.1:1080", "", "", 0, 60) + if err != nil { + log.Println(err) + return + } + client := &http.Client{ + Transport: &http.Transport{ + Dial: func(network, addr string) (net.Conn, error) { + return c.Dial(network, addr) + }, + }, + } + res, err := client.Get("https://ifconfig.co") + if err != nil { + log.Println(err) + return + } + defer res.Body.Close() + b, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Println(err) + return + } + log.Println("tcp", string(b)) + // Output: +} + +func ExampleClient_udp() { + go ExampleServer() + c, err := NewClient("127.0.0.1:1080", "", "", 0, 60) + if err != nil { + log.Println(err) + return + } + conn, err := c.Dial("udp", "8.8.8.8:53") + if err != nil { + log.Println(err) + return + } + b, err := hex.DecodeString("0001010000010000000000000a74787468696e6b696e6703636f6d0000010001") + if err != nil { + log.Println(err) + return + } + if _, err := conn.Write(b); err != nil { + log.Println(err) + return + } + b = make([]byte, 2048) + n, err := conn.Read(b) + if err != nil { + log.Println(err) + return + } + m := &dns.Msg{} + if err := m.Unpack(b[0:n]); err != nil { + log.Println(err) + return + } + log.Println(m.String()) + // Output: +} diff --git a/socks5_txthinking/go.mod b/socks5_txthinking/go.mod new file mode 100644 index 0000000..4280043 --- /dev/null +++ b/socks5_txthinking/go.mod @@ -0,0 +1,8 @@ +module wdd.io/socks5 + +go 1.20 + +require ( + github.com/miekg/dns v1.1.51 + github.com/patrickmn/go-cache v2.1.0+incompatible +) diff --git a/socks5_txthinking/go.sum b/socks5_txthinking/go.sum new file mode 100644 index 0000000..e73ce4c --- /dev/null +++ b/socks5_txthinking/go.sum @@ -0,0 +1,41 @@ +github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo= +github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0= +github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/socks5_txthinking/init.go b/socks5_txthinking/init.go new file mode 100644 index 0000000..36548cf --- /dev/null +++ b/socks5_txthinking/init.go @@ -0,0 +1,52 @@ +package socks5 + +import ( + "net" +) + +var Debug bool + +func init() { + // log.SetFlags(log.LstdFlags | log.Lshortfile) +} + +var Resolve func(network string, addr string) (net.Addr, error) = func(network string, addr string) (net.Addr, error) { + if network == "tcp" { + return net.ResolveTCPAddr("tcp", addr) + } + return net.ResolveUDPAddr("udp", addr) +} + +var DialTCP func(network string, laddr, raddr string) (net.Conn, error) = func(network string, laddr, raddr string) (net.Conn, error) { + var la, ra *net.TCPAddr + if laddr != "" { + var err error + la, err = net.ResolveTCPAddr(network, laddr) + if err != nil { + return nil, err + } + } + a, err := Resolve(network, raddr) + if err != nil { + return nil, err + } + ra = a.(*net.TCPAddr) + return net.DialTCP(network, la, ra) +} + +var DialUDP func(network string, laddr, raddr string) (net.Conn, error) = func(network string, laddr, raddr string) (net.Conn, error) { + var la, ra *net.UDPAddr + if laddr != "" { + var err error + la, err = net.ResolveUDPAddr(network, laddr) + if err != nil { + return nil, err + } + } + a, err := Resolve(network, raddr) + if err != nil { + return nil, err + } + ra = a.(*net.UDPAddr) + return net.DialUDP(network, la, ra) +} diff --git a/socks5_txthinking/main.go b/socks5_txthinking/main.go new file mode 100644 index 0000000..29ad183 --- /dev/null +++ b/socks5_txthinking/main.go @@ -0,0 +1,5 @@ +package socks5 + +func main() { + +} diff --git a/socks5_txthinking/old_tcp_tailscale/logger.go b/socks5_txthinking/old_tcp_tailscale/logger.go new file mode 100644 index 0000000..0ebf6c3 --- /dev/null +++ b/socks5_txthinking/old_tcp_tailscale/logger.go @@ -0,0 +1,211 @@ +package old_tcp_tailscale // Copyright (c) Tailscale Inc & AUTHORS + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "sync" + "time" + + "context" +) + +// Logf is the basic Tailscale logger type: a printf-like func. +// Like log.Printf, the format need not end in a newline. +// Logf functions must be safe for concurrent use. +type Logf func(format string, args ...any) + +// A Context is a context.Context that should contain a custom log function, obtainable from FromContext. +// If no log function is present, FromContext will return log.Printf. +// To construct a Context, use Add +// +// Deprecated: Do not use. +type Context context.Context + +// jenc is a json.Encode + bytes.Buffer pair wired up to be reused in a pool. +type jenc struct { + buf bytes.Buffer + enc *json.Encoder +} + +var jencPool = &sync.Pool{New: func() any { + je := new(jenc) + je.enc = json.NewEncoder(&je.buf) + return je +}} + +// JSON marshals v as JSON and writes it to logf formatted with the annotation to make logtail +// treat it as a structured log. +// +// The recType is the record type and becomes the key of the wrapper +// JSON object that is logged. That is, if recType is "foo" and v is +// 123, the value logged is {"foo":123}. +// +// Do not use recType "logtail", "v", "text", or "metrics", with any case. +// Those are reserved for the logging system. +// +// The level can be from 0 to 9. Levels from 1 to 9 are included in +// the logged JSON object, like {"foo":123,"v":2}. +func (logf Logf) JSON(level int, recType string, v any) { + je := jencPool.Get().(*jenc) + defer jencPool.Put(je) + je.buf.Reset() + je.buf.WriteByte('{') + je.enc.Encode(recType) + je.buf.Truncate(je.buf.Len() - 1) // remove newline from prior Encode + je.buf.WriteByte(':') + if err := je.enc.Encode(v); err != nil { + logf("[unexpected]: failed to encode structured JSON log record of type %q / %T: %v", recType, v, err) + return + } + je.buf.Truncate(je.buf.Len() - 1) // remove newline from prior Encode + je.buf.WriteByte('}') + // Magic prefix recognized by logtail: + logf("[v\x00JSON]%d%s", level%10, je.buf.Bytes()) + +} + +// WithPrefix wraps f, prefixing each format with the provided prefix. +func WithPrefix(f Logf, prefix string) Logf { + return func(format string, args ...any) { + f(prefix+format, args...) + } +} + +// FuncWriter returns an io.Writer that writes to f. +func FuncWriter(f Logf) io.Writer { + return funcWriter{f} +} + +// StdLogger returns a standard library logger from a Logf. +func StdLogger(f Logf) *log.Logger { + return log.New(FuncWriter(f), "", 0) +} + +type funcWriter struct{ f Logf } + +func (w funcWriter) Write(p []byte) (int, error) { + w.f("%s", p) + return len(p), nil +} + +// Discard is a Logf that throws away the logs given to it. +func Discard(string, ...any) {} + +// LogOnChange logs a given line only if line != lastLine, or if maxInterval has passed +// since the last time this identical line was logged. +func LogOnChange(logf Logf, maxInterval time.Duration, timeNow func() time.Time) Logf { + var ( + mu sync.Mutex + sLastLogged string + tLastLogged = timeNow() + ) + + return func(format string, args ...any) { + s := fmt.Sprintf(format, args...) + + mu.Lock() + if s == sLastLogged && timeNow().Sub(tLastLogged) < maxInterval { + mu.Unlock() + return + } + sLastLogged = s + tLastLogged = timeNow() + mu.Unlock() + + // Re-stringify it (instead of using "%s", s) so something like "%s" + // doesn't end up getting rate-limited. (And can't use 's' as the pattern, + // as it might contain formatting directives.) + logf(format, args...) + } +} + +// ArgWriter is a fmt.Formatter that can be passed to any Logf func to +// efficiently write to a %v argument without allocations. +type ArgWriter func(*bufio.Writer) + +func (fn ArgWriter) Format(f fmt.State, _ rune) { + bw := argBufioPool.Get().(*bufio.Writer) + bw.Reset(f) + fn(bw) + bw.Flush() + argBufioPool.Put(bw) +} + +var argBufioPool = &sync.Pool{New: func() any { return bufio.NewWriterSize(io.Discard, 1024) }} + +// Filtered returns a Logf that silently swallows some log lines. +// Each inbound format and args is evaluated and printed to a string s. +// The original format and args are passed to logf if and only if allow(s) returns true. +func Filtered(logf Logf, allow func(s string) bool) Logf { + return func(format string, args ...any) { + msg := fmt.Sprintf(format, args...) + if !allow(msg) { + return + } + logf(format, args...) + } +} + +// LogfCloser wraps logf to create a logger that can be closed. +// Calling close makes all future calls to newLogf into no-ops. +func LogfCloser(logf Logf) (newLogf Logf, close func()) { + var ( + mu sync.Mutex + closed bool + ) + close = func() { + mu.Lock() + defer mu.Unlock() + closed = true + } + newLogf = func(msg string, args ...any) { + mu.Lock() + if closed { + mu.Unlock() + return + } + mu.Unlock() + logf(msg, args...) + } + return newLogf, close +} + +// AsJSON returns a formatter that formats v as JSON. The value is suitable to +// passing to a regular %v printf argument. (%s is not required) +// +// If json.Marshal returns an error, the output is "%%!JSON-ERROR:" followed by +// the error string. +func AsJSON(v any) fmt.Formatter { + return asJSONResult{v} +} + +type asJSONResult struct{ v any } + +func (a asJSONResult) Format(s fmt.State, verb rune) { + v, err := json.Marshal(a.v) + if err != nil { + fmt.Fprintf(s, "%%!JSON-ERROR:%v", err) + return + } + s.Write(v) +} + +// TBLogger is the testing.TB subset needed by TestLogger. +type TBLogger interface { + Helper() + Logf(format string, args ...any) +} + +// TestLogger returns a logger that logs to tb.Logf +// with a prefix to make it easier to distinguish spam +// from explicit test failures. +func TestLogger(tb TBLogger) Logf { + return func(format string, args ...any) { + tb.Helper() + tb.Logf(" ... "+format, args...) + } +} diff --git a/socks5_txthinking/old_tcp_tailscale/main.go b/socks5_txthinking/old_tcp_tailscale/main.go new file mode 100644 index 0000000..2e78aa9 --- /dev/null +++ b/socks5_txthinking/old_tcp_tailscale/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "net" + "os" +) + +func main() { + + if len(os.Args) < 2 { + fmt.Println("start socks5 server error must provide listen port !") + return + } + if len(os.Args) > 3 { + fmt.Println("start socks5 server error !") + return + } + username := "" + password := "" + if len(os.Args) == 4 { + username = os.Args[2] + password = os.Args[3] + } + + port := os.Args[1] + + listener, err := net.Listen("tcp", ":"+port) + if err != nil { + fmt.Println("start listener error ! => " + err.Error()) + } + + server := &Server{ + Username: username, + Password: password, + } + + err = server.Serve(listener) + if err != nil { + fmt.Println("server start to server error ! => " + err.Error()) + } +} diff --git a/socks5_txthinking/old_tcp_tailscale/socks5.go b/socks5_txthinking/old_tcp_tailscale/socks5.go new file mode 100644 index 0000000..43cc800 --- /dev/null +++ b/socks5_txthinking/old_tcp_tailscale/socks5.go @@ -0,0 +1,417 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Package socks5 is a SOCKS5 server implementation. +// +// This is used for userspace networking in Tailscale. Specifically, +// this is used for dialing out of the machine to other nodes, without +// the host kernel's involvement, so it doesn't proper routing tables, +// TUN, IPv6, etc. This package is meant to only handle the SOCKS5 protocol +// details and not any integration with Tailscale internals itself. +// +// The glue between this package and Tailscale is in net/socks5/tssocks. +package old_tcp_tailscale + +import ( + "context" + "encoding/binary" + "fmt" + "io" + "log" + "net" + "strconv" + "time" +) + +// Authentication METHODs described in RFC 1928, section 3. +const ( + noAuthRequired byte = 0 + passwordAuth byte = 2 + noAcceptableAuth byte = 255 +) + +// passwordAuthVersion is the auth version byte described in RFC 1929. +const passwordAuthVersion = 1 + +// socks5Version is the byte that represents the SOCKS version +// in requests. +const socks5Version byte = 5 + +// commandType are the bytes sent in SOCKS5 packets +// that represent the kind of connection the client needs. +type commandType byte + +// The set of valid SOCKS5 commands as described in RFC 1928. +const ( + connect commandType = 1 + bind commandType = 2 + udpAssociate commandType = 3 +) + +// addrType are the bytes sent in SOCKS5 packets +// that represent particular address types. +type addrType byte + +// The set of valid SOCKS5 address types as defined in RFC 1928. +const ( + ipv4 addrType = 1 + domainName addrType = 3 + ipv6 addrType = 4 +) + +// replyCode are the bytes sent in SOCKS5 packets +// that represent replies from the server to a client +// request. +type replyCode byte + +// The set of valid SOCKS5 reply types as per the RFC 1928. +const ( + success replyCode = 0 + generalFailure replyCode = 1 + connectionNotAllowed replyCode = 2 + networkUnreachable replyCode = 3 + hostUnreachable replyCode = 4 + connectionRefused replyCode = 5 + ttlExpired replyCode = 6 + commandNotSupported replyCode = 7 + addrTypeNotSupported replyCode = 8 +) + +// Server is a SOCKS5 proxy server. +type Server struct { + // Logf optionally specifies the logger to use. + // If nil, the standard logger is used. + Logf Logf + + // Dialer optionally specifies the dialer to use for outgoing connections. + // If nil, the net package's standard dialer is used. + Dialer func(ctx context.Context, network, addr string) (net.Conn, error) + + // Username and Password, if set, are the credential clients must provide. + Username string + Password string +} + +func (s *Server) dial(ctx context.Context, network, addr string) (net.Conn, error) { + dial := s.Dialer + if dial == nil { + dialer := &net.Dialer{} + dial = dialer.DialContext + } + return dial(ctx, network, addr) +} + +func (s *Server) logf(format string, args ...any) { + logf := s.Logf + if logf == nil { + logf = log.Printf + } + logf(format, args...) +} + +// Serve accepts and handles incoming connections on the given listener. +func (s *Server) Serve(l net.Listener) error { + defer l.Close() + for { + c, err := l.Accept() + if err != nil { + return err + } + go func() { + defer c.Close() + conn := &Conn{clientConn: c, srv: s} + err := conn.Run() + if err != nil { + s.logf("client connection failed: %v", err) + } + }() + } +} + +// Conn is a SOCKS5 connection for client to reach +// server. +type Conn struct { + // The struct is filled by each of the internal + // methods in turn as the transaction progresses. + + srv *Server + clientConn net.Conn + request *request +} + +// Run starts the new connection. +func (c *Conn) Run() error { + needAuth := c.srv.Username != "" || c.srv.Password != "" + authMethod := noAuthRequired + if needAuth { + authMethod = passwordAuth + } + + err := parseClientGreeting(c.clientConn, authMethod) + if err != nil { + c.clientConn.Write([]byte{socks5Version, noAcceptableAuth}) + return err + } + c.clientConn.Write([]byte{socks5Version, authMethod}) + if !needAuth { + return c.handleRequest() + } + + user, pwd, err := parseClientAuth(c.clientConn) + if err != nil || user != c.srv.Username || pwd != c.srv.Password { + c.clientConn.Write([]byte{1, 1}) // auth error + return err + } + c.clientConn.Write([]byte{1, 0}) // auth success + + return c.handleRequest() +} + +func (c *Conn) handleRequest() error { + + req, err := parseClientRequest(c.clientConn) + if err != nil { + res := &response{reply: generalFailure} + buf, _ := res.marshal() + c.clientConn.Write(buf) + return err + } + if req.command != connect { + res := &response{reply: commandNotSupported} + buf, _ := res.marshal() + c.clientConn.Write(buf) + return fmt.Errorf("unsupported command %v", req.command) + } + c.request = req + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + srv, err := c.srv.dial( + ctx, + "tcp", + net.JoinHostPort(c.request.destination, strconv.Itoa(int(c.request.port))), + ) + if err != nil { + res := &response{reply: generalFailure} + buf, _ := res.marshal() + c.clientConn.Write(buf) + return err + } + defer srv.Close() + serverAddr, serverPortStr, err := net.SplitHostPort(srv.LocalAddr().String()) + if err != nil { + return err + } + serverPort, _ := strconv.Atoi(serverPortStr) + + var bindAddrType addrType + if ip := net.ParseIP(serverAddr); ip != nil { + if ip.To4() != nil { + bindAddrType = ipv4 + } else { + bindAddrType = ipv6 + } + } else { + bindAddrType = domainName + } + + res := &response{ + reply: success, + bindAddrType: bindAddrType, + bindAddr: serverAddr, + bindPort: uint16(serverPort), + } + buf, err := res.marshal() + if err != nil { + res = &response{reply: generalFailure} + buf, _ = res.marshal() + } + c.clientConn.Write(buf) + + errc := make(chan error, 2) + go func() { + _, err := io.Copy(c.clientConn, srv) + if err != nil { + err = fmt.Errorf("from backend to client: %w", err) + } + errc <- err + }() + go func() { + _, err := io.Copy(srv, c.clientConn) + if err != nil { + err = fmt.Errorf("from client to backend: %w", err) + } + errc <- err + }() + return <-errc +} + +// parseClientGreeting parses a request initiation packet. +func parseClientGreeting(r io.Reader, authMethod byte) error { + var hdr [2]byte + _, err := io.ReadFull(r, hdr[:]) + if err != nil { + return fmt.Errorf("could not read packet header") + } + if hdr[0] != socks5Version { + return fmt.Errorf("incompatible SOCKS version") + } + count := int(hdr[1]) + methods := make([]byte, count) + _, err = io.ReadFull(r, methods) + if err != nil { + return fmt.Errorf("could not read methods") + } + for _, m := range methods { + if m == authMethod { + return nil + } + } + return fmt.Errorf("no acceptable auth methods") +} + +func parseClientAuth(r io.Reader) (usr, pwd string, err error) { + var hdr [2]byte + if _, err := io.ReadFull(r, hdr[:]); err != nil { + return "", "", fmt.Errorf("could not read auth packet header") + } + if hdr[0] != passwordAuthVersion { + return "", "", fmt.Errorf("bad SOCKS auth version") + } + usrLen := int(hdr[1]) + usrBytes := make([]byte, usrLen) + if _, err := io.ReadFull(r, usrBytes); err != nil { + return "", "", fmt.Errorf("could not read auth packet username") + } + var hdrPwd [1]byte + if _, err := io.ReadFull(r, hdrPwd[:]); err != nil { + return "", "", fmt.Errorf("could not read auth packet password length") + } + pwdLen := int(hdrPwd[0]) + pwdBytes := make([]byte, pwdLen) + if _, err := io.ReadFull(r, pwdBytes); err != nil { + return "", "", fmt.Errorf("could not read auth packet password") + } + return string(usrBytes), string(pwdBytes), nil +} + +// request represents data contained within a SOCKS5 +// connection request packet. +type request struct { + command commandType + destination string + port uint16 + destAddrType addrType +} + +// parseClientRequest converts raw packet bytes into a +// SOCKS5Request struct. +func parseClientRequest(r io.Reader) (*request, error) { + var hdr [4]byte + _, err := io.ReadFull(r, hdr[:]) + if err != nil { + return nil, fmt.Errorf("could not read packet header") + } + cmd := hdr[1] + destAddrType := addrType(hdr[3]) + + var destination string + var port uint16 + + if destAddrType == ipv4 { + var ip [4]byte + _, err = io.ReadFull(r, ip[:]) + if err != nil { + return nil, fmt.Errorf("could not read IPv4 address") + } + destination = net.IP(ip[:]).String() + } else if destAddrType == domainName { + var dstSizeByte [1]byte + _, err = io.ReadFull(r, dstSizeByte[:]) + if err != nil { + return nil, fmt.Errorf("could not read domain name size") + } + dstSize := int(dstSizeByte[0]) + domainName := make([]byte, dstSize) + _, err = io.ReadFull(r, domainName) + if err != nil { + return nil, fmt.Errorf("could not read domain name") + } + destination = string(domainName) + } else if destAddrType == ipv6 { + var ip [16]byte + _, err = io.ReadFull(r, ip[:]) + if err != nil { + return nil, fmt.Errorf("could not read IPv6 address") + } + destination = net.IP(ip[:]).String() + } else { + return nil, fmt.Errorf("unsupported address type") + } + var portBytes [2]byte + _, err = io.ReadFull(r, portBytes[:]) + if err != nil { + return nil, fmt.Errorf("could not read port") + } + port = binary.BigEndian.Uint16(portBytes[:]) + + return &request{ + command: commandType(cmd), + destination: destination, + port: port, + destAddrType: destAddrType, + }, nil +} + +// response contains the contents of +// a response packet sent from the proxy +// to the client. +type response struct { + reply replyCode + bindAddrType addrType + bindAddr string + bindPort uint16 +} + +// marshal converts a SOCKS5Response struct into +// a packet. If res.reply == Success, it may throw an error on +// receiving an invalid bind address. Otherwise, it will not throw. +func (res *response) marshal() ([]byte, error) { + pkt := make([]byte, 4) + pkt[0] = socks5Version + pkt[1] = byte(res.reply) + pkt[2] = 0 // null reserved byte + pkt[3] = byte(res.bindAddrType) + + if res.reply != success { + return pkt, nil + } + + var addr []byte + switch res.bindAddrType { + case ipv4: + addr = net.ParseIP(res.bindAddr).To4() + if addr == nil { + return nil, fmt.Errorf("invalid IPv4 address for binding") + } + case domainName: + if len(res.bindAddr) > 255 { + return nil, fmt.Errorf("invalid domain name for binding") + } + addr = make([]byte, 0, len(res.bindAddr)+1) + addr = append(addr, byte(len(res.bindAddr))) + addr = append(addr, []byte(res.bindAddr)...) + case ipv6: + addr = net.ParseIP(res.bindAddr).To16() + if addr == nil { + return nil, fmt.Errorf("invalid IPv6 address for binding") + } + default: + return nil, fmt.Errorf("unsupported address type") + } + + pkt = append(pkt, addr...) + pkt = binary.BigEndian.AppendUint16(pkt, uint16(res.bindPort)) + + return pkt, nil +} diff --git a/socks5_txthinking/runnergrounp.go b/socks5_txthinking/runnergrounp.go new file mode 100644 index 0000000..57a59d2 --- /dev/null +++ b/socks5_txthinking/runnergrounp.go @@ -0,0 +1,96 @@ +package socks5 + +import ( + "sync" + "time" +) + +// RunnerGroup is like sync.WaitGroup, +// the diffrence is if one task stops, all will be stopped. +type RunnerGroup struct { + Runners []*Runner + End chan byte +} + +type Runner struct { + // Start is a blocking function. + Start func() error + // Stop is not a blocking function, if Stop called, must let Start return. + // Notice: Stop maybe called multi times even if before start. + Stop func() error + lock sync.Mutex + status int +} + +func NewRunnerGroup() *RunnerGroup { + g := &RunnerGroup{} + g.Runners = make([]*Runner, 0) + g.End = make(chan byte) + return g +} + +func (g *RunnerGroup) Add(r *Runner) { + g.Runners = append(g.Runners, r) +} + +// Call Wait after all task have been added, +// Return the first ended start's result. +func (g *RunnerGroup) Wait() error { + e := make(chan error) + for _, v := range g.Runners { + v.status = 1 + go func(v *Runner) { + err := v.Start() + v.lock.Lock() + v.status = 0 + v.lock.Unlock() + select { + case <-g.End: + case e <- err: + } + }(v) + } + err := <-e + for _, v := range g.Runners { + for { + v.lock.Lock() + if v.status == 0 { + v.lock.Unlock() + break + } + v.lock.Unlock() + _ = v.Stop() + time.Sleep(300 * time.Millisecond) + } + } + close(g.End) + return err +} + +// Call Done if you want to stop all. +// return the stop's return which is not nil, do not guarantee, +// because starts may ended caused by itself. +func (g *RunnerGroup) Done() error { + if len(g.Runners) == 0 { + return nil + } + var e error + for _, v := range g.Runners { + for { + v.lock.Lock() + if v.status == 0 { + v.lock.Unlock() + break + } + v.lock.Unlock() + if err := v.Stop(); err != nil { + if e == nil { + e = err + } + } + time.Sleep(300 * time.Millisecond) + } + } + <-g.End + return e +} diff --git a/socks5_txthinking/server.go b/socks5_txthinking/server.go new file mode 100644 index 0000000..025444f --- /dev/null +++ b/socks5_txthinking/server.go @@ -0,0 +1,438 @@ +package socks5 + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "strings" + "time" + + cache "github.com/patrickmn/go-cache" +) + +var ( + // ErrUnsupportCmd is the error when got unsupport command + ErrUnsupportCmd = errors.New("Unsupport Command") + // ErrUserPassAuth is the error when got invalid username or password + ErrUserPassAuth = errors.New("Invalid Username or Password for Auth") +) + +// Server is socks5 server wrapper +type Server struct { + UserName string + Password string + Method byte + SupportedCommands []byte + Addr string + ServerAddr net.Addr + UDPConn *net.UDPConn + UDPExchanges *cache.Cache + TCPTimeout int + UDPTimeout int + Handle Handler + AssociatedUDP *cache.Cache + UDPSrc *cache.Cache + RunnerGroup *RunnerGroup + // RFC: [UDP ASSOCIATE] The server MAY use this information to limit access to the association. Default false, no limit. + LimitUDP bool +} + +// UDPExchange used to store client address and remote connection +type UDPExchange struct { + ClientAddr *net.UDPAddr + RemoteConn net.Conn +} + +// NewClassicServer return a server which allow none method +func NewClassicServer(addr, ip, username, password string, tcpTimeout, udpTimeout int) (*Server, error) { + _, p, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + saddr, err := Resolve("udp", net.JoinHostPort(ip, p)) + if err != nil { + return nil, err + } + m := MethodNone + if username != "" && password != "" { + m = MethodUsernamePassword + } + cs := cache.New(cache.NoExpiration, cache.NoExpiration) + cs1 := cache.New(cache.NoExpiration, cache.NoExpiration) + cs2 := cache.New(cache.NoExpiration, cache.NoExpiration) + s := &Server{ + Method: m, + UserName: username, + Password: password, + SupportedCommands: []byte{CmdConnect, CmdUDP}, + Addr: addr, + ServerAddr: saddr, + UDPExchanges: cs, + TCPTimeout: tcpTimeout, + UDPTimeout: udpTimeout, + AssociatedUDP: cs1, + UDPSrc: cs2, + RunnerGroup: NewRunnerGroup(), + } + return s, nil +} + +// Negotiate handle negotiate packet. +// This method do not handle gssapi(0x01) method now. +// Error or OK both replied. +func (s *Server) Negotiate(rw io.ReadWriter) error { + rq, err := NewNegotiationRequestFrom(rw) + if err != nil { + return err + } + var got bool + var m byte + for _, m = range rq.Methods { + if m == s.Method { + got = true + } + } + if !got { + rp := NewNegotiationReply(MethodUnsupportAll) + if _, err := rp.WriteTo(rw); err != nil { + return err + } + } + rp := NewNegotiationReply(s.Method) + if _, err := rp.WriteTo(rw); err != nil { + return err + } + + if s.Method == MethodUsernamePassword { + urq, err := NewUserPassNegotiationRequestFrom(rw) + if err != nil { + return err + } + if string(urq.Uname) != s.UserName || string(urq.Passwd) != s.Password { + urp := NewUserPassNegotiationReply(UserPassStatusFailure) + if _, err := urp.WriteTo(rw); err != nil { + return err + } + return ErrUserPassAuth + } + urp := NewUserPassNegotiationReply(UserPassStatusSuccess) + if _, err := urp.WriteTo(rw); err != nil { + return err + } + } + return nil +} + +// GetRequest get request packet from client, and check command according to SupportedCommands +// Error replied. +func (s *Server) GetRequest(rw io.ReadWriter) (*Request, error) { + r, err := NewRequestFrom(rw) + if err != nil { + return nil, err + } + var supported bool + for _, c := range s.SupportedCommands { + if r.Cmd == c { + supported = true + break + } + } + if !supported { + var p *Reply + if r.Atyp == ATYPIPv4 || r.Atyp == ATYPDomain { + p = NewReply(RepCommandNotSupported, ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + } else { + p = NewReply(RepCommandNotSupported, ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00}) + } + if _, err := p.WriteTo(rw); err != nil { + return nil, err + } + return nil, ErrUnsupportCmd + } + return r, nil +} + +// Run server +func (s *Server) ListenAndServe(h Handler) error { + if h == nil { + s.Handle = &DefaultHandle{} + } else { + s.Handle = h + } + + addr, err := net.ResolveTCPAddr("tcp", s.Addr) + if err != nil { + return err + } + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return err + } + s.RunnerGroup.Add(&Runner{ + Start: func() error { + for { + c, err := l.AcceptTCP() + if err != nil { + return err + } + go func(c *net.TCPConn) { + defer c.Close() + if err := s.Negotiate(c); err != nil { + log.Println(err) + return + } + r, err := s.GetRequest(c) + if err != nil { + log.Println(err) + return + } + if err := s.Handle.TCPHandle(s, c, r); err != nil { + log.Println(err) + } + }(c) + } + return nil + }, + Stop: func() error { + return l.Close() + }, + }) + + addr1, err := net.ResolveUDPAddr("udp", s.Addr) + if err != nil { + l.Close() + return err + } + s.UDPConn, err = net.ListenUDP("udp", addr1) + if err != nil { + l.Close() + return err + } + s.RunnerGroup.Add(&Runner{ + Start: func() error { + for { + b := make([]byte, 65507) + n, addr, err := s.UDPConn.ReadFromUDP(b) + if err != nil { + return err + } + go func(addr *net.UDPAddr, b []byte) { + d, err := NewDatagramFromBytes(b) + if err != nil { + log.Println(err) + return + } + if d.Frag != 0x00 { + log.Println("Ignore frag", d.Frag) + return + } + if err := s.Handle.UDPHandle(s, addr, d); err != nil { + log.Println(err) + return + } + }(addr, b[0:n]) + } + return nil + }, + Stop: func() error { + return s.UDPConn.Close() + }, + }) + return s.RunnerGroup.Wait() +} + +// Stop server +func (s *Server) Shutdown() error { + return s.RunnerGroup.Done() +} + +// Handler handle tcp, udp request +type Handler interface { + // Request has not been replied yet + TCPHandle(*Server, *net.TCPConn, *Request) error + UDPHandle(*Server, *net.UDPAddr, *Datagram) error +} + +// DefaultHandle implements Handler interface +type DefaultHandle struct { +} + +// TCPHandle auto handle request. You may prefer to do yourself. +func (h *DefaultHandle) TCPHandle(s *Server, c *net.TCPConn, r *Request) error { + if r.Cmd == CmdConnect { + rc, err := r.Connect(c) + if err != nil { + return err + } + defer rc.Close() + go func() { + var bf [1024 * 2]byte + for { + if s.TCPTimeout != 0 { + if err := rc.SetDeadline(time.Now().Add(time.Duration(s.TCPTimeout) * time.Second)); err != nil { + return + } + } + i, err := rc.Read(bf[:]) + if err != nil { + return + } + if _, err := c.Write(bf[0:i]); err != nil { + return + } + } + }() + var bf [1024 * 2]byte + for { + if s.TCPTimeout != 0 { + if err := c.SetDeadline(time.Now().Add(time.Duration(s.TCPTimeout) * time.Second)); err != nil { + return nil + } + } + i, err := c.Read(bf[:]) + if err != nil { + return nil + } + if _, err := rc.Write(bf[0:i]); err != nil { + return nil + } + } + return nil + } + if r.Cmd == CmdUDP { + caddr, err := r.UDP(c, s.ServerAddr) + if err != nil { + return err + } + ch := make(chan byte) + defer close(ch) + s.AssociatedUDP.Set(caddr.String(), ch, -1) + defer s.AssociatedUDP.Delete(caddr.String()) + io.Copy(ioutil.Discard, c) + if Debug { + log.Printf("A tcp connection that udp %#v associated closed\n", caddr.String()) + } + return nil + } + return ErrUnsupportCmd +} + +// UDPHandle auto handle packet. You may prefer to do yourself. +func (h *DefaultHandle) UDPHandle(s *Server, addr *net.UDPAddr, d *Datagram) error { + src := addr.String() + var ch chan byte + if s.LimitUDP { + any, ok := s.AssociatedUDP.Get(src) + if !ok { + return fmt.Errorf("This udp address %s is not associated with tcp", src) + } + ch = any.(chan byte) + } + send := func(ue *UDPExchange, data []byte) error { + select { + case <-ch: + return fmt.Errorf("This udp address %s is not associated with tcp", src) + default: + _, err := ue.RemoteConn.Write(data) + if err != nil { + return err + } + if Debug { + log.Printf("Sent UDP data to remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), data) + } + } + return nil + } + + dst := d.Address() + var ue *UDPExchange + iue, ok := s.UDPExchanges.Get(src + dst) + if ok { + ue = iue.(*UDPExchange) + return send(ue, d.Data) + } + + if Debug { + log.Printf("Call udp: %#v\n", dst) + } + var laddr string + any, ok := s.UDPSrc.Get(src + dst) + if ok { + laddr = any.(string) + } + rc, err := DialUDP("udp", laddr, dst) + if err != nil { + if !strings.Contains(err.Error(), "address already in use") && !strings.Contains(err.Error(), "can't assign requested address") { + return err + } + rc, err = DialUDP("udp", "", dst) + if err != nil { + return err + } + laddr = "" + } + if laddr == "" { + s.UDPSrc.Set(src+dst, rc.LocalAddr().String(), -1) + } + ue = &UDPExchange{ + ClientAddr: addr, + RemoteConn: rc, + } + if Debug { + log.Printf("Created remote UDP conn for client. client: %#v server: %#v remote: %#v\n", addr.String(), ue.RemoteConn.LocalAddr().String(), d.Address()) + } + if err := send(ue, d.Data); err != nil { + ue.RemoteConn.Close() + return err + } + s.UDPExchanges.Set(src+dst, ue, -1) + go func(ue *UDPExchange, dst string) { + defer func() { + ue.RemoteConn.Close() + s.UDPExchanges.Delete(ue.ClientAddr.String() + dst) + }() + var b [65507]byte + for { + select { + case <-ch: + if Debug { + log.Printf("The tcp that udp address %s associated closed\n", ue.ClientAddr.String()) + } + return + default: + if s.UDPTimeout != 0 { + if err := ue.RemoteConn.SetDeadline(time.Now().Add(time.Duration(s.UDPTimeout) * time.Second)); err != nil { + log.Println(err) + return + } + } + n, err := ue.RemoteConn.Read(b[:]) + if err != nil { + return + } + if Debug { + log.Printf("Got UDP data from remote. client: %#v server: %#v remote: %#v data: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), b[0:n]) + } + a, addr, port, err := ParseAddress(dst) + if err != nil { + log.Println(err) + return + } + if a == ATYPDomain { + addr = addr[1:] + } + d1 := NewDatagram(a, addr, port, b[0:n]) + if _, err := s.UDPConn.WriteToUDP(d1.Bytes(), ue.ClientAddr); err != nil { + return + } + if Debug { + log.Printf("Sent Datagram. client: %#v server: %#v remote: %#v data: %#v %#v %#v %#v %#v %#v datagram address: %#v\n", ue.ClientAddr.String(), ue.RemoteConn.LocalAddr().String(), ue.RemoteConn.RemoteAddr().String(), d1.Rsv, d1.Frag, d1.Atyp, d1.DstAddr, d1.DstPort, d1.Data, d1.Address()) + } + } + } + }(ue, dst) + return nil +} diff --git a/socks5_txthinking/server_side.go b/socks5_txthinking/server_side.go new file mode 100644 index 0000000..fcca015 --- /dev/null +++ b/socks5_txthinking/server_side.go @@ -0,0 +1,282 @@ +package socks5 + +import ( + "errors" + "io" + "log" +) + +var ( + // ErrVersion is version error + ErrVersion = errors.New("Invalid Version") + // ErrUserPassVersion is username/password auth version error + ErrUserPassVersion = errors.New("Invalid Version of Username Password Auth") + // ErrBadRequest is bad request error + ErrBadRequest = errors.New("Bad Request") +) + +// NewNegotiationRequestFrom read negotiation requst packet from client +func NewNegotiationRequestFrom(r io.Reader) (*NegotiationRequest, error) { + // memory strict + bb := make([]byte, 2) + if _, err := io.ReadFull(r, bb); err != nil { + return nil, err + } + if bb[0] != Ver { + return nil, ErrVersion + } + if bb[1] == 0 { + return nil, ErrBadRequest + } + ms := make([]byte, int(bb[1])) + if _, err := io.ReadFull(r, ms); err != nil { + return nil, err + } + if Debug { + log.Printf("Got NegotiationRequest: %#v %#v %#v\n", bb[0], bb[1], ms) + } + return &NegotiationRequest{ + Ver: bb[0], + NMethods: bb[1], + Methods: ms, + }, nil +} + +// NewNegotiationReply return negotiation reply packet can be writed into client +func NewNegotiationReply(method byte) *NegotiationReply { + return &NegotiationReply{ + Ver: Ver, + Method: method, + } +} + +// WriteTo write negotiation reply packet into client +func (r *NegotiationReply) WriteTo(w io.Writer) (int64, error) { + i, err := w.Write([]byte{r.Ver, r.Method}) + if err != nil { + return 0, err + } + if Debug { + log.Printf("Sent NegotiationReply: %#v %#v\n", r.Ver, r.Method) + } + return int64(i), nil +} + +// NewUserPassNegotiationRequestFrom read user password negotiation request packet from client +func NewUserPassNegotiationRequestFrom(r io.Reader) (*UserPassNegotiationRequest, error) { + bb := make([]byte, 2) + if _, err := io.ReadFull(r, bb); err != nil { + return nil, err + } + if bb[0] != UserPassVer { + return nil, ErrUserPassVersion + } + if bb[1] == 0 { + return nil, ErrBadRequest + } + ub := make([]byte, int(bb[1])+1) + if _, err := io.ReadFull(r, ub); err != nil { + return nil, err + } + if ub[int(bb[1])] == 0 { + return nil, ErrBadRequest + } + p := make([]byte, int(ub[int(bb[1])])) + if _, err := io.ReadFull(r, p); err != nil { + return nil, err + } + if Debug { + log.Printf("Got UserPassNegotiationRequest: %#v %#v %#v %#v %#v\n", bb[0], bb[1], ub[:int(bb[1])], ub[int(bb[1])], p) + } + return &UserPassNegotiationRequest{ + Ver: bb[0], + Ulen: bb[1], + Uname: ub[:int(bb[1])], + Plen: ub[int(bb[1])], + Passwd: p, + }, nil +} + +// NewUserPassNegotiationReply return negotiation username password reply packet can be writed into client +func NewUserPassNegotiationReply(status byte) *UserPassNegotiationReply { + return &UserPassNegotiationReply{ + Ver: UserPassVer, + Status: status, + } +} + +// WriteTo write negotiation username password reply packet into client +func (r *UserPassNegotiationReply) WriteTo(w io.Writer) (int64, error) { + i, err := w.Write([]byte{r.Ver, r.Status}) + if err != nil { + return 0, err + } + if Debug { + log.Printf("Sent UserPassNegotiationReply: %#v %#v \n", r.Ver, r.Status) + } + return int64(i), nil +} + +// NewRequestFrom read requst packet from client +func NewRequestFrom(r io.Reader) (*Request, error) { + bb := make([]byte, 4) + if _, err := io.ReadFull(r, bb); err != nil { + return nil, err + } + if bb[0] != Ver { + return nil, ErrVersion + } + var addr []byte + if bb[3] == ATYPIPv4 { + addr = make([]byte, 4) + if _, err := io.ReadFull(r, addr); err != nil { + return nil, err + } + } else if bb[3] == ATYPIPv6 { + addr = make([]byte, 16) + if _, err := io.ReadFull(r, addr); err != nil { + return nil, err + } + } else if bb[3] == ATYPDomain { + dal := make([]byte, 1) + if _, err := io.ReadFull(r, dal); err != nil { + return nil, err + } + if dal[0] == 0 { + return nil, ErrBadRequest + } + addr = make([]byte, int(dal[0])) + if _, err := io.ReadFull(r, addr); err != nil { + return nil, err + } + addr = append(dal, addr...) + } else { + return nil, ErrBadRequest + } + port := make([]byte, 2) + if _, err := io.ReadFull(r, port); err != nil { + return nil, err + } + if Debug { + log.Printf("Got Request: %#v %#v %#v %#v %#v %#v\n", bb[0], bb[1], bb[2], bb[3], addr, port) + } + return &Request{ + Ver: bb[0], + Cmd: bb[1], + Rsv: bb[2], + Atyp: bb[3], + DstAddr: addr, + DstPort: port, + }, nil +} + +// NewReply return reply packet can be writed into client, bndaddr should not have domain length +func NewReply(rep byte, atyp byte, bndaddr []byte, bndport []byte) *Reply { + if atyp == ATYPDomain { + bndaddr = append([]byte{byte(len(bndaddr))}, bndaddr...) + } + return &Reply{ + Ver: Ver, + Rep: rep, + Rsv: 0x00, + Atyp: atyp, + BndAddr: bndaddr, + BndPort: bndport, + } +} + +// WriteTo write reply packet into client +func (r *Reply) WriteTo(w io.Writer) (int64, error) { + i, err := w.Write(append(append([]byte{r.Ver, r.Rep, r.Rsv, r.Atyp}, r.BndAddr...), r.BndPort...)) + if err != nil { + return 0, err + } + if Debug { + log.Printf("Sent Reply: %#v %#v %#v %#v %#v %#v\n", r.Ver, r.Rep, r.Rsv, r.Atyp, r.BndAddr, r.BndPort) + } + return int64(i), nil +} + +func NewDatagramFromBytes(bb []byte) (*Datagram, error) { + n := len(bb) + minl := 4 + if n < minl { + return nil, ErrBadRequest + } + var addr []byte + if bb[3] == ATYPIPv4 { + minl += 4 + if n < minl { + return nil, ErrBadRequest + } + addr = bb[minl-4 : minl] + } else if bb[3] == ATYPIPv6 { + minl += 16 + if n < minl { + return nil, ErrBadRequest + } + addr = bb[minl-16 : minl] + } else if bb[3] == ATYPDomain { + minl += 1 + if n < minl { + return nil, ErrBadRequest + } + l := bb[4] + if l == 0 { + return nil, ErrBadRequest + } + minl += int(l) + if n < minl { + return nil, ErrBadRequest + } + addr = bb[minl-int(l) : minl] + addr = append([]byte{l}, addr...) + } else { + return nil, ErrBadRequest + } + minl += 2 + if n <= minl { + return nil, ErrBadRequest + } + port := bb[minl-2 : minl] + data := bb[minl:] + d := &Datagram{ + Rsv: bb[0:2], + Frag: bb[2], + Atyp: bb[3], + DstAddr: addr, + DstPort: port, + Data: data, + } + if Debug { + log.Printf("Got Datagram. data: %#v %#v %#v %#v %#v %#v datagram address: %#v\n", d.Rsv, d.Frag, d.Atyp, d.DstAddr, d.DstPort, d.Data, d.Address()) + } + return d, nil +} + +// NewDatagram return datagram packet can be writed into client, dstaddr should not have domain length +func NewDatagram(atyp byte, dstaddr []byte, dstport []byte, data []byte) *Datagram { + if atyp == ATYPDomain { + dstaddr = append([]byte{byte(len(dstaddr))}, dstaddr...) + } + return &Datagram{ + Rsv: []byte{0x00, 0x00}, + Frag: 0x00, + Atyp: atyp, + DstAddr: dstaddr, + DstPort: dstport, + Data: data, + } +} + +// Bytes return []byte +func (d *Datagram) Bytes() []byte { + b := make([]byte, 0) + b = append(b, d.Rsv...) + b = append(b, d.Frag) + b = append(b, d.Atyp) + b = append(b, d.DstAddr...) + b = append(b, d.DstPort...) + b = append(b, d.Data...) + return b +} diff --git a/socks5_txthinking/socks5.go b/socks5_txthinking/socks5.go new file mode 100644 index 0000000..d77253b --- /dev/null +++ b/socks5_txthinking/socks5.go @@ -0,0 +1,119 @@ +package socks5 + +const ( + // Ver is socks protocol version + Ver byte = 0x05 + + // MethodNone is none method + MethodNone byte = 0x00 + // MethodGSSAPI is gssapi method + MethodGSSAPI byte = 0x01 // MUST support // todo + // MethodUsernamePassword is username/assword auth method + MethodUsernamePassword byte = 0x02 // SHOULD support + // MethodUnsupportAll means unsupport all given methods + MethodUnsupportAll byte = 0xFF + + // UserPassVer is username/password auth protocol version + UserPassVer byte = 0x01 + // UserPassStatusSuccess is success status of username/password auth + UserPassStatusSuccess byte = 0x00 + // UserPassStatusFailure is failure status of username/password auth + UserPassStatusFailure byte = 0x01 // just other than 0x00 + + // CmdConnect is connect command + CmdConnect byte = 0x01 + // CmdBind is bind command + CmdBind byte = 0x02 + // CmdUDP is UDP command + CmdUDP byte = 0x03 + + // ATYPIPv4 is ipv4 address type + ATYPIPv4 byte = 0x01 // 4 octets + // ATYPDomain is domain address type + ATYPDomain byte = 0x03 // The first octet of the address field contains the number of octets of name that follow, there is no terminating NUL octet. + // ATYPIPv6 is ipv6 address type + ATYPIPv6 byte = 0x04 // 16 octets + + // RepSuccess means that success for repling + RepSuccess byte = 0x00 + // RepServerFailure means the server failure + RepServerFailure byte = 0x01 + // RepNotAllowed means the request not allowed + RepNotAllowed byte = 0x02 + // RepNetworkUnreachable means the network unreachable + RepNetworkUnreachable byte = 0x03 + // RepHostUnreachable means the host unreachable + RepHostUnreachable byte = 0x04 + // RepConnectionRefused means the connection refused + RepConnectionRefused byte = 0x05 + // RepTTLExpired means the TTL expired + RepTTLExpired byte = 0x06 + // RepCommandNotSupported means the request command not supported + RepCommandNotSupported byte = 0x07 + // RepAddressNotSupported means the request address not supported + RepAddressNotSupported byte = 0x08 +) + +// NegotiationRequest is the negotiation reqeust packet +type NegotiationRequest struct { + Ver byte + NMethods byte + Methods []byte // 1-255 bytes +} + +// NegotiationReply is the negotiation reply packet +type NegotiationReply struct { + Ver byte + Method byte +} + +// UserPassNegotiationRequest is the negotiation username/password reqeust packet +type UserPassNegotiationRequest struct { + Ver byte + Ulen byte + Uname []byte // 1-255 bytes + Plen byte + Passwd []byte // 1-255 bytes +} + +// UserPassNegotiationReply is the negotiation username/password reply packet +type UserPassNegotiationReply struct { + Ver byte + Status byte +} + +// Request is the request packet +type Request struct { + Ver byte + Cmd byte + Rsv byte // 0x00 + Atyp byte + DstAddr []byte + DstPort []byte // 2 bytes +} + +// Reply is the reply packet +type Reply struct { + Ver byte + Rep byte + Rsv byte // 0x00 + Atyp byte + // CONNECT socks server's address which used to connect to dst addr + // BIND ... + // UDP socks server's address which used to connect to dst addr + BndAddr []byte + // CONNECT socks server's port which used to connect to dst addr + // BIND ... + // UDP socks server's port which used to connect to dst addr + BndPort []byte // 2 bytes +} + +// Datagram is the UDP packet +type Datagram struct { + Rsv []byte // 0x00 0x00 + Frag byte + Atyp byte + DstAddr []byte + DstPort []byte // 2 bytes + Data []byte +} diff --git a/socks5_txthinking/udp.go b/socks5_txthinking/udp.go new file mode 100644 index 0000000..bc191be --- /dev/null +++ b/socks5_txthinking/udp.go @@ -0,0 +1,59 @@ +package socks5 + +import ( + "bytes" + "log" + "net" +) + +// UDP remote conn which u want to connect with your dialer. +// Error or OK both replied. +// Addr can be used to associate TCP connection with the coming UDP connection. +func (r *Request) UDP(c net.Conn, serverAddr net.Addr) (net.Addr, error) { + var clientAddr net.Addr + var err error + if bytes.Compare(r.DstPort, []byte{0x00, 0x00}) == 0 { + // If the requested Host/Port is all zeros, the relay should simply use the Host/Port that sent the request. + // https://stackoverflow.com/questions/62283351/how-to-use-socks-5-proxy-with-tidudpclient-properly + clientAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String()) + } else { + clientAddr, err = net.ResolveUDPAddr("udp", r.Address()) + } + if err != nil { + var p *Reply + if r.Atyp == ATYPIPv4 || r.Atyp == ATYPDomain { + p = NewReply(RepHostUnreachable, ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + } else { + p = NewReply(RepHostUnreachable, ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00}) + } + if _, err := p.WriteTo(c); err != nil { + return nil, err + } + return nil, err + } + if Debug { + log.Println("Client wants to start UDP talk use", clientAddr.String()) + } + a, addr, port, err := ParseAddress(serverAddr.String()) + if err != nil { + var p *Reply + if r.Atyp == ATYPIPv4 || r.Atyp == ATYPDomain { + p = NewReply(RepHostUnreachable, ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00}) + } else { + p = NewReply(RepHostUnreachable, ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00}) + } + if _, err := p.WriteTo(c); err != nil { + return nil, err + } + return nil, err + } + if a == ATYPDomain { + addr = addr[1:] + } + p := NewReply(RepSuccess, a, addr, port) + if _, err := p.WriteTo(c); err != nil { + return nil, err + } + + return clientAddr, nil +} diff --git a/socks5_txthinking/util.go b/socks5_txthinking/util.go new file mode 100644 index 0000000..cb58675 --- /dev/null +++ b/socks5_txthinking/util.go @@ -0,0 +1,135 @@ +package socks5 + +import ( + "bytes" + "encoding/binary" + "errors" + "net" + "strconv" +) + +// ParseAddress format address x.x.x.x:xx to raw address. +// addr contains domain length +func ParseAddress(address string) (a byte, addr []byte, port []byte, err error) { + var h, p string + h, p, err = net.SplitHostPort(address) + if err != nil { + return + } + ip := net.ParseIP(h) + if ip4 := ip.To4(); ip4 != nil { + a = ATYPIPv4 + addr = []byte(ip4) + } else if ip6 := ip.To16(); ip6 != nil { + a = ATYPIPv6 + addr = []byte(ip6) + } else { + a = ATYPDomain + addr = []byte{byte(len(h))} + addr = append(addr, []byte(h)...) + } + i, _ := strconv.Atoi(p) + port = make([]byte, 2) + binary.BigEndian.PutUint16(port, uint16(i)) + return +} + +// bytes to address +// addr contains domain length +func ParseBytesAddress(b []byte) (a byte, addr []byte, port []byte, err error) { + if len(b) < 1 { + err = errors.New("Invalid address") + return + } + a = b[0] + if a == ATYPIPv4 { + if len(b) < 1+4+2 { + err = errors.New("Invalid address") + return + } + addr = b[1 : 1+4] + port = b[1+4 : 1+4+2] + return + } + if a == ATYPIPv6 { + if len(b) < 1+16+2 { + err = errors.New("Invalid address") + return + } + addr = b[1 : 1+16] + port = b[1+16 : 1+16+2] + return + } + if a == ATYPDomain { + if len(b) < 1+1 { + err = errors.New("Invalid address") + return + } + l := int(b[1]) + if len(b) < 1+1+l+2 { + err = errors.New("Invalid address") + return + } + addr = b[1 : 1+1+l] + port = b[1+1+l : 1+1+l+2] + return + } + err = errors.New("Invalid address") + return +} + +// ToAddress format raw address to x.x.x.x:xx +// addr contains domain length +func ToAddress(a byte, addr []byte, port []byte) string { + var h, p string + if a == ATYPIPv4 || a == ATYPIPv6 { + h = net.IP(addr).String() + } + if a == ATYPDomain { + if len(addr) < 1 { + return "" + } + if len(addr) < int(addr[0])+1 { + return "" + } + h = string(addr[1:]) + } + p = strconv.Itoa(int(binary.BigEndian.Uint16(port))) + return net.JoinHostPort(h, p) +} + +// Address return request address like ip:xx +func (r *Request) Address() string { + var s string + if r.Atyp == ATYPDomain { + s = bytes.NewBuffer(r.DstAddr[1:]).String() + } else { + s = net.IP(r.DstAddr).String() + } + p := strconv.Itoa(int(binary.BigEndian.Uint16(r.DstPort))) + return net.JoinHostPort(s, p) +} + +// Address return request address like ip:xx +func (r *Reply) Address() string { + var s string + if r.Atyp == ATYPDomain { + s = bytes.NewBuffer(r.BndAddr[1:]).String() + } else { + s = net.IP(r.BndAddr).String() + } + p := strconv.Itoa(int(binary.BigEndian.Uint16(r.BndPort))) + return net.JoinHostPort(s, p) +} + +// Address return datagram address like ip:xx +func (d *Datagram) Address() string { + var s string + if d.Atyp == ATYPDomain { + s = bytes.NewBuffer(d.DstAddr[1:]).String() + } else { + s = net.IP(d.DstAddr).String() + } + p := strconv.Itoa(int(binary.BigEndian.Uint16(d.DstPort))) + return net.JoinHostPort(s, p) +} diff --git a/socks5_txthinking/util_test.go b/socks5_txthinking/util_test.go new file mode 100644 index 0000000..9a891e9 --- /dev/null +++ b/socks5_txthinking/util_test.go @@ -0,0 +1,9 @@ +package socks5 + +import "testing" + +func TestParseAddress(t *testing.T) { + t.Log(ParseAddress("127.0.0.1:80")) + t.Log(ParseAddress("[::1]:80")) + t.Log(ParseAddress("a.com:80")) +}