mirror of https://github.com/ginuerzh/gost
committed by
GitHub
19 changed files with 4273 additions and 5 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