quote goptlib from locally when git.torproject.org not available online
This commit is contained in:
parent
08c54cd8af
commit
41ccf890e9
5
go.mod
5
go.mod
@ -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
2
go.sum
@ -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
2
pkg/goptlib-v1.3.0/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/examples/dummy-client/dummy-client
|
||||
/examples/dummy-server/dummy-server
|
121
pkg/goptlib-v1.3.0/COPYING
Normal file
121
pkg/goptlib-v1.3.0/COPYING
Normal 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.
|
80
pkg/goptlib-v1.3.0/ChangeLog
Normal file
80
pkg/goptlib-v1.3.0/ChangeLog
Normal 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
25
pkg/goptlib-v1.3.0/README
Normal 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
219
pkg/goptlib-v1.3.0/args.go
Normal file
@ -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, ",")
|
||||
}
|
374
pkg/goptlib-v1.3.0/args_test.go
Normal file
374
pkg/goptlib-v1.3.0/args_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
125
pkg/goptlib-v1.3.0/examples/dummy-client/dummy-client.go
Normal file
125
pkg/goptlib-v1.3.0/examples/dummy-client/dummy-client.go
Normal 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()
|
||||
}
|
||||
}
|
117
pkg/goptlib-v1.3.0/examples/dummy-server/dummy-server.go
Normal file
117
pkg/goptlib-v1.3.0/examples/dummy-server/dummy-server.go
Normal 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()
|
||||
}
|
||||
}
|
3
pkg/goptlib-v1.3.0/go.mod
Normal file
3
pkg/goptlib-v1.3.0/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module git.torproject.org/pluggable-transports/goptlib.git
|
||||
|
||||
go 1.11
|
80
pkg/goptlib-v1.3.0/proxy_test.go
Normal file
80
pkg/goptlib-v1.3.0/proxy_test.go
Normal 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
1040
pkg/goptlib-v1.3.0/pt.go
Normal file
File diff suppressed because it is too large
Load Diff
1089
pkg/goptlib-v1.3.0/pt_test.go
Normal file
1089
pkg/goptlib-v1.3.0/pt_test.go
Normal file
File diff suppressed because it is too large
Load Diff
507
pkg/goptlib-v1.3.0/socks.go
Normal file
507
pkg/goptlib-v1.3.0/socks.go
Normal 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 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)
|
474
pkg/goptlib-v1.3.0/socks_test.go
Normal file
474
pkg/goptlib-v1.3.0/socks_test.go
Normal 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)
|
2
pkg/goptlib-v1.3.0/test_authcookie
Normal file
2
pkg/goptlib-v1.3.0/test_authcookie
Normal file
@ -0,0 +1,2 @@
|
||||
! Extended ORPort Auth Cookie !
|
||||
this file is used in test code.
|
Loading…
Reference in New Issue
Block a user