Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
ai3
tools
float-debug-proxy
Commits
b3a356ef
Commit
b3a356ef
authored
Sep 05, 2018
by
ale
Browse files
Initial commit
parents
Changes
294
Hide whitespace changes
Inline
Side-by-side
.gitlab-ci.yml
0 → 100644
View file @
b3a356ef
stages
:
-
build
build
:
stage
:
build
image
:
"
ai/build:stretch"
script
:
-
env DEBIAN_FRONTEND=noninteractive apt-get -qy install golang
-
mkdir build/git.autistici.org/ai3
-
ln -s $PWD build/git.autistici.org/ai3/float-debug-proxy
-
env GOPATH=$PWD/build go build -o float-debug-proxy git.autistici.org/ai3/float-debug-proxy
artifacts
:
paths
:
-
float-debug-proxy
only
:
-
master
README.md
0 → 100644
View file @
b3a356ef
float-debug-proxy
===
A SOCKS5 proxy server for testing environments.
This tool solves a very specific problem: when using
[
float
](
https://git.autistici.org/ai3/float
)
test environments, we'd
like to use a browser to debug the services, but the browser needs its
own testing environment, using the DNS provided by the test
environment itself. At the same time, we would like to do this without
any changes to the user environment (such as manually creating entries
in /etc/hosts, for instance), because they pose an unreasonable burden
on the user.
One way to achieve this with low friction is to use a proxy running on
the test environment itself, so that by simply pointing the browser at
it would get you the "internal" view. SSH can easily do this by
running its own SOCKS proxy, for example, but then we run into another
issue: ssh uses the system resolver, and if we point
*/etc/resolv.conf*
at the test environment authoritative DNS servers,
we break the test environment isolation and make it harder to run
environments with "real" domains (if there is any overlap with the CI
domains, for instance, Ansible will break). That's how we get to the
tool you're looking at right now: a simple SOCKS5 proxy, that can be
pointed at a specific DNS resolver, ignoring the system
/etc/resolv.conf. It will run on a test environment frontend node,
without any authentication (these are non-public hosts). You can then
run a test browser session with:
$BROWSER --user-data-dir /tmp/test --proxy-server=socks5://$IP:9051
(with suitable values of $BROWSER and $IP).
proxy.go
0 → 100644
View file @
b3a356ef
package
main
import
(
"context"
"errors"
"flag"
"log"
"net"
"github.com/armon/go-socks5"
"github.com/miekg/dns"
)
var
(
addr
=
flag
.
String
(
"addr"
,
":9051"
,
"`addr`ess to listen on"
)
resolverIP
=
flag
.
String
(
"resolver"
,
"127.0.0.1"
,
"IP `addr`ess of the DNS resolver to use"
)
)
// IPv4-only resolver (for now).
type
resolver
struct
{
addr
string
}
func
(
r
*
resolver
)
Resolve
(
ctx
context
.
Context
,
name
string
)
(
context
.
Context
,
net
.
IP
,
error
)
{
m
:=
new
(
dns
.
Msg
)
m
.
SetQuestion
(
dns
.
Fqdn
(
name
),
dns
.
TypeA
)
m
.
RecursionDesired
=
true
in
,
err
:=
dns
.
Exchange
(
m
,
r
.
addr
)
if
err
!=
nil
{
return
ctx
,
nil
,
err
}
for
_
,
rec
:=
range
in
.
Answer
{
if
a
,
ok
:=
rec
.
(
*
dns
.
A
);
ok
{
return
ctx
,
a
.
A
,
nil
}
}
return
ctx
,
nil
,
errors
.
New
(
"not found"
)
}
func
main
()
{
log
.
SetFlags
(
0
)
flag
.
Parse
()
config
:=
socks5
.
Config
{
Resolver
:
&
resolver
{
*
resolverIP
+
":53"
},
}
server
,
err
:=
socks5
.
New
(
&
config
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
if
err
:=
server
.
ListenAndServe
(
"tcp"
,
*
addr
);
err
!=
nil
{
log
.
Fatal
(
err
)
}
}
vendor/github.com/armon/go-socks5/LICENSE
0 → 100644
View file @
b3a356ef
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.
vendor/github.com/armon/go-socks5/README.md
0 → 100644
View file @
b3a356ef
go-socks5 [](https://travis-ci.org/armon/go-socks5)
=========
Provides the
`socks5`
package that implements a
[
SOCKS5 server
](
http://en.wikipedia.org/wiki/SOCKS
)
.
SOCKS (Secure Sockets) is used to route traffic between a client and server through
an intermediate proxy layer. This can be used to bypass firewalls or NATs.
Feature
=======
The package has the following features:
*
"No Auth" mode
*
User/Password authentication
*
Support for the CONNECT command
*
Rules to do granular filtering of commands
*
Custom DNS resolution
*
Unit tests
TODO
====
The package still needs the following:
*
Support for the BIND command
*
Support for the ASSOCIATE command
Example
=======
Below is a simple example of usage
```
go
// Create a SOCKS5 server
conf
:=
&
socks5
.
Config
{}
server
,
err
:=
socks5
.
New
(
conf
)
if
err
!=
nil
{
panic
(
err
)
}
// Create SOCKS5 proxy on localhost port 8000
if
err
:=
server
.
ListenAndServe
(
"tcp"
,
"127.0.0.1:8000"
);
err
!=
nil
{
panic
(
err
)
}
```
vendor/github.com/armon/go-socks5/auth.go
0 → 100644
View file @
b3a356ef
package
socks5
import
(
"fmt"
"io"
)
const
(
NoAuth
=
uint8
(
0
)
noAcceptable
=
uint8
(
255
)
UserPassAuth
=
uint8
(
2
)
userAuthVersion
=
uint8
(
1
)
authSuccess
=
uint8
(
0
)
authFailure
=
uint8
(
1
)
)
var
(
UserAuthFailed
=
fmt
.
Errorf
(
"User authentication failed"
)
NoSupportedAuth
=
fmt
.
Errorf
(
"No supported authentication mechanism"
)
)
// A Request encapsulates authentication state provided
// during negotiation
type
AuthContext
struct
{
// Provided auth method
Method
uint8
// Payload provided during negotiation.
// Keys depend on the used auth method.
// For UserPassauth contains Username
Payload
map
[
string
]
string
}
type
Authenticator
interface
{
Authenticate
(
reader
io
.
Reader
,
writer
io
.
Writer
)
(
*
AuthContext
,
error
)
GetCode
()
uint8
}
// NoAuthAuthenticator is used to handle the "No Authentication" mode
type
NoAuthAuthenticator
struct
{}
func
(
a
NoAuthAuthenticator
)
GetCode
()
uint8
{
return
NoAuth
}
func
(
a
NoAuthAuthenticator
)
Authenticate
(
reader
io
.
Reader
,
writer
io
.
Writer
)
(
*
AuthContext
,
error
)
{
_
,
err
:=
writer
.
Write
([]
byte
{
socks5Version
,
NoAuth
})
return
&
AuthContext
{
NoAuth
,
nil
},
err
}
// UserPassAuthenticator is used to handle username/password based
// authentication
type
UserPassAuthenticator
struct
{
Credentials
CredentialStore
}
func
(
a
UserPassAuthenticator
)
GetCode
()
uint8
{
return
UserPassAuth
}
func
(
a
UserPassAuthenticator
)
Authenticate
(
reader
io
.
Reader
,
writer
io
.
Writer
)
(
*
AuthContext
,
error
)
{
// Tell the client to use user/pass auth
if
_
,
err
:=
writer
.
Write
([]
byte
{
socks5Version
,
UserPassAuth
});
err
!=
nil
{
return
nil
,
err
}
// Get the version and username length
header
:=
[]
byte
{
0
,
0
}
if
_
,
err
:=
io
.
ReadAtLeast
(
reader
,
header
,
2
);
err
!=
nil
{
return
nil
,
err
}
// Ensure we are compatible
if
header
[
0
]
!=
userAuthVersion
{
return
nil
,
fmt
.
Errorf
(
"Unsupported auth version: %v"
,
header
[
0
])
}
// Get the user name
userLen
:=
int
(
header
[
1
])
user
:=
make
([]
byte
,
userLen
)
if
_
,
err
:=
io
.
ReadAtLeast
(
reader
,
user
,
userLen
);
err
!=
nil
{
return
nil
,
err
}
// Get the password length
if
_
,
err
:=
reader
.
Read
(
header
[
:
1
]);
err
!=
nil
{
return
nil
,
err
}
// Get the password
passLen
:=
int
(
header
[
0
])
pass
:=
make
([]
byte
,
passLen
)
if
_
,
err
:=
io
.
ReadAtLeast
(
reader
,
pass
,
passLen
);
err
!=
nil
{
return
nil
,
err
}
// Verify the password
if
a
.
Credentials
.
Valid
(
string
(
user
),
string
(
pass
))
{
if
_
,
err
:=
writer
.
Write
([]
byte
{
userAuthVersion
,
authSuccess
});
err
!=
nil
{
return
nil
,
err
}
}
else
{
if
_
,
err
:=
writer
.
Write
([]
byte
{
userAuthVersion
,
authFailure
});
err
!=
nil
{
return
nil
,
err
}
return
nil
,
UserAuthFailed
}
// Done
return
&
AuthContext
{
UserPassAuth
,
map
[
string
]
string
{
"Username"
:
string
(
user
)}},
nil
}
// authenticate is used to handle connection authentication
func
(
s
*
Server
)
authenticate
(
conn
io
.
Writer
,
bufConn
io
.
Reader
)
(
*
AuthContext
,
error
)
{
// Get the methods
methods
,
err
:=
readMethods
(
bufConn
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"Failed to get auth methods: %v"
,
err
)
}
// Select a usable method
for
_
,
method
:=
range
methods
{
cator
,
found
:=
s
.
authMethods
[
method
]
if
found
{
return
cator
.
Authenticate
(
bufConn
,
conn
)
}
}
// No usable method found
return
nil
,
noAcceptableAuth
(
conn
)
}
// noAcceptableAuth is used to handle when we have no eligible
// authentication mechanism
func
noAcceptableAuth
(
conn
io
.
Writer
)
error
{
conn
.
Write
([]
byte
{
socks5Version
,
noAcceptable
})
return
NoSupportedAuth
}
// readMethods is used to read the number of methods
// and proceeding auth methods
func
readMethods
(
r
io
.
Reader
)
([]
byte
,
error
)
{
header
:=
[]
byte
{
0
}
if
_
,
err
:=
r
.
Read
(
header
);
err
!=
nil
{
return
nil
,
err
}
numMethods
:=
int
(
header
[
0
])
methods
:=
make
([]
byte
,
numMethods
)
_
,
err
:=
io
.
ReadAtLeast
(
r
,
methods
,
numMethods
)
return
methods
,
err
}
vendor/github.com/armon/go-socks5/credentials.go
0 → 100644
View file @
b3a356ef
package
socks5
// CredentialStore is used to support user/pass authentication
type
CredentialStore
interface
{
Valid
(
user
,
password
string
)
bool
}
// StaticCredentials enables using a map directly as a credential store
type
StaticCredentials
map
[
string
]
string
func
(
s
StaticCredentials
)
Valid
(
user
,
password
string
)
bool
{
pass
,
ok
:=
s
[
user
]
if
!
ok
{
return
false
}
return
password
==
pass
}
vendor/github.com/armon/go-socks5/request.go
0 → 100644
View file @
b3a356ef
package
socks5
import
(
"fmt"
"io"
"net"
"strconv"
"strings"
"golang.org/x/net/context"
)
const
(
ConnectCommand
=
uint8
(
1
)
BindCommand
=
uint8
(
2
)
AssociateCommand
=
uint8
(
3
)
ipv4Address
=
uint8
(
1
)
fqdnAddress
=
uint8
(
3
)
ipv6Address
=
uint8
(
4
)
)
const
(
successReply
uint8
=
iota
serverFailure
ruleFailure
networkUnreachable
hostUnreachable
connectionRefused
ttlExpired
commandNotSupported
addrTypeNotSupported
)
var
(
unrecognizedAddrType
=
fmt
.
Errorf
(
"Unrecognized address type"
)
)
// AddressRewriter is used to rewrite a destination transparently
type
AddressRewriter
interface
{
Rewrite
(
ctx
context
.
Context
,
request
*
Request
)
(
context
.
Context
,
*
AddrSpec
)
}
// AddrSpec is used to return the target AddrSpec
// which may be specified as IPv4, IPv6, or a FQDN
type
AddrSpec
struct
{
FQDN
string
IP
net
.
IP
Port
int
}
func
(
a
*
AddrSpec
)
String
()
string
{
if
a
.
FQDN
!=
""
{
return
fmt
.
Sprintf
(
"%s (%s):%d"
,
a
.
FQDN
,
a
.
IP
,
a
.
Port
)
}
return
fmt
.
Sprintf
(
"%s:%d"
,
a
.
IP
,
a
.
Port
)
}
// Address returns a string suitable to dial; prefer returning IP-based
// address, fallback to FQDN
func
(
a
AddrSpec
)
Address
()
string
{
if
0
!=
len
(
a
.
IP
)
{
return
net
.
JoinHostPort
(
a
.
IP
.
String
(),
strconv
.
Itoa
(
a
.
Port
))
}
return
net
.
JoinHostPort
(
a
.
FQDN
,
strconv
.
Itoa
(
a
.
Port
))
}
// A Request represents request received by a server
type
Request
struct
{
// Protocol version
Version
uint8
// Requested command
Command
uint8
// AuthContext provided during negotiation
AuthContext
*
AuthContext
// AddrSpec of the the network that sent the request
RemoteAddr
*
AddrSpec
// AddrSpec of the desired destination
DestAddr
*
AddrSpec
// AddrSpec of the actual destination (might be affected by rewrite)
realDestAddr
*
AddrSpec
bufConn
io
.
Reader
}
type
conn
interface
{
Write
([]
byte
)
(
int
,
error
)
RemoteAddr
()
net
.
Addr
}
// NewRequest creates a new Request from the tcp connection
func
NewRequest
(
bufConn
io
.
Reader
)
(
*
Request
,
error
)
{
// Read the version byte
header
:=
[]
byte
{
0
,
0
,
0
}
if
_
,
err
:=
io
.
ReadAtLeast
(
bufConn
,
header
,
3
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"Failed to get command version: %v"
,
err
)
}
// Ensure we are compatible
if
header
[
0
]
!=
socks5Version
{
return
nil
,
fmt
.
Errorf
(
"Unsupported command version: %v"
,
header
[
0
])
}
// Read in the destination address
dest
,
err
:=
readAddrSpec
(
bufConn
)
if
err
!=
nil
{
return
nil
,
err
}
request
:=
&
Request
{
Version
:
socks5Version
,
Command
:
header
[
1
],
DestAddr
:
dest
,
bufConn
:
bufConn
,
}
return
request
,
nil
}
// handleRequest is used for request processing after authentication
func
(
s
*
Server
)
handleRequest
(
req
*
Request
,
conn
conn
)
error
{
ctx
:=
context
.
Background
()
// Resolve the address if we have a FQDN
dest
:=
req
.
DestAddr
if
dest
.
FQDN
!=
""
{
ctx_
,
addr
,
err
:=
s
.
config
.
Resolver
.
Resolve
(
ctx
,
dest
.
FQDN
)
if
err
!=
nil
{
if
err
:=
sendReply
(
conn
,
hostUnreachable
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
fmt
.
Errorf
(
"Failed to resolve destination '%v': %v"
,
dest
.
FQDN
,
err
)
}
ctx
=
ctx_
dest
.
IP
=
addr
}
// Apply any address rewrites
req
.
realDestAddr
=
req
.
DestAddr
if
s
.
config
.
Rewriter
!=
nil
{
ctx
,
req
.
realDestAddr
=
s
.
config
.
Rewriter
.
Rewrite
(
ctx
,
req
)
}
// Switch on the command
switch
req
.
Command
{
case
ConnectCommand
:
return
s
.
handleConnect
(
ctx
,
conn
,
req
)
case
BindCommand
:
return
s
.
handleBind
(
ctx
,
conn
,
req
)
case
AssociateCommand
:
return
s
.
handleAssociate
(
ctx
,
conn
,
req
)
default
:
if
err
:=
sendReply
(
conn
,
commandNotSupported
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
fmt
.
Errorf
(
"Unsupported command: %v"
,
req
.
Command
)
}
}
// handleConnect is used to handle a connect command
func
(
s
*
Server
)
handleConnect
(
ctx
context
.
Context
,
conn
conn
,
req
*
Request
)
error
{
// Check if this is allowed
if
ctx_
,
ok
:=
s
.
config
.
Rules
.
Allow
(
ctx
,
req
);
!
ok
{
if
err
:=
sendReply
(
conn
,
ruleFailure
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
fmt
.
Errorf
(
"Connect to %v blocked by rules"
,
req
.
DestAddr
)
}
else
{
ctx
=
ctx_
}
// Attempt to connect
dial
:=
s
.
config
.
Dial
if
dial
==
nil
{
dial
=
func
(
ctx
context
.
Context
,
net_
,
addr
string
)
(
net
.
Conn
,
error
)
{
return
net
.
Dial
(
net_
,
addr
)
}
}
target
,
err
:=
dial
(
ctx
,
"tcp"
,
req
.
realDestAddr
.
Address
())
if
err
!=
nil
{
msg
:=
err
.
Error
()
resp
:=
hostUnreachable
if
strings
.
Contains
(
msg
,
"refused"
)
{
resp
=
connectionRefused
}
else
if
strings
.
Contains
(
msg
,
"network is unreachable"
)
{
resp
=
networkUnreachable
}
if
err
:=
sendReply
(
conn
,
resp
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
fmt
.
Errorf
(
"Connect to %v failed: %v"
,
req
.
DestAddr
,
err
)
}
defer
target
.
Close
()
// Send success
local
:=
target
.
LocalAddr
()
.
(
*
net
.
TCPAddr
)
bind
:=
AddrSpec
{
IP
:
local
.
IP
,
Port
:
local
.
Port
}
if
err
:=
sendReply
(
conn
,
successReply
,
&
bind
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
// Start proxying
errCh
:=
make
(
chan
error
,
2
)
go
proxy
(
target
,
req
.
bufConn
,
errCh
)
go
proxy
(
conn
,
target
,
errCh
)
// Wait
for
i
:=
0
;
i
<
2
;
i
++
{
e
:=
<-
errCh
if
e
!=
nil
{
// return from this function closes target (and conn).
return
e
}
}
return
nil
}
// handleBind is used to handle a connect command
func
(
s
*
Server
)
handleBind
(
ctx
context
.
Context
,
conn
conn
,
req
*
Request
)
error
{
// Check if this is allowed
if
ctx_
,
ok
:=
s
.
config
.
Rules
.
Allow
(
ctx
,
req
);
!
ok
{
if
err
:=
sendReply
(
conn
,
ruleFailure
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
fmt
.
Errorf
(
"Bind to %v blocked by rules"
,
req
.
DestAddr
)
}
else
{
ctx
=
ctx_
}
// TODO: Support bind
if
err
:=
sendReply
(
conn
,
commandNotSupported
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
nil
}
// handleAssociate is used to handle a connect command
func
(
s
*
Server
)
handleAssociate
(
ctx
context
.
Context
,
conn
conn
,
req
*
Request
)
error
{
// Check if this is allowed
if
ctx_
,
ok
:=
s
.
config
.
Rules
.
Allow
(
ctx
,
req
);
!
ok
{
if
err
:=
sendReply
(
conn
,
ruleFailure
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
fmt
.
Errorf
(
"Associate to %v blocked by rules"
,
req
.
DestAddr
)
}
else
{
ctx
=
ctx_
}
// TODO: Support associate
if
err
:=
sendReply
(
conn
,
commandNotSupported
,
nil
);
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to send reply: %v"
,
err
)
}
return
nil
}
// readAddrSpec is used to read AddrSpec.
// Expects an address type byte, follwed by the address and port
func
readAddrSpec
(
r
io
.
Reader
)
(
*
AddrSpec
,
error
)
{
d
:=
&
AddrSpec
{}
// Get the address type
addrType
:=
[]
byte
{
0
}
if
_
,
err
:=
r
.
Read
(
addrType
);
err
!=
nil
{
return
nil
,
err
}
// Handle on a per type basis
switch
addrType
[
0
]
{
case
ipv4Address
:
addr
:=
make
([]
byte
,
4
)