Skip to content

Commit

Permalink
Add Makefile target to generate vendor directory
Browse files Browse the repository at this point in the history
  • Loading branch information
enfein committed Sep 21, 2024
1 parent b3de7db commit 40e0534
Show file tree
Hide file tree
Showing 22 changed files with 437 additions and 517 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
/release
/tools/build/**
!/tools/build/README.md
/vendor

*.exe
*.dll
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ lib: fmt vet
CGO_ENABLED=0 go test -bench=. -benchtime=5s ./pkg/cipher
go tool cover -html coverage.out -o coverage.html

# Generate vendor directory.
.PHONY: vendor
vendor:
go mod tidy
go mod vendor

# Build Android clients.
.PHONY: client-android
client-android: client-android-amd64 client-android-arm64
Expand Down
6 changes: 2 additions & 4 deletions pkg/cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ 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/protocol"
"github.com/enfein/mieru/pkg/socks5"
"github.com/enfein/mieru/pkg/socks5client"
"github.com/enfein/mieru/pkg/stderror"
"github.com/enfein/mieru/pkg/util"
"github.com/enfein/mieru/pkg/util/sockopts"
Expand Down Expand Up @@ -591,7 +589,7 @@ var clientRunFunc = func(s []string) error {
} else {
httpServerAddr = util.MaybeDecorateIPv6(util.LocalIPAddr()) + ":" + strconv.Itoa(int(config.GetHttpProxyPort()))
}
httpServer := http2socks.NewHTTPServer(httpServerAddr, &http2socks.Proxy{
httpServer := socks5.NewHTTPProxyServer(httpServerAddr, &socks5.HTTPProxy{
ProxyURI: "socks5://" + socks5Addr + "?timeout=10s",
})
log.Infof("mieru client HTTP proxy server is running")
Expand Down Expand Up @@ -661,7 +659,7 @@ var clientTestFunc = func(s []string) error {

httpClient := &http.Client{
Transport: &http.Transport{
Dial: socks5client.Dial(fmt.Sprintf("socks5://127.0.0.1:%d", config.GetSocks5Port()), socks5client.ConnectCmd),
Dial: socks5.Dial(fmt.Sprintf("socks5://127.0.0.1:%d", config.GetSocks5Port()), socks5.ConnectCmd),
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return nil
Expand Down
3 changes: 1 addition & 2 deletions pkg/cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
"github.com/enfein/mieru/pkg/appctl/appctlpb"
"github.com/enfein/mieru/pkg/cipher"
"github.com/enfein/mieru/pkg/egress"
"github.com/enfein/mieru/pkg/http2socks"
"github.com/enfein/mieru/pkg/log"
"github.com/enfein/mieru/pkg/metrics"
"github.com/enfein/mieru/pkg/protocol"
Expand Down Expand Up @@ -391,7 +390,7 @@ var serverRunFunc = func(s []string) error {
if clientDecryptionMetricGroup := metrics.GetMetricGroupByName(cipher.ClientDecryptionMetricGroupName); clientDecryptionMetricGroup != nil {
clientDecryptionMetricGroup.DisableLogging()
}
if httpMetricGroup := metrics.GetMetricGroupByName(http2socks.HTTPMetricGroupName); httpMetricGroup != nil {
if httpMetricGroup := metrics.GetMetricGroupByName(socks5.HTTPMetricGroupName); httpMetricGroup != nil {
httpMetricGroup.DisableLogging()
}

Expand Down
25 changes: 25 additions & 0 deletions pkg/socks5/LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,28 @@ 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.

-------------------------------------------------------------------------------

Copyright (c) 2012, Hailiang Wang. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 changes: 0 additions & 37 deletions pkg/socks5/README.md

This file was deleted.

258 changes: 258 additions & 0 deletions pkg/socks5/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// Copyright 2012, Hailiang Wang. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package socks5

import (
"bytes"
"context"
"fmt"
"net"
"strconv"
"time"

"github.com/enfein/mieru/pkg/util"
)

// Client contains socks5 client configuration.
type Client struct {
Host string
Credential *Credential
Timeout time.Duration
CmdType byte
}

// Dial returns the dial function to be used in http.Transport object.
// Argument proxyURI should be in the format: "socks5://user:[email protected]:1080?timeout=5s".
// 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))
}
cfg, err := parse(proxyURI)
if err != nil {
return dialError(err)
}
cfg.CmdType = cmdType
return func(_, targetAddr string) (conn net.Conn, err error) {
return cfg.dialSocks5(targetAddr)
}
}

// DialSocks5Proxy returns two connections that can be used to send TCP and UDP traffic.
func DialSocks5Proxy(c *Client) func(string, string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) {
if c == nil {
return dialErrorLong(fmt.Errorf("socks5 client configuration is nil"))
}
if c.Host == "" {
return dialErrorLong(fmt.Errorf("socks5 client configuration has no proxy host"))
}
if c.CmdType != ConnectCmd && c.CmdType != BindCmd && c.CmdType != UDPAssociateCmd {
return dialErrorLong(fmt.Errorf("socks5 client configuration command type %v is invalid", c.CmdType))
}
return func(_, targetAddr string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) {
return c.dialSocks5Long(targetAddr)
}
}

// TransceiveUDPPacket sends a single UDP associate message and returns the response.
func TransceiveUDPPacket(conn *net.UDPConn, proxyAddr, dstAddr *net.UDPAddr, payload []byte) ([]byte, error) {
header := []byte{0, 0, 0}
if dstAddr.IP.To4() != nil {
header = append(header, ipv4Address)
header = append(header, dstAddr.IP.To4()...)
header = append(header, byte(dstAddr.Port>>8))
header = append(header, byte(dstAddr.Port))
} else {
header = append(header, ipv6Address)
header = append(header, dstAddr.IP.To16()...)
header = append(header, byte(dstAddr.Port>>8))
header = append(header, byte(dstAddr.Port))
}
if _, err := conn.WriteToUDP(append(header, payload...), proxyAddr); err != nil {
return nil, fmt.Errorf("WriteToUDP() failed: %v", err)
}
buf := make([]byte, 65536)
n, readAddr, err := conn.ReadFromUDP(buf)
if err != nil {
return nil, fmt.Errorf("ReadFromUDP() failed: %v", err)
}
if readAddr.Port != proxyAddr.Port {
// We don't compare the IP address because a wildcard address like 0.0.0.0 can be used.
return nil, fmt.Errorf("unexpected read from a different address")
}
if n <= 10 {
return nil, fmt.Errorf("UDP associate response is too short")
}
if buf[3] == ipv4Address {
// Header length is 10 bytes.
return buf[10:n], nil
} else if buf[3] == ipv6Address {
// Header length is 22 bytes.
return buf[22:n], nil
} else {
return nil, fmt.Errorf("UDP assciate unsupport address type")
}
}

func dialError(err error) func(string, string) (net.Conn, error) {
return func(_, _ string) (net.Conn, error) {
return nil, err
}
}

func dialErrorLong(err error) func(string, string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) {
return func(_, _ string) (net.Conn, *net.UDPConn, *net.UDPAddr, error) {
return nil, nil, nil, err
}
}

func (c *Client) dialSocks5(targetAddr string) (conn net.Conn, err error) {
conn, _, _, err = c.dialSocks5Long(targetAddr)
return
}

func (c *Client) dialSocks5Long(targetAddr string) (conn net.Conn, udpConn *net.UDPConn, proxyUDPAddr *net.UDPAddr, err error) {
ctx := context.Background()
var cancelFunc context.CancelFunc
if c.Timeout != 0 {
ctx, cancelFunc = context.WithTimeout(ctx, c.Timeout)
defer cancelFunc()
}
dialer := &net.Dialer{}
conn, err = dialer.DialContext(ctx, "tcp", c.Host)
if err != nil {
return nil, nil, nil, err
}
defer func() {
if err != nil && conn != nil {
conn.Close()
}
}()

// Prepare the first request.
var req bytes.Buffer
version := byte(socks5Version)
method := byte(noAuth)
if c.Credential != nil {
method = userPassAuth
}
req.Write([]byte{
version,
1, // number of methods
method,
})

// Process the first response.
var resp []byte
resp, err = util.SendReceive(ctx, conn, req.Bytes())
if err != nil {
return nil, nil, nil, err
} else if len(resp) != 2 {
return nil, nil, nil, fmt.Errorf("server does not respond properly")
} else if resp[0] != version {
return nil, nil, nil, fmt.Errorf("server does not support socks5")
} else if resp[1] != method {
return nil, nil, nil, fmt.Errorf("socks method negotiation failed")
}
if c.Credential != nil {
version := byte(userPassAuthVersion)
req.Reset()
req.Write([]byte{version})
req.Write([]byte{byte(len(c.Credential.User))})
req.Write([]byte(c.Credential.User))
req.Write([]byte{byte(len(c.Credential.Password))})
req.Write([]byte(c.Credential.Password))

resp, err = util.SendReceive(ctx, conn, req.Bytes())
if err != nil {
return nil, nil, nil, err
} else if len(resp) != 2 {
return nil, nil, nil, fmt.Errorf("server does not respond properly")
} else if resp[0] != version {
return nil, nil, nil, fmt.Errorf("server does not support user/password version 1")
} else if resp[1] != authSuccess {
return nil, nil, nil, fmt.Errorf("user/password login failed")
}
}

// 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.Write([]byte{
socks5Version,
c.CmdType,
0, // reserved, must be zero
})

hostIP := net.ParseIP(host)
if hostIP == nil {
// Domain name.
req.Write([]byte{fqdnAddress, byte(len(host))})
req.Write([]byte(host))
} else {
hostIPv4 := hostIP.To4()
if hostIPv4 != nil {
// IPv4
req.Write([]byte{ipv4Address})
req.Write(hostIPv4)
} else {
// IPv6
req.Write([]byte{ipv6Address})
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 = util.SendReceive(ctx, conn, req.Bytes())
if err != nil {
return
} else if len(resp) < 10 {
return nil, nil, nil, fmt.Errorf("server response is too short")
} else if resp[1] != 0 {
return nil, nil, nil, fmt.Errorf("socks5 connection is not successful")
}

if c.CmdType == UDPAssociateCmd {
// Get the endpoint to relay UDP packets.
var ip net.IP
switch resp[3] {
case ipv4Address:
ip = net.IP(resp[4:8])
port = uint16(resp[8])<<8 + uint16(resp[9])
case ipv6Address:
if len(resp) < 22 {
return nil, nil, nil, fmt.Errorf("server response is too short")
}
ip = net.IP(resp[4:20])
port = uint16(resp[20])<<8 + uint16(resp[21])
default:
return nil, nil, nil, fmt.Errorf("unsupported bind address")
}
proxyUDPAddr = &net.UDPAddr{IP: ip, Port: int(port)}

// Listen to a new UDP endpoint.
udpAddr := &net.UDPAddr{IP: net.IP{0, 0, 0, 0}, Port: 0}
udpConn, err = net.ListenUDP("udp4", udpAddr)
if err != nil {
return nil, nil, nil, err
}
return conn, udpConn, proxyUDPAddr, nil
}

return conn, nil, nil, nil
}
Loading

0 comments on commit 40e0534

Please sign in to comment.