quote goptlib from locally when git.torproject.org not available online

This commit is contained in:
catbestme 2024-07-02 18:03:40 +08:00
parent 08c54cd8af
commit 41ccf890e9
17 changed files with 4262 additions and 3 deletions

5
go.mod
View File

@ -2,7 +2,10 @@ module github.com/ginuerzh/gost
go 1.22
replace github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a
replace (
git.torproject.org/pluggable-transports/goptlib.git v1.3.0 => ./pkg/goptlib-v1.3.0
github.com/templexxx/cpu v0.0.7 => github.com/templexxx/cpu v0.0.10-0.20211111114238-98168dcec14a
)
require (
git.torproject.org/pluggable-transports/goptlib.git v1.3.0

2
go.sum
View File

@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20 h1:iJoUgXvhagsNMrJrvavw7vu1eG8+hm6jLOxlLFcoODw=
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
git.torproject.org/pluggable-transports/goptlib.git v1.0.0/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q=
git.torproject.org/pluggable-transports/goptlib.git v1.3.0 h1:G+iuRUblCCC2xnO+0ag1/4+aaM98D5mjWP1M0v9s8a0=
git.torproject.org/pluggable-transports/goptlib.git v1.3.0/go.mod h1:4PBMl1dg7/3vMWSoWb46eGWlrxkUyn/CAJmxhDLAlDs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4=
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ=

2
pkg/goptlib-v1.3.0/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/examples/dummy-client/dummy-client
/examples/dummy-server/dummy-server

121
pkg/goptlib-v1.3.0/COPYING Normal file
View File

@ -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.

View File

@ -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.

25
pkg/goptlib-v1.3.0/README Normal file
View File

@ -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 tor-dev@lists.torproject.org 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.

219
pkg/goptlib-v1.3.0/args.go Normal file
View File

@ -0,0 +1,219 @@
package pt
import (
"bytes"
"fmt"
"sort"
"strings"
)
// Keyvalue 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 namevalue 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 transportnamevalue 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 namevalue 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, ",")
}

View File

@ -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)
}
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -0,0 +1,3 @@
module git.torproject.org/pluggable-transports/goptlib.git
go 1.11

View File

@ -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)
}
}
}

1040
pkg/goptlib-v1.3.0/pt.go Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

507
pkg/goptlib-v1.3.0/socks.go Normal file
View File

@ -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 keyvalue 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)

View File

@ -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)

View File

@ -0,0 +1,2 @@
! Extended ORPort Auth Cookie !
this file is used in test code.