continuous-integration/drone Build is failing
Migrate the workspace to React and Ant Design, improve account forms and responsive layouts, support outbound diagnostics and SSH fingerprint confirmation, and persist the management startup port. Preserve database migrations, remove S3 settings storage, and improve gateway audit handling.
93 lines
2.4 KiB
Go
93 lines
2.4 KiB
Go
package outbound
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSOCKS5ProxyHandshake(t *testing.T) {
|
|
for _, scenario := range []struct {
|
|
name string
|
|
password, rejected, badVersion bool
|
|
}{
|
|
{name: "no authentication"},
|
|
{name: "password", password: true},
|
|
{name: "rejected password", password: true, rejected: true},
|
|
{name: "rejected method", rejected: true},
|
|
{name: "wrong protocol", badVersion: true},
|
|
} {
|
|
t.Run(scenario.name, func(t *testing.T) {
|
|
method := byte(0)
|
|
if scenario.password {
|
|
method = 2
|
|
}
|
|
proxy, finished := mockSOCKS(t, func(connection net.Conn) error {
|
|
if err := expectBytes(connection, []byte{5, 1, method}); err != nil {
|
|
return err
|
|
}
|
|
reply := []byte{5, method}
|
|
if scenario.badVersion {
|
|
reply[0] = 4
|
|
}
|
|
if scenario.rejected && !scenario.password {
|
|
reply[1] = 255
|
|
}
|
|
if err := write(connection, reply); err != nil {
|
|
return err
|
|
}
|
|
if scenario.password {
|
|
if err := expectBytes(connection, []byte{1, 4, 'u', 's', 'e', 'r', 4, 'p', 'a', 's', 's'}); err != nil {
|
|
return err
|
|
}
|
|
status := byte(0)
|
|
if scenario.rejected {
|
|
status = 1
|
|
}
|
|
if err := write(connection, []byte{1, status}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var unexpected [1]byte
|
|
if count, err := connection.Read(unexpected[:]); count != 0 || !errors.Is(err, io.EOF) {
|
|
return errors.New("probe did not close or attempted a target connection")
|
|
}
|
|
return nil
|
|
})
|
|
if scenario.password {
|
|
proxy.Outbound.Auth, proxy.Outbound.Username, proxy.Outbound.Password = "password", "user", "pass"
|
|
}
|
|
err := TestSOCKS5(t.Context(), proxy.Outbound)
|
|
if (err != nil) != (scenario.rejected || scenario.badVersion) {
|
|
t.Fatalf("unexpected handshake result: %v", err)
|
|
}
|
|
awaitSOCKS(t, finished)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSOCKS5ProxyHandshakeCancellation(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
proxy, finished := mockSOCKS(t, func(connection net.Conn) error {
|
|
if err := expectBytes(connection, []byte{5, 1, 0}); err != nil {
|
|
return err
|
|
}
|
|
cancel()
|
|
var reply [1]byte
|
|
_, err := connection.Read(reply[:])
|
|
if !errors.Is(err, io.EOF) {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
started := time.Now()
|
|
if err := TestSOCKS5(ctx, proxy.Outbound); err == nil || time.Since(started) > 2*time.Second {
|
|
t.Fatalf("cancellation did not stop handshake: %v", err)
|
|
}
|
|
awaitSOCKS(t, finished)
|
|
}
|