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

feat: support for testing socks5 clients #1280

Merged
merged 5 commits into from
Sep 16, 2023
Merged
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
1 change: 0 additions & 1 deletion internal/legacy/netx/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ func TestHTTPTransportWorkingAsIntended(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
log.SetLevel(log.DebugLevel)
counter := bytecounter.New()
config := Config{
BogonIsError: true,
Expand Down
2 changes: 0 additions & 2 deletions internal/netemx/oohelperd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ func TestOOHelperDHandler(t *testing.T) {
}
thReqRaw := runtimex.Try1(json.Marshal(thReq))

//log.SetLevel(log.DebugLevel)

// TODO(https://github.com/ooni/probe/issues/2534): NewHTTPClientStdlib has QUIRKS but they're not needed here
httpClient := netxlite.NewHTTPClientStdlib(log.Log)

Expand Down
2 changes: 0 additions & 2 deletions internal/testingproxy/hosthttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ func (tc *hostNetworkTestCaseWithHTTP) Run(t *testing.T) {
proxyServer := testingx.MustNewHTTPServer(testingx.NewHTTPProxyHandler(log.Log, netx))
defer proxyServer.Close()

//log.SetLevel(log.DebugLevel)

// create an HTTP client configured to use the given proxy
//
// note how we use a dialer that asserts that we're using the proxy IP address
Expand Down
2 changes: 0 additions & 2 deletions internal/testingproxy/hosthttps.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ func (tc *hostNetworkTestCaseWithHTTPWithTLS) Run(t *testing.T) {
proxyServer := testingx.MustNewHTTPServerTLS(testingx.NewHTTPProxyHandler(log.Log, netx))
defer proxyServer.Close()

//log.SetLevel(log.DebugLevel)

// extend the default cert pool with the proxy's own CA
pool := netxlite.NewMozillaCertPool()
pool.AddCert(proxyServer.CACert)
Expand Down
2 changes: 0 additions & 2 deletions internal/testingproxy/netemhttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ func (tc *netemTestCaseWithHTTP) Run(t *testing.T) {
// create the netx instance for the client
netx := &netxlite.Netx{Underlying: &netxlite.NetemUnderlyingNetworkAdapter{UNet: clientStack}}

//log.SetLevel(log.DebugLevel)

// create an HTTP client configured to use the given proxy
//
// note how we use a dialer that asserts that we're using the proxy IP address
Expand Down
2 changes: 0 additions & 2 deletions internal/testingproxy/netemhttps.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ func (tc *netemTestCaseWithHTTPWithTLS) Run(t *testing.T) {
// create the netx instance for the client
netx := &netxlite.Netx{Underlying: &netxlite.NetemUnderlyingNetworkAdapter{UNet: clientStack}}

//log.SetLevel(log.DebugLevel)

// create an HTTP client configured to use the given proxy
//
// note how we use a dialer that asserts that we're using the proxy IP address
Expand Down
16 changes: 14 additions & 2 deletions internal/testingproxy/qa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@ import (
"github.com/ooni/probe-cli/v3/internal/testingproxy"
)

func TestWorkingAsIntended(t *testing.T) {
for _, testCase := range testingproxy.AllTestCases {
func TestHTTPProxy(t *testing.T) {
for _, testCase := range testingproxy.HTTPTestCases {
t.Run(testCase.Name(), func(t *testing.T) {
short := testCase.Short()
if !short && testing.Short() {
t.Skip("skip test in short mode")
}
testCase.Run(t)
})
}
}

func TestSOCKSProxy(t *testing.T) {
for _, testCase := range testingproxy.SOCKSTestCases {
t.Run(testCase.Name(), func(t *testing.T) {
short := testCase.Short()
if !short && testing.Short() {
Expand Down
72 changes: 72 additions & 0 deletions internal/testingproxy/sockshost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package testingproxy

import (
"fmt"
"net"
"net/http"
"testing"

"github.com/apex/log"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/testingsocks5"
)

// WithHostNetworkSOCKSProxyAndURL returns a [TestCase] where:
//
// - we fetch a URL;
//
// - using the host network;
//
// - and a SOCKS5 proxy.
//
// Because this [TestCase] uses the host network, it does not run in -short mode.
func WithHostNetworkSOCKSProxyAndURL(URL string) TestCase {
return &hostNetworkTestCaseWithSOCKS{
TargetURL: URL,
}
}

type hostNetworkTestCaseWithSOCKS struct {
TargetURL string
}

var _ TestCase = &hostNetworkTestCaseWithSOCKS{}

// Name implements TestCase.
func (tc *hostNetworkTestCaseWithSOCKS) Name() string {
return fmt.Sprintf("fetching %s using the host network and a SOCKS5 proxy", tc.TargetURL)
}

// Run implements TestCase.
func (tc *hostNetworkTestCaseWithSOCKS) Run(t *testing.T) {
// create an instance of Netx where the underlying network is nil,
// which means we're using the host's network
netx := &netxlite.Netx{Underlying: nil}

// create the proxy server using the host network
endpoint := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0}
proxyServer := testingsocks5.MustNewServer(log.Log, netx, endpoint)
defer proxyServer.Close()

// create an HTTP client configured to use the given proxy
//
// note how we use a dialer that asserts that we're using the proxy IP address
// rather than the host address, so we're sure that we're using the proxy
dialer := &dialerWithAssertions{
ExpectAddress: "127.0.0.1",
Dialer: netx.NewDialerWithResolver(log.Log, netx.NewStdlibResolver(log.Log)),
}
tlsDialer := netxlite.NewTLSDialer(dialer, netxlite.NewTLSHandshakerStdlib(log.Log))
txp := netxlite.NewHTTPTransportWithOptions(log.Log, dialer, tlsDialer,
netxlite.HTTPTransportOptionProxyURL(proxyServer.URL()))
client := &http.Client{Transport: txp}
defer client.CloseIdleConnections()

// get the homepage and assert we're getting a succesful response
httpCheckResponse(t, client, tc.TargetURL)
}

// Short implements TestCase.
func (tc *hostNetworkTestCaseWithSOCKS) Short() bool {
return false
}
134 changes: 134 additions & 0 deletions internal/testingproxy/socksnetem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package testingproxy

import (
"crypto/tls"
"fmt"
"net"
"net/http"
"testing"

"github.com/apex/log"
"github.com/ooni/netem"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/runtimex"
"github.com/ooni/probe-cli/v3/internal/testingsocks5"
"github.com/ooni/probe-cli/v3/internal/testingx"
)

// WithNetemSOCKSProxyAndURL returns a [TestCase] where:
//
// - we fetch a URL;
//
// - using the github.com/ooni.netem;
//
// - and a SOCKS5 proxy.
//
// Because this [TestCase] uses netem, it also runs in -short mode.
func WithNetemSOCKSProxyAndURL(URL string) TestCase {
return &netemTestCaseWithSOCKS{
TargetURL: URL,
}
}

type netemTestCaseWithSOCKS struct {
TargetURL string
}

var _ TestCase = &netemTestCaseWithSOCKS{}

// Name implements TestCase.
func (tc *netemTestCaseWithSOCKS) Name() string {
return fmt.Sprintf("fetching %s using netem and a SOCKS5 proxy", tc.TargetURL)
}

// Run implements TestCase.
func (tc *netemTestCaseWithSOCKS) Run(t *testing.T) {
topology := runtimex.Try1(netem.NewStarTopology(log.Log))
defer topology.Close()

const (
wwwIPAddr = "93.184.216.34"
proxyIPAddr = "10.0.0.1"
clientIPAddr = "10.0.0.2"
)

// create:
//
// - a www stack modeling www.example.com
//
// - a proxy stack
//
// - a client stack
//
// Note that www.example.com's IP address is also the resolver used by everyone
wwwStack := runtimex.Try1(topology.AddHost(wwwIPAddr, wwwIPAddr, &netem.LinkConfig{}))
proxyStack := runtimex.Try1(topology.AddHost(proxyIPAddr, wwwIPAddr, &netem.LinkConfig{}))
clientStack := runtimex.Try1(topology.AddHost(clientIPAddr, wwwIPAddr, &netem.LinkConfig{}))

// configure the wwwStack as the DNS resolver with proper configuration
dnsConfig := netem.NewDNSConfig()
dnsConfig.AddRecord("www.example.com.", "", wwwIPAddr)
dnsServer := runtimex.Try1(netem.NewDNSServer(log.Log, wwwStack, wwwIPAddr, dnsConfig))
defer dnsServer.Close()

// configure the wwwStack to respond to HTTP requests on port 80
wwwServer80 := testingx.MustNewHTTPServerEx(
&net.TCPAddr{IP: net.ParseIP(wwwIPAddr), Port: 80},
wwwStack,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Bonsoir, Elliot!\r\n"))
}),
)
defer wwwServer80.Close()

// configure the wwwStack to respond to HTTPS requests on port 443
wwwServer443 := testingx.MustNewHTTPServerTLSEx(
&net.TCPAddr{IP: net.ParseIP(wwwIPAddr), Port: 443},
wwwStack,
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Bonsoir, Elliot!\r\n"))
}),
wwwStack,
)
defer wwwServer443.Close()

// configure the proxyStack to implement the SOCKS proxy on port 9050
proxyServer := testingsocks5.MustNewServer(
log.Log,
&netxlite.Netx{
Underlying: &netxlite.NetemUnderlyingNetworkAdapter{UNet: proxyStack}},
&net.TCPAddr{IP: net.ParseIP(proxyIPAddr), Port: 9050},
)
defer proxyServer.Close()

// create the netx instance for the client
netx := &netxlite.Netx{Underlying: &netxlite.NetemUnderlyingNetworkAdapter{UNet: clientStack}}

// create an HTTP client configured to use the given proxy
//
// note how we use a dialer that asserts that we're using the proxy IP address
// rather than the host address, so we're sure that we're using the proxy
dialer := &dialerWithAssertions{
ExpectAddress: proxyIPAddr,
Dialer: netx.NewDialerWithResolver(log.Log, netx.NewStdlibResolver(log.Log)),
}
tlsDialer := netxlite.NewTLSDialer(dialer, netx.NewTLSHandshakerStdlib(log.Log))
txp := netxlite.NewHTTPTransportWithOptions(log.Log, dialer, tlsDialer,
netxlite.HTTPTransportOptionProxyURL(proxyServer.URL()),

// TODO(https://github.com/ooni/probe/issues/2536)
netxlite.HTTPTransportOptionTLSClientConfig(&tls.Config{
RootCAs: runtimex.Try1(clientStack.DefaultCertPool()),
}),
)
client := &http.Client{Transport: txp}
defer client.CloseIdleConnections()

// get the homepage and assert we're getting a succesful response
httpCheckResponse(t, client, tc.TargetURL)
}

// Short implements TestCase.
func (tc *netemTestCaseWithSOCKS) Short() bool {
return true
}
23 changes: 19 additions & 4 deletions internal/testingproxy/testcase.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,28 @@ type TestCase interface {
Short() bool
}

// AllTestCases contains all the test cases.
var AllTestCases = []TestCase{
// host network and HTTP proxy
// SOCKSTestCases contains the SOCKS test cases.
var SOCKSTestCases = []TestCase{
// with host network
WithHostNetworkSOCKSProxyAndURL("http://www.example.com/"),
WithHostNetworkSOCKSProxyAndURL("https://www.example.com/"),

// with netem
WithNetemSOCKSProxyAndURL("http://www.example.com/"),
WithNetemSOCKSProxyAndURL("https://www.example.com/"),

// with netem and IPv4 addresses so we test another SOCKS5 dialing mode
WithNetemSOCKSProxyAndURL("http://93.184.216.34/"),
WithNetemSOCKSProxyAndURL("https://93.184.216.34/"),
}

// HTTPTestCases contains the HTTP test cases.
var HTTPTestCases = []TestCase{
// with host network and HTTP proxy
WithHostNetworkHTTPProxyAndURL("http://www.example.com/"),
WithHostNetworkHTTPProxyAndURL("https://www.example.com/"),

// host network and HTTPS proxy
// with host network and HTTPS proxy
WithHostNetworkHTTPWithTLSProxyAndURL("http://www.example.com/"),
WithHostNetworkHTTPWithTLSProxyAndURL("https://www.example.com/"),

Expand Down
20 changes: 20 additions & 0 deletions internal/testingsocks5/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2014 Armon Dadgar

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Loading