2025-04-11 23:20:07 +07:00
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
2025-11-20 01:25:35 +02:00
|
|
|
"context"
|
2026-04-26 15:35:01 +03:00
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2025-08-12 19:38:17 +03:00
|
|
|
"log"
|
2025-11-20 01:25:35 +02:00
|
|
|
"net"
|
2026-04-26 15:35:01 +03:00
|
|
|
"strings"
|
2025-08-12 19:38:17 +03:00
|
|
|
|
2026-03-25 15:26:39 +03:00
|
|
|
"github.com/Alexey71/opera-proxy/dialer"
|
2025-11-20 01:25:35 +02:00
|
|
|
"github.com/things-go/go-socks5"
|
2026-04-26 15:35:01 +03:00
|
|
|
"github.com/things-go/go-socks5/statute"
|
2025-04-11 23:20:07 +07:00
|
|
|
)
|
|
|
|
|
|
2026-04-26 15:35:01 +03:00
|
|
|
func NewSocksServer(dialer dialer.ContextDialer, logger *log.Logger, fakeSNI string) (*socks5.Server, error) {
|
2025-11-20 01:25:35 +02:00
|
|
|
opts := []socks5.Option{
|
|
|
|
|
socks5.WithLogger(socks5.NewLogger(logger)),
|
|
|
|
|
socks5.WithRule(
|
|
|
|
|
&socks5.PermitCommand{
|
|
|
|
|
EnableConnect: true,
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
socks5.WithResolver(DummySocksResolver{}),
|
2026-04-26 15:35:01 +03:00
|
|
|
socks5.WithConnectHandle(func(ctx context.Context, writer io.Writer, request *socks5.Request) error {
|
|
|
|
|
return handleSocksConnect(ctx, writer, request, dialer, fakeSNI)
|
|
|
|
|
}),
|
2025-11-20 01:25:35 +02:00
|
|
|
}
|
|
|
|
|
return socks5.NewServer(opts...), nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 15:35:01 +03:00
|
|
|
func handleSocksConnect(ctx context.Context, writer io.Writer, request *socks5.Request, upstream dialer.ContextDialer, fakeSNI string) error {
|
|
|
|
|
target, err := upstream.DialContext(ctx, "tcp", request.DestAddr.String())
|
|
|
|
|
if err != nil {
|
|
|
|
|
reply := statute.RepHostUnreachable
|
|
|
|
|
msg := err.Error()
|
|
|
|
|
if strings.Contains(msg, "refused") {
|
|
|
|
|
reply = statute.RepConnectionRefused
|
|
|
|
|
} else if strings.Contains(msg, "network is unreachable") {
|
|
|
|
|
reply = statute.RepNetworkUnreachable
|
|
|
|
|
}
|
|
|
|
|
if sendErr := socks5.SendReply(writer, reply, nil); sendErr != nil {
|
|
|
|
|
return fmt.Errorf("failed to send reply, %v", sendErr)
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("connect to %v failed, %v", request.RawDestAddr, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := socks5.SendReply(writer, statute.RepSuccess, target.LocalAddr()); err != nil {
|
|
|
|
|
target.Close()
|
|
|
|
|
return fmt.Errorf("failed to send reply, %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clientConn, ok := writer.(net.Conn)
|
|
|
|
|
if !ok {
|
|
|
|
|
target.Close()
|
|
|
|
|
return fmt.Errorf("writer is %T, expected net.Conn", writer)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proxy(ctx, clientConn, request.Reader, target, fakeSNI)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 01:25:35 +02:00
|
|
|
type DummySocksResolver struct{}
|
|
|
|
|
|
|
|
|
|
func (_ DummySocksResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
|
|
|
|
|
return ctx, nil, nil
|
2025-04-11 23:20:07 +07:00
|
|
|
}
|