refactor and support user/password authentication
This commit is contained in:
parent
0ac3745d74
commit
a6825e0818
|
@ -14,12 +14,16 @@ SOCKS is a SOCKS4, SOCKS4A and SOCKS5 proxy package for Go.
|
|||
|
||||
import "h12.io/socks"
|
||||
|
||||
### Create a SOCKS proxy dialing function
|
||||
### Create a SOCKS proxy dialling function
|
||||
|
||||
dialSocksProxy := socks.Dial("socks5://127.0.0.1:1080?timeout=5s")
|
||||
tr := &http.Transport{Dial: dialSocksProxy}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
|
||||
### User/password authentication
|
||||
|
||||
dialSocksProxy := socks.Dial("socks5://user:password@127.0.0.1:1080?timeout=5s")
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
|
|
5
go.mod
5
go.mod
|
@ -1,3 +1,8 @@
|
|||
module h12.io/socks
|
||||
|
||||
go 1.9
|
||||
|
||||
require (
|
||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||
)
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
|
||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
|
@ -0,0 +1,72 @@
|
|||
package socks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type requestBuilder struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *requestBuilder) add(data ...byte) {
|
||||
_, _ = b.Write(data)
|
||||
}
|
||||
|
||||
func (c *config) sendReceive(conn net.Conn, req []byte) (resp []byte, err error) {
|
||||
if c.Timeout > 0 {
|
||||
if err := conn.SetWriteDeadline(time.Now().Add(c.Timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, err = conn.Write(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err = c.readAll(conn)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *config) readAll(conn net.Conn) (resp []byte, err error) {
|
||||
resp = make([]byte, 1024)
|
||||
if c.Timeout > 0 {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(c.Timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
n, err := conn.Read(resp)
|
||||
resp = resp[:n]
|
||||
return
|
||||
}
|
||||
|
||||
func lookupIP(host string) (net.IP, error) {
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
return nil, fmt.Errorf("cannot resolve host: %s", host)
|
||||
}
|
||||
ip := ips[0].To4()
|
||||
if len(ip) != net.IPv4len {
|
||||
return nil, errors.New("ipv6 is not supported by SOCKS4")
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func splitHostPort(addr string) (host string, port uint16, err error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
portInt, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
port = uint16(portInt)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package socks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
config struct {
|
||||
Proto int
|
||||
Host string
|
||||
Auth *auth
|
||||
Timeout time.Duration
|
||||
}
|
||||
auth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
)
|
||||
|
||||
func parse(proxyURI string) (*config, error) {
|
||||
uri, err := url.Parse(proxyURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := &config{}
|
||||
switch uri.Scheme {
|
||||
case "socks4":
|
||||
cfg.Proto = SOCKS4
|
||||
case "socks4a":
|
||||
cfg.Proto = SOCKS4A
|
||||
case "socks5":
|
||||
cfg.Proto = SOCKS5
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown SOCKS protocol %s", uri.Scheme)
|
||||
}
|
||||
cfg.Host = uri.Host
|
||||
user := uri.User.Username()
|
||||
password, _ := uri.User.Password()
|
||||
if user != "" || password != "" {
|
||||
if user == "" || password == "" || len(user) > 255 || len(password) > 255 {
|
||||
return nil, errors.New("invalid user name or password")
|
||||
}
|
||||
cfg.Auth = &auth{
|
||||
Username: user,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
query := uri.Query()
|
||||
timeout := query.Get("timeout")
|
||||
if timeout != "" {
|
||||
var err error
|
||||
cfg.Timeout, err = time.ParseDuration(timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
|
@ -11,14 +11,14 @@ func TestParse(t *testing.T) {
|
|||
testcases := []struct {
|
||||
name string
|
||||
uri string
|
||||
cfg Config
|
||||
cfg config
|
||||
}{
|
||||
{
|
||||
name: "full config",
|
||||
uri: "socks5://u1:p1@127.0.0.1:8080?timeout=2s",
|
||||
cfg: Config{
|
||||
cfg: config{
|
||||
Proto: SOCKS5,
|
||||
Auth: Auth{
|
||||
Auth: &auth{
|
||||
Username: "u1",
|
||||
Password: "p1",
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ func TestParse(t *testing.T) {
|
|||
{
|
||||
name: "simple socks5",
|
||||
uri: "socks5://127.0.0.1:8080",
|
||||
cfg: Config{
|
||||
cfg: config{
|
||||
Proto: SOCKS5,
|
||||
Host: "127.0.0.1:8080",
|
||||
},
|
229
socks.go
229
socks.go
|
@ -41,12 +41,8 @@ A complete example using this package:
|
|||
package socks // import "h12.io/socks"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constants to choose which version of SOCKS protocol to use.
|
||||
|
@ -56,52 +52,6 @@ const (
|
|||
SOCKS5
|
||||
)
|
||||
|
||||
type (
|
||||
Config struct {
|
||||
Proto int
|
||||
Host string
|
||||
Auth Auth
|
||||
Timeout time.Duration
|
||||
}
|
||||
Auth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
)
|
||||
|
||||
func parse(proxyURI string) (*Config, error) {
|
||||
uri, err := url.Parse(proxyURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := &Config{}
|
||||
switch uri.Scheme {
|
||||
case "socks4":
|
||||
cfg.Proto = SOCKS4
|
||||
case "socks4a":
|
||||
cfg.Proto = SOCKS4A
|
||||
case "socks5":
|
||||
cfg.Proto = SOCKS5
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown SOCKS protocol %s", uri.Scheme)
|
||||
}
|
||||
cfg.Host = uri.Host
|
||||
if uri.User != nil {
|
||||
cfg.Auth.Username = uri.User.Username()
|
||||
cfg.Auth.Password, _ = uri.User.Password()
|
||||
}
|
||||
query := uri.Query()
|
||||
timeout := query.Get("timeout")
|
||||
if timeout != "" {
|
||||
var err error
|
||||
cfg.Timeout, err = time.ParseDuration(timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Dial returns the dial function to be used in http.Transport object.
|
||||
// Argument proxyURI should be in the format: "socks5://user:password@127.0.0.1:1080?timeout=5s".
|
||||
// The protocol could be socks5, socks4 and socks4a.
|
||||
|
@ -117,10 +67,10 @@ func Dial(proxyURI string) func(string, string) (net.Conn, error) {
|
|||
// Argument socksType should be one of SOCKS4, SOCKS4A and SOCKS5.
|
||||
// Argument proxy should be in this format "127.0.0.1:1080".
|
||||
func DialSocksProxy(socksType int, proxy string) func(string, string) (net.Conn, error) {
|
||||
return (&Config{Proto: socksType, Host: proxy}).dialFunc()
|
||||
return (&config{Proto: socksType, Host: proxy}).dialFunc()
|
||||
}
|
||||
|
||||
func (c *Config) dialFunc() func(string, string) (net.Conn, error) {
|
||||
func (c *config) dialFunc() func(string, string) (net.Conn, error) {
|
||||
switch c.Proto {
|
||||
case SOCKS5:
|
||||
return func(_, targetAddr string) (conn net.Conn, err error) {
|
||||
|
@ -134,181 +84,6 @@ func (c *Config) dialFunc() func(string, string) (net.Conn, error) {
|
|||
return dialError(fmt.Errorf("unknown SOCKS protocol %v", c.Proto))
|
||||
}
|
||||
|
||||
func (cfg *Config) dialSocks5(targetAddr string) (conn net.Conn, err error) {
|
||||
proxy := cfg.Host
|
||||
|
||||
// dial TCP
|
||||
conn, err = net.Dial("tcp", proxy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// version identifier/method selection request
|
||||
req := []byte{
|
||||
5, // version number
|
||||
1, // number of methods
|
||||
0, // method 0: no authentication (only anonymous access supported for now)
|
||||
}
|
||||
resp, err := cfg.sendReceive(conn, req)
|
||||
if err != nil {
|
||||
return
|
||||
} else if len(resp) != 2 {
|
||||
err = errors.New("Server does not respond properly.")
|
||||
return
|
||||
} else if resp[0] != 5 {
|
||||
err = errors.New("Server does not support Socks 5.")
|
||||
return
|
||||
} else if resp[1] != 0 { // no auth
|
||||
err = errors.New("socks method negotiation failed.")
|
||||
return
|
||||
}
|
||||
|
||||
// detail request
|
||||
host, port, err := splitHostPort(targetAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = []byte{
|
||||
5, // version number
|
||||
1, // connect command
|
||||
0, // reserved, must be zero
|
||||
3, // address type, 3 means domain name
|
||||
byte(len(host)), // address length
|
||||
}
|
||||
req = append(req, []byte(host)...)
|
||||
req = append(req, []byte{
|
||||
byte(port >> 8), // higher byte of destination port
|
||||
byte(port), // lower byte of destination port (big endian)
|
||||
}...)
|
||||
resp, err = cfg.sendReceive(conn, req)
|
||||
if err != nil {
|
||||
return
|
||||
} else if len(resp) != 10 {
|
||||
err = errors.New("Server does not respond properly.")
|
||||
} else if resp[1] != 0 {
|
||||
err = errors.New("Can't complete SOCKS5 connection.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *Config) dialSocks4(targetAddr string) (conn net.Conn, err error) {
|
||||
socksType := cfg.Proto
|
||||
proxy := cfg.Host
|
||||
|
||||
// dial TCP
|
||||
conn, err = net.Dial("tcp", proxy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// connection request
|
||||
host, port, err := splitHostPort(targetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ip := net.IPv4(0, 0, 0, 1).To4()
|
||||
if socksType == SOCKS4 {
|
||||
ip, err = lookupIP(host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
req := []byte{
|
||||
4, // version number
|
||||
1, // command CONNECT
|
||||
byte(port >> 8), // higher byte of destination port
|
||||
byte(port), // lower byte of destination port (big endian)
|
||||
ip[0], ip[1], ip[2], ip[3], // special invalid IP address to indicate the host name is provided
|
||||
0, // user id is empty, anonymous proxy only
|
||||
}
|
||||
if socksType == SOCKS4A {
|
||||
req = append(req, []byte(host+"\x00")...)
|
||||
}
|
||||
|
||||
resp, err := cfg.sendReceive(conn, req)
|
||||
if err != nil {
|
||||
return
|
||||
} else if len(resp) != 8 {
|
||||
err = errors.New("Server does not respond properly.")
|
||||
return
|
||||
}
|
||||
switch resp[1] {
|
||||
case 90:
|
||||
// request granted
|
||||
case 91:
|
||||
err = errors.New("Socks connection request rejected or failed.")
|
||||
case 92:
|
||||
err = errors.New("Socks connection request rejected becasue SOCKS server cannot connect to identd on the client.")
|
||||
case 93:
|
||||
err = errors.New("Socks connection request rejected because the client program and identd report different user-ids.")
|
||||
default:
|
||||
err = errors.New("Socks connection request failed, unknown error.")
|
||||
}
|
||||
// clear the deadline before returning
|
||||
if err := conn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *Config) sendReceive(conn net.Conn, req []byte) (resp []byte, err error) {
|
||||
if cfg.Timeout > 0 {
|
||||
if err := conn.SetWriteDeadline(time.Now().Add(cfg.Timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
_, err = conn.Write(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err = cfg.readAll(conn)
|
||||
return
|
||||
}
|
||||
|
||||
func (cfg *Config) readAll(conn net.Conn) (resp []byte, err error) {
|
||||
resp = make([]byte, 1024)
|
||||
if cfg.Timeout > 0 {
|
||||
if err := conn.SetReadDeadline(time.Now().Add(cfg.Timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
n, err := conn.Read(resp)
|
||||
resp = resp[:n]
|
||||
return
|
||||
}
|
||||
|
||||
func lookupIP(host string) (ip net.IP, err error) {
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
err = fmt.Errorf("Cannot resolve host: %s.", host)
|
||||
return
|
||||
}
|
||||
ip = ips[0].To4()
|
||||
if len(ip) != net.IPv4len {
|
||||
fmt.Println(len(ip), ip)
|
||||
err = errors.New("IPv6 is not supported by SOCKS4.")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func splitHostPort(addr string) (host string, port uint16, err error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
portInt, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
port = uint16(portInt)
|
||||
return
|
||||
}
|
||||
|
||||
func dialError(err error) func(string, string) (net.Conn, error) {
|
||||
return func(_, _ string) (net.Conn, error) {
|
||||
return nil, err
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package socks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (cfg *config) dialSocks4(targetAddr string) (_ net.Conn, err error) {
|
||||
socksType := cfg.Proto
|
||||
proxy := cfg.Host
|
||||
|
||||
// dial TCP
|
||||
conn, err := net.Dial("tcp", proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// connection request
|
||||
host, port, err := splitHostPort(targetAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := net.IPv4(0, 0, 0, 1).To4()
|
||||
if socksType == SOCKS4 {
|
||||
ip, err = lookupIP(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
req := []byte{
|
||||
4, // version number
|
||||
1, // command CONNECT
|
||||
byte(port >> 8), // higher byte of destination port
|
||||
byte(port), // lower byte of destination port (big endian)
|
||||
ip[0], ip[1], ip[2], ip[3], // special invalid IP address to indicate the host name is provided
|
||||
0, // user id is empty, anonymous proxy only
|
||||
}
|
||||
if socksType == SOCKS4A {
|
||||
req = append(req, []byte(host+"\x00")...)
|
||||
}
|
||||
|
||||
resp, err := cfg.sendReceive(conn, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(resp) != 8 {
|
||||
return nil, errors.New("server does not respond properly")
|
||||
}
|
||||
switch resp[1] {
|
||||
case 90:
|
||||
// request granted
|
||||
case 91:
|
||||
return nil, errors.New("socks connection request rejected or failed")
|
||||
case 92:
|
||||
return nil, errors.New("socks connection request rejected because SOCKS server cannot connect to identd on the client")
|
||||
case 93:
|
||||
return nil, errors.New("socks connection request rejected because the client program and identd report different user-ids")
|
||||
default:
|
||||
return nil, errors.New("socks connection request failed, unknown error")
|
||||
}
|
||||
// clear the deadline before returning
|
||||
if err := conn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package socks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func (cfg *config) dialSocks5(targetAddr string) (_ net.Conn, err error) {
|
||||
proxy := cfg.Host
|
||||
|
||||
// dial TCP
|
||||
conn, err := net.Dial("tcp", proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var req requestBuilder
|
||||
|
||||
version := byte(5) // socks version 5
|
||||
method := byte(0) // method 0: no authentication (only anonymous access supported for now)
|
||||
if cfg.Auth != nil {
|
||||
method = 2 // method 2: username/password
|
||||
}
|
||||
|
||||
// version identifier/method selection request
|
||||
req.add(
|
||||
version, // socks version
|
||||
1, // number of methods
|
||||
method,
|
||||
)
|
||||
|
||||
resp, err := cfg.sendReceive(conn, req.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(resp) != 2 {
|
||||
return nil, errors.New("server does not respond properly")
|
||||
} else if resp[0] != 5 {
|
||||
return nil, errors.New("server does not support Socks 5")
|
||||
} else if resp[1] != method {
|
||||
return nil, errors.New("socks method negotiation failed")
|
||||
}
|
||||
if cfg.Auth != nil {
|
||||
version := byte(1) // user/password version 1
|
||||
req.Reset()
|
||||
req.add(
|
||||
version, // user/password version
|
||||
byte(len(cfg.Auth.Username)), // length of username
|
||||
)
|
||||
req.add([]byte(cfg.Auth.Username)...)
|
||||
req.add(byte(len(cfg.Auth.Password)))
|
||||
req.add([]byte(cfg.Auth.Password)...)
|
||||
resp, err := cfg.sendReceive(conn, req.Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(resp) != 2 {
|
||||
return nil, errors.New("server does not respond properly")
|
||||
} else if resp[0] != version {
|
||||
return nil, errors.New("server does not support user/password version 1")
|
||||
} else if resp[1] != 0 { // not success
|
||||
return nil, errors.New("user/password login failed")
|
||||
}
|
||||
}
|
||||
|
||||
// detail request
|
||||
host, port, err := splitHostPort(targetAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Reset()
|
||||
req.add(
|
||||
5, // version number
|
||||
1, // connect command
|
||||
0, // reserved, must be zero
|
||||
3, // address type, 3 means domain name
|
||||
byte(len(host)), // address length
|
||||
)
|
||||
req.add([]byte(host)...)
|
||||
req.add(
|
||||
byte(port>>8), // higher byte of destination port
|
||||
byte(port), // lower byte of destination port (big endian)
|
||||
)
|
||||
resp, err = cfg.sendReceive(conn, req.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
} else if len(resp) != 10 {
|
||||
return nil, errors.New("server does not respond properly")
|
||||
} else if resp[1] != 0 {
|
||||
return nil, errors.New("can't complete SOCKS5 connection")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package socks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
socks5 "github.com/h12w/go-socks5"
|
||||
"github.com/phayes/freeport"
|
||||
)
|
||||
|
||||
var httpTestServer = func() *http.Server {
|
||||
var err error
|
||||
httpTestPort, err := freeport.GetFreePort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: ":" + strconv.Itoa(httpTestPort),
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Write([]byte("hello"))
|
||||
}),
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
go s.ListenAndServe()
|
||||
runtime.Gosched()
|
||||
tcpReady(httpTestPort, 2*time.Second)
|
||||
return s
|
||||
}()
|
||||
|
||||
func newTestSocksServer(withAuth bool) (port int) {
|
||||
authenticator := socks5.Authenticator(socks5.NoAuthAuthenticator{})
|
||||
if withAuth {
|
||||
authenticator = socks5.UserPassAuthenticator{
|
||||
Credentials: socks5.StaticCredentials{
|
||||
"test_user": "test_pass",
|
||||
},
|
||||
}
|
||||
}
|
||||
conf := &socks5.Config{
|
||||
Logger: log.New(ioutil.Discard, "", log.LstdFlags),
|
||||
AuthMethods: []socks5.Authenticator{
|
||||
authenticator,
|
||||
},
|
||||
}
|
||||
|
||||
srv, err := socks5.New(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
socksTestPort, err := freeport.GetFreePort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServe("tcp", "0.0.0.0:"+strconv.Itoa(socksTestPort)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
runtime.Gosched()
|
||||
tcpReady(socksTestPort, 2*time.Second)
|
||||
return socksTestPort
|
||||
}
|
||||
|
||||
func TestSocks5Anonymous(t *testing.T) {
|
||||
socksTestPort := newTestSocksServer(false)
|
||||
dialSocksProxy := Dial(fmt.Sprintf("socks5://127.0.0.1:%d?timeout=5s", socksTestPort))
|
||||
tr := &http.Transport{Dial: dialSocksProxy}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
resp, err := httpClient.Get(fmt.Sprintf("http://localhost" + httpTestServer.Addr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if string(respBody) != "hello" {
|
||||
t.Fatalf("expect response hello but got %s", respBody)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSocks5Auth(t *testing.T) {
|
||||
socksTestPort := newTestSocksServer(true)
|
||||
dialSocksProxy := Dial(fmt.Sprintf("socks5://test_user:test_pass@127.0.0.1:%d?timeout=5s", socksTestPort))
|
||||
tr := &http.Transport{Dial: dialSocksProxy}
|
||||
httpClient := &http.Client{Transport: tr}
|
||||
resp, err := httpClient.Get(fmt.Sprintf("http://localhost" + httpTestServer.Addr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if string(respBody) != "hello" {
|
||||
t.Fatalf("expect response hello but got %s", respBody)
|
||||
}
|
||||
}
|
||||
|
||||
func tcpReady(port int, timeout time.Duration) {
|
||||
conn, err := net.DialTimeout("tcp", "127.0.0.1:"+strconv.Itoa(port), timeout)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Network Working Group M. Leech
|
||||
Request for Comments: 1929 Bell-Northern Research Ltd
|
||||
Category: Standards Track March 1996
|
||||
|
||||
|
||||
Username/Password Authentication for SOCKS V5
|
||||
|
||||
Status of this Memo
|
||||
|
||||
This document specifies an Internet standards track protocol for the
|
||||
Internet community, and requests discussion and suggestions for
|
||||
improvements. Please refer to the current edition of the "Internet
|
||||
Official Protocol Standards" (STD 1) for the standardization state
|
||||
and status of this protocol. Distribution of this memo is unlimited.
|
||||
|
||||
1. Introduction
|
||||
|
||||
The protocol specification for SOCKS Version 5 specifies a
|
||||
generalized framework for the use of arbitrary authentication
|
||||
protocols in the initial socks connection setup. This document
|
||||
describes one of those protocols, as it fits into the SOCKS Version 5
|
||||
authentication "subnegotiation".
|
||||
|
||||
Note:
|
||||
|
||||
Unless otherwise noted, the decimal numbers appearing in packet-
|
||||
format diagrams represent the length of the corresponding field, in
|
||||
octets. Where a given octet must take on a specific value, the
|
||||
syntax X'hh' is used to denote the value of the single octet in that
|
||||
field. When the word 'Variable' is used, it indicates that the
|
||||
corresponding field has a variable length defined either by an
|
||||
associated (one or two octet) length field, or by a data type field.
|
||||
|
||||
2. Initial negotiation
|
||||
|
||||
Once the SOCKS V5 server has started, and the client has selected the
|
||||
Username/Password Authentication protocol, the Username/Password
|
||||
subnegotiation begins. This begins with the client producing a
|
||||
Username/Password request:
|
||||
|
||||
+----+------+----------+------+----------+
|
||||
|VER | ULEN | UNAME | PLEN | PASSWD |
|
||||
+----+------+----------+------+----------+
|
||||
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
|
||||
+----+------+----------+------+----------+
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech Standards Track [Page 1]
|
||||
|
||||
RFC 1929 Username Authentication for SOCKS V5 March 1996
|
||||
|
||||
|
||||
The VER field contains the current version of the subnegotiation,
|
||||
which is X'01'. The ULEN field contains the length of the UNAME field
|
||||
that follows. The UNAME field contains the username as known to the
|
||||
source operating system. The PLEN field contains the length of the
|
||||
PASSWD field that follows. The PASSWD field contains the password
|
||||
association with the given UNAME.
|
||||
|
||||
The server verifies the supplied UNAME and PASSWD, and sends the
|
||||
following response:
|
||||
|
||||
+----+--------+
|
||||
|VER | STATUS |
|
||||
+----+--------+
|
||||
| 1 | 1 |
|
||||
+----+--------+
|
||||
|
||||
A STATUS field of X'00' indicates success. If the server returns a
|
||||
`failure' (STATUS value other than X'00') status, it MUST close the
|
||||
connection.
|
||||
|
||||
3. Security Considerations
|
||||
|
||||
This document describes a subnegotiation that provides authentication
|
||||
services to the SOCKS protocol. Since the request carries the
|
||||
password in cleartext, this subnegotiation is not recommended for
|
||||
environments where "sniffing" is possible and practical.
|
||||
|
||||
4. Author's Address
|
||||
|
||||
Marcus Leech
|
||||
Bell-Northern Research Ltd
|
||||
P.O. Box 3511, Station C
|
||||
Ottawa, ON
|
||||
CANADA K1Y 4H7
|
||||
|
||||
Phone: +1 613 763 9145
|
||||
EMail: mleech@bnr.ca
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Leech Standards Track [Page 2]
|
||||
|
Loading…
Reference in New Issue