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