mirror of https://github.com/ginuerzh/gost
committed by
GitHub
19 changed files with 4273 additions and 3 deletions
@ -0,0 +1,2 @@ |
|||||
|
/examples/dummy-client/dummy-client |
||||
|
/examples/dummy-server/dummy-server |
||||
@ -0,0 +1,121 @@ |
|||||
|
Creative Commons Legal Code |
||||
|
|
||||
|
CC0 1.0 Universal |
||||
|
|
||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE |
||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN |
||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS |
||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES |
||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS |
||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM |
||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED |
||||
|
HEREUNDER. |
||||
|
|
||||
|
Statement of Purpose |
||||
|
|
||||
|
The laws of most jurisdictions throughout the world automatically confer |
||||
|
exclusive Copyright and Related Rights (defined below) upon the creator |
||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of |
||||
|
authorship and/or a database (each, a "Work"). |
||||
|
|
||||
|
Certain owners wish to permanently relinquish those rights to a Work for |
||||
|
the purpose of contributing to a commons of creative, cultural and |
||||
|
scientific works ("Commons") that the public can reliably and without fear |
||||
|
of later claims of infringement build upon, modify, incorporate in other |
||||
|
works, reuse and redistribute as freely as possible in any form whatsoever |
||||
|
and for any purposes, including without limitation commercial purposes. |
||||
|
These owners may contribute to the Commons to promote the ideal of a free |
||||
|
culture and the further production of creative, cultural and scientific |
||||
|
works, or to gain reputation or greater distribution for their Work in |
||||
|
part through the use and efforts of others. |
||||
|
|
||||
|
For these and/or other purposes and motivations, and without any |
||||
|
expectation of additional consideration or compensation, the person |
||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she |
||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily |
||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its |
||||
|
terms, with knowledge of his or her Copyright and Related Rights in the |
||||
|
Work and the meaning and intended legal effect of CC0 on those rights. |
||||
|
|
||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be |
||||
|
protected by copyright and related or neighboring rights ("Copyright and |
||||
|
Related Rights"). Copyright and Related Rights include, but are not |
||||
|
limited to, the following: |
||||
|
|
||||
|
i. the right to reproduce, adapt, distribute, perform, display, |
||||
|
communicate, and translate a Work; |
||||
|
ii. moral rights retained by the original author(s) and/or performer(s); |
||||
|
iii. publicity and privacy rights pertaining to a person's image or |
||||
|
likeness depicted in a Work; |
||||
|
iv. rights protecting against unfair competition in regards to a Work, |
||||
|
subject to the limitations in paragraph 4(a), below; |
||||
|
v. rights protecting the extraction, dissemination, use and reuse of data |
||||
|
in a Work; |
||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the |
||||
|
European Parliament and of the Council of 11 March 1996 on the legal |
||||
|
protection of databases, and under any national implementation |
||||
|
thereof, including any amended or successor version of such |
||||
|
directive); and |
||||
|
vii. other similar, equivalent or corresponding rights throughout the |
||||
|
world based on applicable law or treaty, and any national |
||||
|
implementations thereof. |
||||
|
|
||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention |
||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently, |
||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of |
||||
|
Affirmer's Copyright and Related Rights and associated claims and causes |
||||
|
of action, whether now known or unknown (including existing as well as |
||||
|
future claims and causes of action), in the Work (i) in all territories |
||||
|
worldwide, (ii) for the maximum duration provided by applicable law or |
||||
|
treaty (including future time extensions), (iii) in any current or future |
||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever, |
||||
|
including without limitation commercial, advertising or promotional |
||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each |
||||
|
member of the public at large and to the detriment of Affirmer's heirs and |
||||
|
successors, fully intending that such Waiver shall not be subject to |
||||
|
revocation, rescission, cancellation, termination, or any other legal or |
||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public |
||||
|
as contemplated by Affirmer's express Statement of Purpose. |
||||
|
|
||||
|
3. Public License Fallback. Should any part of the Waiver for any reason |
||||
|
be judged legally invalid or ineffective under applicable law, then the |
||||
|
Waiver shall be preserved to the maximum extent permitted taking into |
||||
|
account Affirmer's express Statement of Purpose. In addition, to the |
||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected |
||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive, |
||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and |
||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the |
||||
|
maximum duration provided by applicable law or treaty (including future |
||||
|
time extensions), (iii) in any current or future medium and for any number |
||||
|
of copies, and (iv) for any purpose whatsoever, including without |
||||
|
limitation commercial, advertising or promotional purposes (the |
||||
|
"License"). The License shall be deemed effective as of the date CC0 was |
||||
|
applied by Affirmer to the Work. Should any part of the License for any |
||||
|
reason be judged legally invalid or ineffective under applicable law, such |
||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder |
||||
|
of the License, and in such case Affirmer hereby affirms that he or she |
||||
|
will not (i) exercise any of his or her remaining Copyright and Related |
||||
|
Rights in the Work or (ii) assert any associated claims and causes of |
||||
|
action with respect to the Work, in either case contrary to Affirmer's |
||||
|
express Statement of Purpose. |
||||
|
|
||||
|
4. Limitations and Disclaimers. |
||||
|
|
||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned, |
||||
|
surrendered, licensed or otherwise affected by this document. |
||||
|
b. Affirmer offers the Work as-is and makes no representations or |
||||
|
warranties of any kind concerning the Work, express, implied, |
||||
|
statutory or otherwise, including without limitation warranties of |
||||
|
title, merchantability, fitness for a particular purpose, non |
||||
|
infringement, or the absence of latent or other defects, accuracy, or |
||||
|
the present or absence of errors, whether or not discoverable, all to |
||||
|
the greatest extent permissible under applicable law. |
||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons |
||||
|
that may apply to the Work or any use thereof, including without |
||||
|
limitation any person's Copyright and Related Rights in the Work. |
||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary |
||||
|
consents, permissions or other rights required for any use of the |
||||
|
Work. |
||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a |
||||
|
party to this document and has no duty or obligation with respect to |
||||
|
this CC0 or use of the Work. |
||||
@ -0,0 +1,80 @@ |
|||||
|
== v1.3.0 |
||||
|
|
||||
|
Added a DialOrWithDialer function that allows you to, for example, use a |
||||
|
specific source address when dialing the ORPort. |
||||
|
|
||||
|
== v1.2.0 |
||||
|
|
||||
|
The default and development branch is now "main" rather than "master". |
||||
|
The master branch will no longer be updated. |
||||
|
https://lists.torproject.org/pipermail/anti-censorship-team/2021-May/000168.html |
||||
|
If you have an existing clone of the master branch, run these commands |
||||
|
to update it: |
||||
|
git fetch origin |
||||
|
git remote set-head origin -a |
||||
|
git branch --move master main |
||||
|
git branch --set-upstream-to=origin/main main |
||||
|
|
||||
|
Added a go.mod file. |
||||
|
https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40065 |
||||
|
|
||||
|
== v1.1.0 |
||||
|
|
||||
|
Added the Log function. |
||||
|
https://bugs.torproject.org/28940 |
||||
|
|
||||
|
== v1.0.0 |
||||
|
|
||||
|
Changed the tag naming scheme to work better with Go modules. |
||||
|
https://github.com/golang/go/wiki/Modules#semantic-import-versioning |
||||
|
|
||||
|
== 0.7 |
||||
|
|
||||
|
Fixed the ProxyError function; previously it would always panic. |
||||
|
|
||||
|
Repeated transport names in TOR_PT_SERVER_BINDADDR now result in an |
||||
|
ENV-ERROR. |
||||
|
https://bugs.torproject.org/21261 |
||||
|
|
||||
|
== 0.6 |
||||
|
|
||||
|
Remove all support for the "*" transport specification. The argument to |
||||
|
the ClientSetup and ServerSetup functions is now unused. |
||||
|
https://bugs.torproject.org/15612 |
||||
|
|
||||
|
Replaced SOCKS4a with SOCKS5. |
||||
|
https://bugs.torproject.org/12535 |
||||
|
|
||||
|
== 0.5 |
||||
|
|
||||
|
The AcceptSocks function no longer reports non-permanent errors, such as |
||||
|
those caused by a faulty SOCKS handshake. |
||||
|
|
||||
|
Added support for an upstream proxy (TOR_PT_PROXY). The two new |
||||
|
functions are ProxyError and ProxyDone. The ClientInfo struct has a new |
||||
|
ProxyURL member. |
||||
|
https://bugs.torproject.org/12125 |
||||
|
|
||||
|
== 0.4 |
||||
|
|
||||
|
Read the ExtORPort cookie file on every call to DialOr, instead of |
||||
|
reading it once and caching the result. This is to work around a tor bug |
||||
|
where tor doesn't ensure a new cookie file is written before starting |
||||
|
pluggable transports. |
||||
|
https://bugs.torproject.org/15240 |
||||
|
|
||||
|
== 0.3 |
||||
|
|
||||
|
Made output functions panic intead of backslash-escaping. Escaping of |
||||
|
invalid bytes is not specified by pt-spec, and backslashes conflicted |
||||
|
with the specified escaping of SMETHOD ARGS. |
||||
|
https://bugs.torproject.org/13370 |
||||
|
|
||||
|
== 0.2 |
||||
|
|
||||
|
Added the MakeStateDir function. |
||||
|
|
||||
|
== 0.1 |
||||
|
== 0.0 |
||||
|
|
||||
|
Initial release. |
||||
@ -0,0 +1,25 @@ |
|||||
|
goptlib is a library for writing Tor pluggable transports in Go. |
||||
|
|
||||
|
https://spec.torproject.org/pt-spec |
||||
|
https://gitweb.torproject.org/torspec.git/tree/ext-orport-spec.txt |
||||
|
|
||||
|
To download a copy of the library into $GOPATH: |
||||
|
go get git.torproject.org/pluggable-transports/goptlib.git |
||||
|
|
||||
|
See the included example programs for examples of how to use the |
||||
|
library. To build them, enter their directory and run "go build". |
||||
|
examples/dummy-client/dummy-client.go |
||||
|
examples/dummy-server/dummy-server.go |
||||
|
The recommended way to start writing a new transport plugin is to copy |
||||
|
dummy-client or dummy-server and make changes to it. |
||||
|
|
||||
|
There is browseable documentation here: |
||||
|
https://godoc.org/git.torproject.org/pluggable-transports/goptlib.git |
||||
|
|
||||
|
Report bugs to the [email protected] mailing list or to the |
||||
|
bug tracker at https://trac.torproject.org/projects/tor. |
||||
|
|
||||
|
To the extent possible under law, the authors have dedicated all |
||||
|
copyright and related and neighboring rights to this software to the |
||||
|
public domain worldwide. This software is distributed without any |
||||
|
warranty. See COPYING. |
||||
@ -0,0 +1,219 @@ |
|||||
|
package pt |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"fmt" |
||||
|
"sort" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
// Key–value mappings for the representation of client and server options.
|
||||
|
|
||||
|
// Args maps a string key to a list of values. It is similar to url.Values.
|
||||
|
type Args map[string][]string |
||||
|
|
||||
|
// Get the first value associated with the given key. If there are any values
|
||||
|
// associated with the key, the value return has the value and ok is set to
|
||||
|
// true. If there are no values for the given key, value is "" and ok is false.
|
||||
|
// If you need access to multiple values, use the map directly.
|
||||
|
func (args Args) Get(key string) (value string, ok bool) { |
||||
|
if args == nil { |
||||
|
return "", false |
||||
|
} |
||||
|
vals, ok := args[key] |
||||
|
if !ok || len(vals) == 0 { |
||||
|
return "", false |
||||
|
} |
||||
|
return vals[0], true |
||||
|
} |
||||
|
|
||||
|
// Append value to the list of values for key.
|
||||
|
func (args Args) Add(key, value string) { |
||||
|
args[key] = append(args[key], value) |
||||
|
} |
||||
|
|
||||
|
// Return the index of the next unescaped byte in s that is in the term set, or
|
||||
|
// else the length of the string if no terminators appear. Additionally return
|
||||
|
// the unescaped string up to the returned index.
|
||||
|
func indexUnescaped(s string, term []byte) (int, string, error) { |
||||
|
var i int |
||||
|
unesc := make([]byte, 0) |
||||
|
for i = 0; i < len(s); i++ { |
||||
|
b := s[i] |
||||
|
// A terminator byte?
|
||||
|
if bytes.IndexByte(term, b) != -1 { |
||||
|
break |
||||
|
} |
||||
|
if b == '\\' { |
||||
|
i++ |
||||
|
if i >= len(s) { |
||||
|
return 0, "", fmt.Errorf("nothing following final escape in %q", s) |
||||
|
} |
||||
|
b = s[i] |
||||
|
} |
||||
|
unesc = append(unesc, b) |
||||
|
} |
||||
|
return i, string(unesc), nil |
||||
|
} |
||||
|
|
||||
|
// Parse a name–value mapping as from an encoded SOCKS username/password.
|
||||
|
//
|
||||
|
// "First the '<Key>=<Value>' formatted arguments MUST be escaped, such that all
|
||||
|
// backslash, equal sign, and semicolon characters are escaped with a
|
||||
|
// backslash."
|
||||
|
func parseClientParameters(s string) (args Args, err error) { |
||||
|
args = make(Args) |
||||
|
if len(s) == 0 { |
||||
|
return |
||||
|
} |
||||
|
i := 0 |
||||
|
for { |
||||
|
var key, value string |
||||
|
var offset, begin int |
||||
|
|
||||
|
begin = i |
||||
|
// Read the key.
|
||||
|
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
i += offset |
||||
|
// End of string or no equals sign?
|
||||
|
if i >= len(s) || s[i] != '=' { |
||||
|
err = fmt.Errorf("no equals sign in %q", s[begin:i]) |
||||
|
return |
||||
|
} |
||||
|
// Skip the equals sign.
|
||||
|
i++ |
||||
|
// Read the value.
|
||||
|
offset, value, err = indexUnescaped(s[i:], []byte{';'}) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
i += offset |
||||
|
if len(key) == 0 { |
||||
|
err = fmt.Errorf("empty key in %q", s[begin:i]) |
||||
|
return |
||||
|
} |
||||
|
args.Add(key, value) |
||||
|
if i >= len(s) { |
||||
|
break |
||||
|
} |
||||
|
// Skip the semicolon.
|
||||
|
i++ |
||||
|
} |
||||
|
return args, nil |
||||
|
} |
||||
|
|
||||
|
// Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS.
|
||||
|
//
|
||||
|
// "...a semicolon-separated list of <key>:<value> pairs, where <key> is a PT
|
||||
|
// name and <value> is a k=v string value with options that are to be passed to
|
||||
|
// the transport. Colons, semicolons, equal signs and backslashes must be
|
||||
|
// escaped with a backslash."
|
||||
|
// Example: scramblesuit:key=banana;automata:rule=110;automata:depth=3
|
||||
|
func parseServerTransportOptions(s string) (opts map[string]Args, err error) { |
||||
|
opts = make(map[string]Args) |
||||
|
if len(s) == 0 { |
||||
|
return |
||||
|
} |
||||
|
i := 0 |
||||
|
for { |
||||
|
var methodName, key, value string |
||||
|
var offset, begin int |
||||
|
|
||||
|
begin = i |
||||
|
// Read the method name.
|
||||
|
offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
i += offset |
||||
|
// End of string or no colon?
|
||||
|
if i >= len(s) || s[i] != ':' { |
||||
|
err = fmt.Errorf("no colon in %q", s[begin:i]) |
||||
|
return |
||||
|
} |
||||
|
// Skip the colon.
|
||||
|
i++ |
||||
|
// Read the key.
|
||||
|
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
i += offset |
||||
|
// End of string or no equals sign?
|
||||
|
if i >= len(s) || s[i] != '=' { |
||||
|
err = fmt.Errorf("no equals sign in %q", s[begin:i]) |
||||
|
return |
||||
|
} |
||||
|
// Skip the equals sign.
|
||||
|
i++ |
||||
|
// Read the value.
|
||||
|
offset, value, err = indexUnescaped(s[i:], []byte{';'}) |
||||
|
if err != nil { |
||||
|
return |
||||
|
} |
||||
|
i += offset |
||||
|
if len(methodName) == 0 { |
||||
|
err = fmt.Errorf("empty method name in %q", s[begin:i]) |
||||
|
return |
||||
|
} |
||||
|
if len(key) == 0 { |
||||
|
err = fmt.Errorf("empty key in %q", s[begin:i]) |
||||
|
return |
||||
|
} |
||||
|
if opts[methodName] == nil { |
||||
|
opts[methodName] = make(Args) |
||||
|
} |
||||
|
opts[methodName].Add(key, value) |
||||
|
if i >= len(s) { |
||||
|
break |
||||
|
} |
||||
|
// Skip the semicolon.
|
||||
|
i++ |
||||
|
} |
||||
|
return opts, nil |
||||
|
} |
||||
|
|
||||
|
// Escape backslashes and all the bytes that are in set.
|
||||
|
func backslashEscape(s string, set []byte) string { |
||||
|
var buf bytes.Buffer |
||||
|
for _, b := range []byte(s) { |
||||
|
if b == '\\' || bytes.IndexByte(set, b) != -1 { |
||||
|
buf.WriteByte('\\') |
||||
|
} |
||||
|
buf.WriteByte(b) |
||||
|
} |
||||
|
return buf.String() |
||||
|
} |
||||
|
|
||||
|
// Encode a name–value mapping so that it is suitable to go in the ARGS option
|
||||
|
// of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not
|
||||
|
// added.
|
||||
|
//
|
||||
|
// "Equal signs and commas [and backslashes] MUST be escaped with a backslash."
|
||||
|
func encodeSmethodArgs(args Args) string { |
||||
|
if args == nil { |
||||
|
return "" |
||||
|
} |
||||
|
|
||||
|
keys := make([]string, 0, len(args)) |
||||
|
for key := range args { |
||||
|
keys = append(keys, key) |
||||
|
} |
||||
|
sort.Strings(keys) |
||||
|
|
||||
|
escape := func(s string) string { |
||||
|
return backslashEscape(s, []byte{'=', ','}) |
||||
|
} |
||||
|
|
||||
|
var pairs []string |
||||
|
for _, key := range keys { |
||||
|
for _, value := range args[key] { |
||||
|
pairs = append(pairs, escape(key)+"="+escape(value)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return strings.Join(pairs, ",") |
||||
|
} |
||||
@ -0,0 +1,374 @@ |
|||||
|
package pt |
||||
|
|
||||
|
import ( |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func stringSlicesEqual(a, b []string) bool { |
||||
|
if len(a) != len(b) { |
||||
|
return false |
||||
|
} |
||||
|
for i := range a { |
||||
|
if a[i] != b[i] { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
func argsEqual(a, b Args) bool { |
||||
|
for k, av := range a { |
||||
|
bv := b[k] |
||||
|
if !stringSlicesEqual(av, bv) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
for k, bv := range b { |
||||
|
av := a[k] |
||||
|
if !stringSlicesEqual(av, bv) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
func TestArgsGet(t *testing.T) { |
||||
|
args := Args{ |
||||
|
"a": []string{}, |
||||
|
"b": []string{"value"}, |
||||
|
"c": []string{"v1", "v2", "v3"}, |
||||
|
} |
||||
|
var uninit Args |
||||
|
|
||||
|
var v string |
||||
|
var ok bool |
||||
|
|
||||
|
// Get on nil map should be the same as Get on empty map.
|
||||
|
v, ok = uninit.Get("a") |
||||
|
if !(v == "" && !ok) { |
||||
|
t.Errorf("unexpected result from Get on nil Args: %q %v", v, ok) |
||||
|
} |
||||
|
|
||||
|
v, ok = args.Get("a") |
||||
|
if ok { |
||||
|
t.Errorf("Unexpected Get success for %q", "a") |
||||
|
} |
||||
|
if v != "" { |
||||
|
t.Errorf("Get failure returned other than %q: %q", "", v) |
||||
|
} |
||||
|
v, ok = args.Get("b") |
||||
|
if !ok { |
||||
|
t.Errorf("Unexpected Get failure for %q", "b") |
||||
|
} |
||||
|
if v != "value" { |
||||
|
t.Errorf("Get(%q) → %q (expected %q)", "b", v, "value") |
||||
|
} |
||||
|
v, ok = args.Get("c") |
||||
|
if !ok { |
||||
|
t.Errorf("Unexpected Get failure for %q", "c") |
||||
|
} |
||||
|
if v != "v1" { |
||||
|
t.Errorf("Get(%q) → %q (expected %q)", "c", v, "v1") |
||||
|
} |
||||
|
v, ok = args.Get("d") |
||||
|
if ok { |
||||
|
t.Errorf("Unexpected Get success for %q", "d") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestArgsAdd(t *testing.T) { |
||||
|
args := make(Args) |
||||
|
expected := Args{} |
||||
|
if !argsEqual(args, expected) { |
||||
|
t.Fatalf("%q != %q", args, expected) |
||||
|
} |
||||
|
args.Add("k1", "v1") |
||||
|
expected = Args{"k1": []string{"v1"}} |
||||
|
if !argsEqual(args, expected) { |
||||
|
t.Fatalf("%q != %q", args, expected) |
||||
|
} |
||||
|
args.Add("k2", "v2") |
||||
|
expected = Args{"k1": []string{"v1"}, "k2": []string{"v2"}} |
||||
|
if !argsEqual(args, expected) { |
||||
|
t.Fatalf("%q != %q", args, expected) |
||||
|
} |
||||
|
args.Add("k1", "v3") |
||||
|
expected = Args{"k1": []string{"v1", "v3"}, "k2": []string{"v2"}} |
||||
|
if !argsEqual(args, expected) { |
||||
|
t.Fatalf("%q != %q", args, expected) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestParseClientParameters(t *testing.T) { |
||||
|
badTests := [...]string{ |
||||
|
"key", |
||||
|
"key\\", |
||||
|
"=value", |
||||
|
"==value", |
||||
|
"==key=value", |
||||
|
"key=value\\", |
||||
|
"a=b;key=value\\", |
||||
|
"a;b=c", |
||||
|
";", |
||||
|
"key=value;", |
||||
|
";key=value", |
||||
|
"key\\=value", |
||||
|
} |
||||
|
goodTests := [...]struct { |
||||
|
input string |
||||
|
expected Args |
||||
|
}{ |
||||
|
{ |
||||
|
"", |
||||
|
Args{}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=", |
||||
|
Args{"key": []string{""}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key==", |
||||
|
Args{"key": []string{"="}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=value", |
||||
|
Args{"key": []string{"value"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"a=b=c", |
||||
|
Args{"a": []string{"b=c"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"a=bc==", |
||||
|
Args{"a": []string{"bc=="}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=a\nb", |
||||
|
Args{"key": []string{"a\nb"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=value\\;", |
||||
|
Args{"key": []string{"value;"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=\"value\"", |
||||
|
Args{"key": []string{"\"value\""}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=\"\"value\"\"", |
||||
|
Args{"key": []string{"\"\"value\"\""}}, |
||||
|
}, |
||||
|
{ |
||||
|
"\"key=value\"", |
||||
|
Args{"\"key": []string{"value\""}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=value;key=value", |
||||
|
Args{"key": []string{"value", "value"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key=value1;key=value2", |
||||
|
Args{"key": []string{"value1", "value2"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"key1=value1;key2=value2;key1=value3", |
||||
|
Args{"key1": []string{"value1", "value3"}, "key2": []string{"value2"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"\\;=\\;;\\\\=\\;", |
||||
|
Args{";": []string{";"}, "\\": []string{";"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"a\\=b=c", |
||||
|
Args{"a=b": []string{"c"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"shared-secret=rahasia;secrets-file=/tmp/blob", |
||||
|
Args{"shared-secret": []string{"rahasia"}, "secrets-file": []string{"/tmp/blob"}}, |
||||
|
}, |
||||
|
{ |
||||
|
"rocks=20;height=5.6", |
||||
|
Args{"rocks": []string{"20"}, "height": []string{"5.6"}}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
for _, input := range badTests { |
||||
|
_, err := parseClientParameters(input) |
||||
|
if err == nil { |
||||
|
t.Errorf("%q unexpectedly succeeded", input) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for _, test := range goodTests { |
||||
|
args, err := parseClientParameters(test.input) |
||||
|
if err != nil { |
||||
|
t.Errorf("%q unexpectedly returned an error: %s", test.input, err) |
||||
|
} |
||||
|
if !argsEqual(args, test.expected) { |
||||
|
t.Errorf("%q → %q (expected %q)", test.input, args, test.expected) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func optsEqual(a, b map[string]Args) bool { |
||||
|
for k, av := range a { |
||||
|
bv, ok := b[k] |
||||
|
if !ok || !argsEqual(av, bv) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
for k, bv := range b { |
||||
|
av, ok := a[k] |
||||
|
if !ok || !argsEqual(av, bv) { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
|
||||
|
func TestParseServerTransportOptions(t *testing.T) { |
||||
|
badTests := [...]string{ |
||||
|
"t\\", |
||||
|
":=", |
||||
|
"t:=", |
||||
|
":k=", |
||||
|
":=v", |
||||
|
"t:=v", |
||||
|
"t:=v", |
||||
|
"t:k\\", |
||||
|
"t:k=v;", |
||||
|
"abc", |
||||
|
"t:", |
||||
|
"key=value", |
||||
|
"=value", |
||||
|
"t:k=v\\", |
||||
|
"t1:k=v;t2:k=v\\", |
||||
|
"t:=key=value", |
||||
|
"t:==key=value", |
||||
|
"t:;key=value", |
||||
|
"t:key\\=value", |
||||
|
} |
||||
|
goodTests := [...]struct { |
||||
|
input string |
||||
|
expected map[string]Args |
||||
|
}{ |
||||
|
{ |
||||
|
"", |
||||
|
map[string]Args{}, |
||||
|
}, |
||||
|
{ |
||||
|
"t:k=v", |
||||
|
map[string]Args{ |
||||
|
"t": {"k": []string{"v"}}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"t:k=v=v", |
||||
|
map[string]Args{ |
||||
|
"t": {"k": []string{"v=v"}}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"t:k=vv==", |
||||
|
map[string]Args{ |
||||
|
"t": {"k": []string{"vv=="}}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"t1:k=v1;t2:k=v2;t1:k=v3", |
||||
|
map[string]Args{ |
||||
|
"t1": {"k": []string{"v1", "v3"}}, |
||||
|
"t2": {"k": []string{"v2"}}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"t\\:1:k=v;t\\=2:k=v;t\\;3:k=v;t\\\\4:k=v", |
||||
|
map[string]Args{ |
||||
|
"t:1": {"k": []string{"v"}}, |
||||
|
"t=2": {"k": []string{"v"}}, |
||||
|
"t;3": {"k": []string{"v"}}, |
||||
|
"t\\4": {"k": []string{"v"}}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"t:k\\:1=v;t:k\\=2=v;t:k\\;3=v;t:k\\\\4=v", |
||||
|
map[string]Args{ |
||||
|
"t": { |
||||
|
"k:1": []string{"v"}, |
||||
|
"k=2": []string{"v"}, |
||||
|
"k;3": []string{"v"}, |
||||
|
"k\\4": []string{"v"}, |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"t:k=v\\:1;t:k=v\\=2;t:k=v\\;3;t:k=v\\\\4", |
||||
|
map[string]Args{ |
||||
|
"t": {"k": []string{"v:1", "v=2", "v;3", "v\\4"}}, |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
"trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes", |
||||
|
map[string]Args{ |
||||
|
"trebuchet": {"secret": []string{"nou"}, "cache": []string{"/tmp/cache"}}, |
||||
|
"ballista": {"secret": []string{"yes"}}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
for _, input := range badTests { |
||||
|
_, err := parseServerTransportOptions(input) |
||||
|
if err == nil { |
||||
|
t.Errorf("%q unexpectedly succeeded", input) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for _, test := range goodTests { |
||||
|
opts, err := parseServerTransportOptions(test.input) |
||||
|
if err != nil { |
||||
|
t.Errorf("%q unexpectedly returned an error: %s", test.input, err) |
||||
|
} |
||||
|
if !optsEqual(opts, test.expected) { |
||||
|
t.Errorf("%q → %q (expected %q)", test.input, opts, test.expected) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func TestEncodeSmethodArgs(t *testing.T) { |
||||
|
tests := [...]struct { |
||||
|
args Args |
||||
|
expected string |
||||
|
}{ |
||||
|
{ |
||||
|
nil, |
||||
|
"", |
||||
|
}, |
||||
|
{ |
||||
|
Args{}, |
||||
|
"", |
||||
|
}, |
||||
|
{ |
||||
|
Args{"j": []string{"v1", "v2", "v3"}, "k": []string{"v1", "v2", "v3"}}, |
||||
|
"j=v1,j=v2,j=v3,k=v1,k=v2,k=v3", |
||||
|
}, |
||||
|
{ |
||||
|
Args{"=,\\": []string{"=", ",", "\\"}}, |
||||
|
"\\=\\,\\\\=\\=,\\=\\,\\\\=\\,,\\=\\,\\\\=\\\\", |
||||
|
}, |
||||
|
{ |
||||
|
Args{"secret": []string{"yes"}}, |
||||
|
"secret=yes", |
||||
|
}, |
||||
|
{ |
||||
|
Args{"secret": []string{"nou"}, "cache": []string{"/tmp/cache"}}, |
||||
|
"cache=/tmp/cache,secret=nou", |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
for _, test := range tests { |
||||
|
encoded := encodeSmethodArgs(test.args) |
||||
|
if encoded != test.expected { |
||||
|
t.Errorf("%q → %q (expected %q)", test.args, encoded, test.expected) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
// Dummy no-op pluggable transport client. Works only as a managed proxy.
|
||||
|
//
|
||||
|
// Usage (in torrc):
|
||||
|
// UseBridges 1
|
||||
|
// Bridge dummy X.X.X.X:YYYY
|
||||
|
// ClientTransportPlugin dummy exec dummy-client
|
||||
|
//
|
||||
|
// Because this transport doesn't do anything to the traffic, you can use the
|
||||
|
// ORPort of any ordinary bridge (or relay that has DirPort set) in the bridge
|
||||
|
// line; it doesn't have to declare support for the dummy transport.
|
||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"io" |
||||
|
"io/ioutil" |
||||
|
"net" |
||||
|
"os" |
||||
|
"os/signal" |
||||
|
"sync" |
||||
|
"syscall" |
||||
|
) |
||||
|
|
||||
|
import "git.torproject.org/pluggable-transports/goptlib.git" |
||||
|
|
||||
|
var ptInfo pt.ClientInfo |
||||
|
|
||||
|
func copyLoop(a, b net.Conn) { |
||||
|
var wg sync.WaitGroup |
||||
|
wg.Add(2) |
||||
|
|
||||
|
go func() { |
||||
|
io.Copy(b, a) |
||||
|
wg.Done() |
||||
|
}() |
||||
|
go func() { |
||||
|
io.Copy(a, b) |
||||
|
wg.Done() |
||||
|
}() |
||||
|
|
||||
|
wg.Wait() |
||||
|
} |
||||
|
|
||||
|
func handler(conn *pt.SocksConn) error { |
||||
|
defer conn.Close() |
||||
|
remote, err := net.Dial("tcp", conn.Req.Target) |
||||
|
if err != nil { |
||||
|
conn.Reject() |
||||
|
return err |
||||
|
} |
||||
|
defer remote.Close() |
||||
|
err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
copyLoop(conn, remote) |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func acceptLoop(ln *pt.SocksListener) error { |
||||
|
defer ln.Close() |
||||
|
for { |
||||
|
conn, err := ln.AcceptSocks() |
||||
|
if err != nil { |
||||
|
if e, ok := err.(net.Error); ok && e.Temporary() { |
||||
|
continue |
||||
|
} |
||||
|
return err |
||||
|
} |
||||
|
go handler(conn) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func main() { |
||||
|
var err error |
||||
|
|
||||
|
ptInfo, err = pt.ClientSetup(nil) |
||||
|
if err != nil { |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
|
||||
|
if ptInfo.ProxyURL != nil { |
||||
|
pt.ProxyError("proxy is not supported") |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
|
||||
|
listeners := make([]net.Listener, 0) |
||||
|
for _, methodName := range ptInfo.MethodNames { |
||||
|
switch methodName { |
||||
|
case "dummy": |
||||
|
ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") |
||||
|
if err != nil { |
||||
|
pt.CmethodError(methodName, err.Error()) |
||||
|
break |
||||
|
} |
||||
|
go acceptLoop(ln) |
||||
|
pt.Cmethod(methodName, ln.Version(), ln.Addr()) |
||||
|
listeners = append(listeners, ln) |
||||
|
default: |
||||
|
pt.CmethodError(methodName, "no such method") |
||||
|
} |
||||
|
} |
||||
|
pt.CmethodsDone() |
||||
|
|
||||
|
sigChan := make(chan os.Signal, 1) |
||||
|
signal.Notify(sigChan, syscall.SIGTERM) |
||||
|
|
||||
|
if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" { |
||||
|
// This environment variable means we should treat EOF on stdin
|
||||
|
// just like SIGTERM: https://bugs.torproject.org/15435.
|
||||
|
go func() { |
||||
|
io.Copy(ioutil.Discard, os.Stdin) |
||||
|
sigChan <- syscall.SIGTERM |
||||
|
}() |
||||
|
} |
||||
|
|
||||
|
// wait for a signal
|
||||
|
<-sigChan |
||||
|
|
||||
|
// signal received, shut down
|
||||
|
for _, ln := range listeners { |
||||
|
ln.Close() |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,117 @@ |
|||||
|
// Dummy no-op pluggable transport server. Works only as a managed proxy.
|
||||
|
//
|
||||
|
// Usage (in torrc):
|
||||
|
// BridgeRelay 1
|
||||
|
// ORPort 9001
|
||||
|
// ExtORPort 6669
|
||||
|
// ServerTransportPlugin dummy exec dummy-server
|
||||
|
//
|
||||
|
// Because the dummy transport doesn't do anything to the traffic, you can
|
||||
|
// connect to it with any ordinary Tor client; you don't have to use
|
||||
|
// dummy-client.
|
||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"io" |
||||
|
"io/ioutil" |
||||
|
"net" |
||||
|
"os" |
||||
|
"os/signal" |
||||
|
"sync" |
||||
|
"syscall" |
||||
|
) |
||||
|
|
||||
|
import "git.torproject.org/pluggable-transports/goptlib.git" |
||||
|
|
||||
|
var ptInfo pt.ServerInfo |
||||
|
|
||||
|
func copyLoop(a, b net.Conn) { |
||||
|
var wg sync.WaitGroup |
||||
|
wg.Add(2) |
||||
|
|
||||
|
go func() { |
||||
|
io.Copy(b, a) |
||||
|
wg.Done() |
||||
|
}() |
||||
|
go func() { |
||||
|
io.Copy(a, b) |
||||
|
wg.Done() |
||||
|
}() |
||||
|
|
||||
|
wg.Wait() |
||||
|
} |
||||
|
|
||||
|
func handler(conn net.Conn) error { |
||||
|
defer conn.Close() |
||||
|
|
||||
|
or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "dummy") |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
defer or.Close() |
||||
|
|
||||
|
copyLoop(conn, or) |
||||
|
|
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func acceptLoop(ln net.Listener) error { |
||||
|
defer ln.Close() |
||||
|
for { |
||||
|
conn, err := ln.Accept() |
||||
|
if err != nil { |
||||
|
if e, ok := err.(net.Error); ok && e.Temporary() { |
||||
|
continue |
||||
|
} |
||||
|
return err |
||||
|
} |
||||
|
go handler(conn) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func main() { |
||||
|
var err error |
||||
|
|
||||
|
ptInfo, err = pt.ServerSetup(nil) |
||||
|
if err != nil { |
||||
|
os.Exit(1) |
||||
|
} |
||||
|
|
||||
|
listeners := make([]net.Listener, 0) |
||||
|
for _, bindaddr := range ptInfo.Bindaddrs { |
||||
|
switch bindaddr.MethodName { |
||||
|
case "dummy": |
||||
|
ln, err := net.ListenTCP("tcp", bindaddr.Addr) |
||||
|
if err != nil { |
||||
|
pt.SmethodError(bindaddr.MethodName, err.Error()) |
||||
|
break |
||||
|
} |
||||
|
go acceptLoop(ln) |
||||
|
pt.Smethod(bindaddr.MethodName, ln.Addr()) |
||||
|
listeners = append(listeners, ln) |
||||
|
default: |
||||
|
pt.SmethodError(bindaddr.MethodName, "no such method") |
||||
|
} |
||||
|
} |
||||
|
pt.SmethodsDone() |
||||
|
|
||||
|
sigChan := make(chan os.Signal, 1) |
||||
|
signal.Notify(sigChan, syscall.SIGTERM) |
||||
|
|
||||
|
if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" { |
||||
|
// This environment variable means we should treat EOF on stdin
|
||||
|
// just like SIGTERM: https://bugs.torproject.org/15435.
|
||||
|
go func() { |
||||
|
io.Copy(ioutil.Discard, os.Stdin) |
||||
|
sigChan <- syscall.SIGTERM |
||||
|
}() |
||||
|
} |
||||
|
|
||||
|
// wait for a signal
|
||||
|
<-sigChan |
||||
|
|
||||
|
// signal received, shut down
|
||||
|
for _, ln := range listeners { |
||||
|
ln.Close() |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
module git.torproject.org/pluggable-transports/goptlib.git |
||||
|
|
||||
|
go 1.11 |
||||
@ -0,0 +1,80 @@ |
|||||
|
package pt |
||||
|
|
||||
|
import ( |
||||
|
"os" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func TestGetProxyURL(t *testing.T) { |
||||
|
badTests := [...]string{ |
||||
|
"bogus", |
||||
|
"http:", |
||||
|
"://127.0.0.1", |
||||
|
"//127.0.0.1", |
||||
|
"http:127.0.0.1", |
||||
|
"://[::1]", |
||||
|
"//[::1]", |
||||
|
"http:[::1]", |
||||
|
"://localhost", |
||||
|
"//localhost", |
||||
|
"http:localhost", |
||||
|
// No port in these.
|
||||
|
"http://127.0.0.1", |
||||
|
"socks4a://127.0.0.1", |
||||
|
"socks5://127.0.0.1", |
||||
|
"http://127.0.0.1:", |
||||
|
"http://[::1]", |
||||
|
"http://localhost", |
||||
|
"unknown://localhost/whatever", |
||||
|
// No host in these.
|
||||
|
"http://:8080", |
||||
|
"socks4a://:1080", |
||||
|
"socks5://:1080", |
||||
|
} |
||||
|
goodTests := [...]struct { |
||||
|
input, expected string |
||||
|
}{ |
||||
|
{"http://127.0.0.1:8080", "http://127.0.0.1:8080"}, |
||||
|
{"http://127.0.0.1:8080/", "http://127.0.0.1:8080/"}, |
||||
|
{"http://127.0.0.1:8080/path", "http://127.0.0.1:8080/path"}, |
||||
|
{"http://[::1]:8080", "http://[::1]:8080"}, |
||||
|
{"http://[::1]:8080/", "http://[::1]:8080/"}, |
||||
|
{"http://[::1]:8080/path", "http://[::1]:8080/path"}, |
||||
|
{"http://localhost:8080", "http://localhost:8080"}, |
||||
|
{"http://localhost:8080/", "http://localhost:8080/"}, |
||||
|
{"http://localhost:8080/path", "http://localhost:8080/path"}, |
||||
|
{"http://user@localhost:8080", "http://user@localhost:8080"}, |
||||
|
{"http://user:password@localhost:8080", "http://user:password@localhost:8080"}, |
||||
|
{"socks5://localhost:1080", "socks5://localhost:1080"}, |
||||
|
{"socks4a://localhost:1080", "socks4a://localhost:1080"}, |
||||
|
{"unknown://localhost:9999/whatever", "unknown://localhost:9999/whatever"}, |
||||
|
} |
||||
|
|
||||
|
os.Clearenv() |
||||
|
u, err := getProxyURL() |
||||
|
if err != nil { |
||||
|
t.Errorf("empty environment unexpectedly returned an error: %s", err) |
||||
|
} |
||||
|
if u != nil { |
||||
|
t.Errorf("empty environment returned %q", u) |
||||
|
} |
||||
|
|
||||
|
for _, input := range badTests { |
||||
|
os.Setenv("TOR_PT_PROXY", input) |
||||
|
u, err = getProxyURL() |
||||
|
if err == nil { |
||||
|
t.Errorf("TOR_PT_PROXY=%q unexpectedly succeeded and returned %q", input, u) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for _, test := range goodTests { |
||||
|
os.Setenv("TOR_PT_PROXY", test.input) |
||||
|
u, err := getProxyURL() |
||||
|
if err != nil { |
||||
|
t.Errorf("TOR_PT_PROXY=%q unexpectedly returned an error: %s", test.input, err) |
||||
|
} |
||||
|
if u == nil || u.String() != test.expected { |
||||
|
t.Errorf("TOR_PT_PROXY=%q → %q (expected %q)", test.input, u, test.expected) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,507 @@ |
|||||
|
package pt |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"net" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
socksVersion = 0x05 |
||||
|
|
||||
|
socksAuthNoneRequired = 0x00 |
||||
|
socksAuthUsernamePassword = 0x02 |
||||
|
socksAuthNoAcceptableMethods = 0xff |
||||
|
|
||||
|
socksCmdConnect = 0x01 |
||||
|
socksRsv = 0x00 |
||||
|
|
||||
|
socksAtypeV4 = 0x01 |
||||
|
socksAtypeDomainName = 0x03 |
||||
|
socksAtypeV6 = 0x04 |
||||
|
|
||||
|
socksAuthRFC1929Ver = 0x01 |
||||
|
socksAuthRFC1929Success = 0x00 |
||||
|
socksAuthRFC1929Fail = 0x01 |
||||
|
|
||||
|
socksRepSucceeded = 0x00 |
||||
|
// "general SOCKS server failure"
|
||||
|
SocksRepGeneralFailure = 0x01 |
||||
|
// "connection not allowed by ruleset"
|
||||
|
SocksRepConnectionNotAllowed = 0x02 |
||||
|
// "Network unreachable"
|
||||
|
SocksRepNetworkUnreachable = 0x03 |
||||
|
// "Host unreachable"
|
||||
|
SocksRepHostUnreachable = 0x04 |
||||
|
// "Connection refused"
|
||||
|
SocksRepConnectionRefused = 0x05 |
||||
|
// "TTL expired"
|
||||
|
SocksRepTTLExpired = 0x06 |
||||
|
// "Command not supported"
|
||||
|
SocksRepCommandNotSupported = 0x07 |
||||
|
// "Address type not supported"
|
||||
|
SocksRepAddressNotSupported = 0x08 |
||||
|
) |
||||
|
|
||||
|
// Put a sanity timeout on how long we wait for a SOCKS request.
|
||||
|
const socksRequestTimeout = 5 * time.Second |
||||
|
|
||||
|
// SocksRequest describes a SOCKS request.
|
||||
|
type SocksRequest struct { |
||||
|
// The endpoint requested by the client as a "host:port" string.
|
||||
|
Target string |
||||
|
// The userid string sent by the client.
|
||||
|
Username string |
||||
|
// The password string sent by the client.
|
||||
|
Password string |
||||
|
// The parsed contents of Username as a key–value mapping.
|
||||
|
Args Args |
||||
|
} |
||||
|
|
||||
|
// SocksConn encapsulates a net.Conn and information associated with a SOCKS request.
|
||||
|
type SocksConn struct { |
||||
|
net.Conn |
||||
|
Req SocksRequest |
||||
|
} |
||||
|
|
||||
|
// Send a message to the proxy client that access to the given address is
|
||||
|
// granted. Addr is ignored, and "0.0.0.0:0" is always sent back for
|
||||
|
// BND.ADDR/BND.PORT in the SOCKS response.
|
||||
|
func (conn *SocksConn) Grant(addr *net.TCPAddr) error { |
||||
|
return sendSocks5ResponseGranted(conn) |
||||
|
} |
||||
|
|
||||
|
// Send a message to the proxy client that access was rejected or failed. This
|
||||
|
// sends back a "General Failure" error code. RejectReason should be used if
|
||||
|
// more specific error reporting is desired.
|
||||
|
func (conn *SocksConn) Reject() error { |
||||
|
return conn.RejectReason(SocksRepGeneralFailure) |
||||
|
} |
||||
|
|
||||
|
// Send a message to the proxy client that access was rejected, with the
|
||||
|
// specific error code indicating the reason behind the rejection.
|
||||
|
func (conn *SocksConn) RejectReason(reason byte) error { |
||||
|
return sendSocks5ResponseRejected(conn, reason) |
||||
|
} |
||||
|
|
||||
|
// SocksListener wraps a net.Listener in order to read a SOCKS request on Accept.
|
||||
|
//
|
||||
|
// func handleConn(conn *pt.SocksConn) error {
|
||||
|
// defer conn.Close()
|
||||
|
// remote, err := net.Dial("tcp", conn.Req.Target)
|
||||
|
// if err != nil {
|
||||
|
// conn.Reject()
|
||||
|
// return err
|
||||
|
// }
|
||||
|
// defer remote.Close()
|
||||
|
// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
|
||||
|
// if err != nil {
|
||||
|
// return err
|
||||
|
// }
|
||||
|
// // do something with conn and remote
|
||||
|
// return nil
|
||||
|
// }
|
||||
|
// ...
|
||||
|
// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
|
||||
|
// if err != nil {
|
||||
|
// panic(err.Error())
|
||||
|
// }
|
||||
|
// for {
|
||||
|
// conn, err := ln.AcceptSocks()
|
||||
|
// if err != nil {
|
||||
|
// log.Printf("accept error: %s", err)
|
||||
|
// if e, ok := err.(net.Error); ok && e.Temporary() {
|
||||
|
// continue
|
||||
|
// }
|
||||
|
// break
|
||||
|
// }
|
||||
|
// go handleConn(conn)
|
||||
|
// }
|
||||
|
type SocksListener struct { |
||||
|
net.Listener |
||||
|
} |
||||
|
|
||||
|
// Open a net.Listener according to network and laddr, and return it as a
|
||||
|
// SocksListener.
|
||||
|
func ListenSocks(network, laddr string) (*SocksListener, error) { |
||||
|
ln, err := net.Listen(network, laddr) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
return NewSocksListener(ln), nil |
||||
|
} |
||||
|
|
||||
|
// Create a new SocksListener wrapping the given net.Listener.
|
||||
|
func NewSocksListener(ln net.Listener) *SocksListener { |
||||
|
return &SocksListener{ln} |
||||
|
} |
||||
|
|
||||
|
// Accept is the same as AcceptSocks, except that it returns a generic net.Conn.
|
||||
|
// It is present for the sake of satisfying the net.Listener interface.
|
||||
|
func (ln *SocksListener) Accept() (net.Conn, error) { |
||||
|
return ln.AcceptSocks() |
||||
|
} |
||||
|
|
||||
|
// Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a
|
||||
|
// SocksConn. After accepting, you must call either conn.Grant or conn.Reject
|
||||
|
// (presumably after trying to connect to conn.Req.Target).
|
||||
|
//
|
||||
|
// Errors returned by AcceptSocks may be temporary (for example, EOF while
|
||||
|
// reading the request, or a badly formatted userid string), or permanent (e.g.,
|
||||
|
// the underlying socket is closed). You can determine whether an error is
|
||||
|
// temporary and take appropriate action with a type conversion to net.Error.
|
||||
|
// For example:
|
||||
|
//
|
||||
|
// for {
|
||||
|
// conn, err := ln.AcceptSocks()
|
||||
|
// if err != nil {
|
||||
|
// if e, ok := err.(net.Error); ok && e.Temporary() {
|
||||
|
// log.Printf("temporary accept error; trying again: %s", err)
|
||||
|
// continue
|
||||
|
// }
|
||||
|
// log.Printf("permanent accept error; giving up: %s", err)
|
||||
|
// break
|
||||
|
// }
|
||||
|
// go handleConn(conn)
|
||||
|
// }
|
||||
|
func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { |
||||
|
retry: |
||||
|
c, err := ln.Listener.Accept() |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
conn := new(SocksConn) |
||||
|
conn.Conn = c |
||||
|
err = conn.SetDeadline(time.Now().Add(socksRequestTimeout)) |
||||
|
if err != nil { |
||||
|
conn.Close() |
||||
|
goto retry |
||||
|
} |
||||
|
conn.Req, err = socks5Handshake(conn) |
||||
|
if err != nil { |
||||
|
conn.Close() |
||||
|
goto retry |
||||
|
} |
||||
|
err = conn.SetDeadline(time.Time{}) |
||||
|
if err != nil { |
||||
|
conn.Close() |
||||
|
goto retry |
||||
|
} |
||||
|
return conn, nil |
||||
|
} |
||||
|
|
||||
|
// Returns "socks5", suitable to be included in a call to Cmethod.
|
||||
|
func (ln *SocksListener) Version() string { |
||||
|
return "socks5" |
||||
|
} |
||||
|
|
||||
|
// socks5handshake conducts the SOCKS5 handshake up to the point where the
|
||||
|
// client command is read and the proxy must open the outgoing connection.
|
||||
|
// Returns a SocksRequest.
|
||||
|
func socks5Handshake(s io.ReadWriter) (req SocksRequest, err error) { |
||||
|
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) |
||||
|
|
||||
|
// Negotiate the authentication method.
|
||||
|
var method byte |
||||
|
if method, err = socksNegotiateAuth(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Authenticate the client.
|
||||
|
if err = socksAuthenticate(rw, method, &req); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Read the command.
|
||||
|
err = socksReadCommand(rw, &req) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// socksNegotiateAuth negotiates the authentication method and returns the
|
||||
|
// selected method as a byte. On negotiation failures an error is returned.
|
||||
|
func socksNegotiateAuth(rw *bufio.ReadWriter) (method byte, err error) { |
||||
|
// Validate the version.
|
||||
|
if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Read the number of methods.
|
||||
|
var nmethods byte |
||||
|
if nmethods, err = socksReadByte(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Read the methods.
|
||||
|
var methods []byte |
||||
|
if methods, err = socksReadBytes(rw, int(nmethods)); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Pick the most "suitable" method.
|
||||
|
method = socksAuthNoAcceptableMethods |
||||
|
for _, m := range methods { |
||||
|
switch m { |
||||
|
case socksAuthNoneRequired: |
||||
|
// Pick Username/Password over None if the client happens to
|
||||
|
// send both.
|
||||
|
if method == socksAuthNoAcceptableMethods { |
||||
|
method = m |
||||
|
} |
||||
|
|
||||
|
case socksAuthUsernamePassword: |
||||
|
method = m |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Send the negotiated method.
|
||||
|
var msg [2]byte |
||||
|
msg[0] = socksVersion |
||||
|
msg[1] = method |
||||
|
if _, err = rw.Writer.Write(msg[:]); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if err = socksFlushBuffers(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// socksAuthenticate authenticates the client via the chosen authentication
|
||||
|
// mechanism.
|
||||
|
func socksAuthenticate(rw *bufio.ReadWriter, method byte, req *SocksRequest) (err error) { |
||||
|
switch method { |
||||
|
case socksAuthNoneRequired: |
||||
|
// Straight into reading the connect.
|
||||
|
|
||||
|
case socksAuthUsernamePassword: |
||||
|
if err = socksAuthRFC1929(rw, req); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
case socksAuthNoAcceptableMethods: |
||||
|
err = fmt.Errorf("SOCKS method select had no compatible methods") |
||||
|
return |
||||
|
|
||||
|
default: |
||||
|
err = fmt.Errorf("SOCKS method select picked a unsupported method 0x%02x", method) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
if err = socksFlushBuffers(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// socksAuthRFC1929 authenticates the client via RFC 1929 username/password
|
||||
|
// auth. As a design decision any valid username/password is accepted as this
|
||||
|
// field is primarily used as an out-of-band argument passing mechanism for
|
||||
|
// pluggable transports.
|
||||
|
func socksAuthRFC1929(rw *bufio.ReadWriter, req *SocksRequest) (err error) { |
||||
|
sendErrResp := func() { |
||||
|
// Swallow the write/flush error here, we are going to close the
|
||||
|
// connection and the original failure is more useful.
|
||||
|
resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Fail} |
||||
|
rw.Write(resp[:]) |
||||
|
socksFlushBuffers(rw) |
||||
|
} |
||||
|
|
||||
|
// Validate the fixed parts of the command message.
|
||||
|
if err = socksReadByteVerify(rw, "auth version", socksAuthRFC1929Ver); err != nil { |
||||
|
sendErrResp() |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Read the username.
|
||||
|
var ulen byte |
||||
|
if ulen, err = socksReadByte(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
if ulen < 1 { |
||||
|
sendErrResp() |
||||
|
err = fmt.Errorf("RFC1929 username with 0 length") |
||||
|
return |
||||
|
} |
||||
|
var uname []byte |
||||
|
if uname, err = socksReadBytes(rw, int(ulen)); err != nil { |
||||
|
return |
||||
|
} |
||||
|
req.Username = string(uname) |
||||
|
|
||||
|
// Read the password.
|
||||
|
var plen byte |
||||
|
if plen, err = socksReadByte(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
if plen < 1 { |
||||
|
sendErrResp() |
||||
|
err = fmt.Errorf("RFC1929 password with 0 length") |
||||
|
return |
||||
|
} |
||||
|
var passwd []byte |
||||
|
if passwd, err = socksReadBytes(rw, int(plen)); err != nil { |
||||
|
return |
||||
|
} |
||||
|
if !(plen == 1 && passwd[0] == 0x00) { |
||||
|
// tor will set the password to 'NUL' if there are no arguments.
|
||||
|
req.Password = string(passwd) |
||||
|
} |
||||
|
|
||||
|
// Mash the username/password together and parse it as a pluggable
|
||||
|
// transport argument string.
|
||||
|
if req.Args, err = parseClientParameters(req.Username + req.Password); err != nil { |
||||
|
sendErrResp() |
||||
|
} else { |
||||
|
resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Success} |
||||
|
_, err = rw.Write(resp[:]) |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// socksReadCommand reads a SOCKS5 client command and parses out the relevant
|
||||
|
// fields into a SocksRequest. Only CMD_CONNECT is supported.
|
||||
|
func socksReadCommand(rw *bufio.ReadWriter, req *SocksRequest) (err error) { |
||||
|
sendErrResp := func(reason byte) { |
||||
|
// Swallow errors that occur when writing/flushing the response,
|
||||
|
// connection will be closed anyway.
|
||||
|
sendSocks5ResponseRejected(rw, reason) |
||||
|
socksFlushBuffers(rw) |
||||
|
} |
||||
|
|
||||
|
// Validate the fixed parts of the command message.
|
||||
|
if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { |
||||
|
sendErrResp(SocksRepGeneralFailure) |
||||
|
return |
||||
|
} |
||||
|
if err = socksReadByteVerify(rw, "command", socksCmdConnect); err != nil { |
||||
|
sendErrResp(SocksRepCommandNotSupported) |
||||
|
return |
||||
|
} |
||||
|
if err = socksReadByteVerify(rw, "reserved", socksRsv); err != nil { |
||||
|
sendErrResp(SocksRepGeneralFailure) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Read the destination address/port.
|
||||
|
// XXX: This should probably eventually send socks 5 error messages instead
|
||||
|
// of rudely closing connections on invalid addresses.
|
||||
|
var atype byte |
||||
|
if atype, err = socksReadByte(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
var host string |
||||
|
switch atype { |
||||
|
case socksAtypeV4: |
||||
|
var addr []byte |
||||
|
if addr, err = socksReadBytes(rw, net.IPv4len); err != nil { |
||||
|
return |
||||
|
} |
||||
|
host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String() |
||||
|
|
||||
|
case socksAtypeDomainName: |
||||
|
var alen byte |
||||
|
if alen, err = socksReadByte(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
if alen == 0 { |
||||
|
err = fmt.Errorf("SOCKS request had domain name with 0 length") |
||||
|
return |
||||
|
} |
||||
|
var addr []byte |
||||
|
if addr, err = socksReadBytes(rw, int(alen)); err != nil { |
||||
|
return |
||||
|
} |
||||
|
host = string(addr) |
||||
|
|
||||
|
case socksAtypeV6: |
||||
|
var rawAddr []byte |
||||
|
if rawAddr, err = socksReadBytes(rw, net.IPv6len); err != nil { |
||||
|
return |
||||
|
} |
||||
|
addr := make(net.IP, net.IPv6len) |
||||
|
copy(addr[:], rawAddr[:]) |
||||
|
host = fmt.Sprintf("[%s]", addr.String()) |
||||
|
|
||||
|
default: |
||||
|
sendErrResp(SocksRepAddressNotSupported) |
||||
|
err = fmt.Errorf("SOCKS request had unsupported address type 0x%02x", atype) |
||||
|
return |
||||
|
} |
||||
|
var rawPort []byte |
||||
|
if rawPort, err = socksReadBytes(rw, 2); err != nil { |
||||
|
return |
||||
|
} |
||||
|
port := int(rawPort[0])<<8 | int(rawPort[1])<<0 |
||||
|
|
||||
|
if err = socksFlushBuffers(rw); err != nil { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
req.Target = fmt.Sprintf("%s:%d", host, port) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
// Send a SOCKS5 response with the given code. BND.ADDR/BND.PORT is always the
|
||||
|
// IPv4 address/port "0.0.0.0:0".
|
||||
|
func sendSocks5Response(w io.Writer, code byte) error { |
||||
|
resp := make([]byte, 4+4+2) |
||||
|
resp[0] = socksVersion |
||||
|
resp[1] = code |
||||
|
resp[2] = socksRsv |
||||
|
resp[3] = socksAtypeV4 |
||||
|
|
||||
|
// BND.ADDR/BND.PORT should be the address and port that the outgoing
|
||||
|
// connection is bound to on the proxy, but Tor does not use this
|
||||
|
// information, so all zeroes are sent.
|
||||
|
|
||||
|
_, err := w.Write(resp[:]) |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
// Send a SOCKS5 response code 0x00.
|
||||
|
func sendSocks5ResponseGranted(w io.Writer) error { |
||||
|
return sendSocks5Response(w, socksRepSucceeded) |
||||
|
} |
||||
|
|
||||
|
// Send a SOCKS5 response with the provided failure reason.
|
||||
|
func sendSocks5ResponseRejected(w io.Writer, reason byte) error { |
||||
|
return sendSocks5Response(w, reason) |
||||
|
} |
||||
|
|
||||
|
func socksFlushBuffers(rw *bufio.ReadWriter) error { |
||||
|
if err := rw.Writer.Flush(); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if rw.Reader.Buffered() > 0 { |
||||
|
return fmt.Errorf("%d bytes left after SOCKS message", rw.Reader.Buffered()) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func socksReadByte(rw *bufio.ReadWriter) (byte, error) { |
||||
|
return rw.Reader.ReadByte() |
||||
|
} |
||||
|
|
||||
|
func socksReadBytes(rw *bufio.ReadWriter, n int) ([]byte, error) { |
||||
|
ret := make([]byte, n) |
||||
|
if _, err := io.ReadFull(rw.Reader, ret); err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
return ret, nil |
||||
|
} |
||||
|
|
||||
|
func socksReadByteVerify(rw *bufio.ReadWriter, descr string, expected byte) error { |
||||
|
val, err := socksReadByte(rw) |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
if val != expected { |
||||
|
return fmt.Errorf("SOCKS message field %s was 0x%02x, not 0x%02x", descr, val, expected) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
var _ net.Listener = (*SocksListener)(nil) |
||||
@ -0,0 +1,474 @@ |
|||||
|
package pt |
||||
|
|
||||
|
import ( |
||||
|
"bufio" |
||||
|
"bytes" |
||||
|
"encoding/hex" |
||||
|
"errors" |
||||
|
"io" |
||||
|
"net" |
||||
|
"testing" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
// testReadWriter is a bytes.Buffer backed io.ReadWriter used for testing. The
|
||||
|
// Read and Write routines are to be used by the component being tested. Data
|
||||
|
// can be written to and read back via the writeHex and readHex routines.
|
||||
|
type testReadWriter struct { |
||||
|
readBuf bytes.Buffer |
||||
|
writeBuf bytes.Buffer |
||||
|
} |
||||
|
|
||||
|
func (c *testReadWriter) Read(buf []byte) (n int, err error) { |
||||
|
return c.readBuf.Read(buf) |
||||
|
} |
||||
|
|
||||
|
func (c *testReadWriter) Write(buf []byte) (n int, err error) { |
||||
|
return c.writeBuf.Write(buf) |
||||
|
} |
||||
|
|
||||
|
func (c *testReadWriter) writeHex(str string) (n int, err error) { |
||||
|
var buf []byte |
||||
|
if buf, err = hex.DecodeString(str); err != nil { |
||||
|
return |
||||
|
} |
||||
|
return c.readBuf.Write(buf) |
||||
|
} |
||||
|
|
||||
|
func (c *testReadWriter) readHex() string { |
||||
|
return hex.EncodeToString(c.writeBuf.Bytes()) |
||||
|
} |
||||
|
|
||||
|
func (c *testReadWriter) toBufio() *bufio.ReadWriter { |
||||
|
return bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) |
||||
|
} |
||||
|
|
||||
|
func (c *testReadWriter) reset() { |
||||
|
c.readBuf.Reset() |
||||
|
c.writeBuf.Reset() |
||||
|
} |
||||
|
|
||||
|
// TestAuthInvalidVersion tests auth negotiation with an invalid version.
|
||||
|
func TestAuthInvalidVersion(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
|
||||
|
// VER = 03, NMETHODS = 01, METHODS = [00]
|
||||
|
c.writeHex("030100") |
||||
|
if _, err := socksNegotiateAuth(c.toBufio()); err == nil { |
||||
|
t.Error("socksNegotiateAuth(InvalidVersion) succeded") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestAuthInvalidNMethods tests auth negotiaton with no methods.
|
||||
|
func TestAuthInvalidNMethods(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var err error |
||||
|
var method byte |
||||
|
|
||||
|
// VER = 05, NMETHODS = 00
|
||||
|
c.writeHex("0500") |
||||
|
if method, err = socksNegotiateAuth(c.toBufio()); err != nil { |
||||
|
t.Error("socksNegotiateAuth(No Methods) failed:", err) |
||||
|
} |
||||
|
if method != socksAuthNoAcceptableMethods { |
||||
|
t.Error("socksNegotiateAuth(No Methods) picked unexpected method:", method) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "05ff" { |
||||
|
t.Error("socksNegotiateAuth(No Methods) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestAuthNoneRequired tests auth negotiaton with NO AUTHENTICATION REQUIRED.
|
||||
|
func TestAuthNoneRequired(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var err error |
||||
|
var method byte |
||||
|
|
||||
|
// VER = 05, NMETHODS = 01, METHODS = [00]
|
||||
|
c.writeHex("050100") |
||||
|
if method, err = socksNegotiateAuth(c.toBufio()); err != nil { |
||||
|
t.Error("socksNegotiateAuth(None) failed:", err) |
||||
|
} |
||||
|
if method != socksAuthNoneRequired { |
||||
|
t.Error("socksNegotiateAuth(None) unexpected method:", method) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0500" { |
||||
|
t.Error("socksNegotiateAuth(None) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestAuthUsernamePassword tests auth negotiation with USERNAME/PASSWORD.
|
||||
|
func TestAuthUsernamePassword(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var err error |
||||
|
var method byte |
||||
|
|
||||
|
// VER = 05, NMETHODS = 01, METHODS = [02]
|
||||
|
c.writeHex("050102") |
||||
|
if method, err = socksNegotiateAuth(c.toBufio()); err != nil { |
||||
|
t.Error("socksNegotiateAuth(UsernamePassword) failed:", err) |
||||
|
} |
||||
|
if method != socksAuthUsernamePassword { |
||||
|
t.Error("socksNegotiateAuth(UsernamePassword) unexpected method:", method) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0502" { |
||||
|
t.Error("socksNegotiateAuth(UsernamePassword) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var fakeListenerDistinguishedError = errors.New("distinguished error") |
||||
|
|
||||
|
// fakeListener is a fake dummy net.Listener that returns the given net.Conn and
|
||||
|
// error the first time Accept is called. After the first call, it returns
|
||||
|
// (nil, fakeListenerDistinguishedError).
|
||||
|
type fakeListener struct { |
||||
|
c net.Conn |
||||
|
err error |
||||
|
} |
||||
|
|
||||
|
func (ln *fakeListener) Accept() (net.Conn, error) { |
||||
|
c := ln.c |
||||
|
err := ln.err |
||||
|
ln.c = nil |
||||
|
ln.err = fakeListenerDistinguishedError |
||||
|
return c, err |
||||
|
} |
||||
|
|
||||
|
func (ln *fakeListener) Close() error { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (ln *fakeListener) Addr() net.Addr { |
||||
|
return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0, Zone: ""} |
||||
|
} |
||||
|
|
||||
|
// A trivial net.Error that lets you control whether it is considered Temporary.
|
||||
|
type netError struct { |
||||
|
errString string |
||||
|
temporary bool |
||||
|
} |
||||
|
|
||||
|
func (e *netError) Error() string { |
||||
|
return e.errString |
||||
|
} |
||||
|
|
||||
|
func (e *netError) Temporary() bool { |
||||
|
return e.temporary |
||||
|
} |
||||
|
|
||||
|
func (e *netError) Timeout() bool { |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
// The purpose of ignoreDeadlineConn is to wrap net.Pipe so that the deadline
|
||||
|
// functions don't return an error ("net.Pipe does not support deadlines").
|
||||
|
type ignoreDeadlineConn struct { |
||||
|
net.Conn |
||||
|
} |
||||
|
|
||||
|
func (c *ignoreDeadlineConn) SetDeadline(t time.Time) error { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *ignoreDeadlineConn) SetReadDeadline(t time.Time) error { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (c *ignoreDeadlineConn) SetWriteDeadline(t time.Time) error { |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func TestAcceptErrors(t *testing.T) { |
||||
|
// Check that AcceptSocks accurately reflects net.Errors returned by the
|
||||
|
// underlying call to Accept. This is important for the handling of
|
||||
|
// Temporary and non-Temporary errors. The loop iterates over
|
||||
|
// non-net.Error, non-Temporary net.Error, and Temporary net.Error.
|
||||
|
for _, expectedErr := range []error{io.EOF, &netError{"non-temp", false}, &netError{"temp", true}} { |
||||
|
ln := NewSocksListener(&fakeListener{nil, expectedErr}) |
||||
|
_, err := ln.AcceptSocks() |
||||
|
if expectedNerr, ok := expectedErr.(net.Error); ok { |
||||
|
nerr, ok := err.(net.Error) |
||||
|
if !ok { |
||||
|
t.Errorf("AcceptSocks returned non-net.Error %v", nerr) |
||||
|
} else { |
||||
|
if expectedNerr.Temporary() != expectedNerr.Temporary() { |
||||
|
t.Errorf("AcceptSocks did not keep Temporary status of net.Error: %v", nerr) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
c1, c2 := net.Pipe() |
||||
|
go func() { |
||||
|
// Bogus request: SOCKS 5 then EOF.
|
||||
|
c2.Write([]byte("\x05\x01\x00")) |
||||
|
c2.Close() |
||||
|
}() |
||||
|
ln := NewSocksListener(&fakeListener{c: &ignoreDeadlineConn{c1}, err: nil}) |
||||
|
_, err := ln.AcceptSocks() |
||||
|
// The error in parsing the SOCKS request must be either silently
|
||||
|
// ignored, or else must be a Temporary net.Error. I.e., it must not be
|
||||
|
// the io.ErrUnexpectedEOF caused by the short request.
|
||||
|
if err == fakeListenerDistinguishedError { |
||||
|
// Was silently ignored.
|
||||
|
} else if nerr, ok := err.(net.Error); ok { |
||||
|
if !nerr.Temporary() { |
||||
|
t.Errorf("AcceptSocks returned non-Temporary net.Error: %v", nerr) |
||||
|
} |
||||
|
} else { |
||||
|
t.Errorf("AcceptSocks returned non-net.Error: %v", err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestAuthBoth tests auth negotiation containing both NO AUTHENTICATION
|
||||
|
// REQUIRED and USERNAME/PASSWORD.
|
||||
|
func TestAuthBoth(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var err error |
||||
|
var method byte |
||||
|
|
||||
|
// VER = 05, NMETHODS = 02, METHODS = [00, 02]
|
||||
|
c.writeHex("05020002") |
||||
|
if method, err = socksNegotiateAuth(c.toBufio()); err != nil { |
||||
|
t.Error("socksNegotiateAuth(Both) failed:", err) |
||||
|
} |
||||
|
if method != socksAuthUsernamePassword { |
||||
|
t.Error("socksNegotiateAuth(Both) unexpected method:", method) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0502" { |
||||
|
t.Error("socksNegotiateAuth(Both) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestAuthUnsupported tests auth negotiation with a unsupported method.
|
||||
|
func TestAuthUnsupported(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var err error |
||||
|
var method byte |
||||
|
|
||||
|
// VER = 05, NMETHODS = 01, METHODS = [01] (GSSAPI)
|
||||
|
c.writeHex("050101") |
||||
|
if method, err = socksNegotiateAuth(c.toBufio()); err != nil { |
||||
|
t.Error("socksNegotiateAuth(Unknown) failed:", err) |
||||
|
} |
||||
|
if method != socksAuthNoAcceptableMethods { |
||||
|
t.Error("socksNegotiateAuth(Unknown) picked unexpected method:", method) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "05ff" { |
||||
|
t.Error("socksNegotiateAuth(Unknown) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestAuthUnsupported2 tests auth negotiation with supported and unsupported
|
||||
|
// methods.
|
||||
|
func TestAuthUnsupported2(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var err error |
||||
|
var method byte |
||||
|
|
||||
|
// VER = 05, NMETHODS = 03, METHODS = [00,01,02]
|
||||
|
c.writeHex("0503000102") |
||||
|
if method, err = socksNegotiateAuth(c.toBufio()); err != nil { |
||||
|
t.Error("socksNegotiateAuth(Unknown2) failed:", err) |
||||
|
} |
||||
|
if method != socksAuthUsernamePassword { |
||||
|
t.Error("socksNegotiateAuth(Unknown2) picked unexpected method:", method) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0502" { |
||||
|
t.Error("socksNegotiateAuth(Unknown2) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRFC1929InvalidVersion tests RFC1929 auth with an invalid version.
|
||||
|
func TestRFC1929InvalidVersion(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 03, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde"
|
||||
|
c.writeHex("03054142434445056162636465") |
||||
|
if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { |
||||
|
t.Error("socksAuthenticate(InvalidVersion) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0101" { |
||||
|
t.Error("socksAuthenticate(InvalidVersion) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRFC1929InvalidUlen tests RFC1929 auth with an invalid ULEN.
|
||||
|
func TestRFC1929InvalidUlen(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 01, ULEN = 0, UNAME = "", PLEN = 5, PASSWD = "abcde"
|
||||
|
c.writeHex("0100056162636465") |
||||
|
if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { |
||||
|
t.Error("socksAuthenticate(InvalidUlen) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0101" { |
||||
|
t.Error("socksAuthenticate(InvalidUlen) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRFC1929InvalidPlen tests RFC1929 auth with an invalid PLEN.
|
||||
|
func TestRFC1929InvalidPlen(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 0, PASSWD = ""
|
||||
|
c.writeHex("0105414243444500") |
||||
|
if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { |
||||
|
t.Error("socksAuthenticate(InvalidPlen) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0101" { |
||||
|
t.Error("socksAuthenticate(InvalidPlen) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRFC1929InvalidArgs tests RFC1929 auth with invalid pt args.
|
||||
|
func TestRFC1929InvalidPTArgs(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde"
|
||||
|
c.writeHex("01054142434445056162636465") |
||||
|
if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { |
||||
|
t.Error("socksAuthenticate(InvalidArgs) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0101" { |
||||
|
t.Error("socksAuthenticate(InvalidArgs) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRFC1929Success tests RFC1929 auth with valid pt args.
|
||||
|
func TestRFC1929Success(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 01, ULEN = 9, UNAME = "key=value", PLEN = 1, PASSWD = "\0"
|
||||
|
c.writeHex("01096b65793d76616c75650100") |
||||
|
if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err != nil { |
||||
|
t.Error("socksAuthenticate(Success) failed:", err) |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "0100" { |
||||
|
t.Error("socksAuthenticate(Success) invalid response:", msg) |
||||
|
} |
||||
|
v, ok := req.Args.Get("key") |
||||
|
if v != "value" || !ok { |
||||
|
t.Error("RFC1929 k,v parse failure:", v) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRequestInvalidHdr tests SOCKS5 requests with invalid VER/CMD/RSV/ATYPE
|
||||
|
func TestRequestInvalidHdr(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 03, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050
|
||||
|
c.writeHex("030100017f000001235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err == nil { |
||||
|
t.Error("socksReadCommand(InvalidVer) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "05010001000000000000" { |
||||
|
t.Error("socksReadCommand(InvalidVer) invalid response:", msg) |
||||
|
} |
||||
|
c.reset() |
||||
|
|
||||
|
// VER = 05, CMD = 05, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050
|
||||
|
c.writeHex("050500017f000001235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err == nil { |
||||
|
t.Error("socksReadCommand(InvalidCmd) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "05070001000000000000" { |
||||
|
t.Error("socksReadCommand(InvalidCmd) invalid response:", msg) |
||||
|
} |
||||
|
c.reset() |
||||
|
|
||||
|
// VER = 05, CMD = 01, RSV = 30, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050
|
||||
|
c.writeHex("050130017f000001235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err == nil { |
||||
|
t.Error("socksReadCommand(InvalidRsv) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "05010001000000000000" { |
||||
|
t.Error("socksReadCommand(InvalidRsv) invalid response:", msg) |
||||
|
} |
||||
|
c.reset() |
||||
|
|
||||
|
// VER = 05, CMD = 01, RSV = 01, ATYPE = 05, DST.ADDR = 127.0.0.1, DST.PORT = 9050
|
||||
|
c.writeHex("050100057f000001235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err == nil { |
||||
|
t.Error("socksReadCommand(InvalidAtype) succeded") |
||||
|
} |
||||
|
if msg := c.readHex(); msg != "05080001000000000000" { |
||||
|
t.Error("socksAuthenticate(InvalidAtype) invalid response:", msg) |
||||
|
} |
||||
|
c.reset() |
||||
|
} |
||||
|
|
||||
|
// TestRequestIPv4 tests IPv4 SOCKS5 requests.
|
||||
|
func TestRequestIPv4(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 05, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050
|
||||
|
c.writeHex("050100017f000001235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err != nil { |
||||
|
t.Error("socksReadCommand(IPv4) failed:", err) |
||||
|
} |
||||
|
addr, err := net.ResolveTCPAddr("tcp", req.Target) |
||||
|
if err != nil { |
||||
|
t.Error("net.ResolveTCPAddr failed:", err) |
||||
|
} |
||||
|
if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9050}) { |
||||
|
t.Error("Unexpected target:", addr) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRequestIPv6 tests IPv4 SOCKS5 requests.
|
||||
|
func TestRequestIPv6(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = 0102:0304:0506:0708:090a:0b0c:0d0e:0f10, DST.PORT = 9050
|
||||
|
c.writeHex("050100040102030405060708090a0b0c0d0e0f10235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err != nil { |
||||
|
t.Error("socksReadCommand(IPv6) failed:", err) |
||||
|
} |
||||
|
addr, err := net.ResolveTCPAddr("tcp", req.Target) |
||||
|
if err != nil { |
||||
|
t.Error("net.ResolveTCPAddr failed:", err) |
||||
|
} |
||||
|
if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("0102:0304:0506:0708:090a:0b0c:0d0e:0f10"), Port: 9050}) { |
||||
|
t.Error("Unexpected target:", addr) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestRequestFQDN tests FQDN (DOMAINNAME) SOCKS5 requests.
|
||||
|
func TestRequestFQDN(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
var req SocksRequest |
||||
|
|
||||
|
// VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = example.com, DST.PORT = 9050
|
||||
|
c.writeHex("050100030b6578616d706c652e636f6d235a") |
||||
|
if err := socksReadCommand(c.toBufio(), &req); err != nil { |
||||
|
t.Error("socksReadCommand(FQDN) failed:", err) |
||||
|
} |
||||
|
if req.Target != "example.com:9050" { |
||||
|
t.Error("Unexpected target:", req.Target) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TestResponseNil tests nil address SOCKS5 responses.
|
||||
|
func TestResponseNil(t *testing.T) { |
||||
|
c := new(testReadWriter) |
||||
|
|
||||
|
b := c.toBufio() |
||||
|
if err := sendSocks5ResponseGranted(b); err != nil { |
||||
|
t.Error("sendSocks5ResponseGranted() failed:", err) |
||||
|
} |
||||
|
b.Flush() |
||||
|
if msg := c.readHex(); msg != "05000001000000000000" { |
||||
|
t.Error("sendSocks5ResponseGranted(nil) invalid response:", msg) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
var _ io.ReadWriter = (*testReadWriter)(nil) |
||||
@ -0,0 +1,2 @@ |
|||||
|
! Extended ORPort Auth Cookie ! |
||||
|
this file is used in test code. |
||||
Loading…
Reference in new issue