Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dialer interface according to golang.org/x/net/proxy. #42

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
53 changes: 53 additions & 0 deletions proxy/dialer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package core

import (
"net"
"errors"
"github.com/shadowsocks/go-shadowsocks2/socks"
"github.com/shadowsocks/go-shadowsocks2/core"
)

type ProxyDialer struct {
core.StreamConnCipher
addr string
}

func NewDialer(addr string, algo string, password string) (s *ProxyDialer, err error) {
cipher, err := core.PickCipher(algo, nil, password)
if err != nil {
return
}
s = new(ProxyDialer)
s.StreamConnCipher = cipher
s.addr = addr
return
}

func NewDialerFromKey(addr string, algo string, key []byte) (s *ProxyDialer, err error) {
cipher, err := core.PickCipher(algo, key, "")
if err != nil {
return
}
s = new(ProxyDialer)
s.StreamConnCipher = cipher
s.addr = addr
return
}

func (dialer *ProxyDialer) Dial(network, addr string) (c net.Conn, err error) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should call this DialTCP, as in net.DialTCP.
Unless you also support UDP or other networks.

In any case, you should be checking the value of the network parameter.

target := socks.ParseAddr(addr)
if target == nil {
return nil, errors.New("Unable to parse address: " + addr)
}
conn, err := net.Dial("tcp", dialer.addr)
if err != nil {
return nil, err
}
conn.(*net.TCPConn).SetKeepAlive(true)
c = dialer.StreamConn(conn)
if _, err = c.Write(target); err != nil {
c.Close()
return nil, err
}
return
}
133 changes: 133 additions & 0 deletions proxy/dialer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package core

import (
"testing"
"net/http"
"golang.org/x/net/proxy"
"github.com/shadowsocks/go-shadowsocks2/socks"
"github.com/shadowsocks/go-shadowsocks2/core"
"io/ioutil"
"net"
"io"
"time"
"strings"
)

func TestDialerInterface(t *testing.T) {
var dialer proxy.Dialer
dialer, err := NewDialer("198.51.100.254:65534", "aes-128-gcm", "dialer-test")
if err != nil {
t.Fatal("Unable to create new dialer:", err.Error())
return
}
// AES-128 key is 16 bytes long.
// This key is just for interface testing only, so zeroKey doesn't matter.
zeroKey := make([]byte, 16)
dialer, err = NewDialerFromKey("195.51.100.254:65533", "aes-128-gcm", zeroKey)
if err != nil {
t.Fatal("Unable to create new dialer with pre-generated key:", err.Error())
return
}
_ = dialer
t.Log("Dialer interface works.")
}

func TestDialerFunctionality(t *testing.T) {
// First let's launch a dummy shadowsocks server.
ciph, err := core.PickCipher("aes-128-gcm", nil, "dialer-test")
if err != nil {
t.Fatal("Unable to pick cipher", ciph, ":", err.Error())
return
}
go tcpRemote("127.0.0.1:60001", ciph.StreamConn)
dialer, err := NewDialer("127.0.0.1:60001", "aes-128-gcm", "dialer-test")
if err != nil {
t.Fatal("Unable to create new dialer:", err.Error())
return
}
client := &http.Client {
Transport: &http.Transport {
Dial: dialer.Dial,
},
}
resp, err := client.Get("http://ifconfig.co")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your unit tests shouldn't depend on external services.

Why don't you run a simple TCP service locally instead?
Here is an example where I do that: https://github.com/Jigsaw-Code/outline-ss-server/blob/master/shadowsocks/tcp_test.go

if err != nil {
t.Fatal("Unable to connect ifconfig.co through shadowsocks server:", err.Error())
return
}
t.Log("http.Get ifconfig.co =", resp.Status)
body, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
ip := strings.TrimSpace(string(body))
t.Log("My IP address is:", ip)
if ipp := net.ParseIP(ip); ipp == nil {
t.Errorf("Responsed IP address is not a valid IP.")
} else {
t.Log("IP address looks good.")
}

}

// Code lines below are copied from main package, since go can't import main package.
func tcpRemote(addr string, shadow func(net.Conn) net.Conn) {
l, err := net.Listen("tcp", addr)
if err != nil {
return
}
for {
c, err := l.Accept()
if err != nil {
continue
}

go func() {
defer c.Close()
c.(*net.TCPConn).SetKeepAlive(true)
c = shadow(c)

tgt, err := socks.ReadAddr(c)
if err != nil {
return
}

rc, err := net.Dial("tcp", tgt.String())
if err != nil {
return
}
defer rc.Close()
rc.(*net.TCPConn).SetKeepAlive(true)

_, _, err = relay(c, rc)
if err != nil {
if err, ok := err.(net.Error); ok && err.Timeout() {
return // ignore i/o timeout
}
}
}()
}
}

func relay(left, right net.Conn) (int64, int64, error) {
type res struct {
N int64
Err error
}
ch := make(chan res)

go func() {
n, err := io.Copy(right, left)
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
ch <- res{n, err}
}()

n, err := io.Copy(left, right)
right.SetDeadline(time.Now()) // wake up the other goroutine blocking on right
left.SetDeadline(time.Now()) // wake up the other goroutine blocking on left
rs := <-ch

if err == nil {
err = rs.Err
}
return n, rs.N, err
}