mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 20:44:02 +03:00
FILE / ScuroNeko/mtg
network/socks5.go
Исходный файл и его история в репозитории.
163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package network
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/url"
|
|
|
|
"github.com/9seconds/mtg/v2/essentials"
|
|
"github.com/txthinking/socks5"
|
|
)
|
|
|
|
type socks5Dialer struct {
|
|
Dialer
|
|
|
|
username []byte
|
|
password []byte
|
|
proxyAddress string
|
|
}
|
|
|
|
func (s socks5Dialer) Dial(network, address string) (essentials.Conn, error) {
|
|
return s.DialContext(context.Background(), network, address)
|
|
}
|
|
|
|
func (s socks5Dialer) DialContext(ctx context.Context, network, address string) (essentials.Conn, error) {
|
|
switch network {
|
|
case "tcp", "tcp4", "tcp6":
|
|
default:
|
|
return nil, fmt.Errorf("%s network type is not supported", network)
|
|
}
|
|
|
|
conn, err := s.Dialer.DialContext(ctx, network, s.proxyAddress)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot dial to the proxy: %w", err)
|
|
}
|
|
|
|
if err := s.handshake(conn); err != nil {
|
|
conn.Close() //nolint: errcheck
|
|
|
|
return nil, fmt.Errorf("cannot perform a handshake: %w", err)
|
|
}
|
|
|
|
if err := s.connect(conn, address); err != nil {
|
|
conn.Close() //nolint: errcheck
|
|
|
|
return nil, fmt.Errorf("cannot connect to a destination host %s: %w", address, err)
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func (s socks5Dialer) handshake(conn io.ReadWriter) error {
|
|
authMethod := socks5.MethodUsernamePassword
|
|
if len(s.username)+len(s.password) == 0 {
|
|
authMethod = socks5.MethodNone
|
|
}
|
|
|
|
if err := s.handshakeNegotiation(conn, authMethod); err != nil {
|
|
return fmt.Errorf("cannot perform negotiation: %w", err)
|
|
}
|
|
|
|
if authMethod == socks5.MethodNone {
|
|
return nil
|
|
}
|
|
|
|
if err := s.handshakeAuth(conn); err != nil {
|
|
return fmt.Errorf("cannot authenticate: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s socks5Dialer) handshakeNegotiation(conn io.ReadWriter, authMethod byte) error {
|
|
request := socks5.NewNegotiationRequest([]byte{authMethod})
|
|
if _, err := request.WriteTo(conn); err != nil {
|
|
return fmt.Errorf("cannot send request: %w", err)
|
|
}
|
|
|
|
response, err := socks5.NewNegotiationReplyFrom(conn)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot read response: %w", err)
|
|
}
|
|
|
|
if response.Method != authMethod {
|
|
return fmt.Errorf("%v is unsupported auth method", authMethod)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s socks5Dialer) handshakeAuth(conn io.ReadWriter) error {
|
|
request := socks5.NewUserPassNegotiationRequest(s.username, s.password)
|
|
|
|
if _, err := request.WriteTo(conn); err != nil {
|
|
return fmt.Errorf("cannot send a request: %w", err)
|
|
}
|
|
|
|
response, err := socks5.NewUserPassNegotiationReplyFrom(conn)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot read a response: %w", err)
|
|
}
|
|
|
|
if response.Status != socks5.UserPassStatusSuccess {
|
|
return fmt.Errorf("authenticate has failed: %v", response.Status)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s socks5Dialer) connect(conn io.ReadWriter, address string) error {
|
|
addrType, host, port, err := socks5.ParseAddress(address)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot parse address: %w", err)
|
|
}
|
|
|
|
if addrType == socks5.ATYPDomain {
|
|
host = host[1:]
|
|
}
|
|
|
|
request := socks5.NewRequest(socks5.CmdConnect, addrType, host, port)
|
|
|
|
if _, err := request.WriteTo(conn); err != nil {
|
|
return fmt.Errorf("cannot send a request: %w", err)
|
|
}
|
|
|
|
response, err := socks5.NewReplyFrom(conn)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot read a response: %w", err)
|
|
}
|
|
|
|
if response.Rep != socks5.RepSuccess {
|
|
return fmt.Errorf("unsuccessful request: %v", response.Rep)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewSocks5Dialer build a new dialer from a given one (so, in theory you can
|
|
// chain here). Proxy parameters are passed with URI in a form of:
|
|
//
|
|
// socks5://[user:[password]]@host:port
|
|
func NewSocks5Dialer(baseDialer Dialer, proxyURL *url.URL) (Dialer, error) {
|
|
if _, _, err := net.SplitHostPort(proxyURL.Host); err != nil {
|
|
return nil, fmt.Errorf("incorrect url %s", proxyURL.Redacted())
|
|
}
|
|
|
|
dialer := socks5Dialer{
|
|
Dialer: baseDialer,
|
|
proxyAddress: proxyURL.Host,
|
|
}
|
|
|
|
if proxyURL.User != nil {
|
|
password, isSet := proxyURL.User.Password()
|
|
if isSet {
|
|
dialer.username = []byte(proxyURL.User.Username())
|
|
dialer.password = []byte(password)
|
|
}
|
|
}
|
|
|
|
return dialer, nil
|
|
}
|