Merge 661e4b2d3e
into 91e12c4428
This commit is contained in:
commit
ac0430c515
@ -170,6 +170,7 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
wsOpts.WriteBufferSize = node.GetInt("wbuf")
|
wsOpts.WriteBufferSize = node.GetInt("wbuf")
|
||||||
wsOpts.UserAgent = node.Get("agent")
|
wsOpts.UserAgent = node.Get("agent")
|
||||||
wsOpts.Path = node.Get("path")
|
wsOpts.Path = node.Get("path")
|
||||||
|
wsOpts.Host = node.Get("host")
|
||||||
|
|
||||||
timeout := node.GetDuration("timeout")
|
timeout := node.GetDuration("timeout")
|
||||||
|
|
||||||
|
5
go.mod
5
go.mod
@ -2,7 +2,10 @@ module github.com/ginuerzh/gost
|
|||||||
|
|
||||||
go 1.22
|
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 (
|
require (
|
||||||
git.torproject.org/pluggable-transports/goptlib.git v1.3.0
|
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 h1:iJoUgXvhagsNMrJrvavw7vu1eG8+hm6jLOxlLFcoODw=
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1.0.20210721174708-390f27c3be20/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
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.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/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 h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4=
|
||||||
github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ=
|
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.
|
12
ws.go
12
ws.go
@ -5,15 +5,15 @@ import (
|
|||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/go-log/log"
|
"github.com/go-log/log"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
smux "github.com/xtaci/smux"
|
smux "github.com/xtaci/smux"
|
||||||
@ -31,6 +31,7 @@ type WSOptions struct {
|
|||||||
EnableCompression bool
|
EnableCompression bool
|
||||||
UserAgent string
|
UserAgent string
|
||||||
Path string
|
Path string
|
||||||
|
Host string
|
||||||
}
|
}
|
||||||
|
|
||||||
type wsTransporter struct {
|
type wsTransporter struct {
|
||||||
@ -749,6 +750,13 @@ func websocketClientConn(url string, conn net.Conn, tlsConfig *tls.Config, optio
|
|||||||
if options.UserAgent != "" {
|
if options.UserAgent != "" {
|
||||||
header.Set("User-Agent", options.UserAgent)
|
header.Set("User-Agent", options.UserAgent)
|
||||||
}
|
}
|
||||||
|
if options.Host != "" {
|
||||||
|
fmt.Println("获取到命令行传参host==>", options.Host)
|
||||||
|
dialer.TLSClientConfig.ServerName = options.Host
|
||||||
|
header.Set("Host", options.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("TLSClientConfig==>%+v\n", dialer.TLSClientConfig)
|
||||||
c, resp, err := dialer.Dial(url, header)
|
c, resp, err := dialer.Dial(url, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
Reference in New Issue
Block a user