Files
opera-proxy/handler/socks.go
T
2026-04-26 15:35:01 +03:00

68 lines
1.9 KiB
Go

package handler
import (
"context"
"fmt"
"io"
"log"
"net"
"strings"
"github.com/Alexey71/opera-proxy/dialer"
"github.com/things-go/go-socks5"
"github.com/things-go/go-socks5/statute"
)
func NewSocksServer(dialer dialer.ContextDialer, logger *log.Logger, fakeSNI string) (*socks5.Server, error) {
opts := []socks5.Option{
socks5.WithLogger(socks5.NewLogger(logger)),
socks5.WithRule(
&socks5.PermitCommand{
EnableConnect: true,
},
),
socks5.WithResolver(DummySocksResolver{}),
socks5.WithConnectHandle(func(ctx context.Context, writer io.Writer, request *socks5.Request) error {
return handleSocksConnect(ctx, writer, request, dialer, fakeSNI)
}),
}
return socks5.NewServer(opts...), nil
}
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
}
type DummySocksResolver struct{}
func (_ DummySocksResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
return ctx, nil, nil
}