fea: go-socks in self.
Some checks failed
Coverage CI / build (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Ubuntu CI / build (push) Has been cancelled

This commit is contained in:
Daniel Ding
2025-01-02 13:09:08 +08:00
parent 2b2b737e87
commit 70b76392ca
20 changed files with 468 additions and 33 deletions

1
go.mod
View File

@@ -14,7 +14,6 @@ exclude github.com/sirupsen/logrus v1.8.1
require (
github.com/Sirupsen/logrus v0.0.0-00010101000000-000000000000 // indirect
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/chzyer/logex v1.2.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/test v0.0.0-20210722231415-061457976a23 // indirect

2
go.sum
View File

@@ -8,8 +8,6 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View File

@@ -3,9 +3,9 @@ package proxy
import (
"time"
"github.com/armon/go-socks5"
"github.com/luscis/openlan/pkg/config"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/socks5"
)
type SocksProxy struct {

119
pkg/socks5/auth_test.go Normal file
View File

@@ -0,0 +1,119 @@
package socks5
import (
"bytes"
"testing"
)
func TestNoAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, NoAuth})
var resp bytes.Buffer
s, _ := New(&Config{})
ctx, err := s.authenticate(&resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if ctx.Method != NoAuth {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, NoAuth}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Valid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if ctx.Method != UserPassAuth {
t.Fatal("Invalid Context Method")
}
val, ok := ctx.Payload["Username"]
if !ok {
t.Fatal("Missing key Username in auth context's payload")
}
if val != "foo" {
t.Fatal("Invalid Username in auth context's payload")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authSuccess}) {
t.Fatalf("bad: %v", out)
}
}
func TestPasswordAuth_Invalid(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != UserAuthFailed {
t.Fatalf("err: %v", err)
}
if ctx != nil {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authFailure}) {
t.Fatalf("bad: %v", out)
}
}
func TestNoSupportedAuth(t *testing.T) {
req := bytes.NewBuffer(nil)
req.Write([]byte{1, NoAuth})
var resp bytes.Buffer
cred := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: cred}
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
ctx, err := s.authenticate(&resp, req)
if err != NoSupportedAuth {
t.Fatalf("err: %v", err)
}
if ctx != nil {
t.Fatal("Invalid Context Method")
}
out := resp.Bytes()
if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
t.Fatalf("bad: %v", out)
}
}

View File

@@ -0,0 +1,24 @@
package socks5
import (
"testing"
)
func TestStaticCredentials(t *testing.T) {
creds := StaticCredentials{
"foo": "bar",
"baz": "",
}
if !creds.Valid("foo", "bar") {
t.Fatalf("expect valid")
}
if !creds.Valid("baz", "") {
t.Fatalf("expect valid")
}
if creds.Valid("foo", "") {
t.Fatalf("expect invalid")
}
}

169
pkg/socks5/request_test.go Normal file
View File

@@ -0,0 +1,169 @@
package socks5
import (
"bytes"
"encoding/binary"
"io"
"log"
"net"
"os"
"strings"
"testing"
)
type MockConn struct {
buf bytes.Buffer
}
func (m *MockConn) Write(b []byte) (int, error) {
return m.buf.Write(b)
}
func (m *MockConn) RemoteAddr() net.Addr {
return &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
}
func TestRequest_Connect(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Make server
s := &Server{config: &Config{
Rules: PermitAll(),
Resolver: DNSResolver{},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}}
// Create the connect request
buf := bytes.NewBuffer(nil)
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
buf.Write(port)
// Send a ping
buf.Write([]byte("ping"))
// Handle the request
resp := &MockConn{}
req, err := NewRequest(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.handleRequest(req, resp); err != nil {
t.Fatalf("err: %v", err)
}
// Verify response
out := resp.buf.Bytes()
expected := []byte{
5,
0,
0,
1,
127, 0, 0, 1,
0, 0,
'p', 'o', 'n', 'g',
}
// Ignore the port for both
out[8] = 0
out[9] = 0
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v %v", out, expected)
}
}
func TestRequest_Connect_RuleFail(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Make server
s := &Server{config: &Config{
Rules: PermitNone(),
Resolver: DNSResolver{},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}}
// Create the connect request
buf := bytes.NewBuffer(nil)
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
buf.Write(port)
// Send a ping
buf.Write([]byte("ping"))
// Handle the request
resp := &MockConn{}
req, err := NewRequest(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.handleRequest(req, resp); !strings.Contains(err.Error(), "blocked by rules") {
t.Fatalf("err: %v", err)
}
// Verify response
out := resp.buf.Bytes()
expected := []byte{
5,
2,
0,
1,
0, 0, 0, 0,
0, 0,
}
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v %v", out, expected)
}
}

View File

@@ -0,0 +1,21 @@
package socks5
import (
"testing"
"golang.org/x/net/context"
)
func TestDNSResolver(t *testing.T) {
d := DNSResolver{}
ctx := context.Background()
_, addr, err := d.Resolve(ctx, "localhost")
if err != nil {
t.Fatalf("err: %v", err)
}
if !addr.IsLoopback() {
t.Fatalf("expected loopback")
}
}

View File

@@ -0,0 +1,24 @@
package socks5
import (
"testing"
"golang.org/x/net/context"
)
func TestPermitCommand(t *testing.T) {
ctx := context.Background()
r := &PermitCommand{true, false, false}
if _, ok := r.Allow(ctx, &Request{Command: ConnectCommand}); !ok {
t.Fatalf("expect connect")
}
if _, ok := r.Allow(ctx, &Request{Command: BindCommand}); ok {
t.Fatalf("do not expect bind")
}
if _, ok := r.Allow(ctx, &Request{Command: AssociateCommand}); ok {
t.Fatalf("do not expect associate")
}
}

110
pkg/socks5/socks5_test.go Normal file
View File

@@ -0,0 +1,110 @@
package socks5
import (
"bytes"
"encoding/binary"
"io"
"log"
"net"
"os"
"testing"
"time"
)
func TestSOCKS5_Connect(t *testing.T) {
// Create a local listener
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("err: %v", err)
}
go func() {
conn, err := l.Accept()
if err != nil {
t.Fatalf("err: %v", err)
}
defer conn.Close()
buf := make([]byte, 4)
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
t.Fatalf("err: %v", err)
}
if !bytes.Equal(buf, []byte("ping")) {
t.Fatalf("bad: %v", buf)
}
conn.Write([]byte("pong"))
}()
lAddr := l.Addr().(*net.TCPAddr)
// Create a socks server
creds := StaticCredentials{
"foo": "bar",
}
cator := UserPassAuthenticator{Credentials: creds}
conf := &Config{
AuthMethods: []Authenticator{cator},
Logger: log.New(os.Stdout, "", log.LstdFlags),
}
serv, err := New(conf)
if err != nil {
t.Fatalf("err: %v", err)
}
// Start listening
go func() {
if err := serv.ListenAndServe("tcp", "127.0.0.1:12365"); err != nil {
t.Fatalf("err: %v", err)
}
}()
time.Sleep(10 * time.Millisecond)
// Get a local conn
conn, err := net.Dial("tcp", "127.0.0.1:12365")
if err != nil {
t.Fatalf("err: %v", err)
}
// Connect, auth and connec to local
req := bytes.NewBuffer(nil)
req.Write([]byte{5})
req.Write([]byte{2, NoAuth, UserPassAuth})
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
port := []byte{0, 0}
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
req.Write(port)
// Send a ping
req.Write([]byte("ping"))
// Send all the bytes
conn.Write(req.Bytes())
// Verify response
expected := []byte{
socks5Version, UserPassAuth,
1, authSuccess,
5,
0,
0,
1,
127, 0, 0, 1,
0, 0,
'p', 'o', 'n', 'g',
}
out := make([]byte, len(expected))
conn.SetDeadline(time.Now().Add(time.Second))
if _, err := io.ReadAtLeast(conn, out, len(out)); err != nil {
t.Fatalf("err: %v", err)
}
// Ignore the port
out[12] = 0
out[13] = 0
if !bytes.Equal(out, expected) {
t.Fatalf("bad: %v", out)
}
}

View File

@@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe

View File

@@ -1,4 +0,0 @@
language: go
go:
- 1.1
- tip

3
vendor/modules.txt vendored
View File

@@ -1,9 +1,6 @@
# github.com/Sirupsen/logrus v0.0.0-00010101000000-000000000000 => github.com/sirupsen/logrus v1.8.1
## explicit
github.com/Sirupsen/logrus
# github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
## explicit
github.com/armon/go-socks5
# github.com/beorn7/perks v1.0.1
github.com/beorn7/perks/quantile
# github.com/cenkalti/backoff/v4 v4.1.1