Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions examples/shadowsocks-dial/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package main

import (
"context"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"

"github.com/33TU/socks/shadowsocks"
)

func main() {
proxyURL := flag.String(
"proxy",
os.Getenv("SS_PROXY_URL"),
`Shadowsocks proxy URL, e.g. ss://2022-blake3-aes-128-gcm:iDN+jVYAcTkUxwNICMTQRA==@127.0.0.1:8388`,
)

flag.Parse()

if *proxyURL == "" {
log.Fatal("missing proxy URL; set -proxy or SS_PROXY_URL")
}

dialer, err := shadowsocks.NewDialerFromURLString(*proxyURL, nil)
if err != nil {
log.Fatalf("failed to create dialer: %v", err)
}

transport := &http.Transport{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
},
}

client := &http.Client{
Transport: transport,
}

resp, err := client.Get("https://httpbin.org/ip")
if err != nil {
log.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("failed to read response body: %v", err)
}

fmt.Printf("status: %s\n", resp.Status)
fmt.Printf("body:\n%s\n", body)
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ module github.com/33TU/socks
go 1.25.1

require golang.org/x/sync v0.20.0

require (
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/sys v0.43.0 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
189 changes: 189 additions & 0 deletions shadowsocks/addr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package shadowsocks

import (
"encoding/binary"
"errors"
"fmt"
"net"
)

// Common validation and decode/encode errors for Shadowsocks addresses.
var (
ErrInvalidAddrType = errors.New("invalid address type")
ErrInvalidAddr = errors.New("invalid address")
ErrInvalidDomain = errors.New("invalid domain")
ErrShortAddr = errors.New("short address")
ErrShortAddrBuffer = errors.New("short address buffer")
)

// Addr represents a SOCKS5-style address field used by Shadowsocks.
type Addr struct {
AddrType byte
IP net.IP
Domain string
Port uint16
}

// Init initializes an Addr.
func (a *Addr) Init(addrType byte, ip net.IP, domain string, port uint16) {
a.AddrType = addrType
a.IP = ip
a.Domain = domain
a.Port = port
}

// GetHost returns the address host as either the domain or IP string.
func (a *Addr) GetHost() string {
if a.AddrType == AddrTypeDomain {
return a.Domain
}
if a.IP == nil {
return ""
}
return a.IP.String()
}

// Addr returns the address as a combined host:port string.
func (a *Addr) Addr() string {
return net.JoinHostPort(a.GetHost(), fmt.Sprint(a.Port))
}

// Validate checks the correctness of the address fields.
func (a *Addr) Validate() error {
switch a.AddrType {
case AddrTypeIPv4:
if a.IP == nil || a.IP.To4() == nil {
return ErrInvalidAddr
}
case AddrTypeIPv6:
if a.IP == nil || a.IP.To16() == nil || a.IP.To4() != nil {
return ErrInvalidAddr
}
case AddrTypeDomain:
if len(a.Domain) == 0 || len(a.Domain) > 255 {
return ErrInvalidDomain
}
default:
return ErrInvalidAddrType
}

return nil
}

// EncodedLen returns the number of bytes required to encode the address.
func (a *Addr) EncodedLen() int {
switch a.AddrType {
case AddrTypeIPv4:
return 1 + 4 + 2
case AddrTypeIPv6:
return 1 + 16 + 2
case AddrTypeDomain:
return 1 + 1 + len(a.Domain) + 2
default:
return 0
}
}

// Decode decodes an address from src.
// It returns the number of bytes consumed.
func (a *Addr) Decode(src []byte) (int, error) {
if len(src) < 1 {
return 0, ErrShortAddr
}

a.AddrType = src[0]
a.IP = nil
a.Domain = ""

switch a.AddrType {
case AddrTypeIPv4:
if len(src) < 1+4+2 {
return 0, ErrShortAddr
}
a.IP = net.IP(src[1 : 1+4]).To4()
if a.IP == nil {
return 0, ErrInvalidAddr
}
a.Port = binary.BigEndian.Uint16(src[5:7])
return 7, nil

case AddrTypeIPv6:
if len(src) < 1+16+2 {
return 0, ErrShortAddr
}
a.IP = net.IP(src[1 : 1+16]).To16()
if a.IP == nil || a.IP.To4() != nil {
return 0, ErrInvalidAddr
}
a.Port = binary.BigEndian.Uint16(src[17:19])
return 19, nil

case AddrTypeDomain:
if len(src) < 2 {
return 0, ErrShortAddr
}
n := int(src[1])
if len(src) < 1+1+n+2 {
return 0, ErrShortAddr
}
a.Domain = string(src[2 : 2+n])
a.Port = binary.BigEndian.Uint16(src[2+n : 2+n+2])
if err := a.Validate(); err != nil {
return 0, err
}
return 1 + 1 + n + 2, nil

default:
return 0, ErrInvalidAddrType
}
}

// EncodeTo encodes the address into dst and returns the extended slice.
func (a *Addr) EncodeTo(dst []byte) ([]byte, error) {
if err := a.Validate(); err != nil {
return nil, err
}

dst = append(dst, a.AddrType)

switch a.AddrType {
case AddrTypeIPv4:
dst = append(dst, a.IP.To4()...)
dst = binary.BigEndian.AppendUint16(dst, a.Port)
return dst, nil

case AddrTypeIPv6:
dst = append(dst, a.IP.To16()...)
dst = binary.BigEndian.AppendUint16(dst, a.Port)
return dst, nil

case AddrTypeDomain:
dst = append(dst, byte(len(a.Domain)))
dst = append(dst, a.Domain...)
dst = binary.BigEndian.AppendUint16(dst, a.Port)
return dst, nil

default:
return nil, ErrInvalidAddrType
}
}

// String returns a human-readable representation of the address.
func (a *Addr) String() string {
var atype string
switch a.AddrType {
case AddrTypeIPv4:
atype = "IPv4"
case AddrTypeDomain:
atype = "DOMAIN"
case AddrTypeIPv6:
atype = "IPv6"
default:
atype = fmt.Sprintf("0x%02X", a.AddrType)
}

return fmt.Sprintf(
"Addr{AddrType=%s, Host=%s, Port=%d}",
atype, a.GetHost(), a.Port,
)
}
Loading