194 lines
4.2 KiB
Go
194 lines
4.2 KiB
Go
package lspool
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPrepareLSProxyURLPassesThroughHTTPProxy(t *testing.T) {
|
|
t.Cleanup(closeAllLSProxyBridgesForTest)
|
|
|
|
got, err := prepareLSProxyURL("http://proxy.example.com:8080")
|
|
require.NoError(t, err)
|
|
require.Equal(t, "http://proxy.example.com:8080", got)
|
|
}
|
|
|
|
func TestPrepareLSProxyURLBridgesSOCKS5ForLS(t *testing.T) {
|
|
t.Cleanup(closeAllLSProxyBridgesForTest)
|
|
|
|
targetAddr, closeTarget := startBridgeEchoServer(t)
|
|
defer closeTarget()
|
|
|
|
socksURL, closeSOCKS := startBridgeSOCKS5Server(t)
|
|
defer closeSOCKS()
|
|
|
|
bridgeURL, err := prepareLSProxyURL(socksURL)
|
|
require.NoError(t, err)
|
|
require.True(t, strings.HasPrefix(bridgeURL, "http://127.0.0.1:"))
|
|
|
|
// Same SOCKS upstream should reuse the same local bridge.
|
|
reusedURL, err := prepareLSProxyURL(socksURL)
|
|
require.NoError(t, err)
|
|
require.Equal(t, bridgeURL, reusedURL)
|
|
|
|
bridgeAddr := strings.TrimPrefix(bridgeURL, "http://")
|
|
conn, err := net.Dial("tcp", bridgeAddr)
|
|
require.NoError(t, err)
|
|
defer conn.Close()
|
|
|
|
_, err = fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", targetAddr, targetAddr)
|
|
require.NoError(t, err)
|
|
|
|
reader := bufio.NewReader(conn)
|
|
resp, err := readConnectResponse(reader)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
|
|
_, err = conn.Write([]byte("ping"))
|
|
require.NoError(t, err)
|
|
|
|
reply := make([]byte, 4)
|
|
_, err = io.ReadFull(reader, reply)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "pong", string(reply))
|
|
}
|
|
|
|
func startBridgeEchoServer(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func(c net.Conn) {
|
|
defer c.Close()
|
|
buf := make([]byte, 4)
|
|
if _, err := io.ReadFull(c, buf); err != nil {
|
|
return
|
|
}
|
|
if string(buf) == "ping" {
|
|
_, _ = c.Write([]byte("pong"))
|
|
}
|
|
}(conn)
|
|
}
|
|
}()
|
|
|
|
return ln.Addr().String(), func() {
|
|
_ = ln.Close()
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func startBridgeSOCKS5Server(t *testing.T) (string, func()) {
|
|
t.Helper()
|
|
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
for {
|
|
conn, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go handleBridgeSOCKS5Conn(conn)
|
|
}
|
|
}()
|
|
|
|
return "socks5://" + ln.Addr().String(), func() {
|
|
_ = ln.Close()
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func handleBridgeSOCKS5Conn(conn net.Conn) {
|
|
header := make([]byte, 2)
|
|
if _, err := io.ReadFull(conn, header); err != nil {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
methods := make([]byte, int(header[1]))
|
|
if _, err := io.ReadFull(conn, methods); err != nil {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
_, _ = conn.Write([]byte{0x05, 0x00})
|
|
|
|
reqHeader := make([]byte, 4)
|
|
if _, err := io.ReadFull(conn, reqHeader); err != nil {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
if reqHeader[0] != 0x05 || reqHeader[1] != 0x01 {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
|
|
targetHost, ok := readSOCKS5Addr(conn, reqHeader[3])
|
|
if !ok {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
portBuf := make([]byte, 2)
|
|
if _, err := io.ReadFull(conn, portBuf); err != nil {
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
targetAddr := fmt.Sprintf("%s:%d", targetHost, binary.BigEndian.Uint16(portBuf))
|
|
|
|
targetConn, err := net.Dial("tcp", targetAddr)
|
|
if err != nil {
|
|
_, _ = conn.Write([]byte{0x05, 0x01, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
|
|
_ = conn.Close()
|
|
return
|
|
}
|
|
|
|
_, _ = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
|
|
tunnelConns(conn, targetConn)
|
|
}
|
|
|
|
func readSOCKS5Addr(conn net.Conn, atyp byte) (string, bool) {
|
|
switch atyp {
|
|
case 0x01:
|
|
buf := make([]byte, 4)
|
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
return "", false
|
|
}
|
|
return net.IP(buf).String(), true
|
|
case 0x03:
|
|
lenBuf := make([]byte, 1)
|
|
if _, err := io.ReadFull(conn, lenBuf); err != nil {
|
|
return "", false
|
|
}
|
|
buf := make([]byte, int(lenBuf[0]))
|
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
return "", false
|
|
}
|
|
return string(buf), true
|
|
case 0x04:
|
|
buf := make([]byte, 16)
|
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
return "", false
|
|
}
|
|
return net.IP(buf).String(), true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|