Initial commit
This commit is contained in:
commit
786f71c7f7
116
addr.go
Normal file
116
addr.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AddrZero is zero address
|
||||||
|
AddrZero = &Addr{IP: net.IPv4zero, Port: 0}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Addr implements net.Addr interface
|
||||||
|
type Addr struct {
|
||||||
|
IP net.IP
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrFromString returns new address from string representation
|
||||||
|
func AddrFromString(address string) (*Addr, error) {
|
||||||
|
if address == "" {
|
||||||
|
return AddrZero, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, err := splitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &Addr{Port: port}
|
||||||
|
addr.IP = net.ParseIP(host)
|
||||||
|
if addr.IP == nil {
|
||||||
|
addr.Host = host
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddrFromSocks(atyp ATYP, host []byte, port []byte) *Addr {
|
||||||
|
addr := &Addr{Port: int(binary.BigEndian.Uint16(port))}
|
||||||
|
|
||||||
|
switch atyp {
|
||||||
|
case ATYPFQDN:
|
||||||
|
addr.Host = string(host[1:])
|
||||||
|
default:
|
||||||
|
addr.IP = net.IP(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Socks returns socks protocol formatted address type, address and port
|
||||||
|
func (s *Addr) Socks() (ATYP, []byte, []byte) {
|
||||||
|
portBytes := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(portBytes, uint16(s.Port))
|
||||||
|
|
||||||
|
if s.IP != nil {
|
||||||
|
if ip4 := s.IP.To4(); ip4 != nil {
|
||||||
|
return ATYPIPv4, []byte(ip4), portBytes
|
||||||
|
} else if ip6 := s.IP.To16(); ip6 != nil {
|
||||||
|
return ATYPIPv6, []byte(ip6), portBytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := []byte{byte(len(s.Host))}
|
||||||
|
addr = append(addr, []byte(s.Host)...)
|
||||||
|
return ATYPFQDN, addr, portBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDP returns udp address
|
||||||
|
func (s *Addr) UDP() (*net.UDPAddr, error) {
|
||||||
|
if s.IP != nil {
|
||||||
|
return &net.UDPAddr{IP: s.IP, Port: s.Port}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.ResolveUDPAddr("udp", s.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network returns address Network
|
||||||
|
func (s *Addr) Network() string {
|
||||||
|
return "socks"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Addr) String() string {
|
||||||
|
if s == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
|
||||||
|
port := strconv.Itoa(s.Port)
|
||||||
|
if s.IP == nil {
|
||||||
|
return net.JoinHostPort(s.Host, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return net.JoinHostPort(s.IP.String(), port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitHostPort(address string) (string, int, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
portInt, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if 1 > portInt || portInt > 0xffff {
|
||||||
|
return "", 0, errors.New("port number out of range " + port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return host, portInt, nil
|
||||||
|
}
|
83
datagram.go
Normal file
83
datagram.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Datagram is the datagram packet
|
||||||
|
type Datagram struct {
|
||||||
|
ATYP ATYP
|
||||||
|
DstAddr []byte
|
||||||
|
DstPort []byte
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatagram creates new datagram packet
|
||||||
|
func NewDatagram(atyp ATYP, host []byte, port []byte, data []byte) *Datagram {
|
||||||
|
return &Datagram{
|
||||||
|
ATYP: atyp,
|
||||||
|
DstAddr: host,
|
||||||
|
DstPort: port,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDatagramFrom reads datagram packet from reader
|
||||||
|
func NewDatagramFrom(r io.Reader) (*Datagram, error) {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal([]byte{Reserved, Reserved}, buf[:1]) {
|
||||||
|
return nil, ErrBadDatagram
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[2] != DatagramStandalone {
|
||||||
|
return nil, ErrFragmentedDatagram
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := ReadAddress(r, ATYP(buf[3]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, port); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
datagram := &Datagram{
|
||||||
|
ATYP: ATYP(buf[3]),
|
||||||
|
DstAddr: addr,
|
||||||
|
DstPort: port,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
return datagram, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes datagram packet
|
||||||
|
func (s *Datagram) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write(s.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Datagram) Bytes() []byte {
|
||||||
|
buf := bytes.NewBuffer([]byte{Reserved, Reserved, DatagramStandalone, byte(s.ATYP)})
|
||||||
|
buf.Write(s.DstAddr)
|
||||||
|
buf.Write(s.DstPort)
|
||||||
|
buf.Write(s.Data)
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
237
dialer.go
Normal file
237
dialer.go
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNetworkNotImplemented is the network not implemented error
|
||||||
|
ErrNetworkNotImplemented = errors.New("network not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
noDeadline = time.Time{}
|
||||||
|
expiredDeadline = time.Unix(1, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dialer is the socks5 dialer
|
||||||
|
type Dialer struct {
|
||||||
|
Server string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
ProxyDial func(ctx context.Context, network string, addr string) (net.Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Dialer) Dial(network string, address string) (net.Conn, error) {
|
||||||
|
return s.DialContext(context.Background(), network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Dialer) DialContext(ctx context.Context, network string, address string) (net.Conn, error) {
|
||||||
|
proxy, err := AddrFromString(s.Server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: network, Source: proxy, Addr: AddrZero, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := AddrFromString(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: network, Source: proxy, Addr: AddrZero, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "tcp", "tcp4", "tcp6":
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
default:
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: network, Source: proxy, Addr: addr, Err: ErrNetworkNotImplemented}
|
||||||
|
}
|
||||||
|
|
||||||
|
var conn net.Conn
|
||||||
|
if s.ProxyDial != nil {
|
||||||
|
conn, err = s.ProxyDial(ctx, "tcp", s.Server)
|
||||||
|
} else {
|
||||||
|
var dd net.Dialer
|
||||||
|
conn, err = dd.DialContext(ctx, "tcp", s.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: "tcp", Source: proxy, Addr: addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() {
|
||||||
|
conn.SetDeadline(deadline)
|
||||||
|
defer conn.SetDeadline(noDeadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctxErr error
|
||||||
|
if ctx != context.Background() {
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
close(doneCh)
|
||||||
|
if ctxErr == nil {
|
||||||
|
ctxErr = <-errCh
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
conn.SetDeadline(expiredDeadline)
|
||||||
|
errCh <- ctx.Err()
|
||||||
|
case <-doneCh:
|
||||||
|
errCh <- nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.negotiate(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: "tcp", Source: proxy, Addr: addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||||
|
uc, err := s.handleUDP(conn, addr)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: "udp", Source: proxy, Addr: addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uc, ctxErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.handleTCP(conn, addr); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, &net.OpError{Op: "socks", Net: "tcp", Source: proxy, Addr: addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, ctxErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Dialer) DialUDP() (net.PacketConn, error) {
|
||||||
|
conn, err := s.DialContext(context.Background(), "udp", "")
|
||||||
|
return conn.(net.PacketConn), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Dialer) handleTCP(conn net.Conn, addr *Addr) error {
|
||||||
|
atyp, host, port := addr.Socks()
|
||||||
|
|
||||||
|
req := NewRequest(CommandConnect, atyp, host, port)
|
||||||
|
if _, err := req.WriteTo(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep, err := NewReplyFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rep.Status != ReplyStatusSuccess {
|
||||||
|
return fmt.Errorf("bad reply status: %s", rep.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Dialer) handleUDP(conn net.Conn, addr *Addr) (*UDPConn, error) {
|
||||||
|
atyp, host, port := addr.Socks()
|
||||||
|
|
||||||
|
req := NewRequest(CommandUDPAssociate, atyp, host, port)
|
||||||
|
if _, err := req.WriteTo(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep, err := NewReplyFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rep.Status != ReplyStatusSuccess {
|
||||||
|
return nil, fmt.Errorf("bad reply status: %s", rep.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
packetConn, err := net.ListenPacket("udp", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
io.Copy(io.Discard, conn)
|
||||||
|
conn.Close()
|
||||||
|
packetConn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
remoteAddr, err := AddrFromSocks(rep.ATYP, rep.BindAddr, rep.BindPort).UDP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if remoteAddr.IP.IsUnspecified() {
|
||||||
|
serverAddr, err := net.ResolveUDPAddr("udp", s.Server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolve udp address %s: %w", s.Server, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteAddr.IP = serverAddr.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
udpConn := &UDPConn{
|
||||||
|
PacketConn: packetConn,
|
||||||
|
TCPConn: conn,
|
||||||
|
ServerAddr: remoteAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr != AddrZero {
|
||||||
|
connectedAddr, err := addr.UDP()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
udpConn.ConnectedAddr = connectedAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
return udpConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Dialer) negotiate(conn net.Conn) error {
|
||||||
|
method := AuthMethodNone
|
||||||
|
if s.Username != "" && s.Password != "" {
|
||||||
|
method = AuthMethodPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
negReq := NewNegotiationRequest([]AuthMethod{method})
|
||||||
|
if _, err := negReq.WriteTo(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
negRep, err := NewNegotiationReplyFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if negRep.Method != method {
|
||||||
|
return ErrAuthMethodNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
if negRep.Method == AuthMethodPassword {
|
||||||
|
passReq := NewPasswordNegotiationRequest(s.Username, s.Password)
|
||||||
|
if _, err := passReq.WriteTo(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
passRep, err := NewPasswordNegotiationReplyFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if passRep.Status != PasswordStatusSuccess {
|
||||||
|
return ErrPasswordAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
108
negotiation.go
Normal file
108
negotiation.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NegotiationRequest is the negotiation request packet
|
||||||
|
type NegotiationRequest struct {
|
||||||
|
NMethods int
|
||||||
|
Methods []AuthMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNegotiationRequest returns new negotiation request packet
|
||||||
|
func NewNegotiationRequest(methods []AuthMethod) *NegotiationRequest {
|
||||||
|
return &NegotiationRequest{
|
||||||
|
NMethods: len(methods),
|
||||||
|
Methods: methods,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNegotiationRequestFrom reads negotiation request packet from reader
|
||||||
|
func NewNegotiationRequestFrom(r io.Reader) (*NegotiationRequest, error) {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != Version {
|
||||||
|
return nil, ErrVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
methodsLen := int(buf[1])
|
||||||
|
if methodsLen == 0 {
|
||||||
|
return nil, ErrBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
methodsBytes := make([]byte, methodsLen)
|
||||||
|
if _, err := io.ReadFull(r, methodsBytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
methods := make([]AuthMethod, methodsLen)
|
||||||
|
for i := range methodsBytes {
|
||||||
|
methods[i] = AuthMethod(methodsBytes[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &NegotiationRequest{
|
||||||
|
NMethods: methodsLen,
|
||||||
|
Methods: methods,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes negotiation request packet
|
||||||
|
func (s *NegotiationRequest) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
buf := []byte{Version, byte(s.NMethods)}
|
||||||
|
for i := range s.Methods {
|
||||||
|
buf = append(buf, byte(s.Methods[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiationReply is the negotiation reply packet
|
||||||
|
type NegotiationReply struct {
|
||||||
|
Method AuthMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNegotiationReply returns new negotiation reply packet
|
||||||
|
func NewNegotiationReply(method AuthMethod) *NegotiationReply {
|
||||||
|
return &NegotiationReply{
|
||||||
|
Method: method,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNegotiationReplyFrom reads negotiation reply packet from reader
|
||||||
|
func NewNegotiationReplyFrom(r io.Reader) (*NegotiationReply, error) {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != Version {
|
||||||
|
return nil, ErrVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := &NegotiationReply{
|
||||||
|
Method: AuthMethod(buf[1]),
|
||||||
|
}
|
||||||
|
|
||||||
|
return rep, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes negotiation reply packet
|
||||||
|
func (s *NegotiationReply) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write([]byte{Version, byte(s.Method)})
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
114
password_negotiation.go
Normal file
114
password_negotiation.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PasswordNegotiationRequest is the password negotiation reqeust packet
|
||||||
|
type PasswordNegotiationRequest struct {
|
||||||
|
UsernameLen byte
|
||||||
|
Username string
|
||||||
|
PasswordLen byte
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPasswordNegotiationRequest returns new password negotiation request packet
|
||||||
|
func NewPasswordNegotiationRequest(username string, password string) *PasswordNegotiationRequest {
|
||||||
|
return &PasswordNegotiationRequest{
|
||||||
|
UsernameLen: byte(len(username)),
|
||||||
|
Username: username,
|
||||||
|
PasswordLen: byte(len(password)),
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPasswordNegotiationRequestFrom reads password negotiation request packet from reader
|
||||||
|
func NewPasswordNegotiationRequestFrom(r io.Reader) (*PasswordNegotiationRequest, error) {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != PasswordVersion {
|
||||||
|
return nil, ErrPasswordAuthVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[1] == 0 {
|
||||||
|
return nil, ErrBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
usernameBuf := make([]byte, int(buf[1])+1)
|
||||||
|
if _, err := io.ReadFull(r, usernameBuf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var passwordBuf []byte
|
||||||
|
if usernameBuf[int(buf[1])] != 0 {
|
||||||
|
passwordBuf = make([]byte, int(usernameBuf[int(buf[1])]))
|
||||||
|
if _, err := io.ReadFull(r, passwordBuf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &PasswordNegotiationRequest{
|
||||||
|
UsernameLen: buf[1],
|
||||||
|
Username: string(usernameBuf[:int(buf[1])]),
|
||||||
|
PasswordLen: usernameBuf[int(buf[1])],
|
||||||
|
Password: string(passwordBuf),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes password negotiation request packet
|
||||||
|
func (s *PasswordNegotiationRequest) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
buf := []byte{PasswordVersion, s.UsernameLen}
|
||||||
|
buf = append(buf, []byte(s.Username)...)
|
||||||
|
buf = append(buf, s.PasswordLen)
|
||||||
|
buf = append(buf, []byte(s.Password)...)
|
||||||
|
|
||||||
|
n, err := w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordNegotiationReply is the password negotiation reply packet
|
||||||
|
type PasswordNegotiationReply struct {
|
||||||
|
Status PasswordStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPasswordNegotiationReply returns password negotiation reply packet
|
||||||
|
func NewPasswordNegotiationReply(status PasswordStatus) *PasswordNegotiationReply {
|
||||||
|
return &PasswordNegotiationReply{
|
||||||
|
Status: status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPasswordNegotiationReplyFrom reads password negotiation reply packet from reader
|
||||||
|
func NewPasswordNegotiationReplyFrom(r io.Reader) (*PasswordNegotiationReply, error) {
|
||||||
|
buf := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != PasswordVersion {
|
||||||
|
return nil, ErrPasswordAuthVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := &PasswordNegotiationReply{
|
||||||
|
Status: PasswordStatus(buf[1]),
|
||||||
|
}
|
||||||
|
|
||||||
|
return rep, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes password negotiation reply packet
|
||||||
|
func (s *PasswordNegotiationReply) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write([]byte{PasswordVersion, byte(s.Status)})
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
68
reply.go
Normal file
68
reply.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reply is the reply packet
|
||||||
|
type Reply struct {
|
||||||
|
Status ReplyStatus
|
||||||
|
ATYP ATYP
|
||||||
|
BindAddr []byte
|
||||||
|
BindPort []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReply returns new reply packet
|
||||||
|
func NewReply(status ReplyStatus, atyp ATYP, host []byte, port []byte) *Reply {
|
||||||
|
return &Reply{
|
||||||
|
Status: status,
|
||||||
|
ATYP: atyp,
|
||||||
|
BindAddr: host,
|
||||||
|
BindPort: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReplyFrom reads reply packet from reader
|
||||||
|
func NewReplyFrom(r io.Reader) (*Reply, error) {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != Version {
|
||||||
|
return nil, ErrVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := ReadAddress(r, ATYP(buf[3]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, port); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := &Reply{
|
||||||
|
Status: ReplyStatus(buf[1]),
|
||||||
|
ATYP: ATYP(buf[3]),
|
||||||
|
BindAddr: addr,
|
||||||
|
BindPort: port,
|
||||||
|
}
|
||||||
|
|
||||||
|
return rep, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes reply packet
|
||||||
|
func (s *Reply) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
buf := []byte{Version, byte(s.Status), Reserved, byte(s.ATYP)}
|
||||||
|
buf = append(buf, s.BindAddr...)
|
||||||
|
buf = append(buf, s.BindPort...)
|
||||||
|
|
||||||
|
n, err := w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
68
request.go
Normal file
68
request.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request is the request packet
|
||||||
|
type Request struct {
|
||||||
|
Command Command
|
||||||
|
ATYP ATYP
|
||||||
|
DstAddr []byte
|
||||||
|
DstPort []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest returns new request packet
|
||||||
|
func NewRequest(command Command, atyp ATYP, host []byte, port []byte) *Request {
|
||||||
|
return &Request{
|
||||||
|
Command: command,
|
||||||
|
ATYP: atyp,
|
||||||
|
DstAddr: host,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequestFrom reads request packet from client
|
||||||
|
func NewRequestFrom(r io.Reader) (*Request, error) {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != Version {
|
||||||
|
return nil, ErrVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := ReadAddress(r, ATYP(buf[3]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, port); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &Request{
|
||||||
|
Command: Command(buf[1]),
|
||||||
|
ATYP: ATYP(buf[3]),
|
||||||
|
DstAddr: addr,
|
||||||
|
DstPort: port,
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes request packet
|
||||||
|
func (s *Request) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
buf := []byte{Version, byte(s.Command), Reserved, byte(s.ATYP)}
|
||||||
|
buf = append(buf, s.DstAddr...)
|
||||||
|
buf = append(buf, s.DstPort...)
|
||||||
|
|
||||||
|
n, err := w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(n), nil
|
||||||
|
}
|
151
socks5.go
Normal file
151
socks5.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Version is the protocol version
|
||||||
|
Version byte = 0x05
|
||||||
|
// Reserved is the reserved field value
|
||||||
|
Reserved byte = 0x00
|
||||||
|
// PasswordVersion is the username/password auth protocol version
|
||||||
|
PasswordVersion byte = 0x01
|
||||||
|
// DatagramStandalone is the standalone datagram fragment field value
|
||||||
|
DatagramStandalone byte = 0x00
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthMethod is the auth method
|
||||||
|
type AuthMethod byte
|
||||||
|
|
||||||
|
// Auth methods
|
||||||
|
const (
|
||||||
|
AuthMethodNone AuthMethod = 0x00
|
||||||
|
AuthMethodGSSAPI AuthMethod = 0x01
|
||||||
|
AuthMethodPassword AuthMethod = 0x02
|
||||||
|
AuthMethodNotSupported AuthMethod = 0xFF
|
||||||
|
)
|
||||||
|
|
||||||
|
func (am AuthMethod) String() string {
|
||||||
|
return authMethodNames[am]
|
||||||
|
}
|
||||||
|
|
||||||
|
var authMethodNames = map[AuthMethod]string{
|
||||||
|
AuthMethodNone: "none",
|
||||||
|
AuthMethodGSSAPI: "gssapi",
|
||||||
|
AuthMethodPassword: "password",
|
||||||
|
AuthMethodNotSupported: "not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordStatus is the password auth status
|
||||||
|
type PasswordStatus byte
|
||||||
|
|
||||||
|
// Password statuses
|
||||||
|
const (
|
||||||
|
PasswordStatusSuccess PasswordStatus = 0x00
|
||||||
|
PasswordStatusFailure PasswordStatus = 0x01
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ps PasswordStatus) String() string {
|
||||||
|
return passwordStatusNames[ps]
|
||||||
|
}
|
||||||
|
|
||||||
|
var passwordStatusNames = map[PasswordStatus]string{
|
||||||
|
PasswordStatusSuccess: "success",
|
||||||
|
PasswordStatusFailure: "failure",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command is the command
|
||||||
|
type Command byte
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
const (
|
||||||
|
CommandConnect Command = 0x01
|
||||||
|
CommandBind Command = 0x02
|
||||||
|
CommandUDPAssociate Command = 0x03
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c Command) String() string {
|
||||||
|
return commandNames[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandNames = map[Command]string{
|
||||||
|
CommandConnect: "connect",
|
||||||
|
CommandBind: "bind",
|
||||||
|
CommandUDPAssociate: "udp associate",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ATYP is the address type
|
||||||
|
type ATYP byte
|
||||||
|
|
||||||
|
// Address types
|
||||||
|
const (
|
||||||
|
ATYPIPv4 ATYP = 0x01
|
||||||
|
ATYPFQDN ATYP = 0x03
|
||||||
|
ATYPIPv6 ATYP = 0x04
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a ATYP) String() string {
|
||||||
|
return atypNames[a]
|
||||||
|
}
|
||||||
|
|
||||||
|
var atypNames = map[ATYP]string{
|
||||||
|
ATYPIPv4: "ipv4",
|
||||||
|
ATYPFQDN: "fqdn",
|
||||||
|
ATYPIPv6: "ipv6",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplyStatus is the reply status
|
||||||
|
type ReplyStatus byte
|
||||||
|
|
||||||
|
// Reply statuses
|
||||||
|
const (
|
||||||
|
ReplyStatusSuccess ReplyStatus = 0x00
|
||||||
|
ReplyStatusServerFailure ReplyStatus = 0x01
|
||||||
|
ReplyStatusNotAllowed ReplyStatus = 0x02
|
||||||
|
ReplyStatusNetworkUnreachable ReplyStatus = 0x03
|
||||||
|
ReplyStatusHostUnreachable ReplyStatus = 0x04
|
||||||
|
ReplyStatusConnectionRefused ReplyStatus = 0x05
|
||||||
|
ReplyStatusTTLExpired ReplyStatus = 0x06
|
||||||
|
ReplyStatusCommandNotSupported ReplyStatus = 0x07
|
||||||
|
ReplyStatusAddressNotSupported ReplyStatus = 0x08
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rs ReplyStatus) String() string {
|
||||||
|
return replyStatusNames[rs]
|
||||||
|
}
|
||||||
|
|
||||||
|
var replyStatusNames = map[ReplyStatus]string{
|
||||||
|
ReplyStatusSuccess: "success",
|
||||||
|
ReplyStatusServerFailure: "server failure",
|
||||||
|
ReplyStatusNotAllowed: "not allowed",
|
||||||
|
ReplyStatusNetworkUnreachable: "network unreachable",
|
||||||
|
ReplyStatusHostUnreachable: "host unreachable",
|
||||||
|
ReplyStatusConnectionRefused: "connection refused",
|
||||||
|
ReplyStatusTTLExpired: "TTL expired",
|
||||||
|
ReplyStatusCommandNotSupported: "command not supported",
|
||||||
|
ReplyStatusAddressNotSupported: "address not supported",
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrVersion is the protocol version error
|
||||||
|
ErrVersion = errors.New("invalid protocol version")
|
||||||
|
// ErrBadReply is the bad reply error
|
||||||
|
ErrBadReply = errors.New("bad reply")
|
||||||
|
// ErrBadRequest is the bad request error
|
||||||
|
ErrBadRequest = errors.New("bad request")
|
||||||
|
// ErrBadDatagram is the bad datagram error
|
||||||
|
ErrBadDatagram = errors.New("bad datagram")
|
||||||
|
// ErrFragmentedDatagram us the fragmented datagram error
|
||||||
|
ErrFragmentedDatagram = errors.New("fragmented datagram")
|
||||||
|
// ErrIPAuth is the invalid ip error
|
||||||
|
ErrIPAuth = errors.New("invalid ip auth")
|
||||||
|
// ErrPasswordAuth is the invalid username or password error
|
||||||
|
ErrPasswordAuth = errors.New("invalid username or password")
|
||||||
|
// ErrAuthMethodNotSupported is the error when auth method not supported
|
||||||
|
ErrAuthMethodNotSupported = errors.New("auth method not supported")
|
||||||
|
// ErrCommandNotSupported is the error when command not supported
|
||||||
|
ErrCommandNotSupported = errors.New("command not supported")
|
||||||
|
// ErrPasswordAuthVersion is the password auth version error
|
||||||
|
ErrPasswordAuthVersion = errors.New("invalid password auth version")
|
||||||
|
)
|
68
udp.go
Normal file
68
udp.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoConnectedAddress is the no connected address error
|
||||||
|
ErrNoConnectedAddress = errors.New("no connected address")
|
||||||
|
)
|
||||||
|
|
||||||
|
type UDPConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
|
||||||
|
TCPConn net.Conn
|
||||||
|
ServerAddr net.Addr
|
||||||
|
ConnectedAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
_, _, err := s.PacketConn.ReadFrom(b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
datagram, err := NewDatagramFrom(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := copy(b, datagram.Data)
|
||||||
|
return n, AddrFromSocks(datagram.ATYP, datagram.DstAddr, datagram.DstPort), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPConn) Read(b []byte) (int, error) {
|
||||||
|
n, _, err := s.ReadFrom(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||||
|
socksAddr, err := AddrFromString(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
atyp, host, port := socksAddr.Socks()
|
||||||
|
datagram := NewDatagram(atyp, host, port, b)
|
||||||
|
return s.PacketConn.WriteTo(datagram.Bytes(), s.ServerAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPConn) Write(b []byte) (int, error) {
|
||||||
|
if s.ConnectedAddr == nil {
|
||||||
|
return 0, ErrNoConnectedAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.WriteTo(b, s.ConnectedAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPConn) Close() error {
|
||||||
|
s.TCPConn.Close()
|
||||||
|
return s.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UDPConn) RemoteAddr() net.Addr {
|
||||||
|
return s.ConnectedAddr
|
||||||
|
}
|
92
util.go
Normal file
92
util.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewEmptyAddrReply returns reply with empty address and port
|
||||||
|
func NewEmptyAddrReply(status ReplyStatus, atyp ATYP) *Reply {
|
||||||
|
if atyp == ATYPIPv6 {
|
||||||
|
return NewReply(status, ATYPIPv6, []byte(net.IPv6zero), []byte{0x00, 0x00})
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewReply(status, ATYPIPv4, []byte{0x00, 0x00, 0x00, 0x00}, []byte{0x00, 0x00})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAddress reads address from reader
|
||||||
|
func ReadAddress(r io.Reader, atyp ATYP) ([]byte, error) {
|
||||||
|
var addr []byte
|
||||||
|
|
||||||
|
//nolint:exhaustive
|
||||||
|
switch atyp {
|
||||||
|
case ATYPIPv4:
|
||||||
|
addr = make([]byte, 4)
|
||||||
|
if _, err := io.ReadFull(r, addr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case ATYPIPv6:
|
||||||
|
addr = make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(r, addr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case ATYPFQDN:
|
||||||
|
domainLen := make([]byte, 1)
|
||||||
|
if _, err := io.ReadFull(r, domainLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if domainLen[0] == 0 {
|
||||||
|
return nil, ErrBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = make([]byte, int(domainLen[0]))
|
||||||
|
if _, err := io.ReadFull(r, addr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr = append(domainLen, addr...) //nolint:makezero
|
||||||
|
default:
|
||||||
|
return nil, ErrBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAddress parses address from string
|
||||||
|
func ParseAddress(address string) (ATYP, []byte, []byte, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return 0x00, []byte{}, []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
atyp ATYP
|
||||||
|
addr []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
atyp = ATYPIPv4
|
||||||
|
addr = []byte(ip4)
|
||||||
|
} else if ip6 := ip.To16(); ip6 != nil {
|
||||||
|
atyp = ATYPIPv6
|
||||||
|
addr = []byte(ip6)
|
||||||
|
} else {
|
||||||
|
atyp = ATYPFQDN
|
||||||
|
addr = []byte{byte(len(host))}
|
||||||
|
addr = append(addr, []byte(host)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
portInt, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return 0x00, []byte{}, []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
portBytes := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(portBytes, uint16(portInt))
|
||||||
|
|
||||||
|
return atyp, addr, portBytes, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user