#73: Add shadowsocks UDP relay support

This commit is contained in:
rui.zheng 2017-01-24 21:01:52 +08:00
parent 321b03712a
commit 8861ffba01
22 changed files with 4107 additions and 153 deletions

122
cmd/gost/vendor/github.com/Yawning/chacha20/LICENSE generated vendored Normal file
View File

@ -0,0 +1,122 @@
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.

14
cmd/gost/vendor/github.com/Yawning/chacha20/README.md generated vendored Normal file
View File

@ -0,0 +1,14 @@
### chacha20 - ChaCha20
#### Yawning Angel (yawning at schwanenlied dot me)
Yet another Go ChaCha20 implementation. Everything else I found was slow,
didn't support all the variants I need to use, or relied on cgo to go fast.
Features:
* 20 round, 256 bit key only. Everything else is pointless and stupid.
* IETF 96 bit nonce variant.
* XChaCha 24 byte nonce variant.
* SSE2 and AVX2 support on amd64 targets.
* Incremental encrypt/decrypt support, unlike golang.org/x/crypto/salsa20.

273
cmd/gost/vendor/github.com/Yawning/chacha20/chacha20.go generated vendored Normal file
View File

@ -0,0 +1,273 @@
// chacha20.go - A ChaCha stream cipher implementation.
//
// To the extent possible under law, Yawning Angel has waived all copyright
// and related or neighboring rights to chacha20, using the Creative
// Commons "CC0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package chacha20
import (
"crypto/cipher"
"encoding/binary"
"errors"
"math"
"runtime"
)
const (
// KeySize is the ChaCha20 key size in bytes.
KeySize = 32
// NonceSize is the ChaCha20 nonce size in bytes.
NonceSize = 8
// INonceSize is the IETF ChaCha20 nonce size in bytes.
INonceSize = 12
// XNonceSize is the XChaCha20 nonce size in bytes.
XNonceSize = 24
// HNonceSize is the HChaCha20 nonce size in bytes.
HNonceSize = 16
// BlockSize is the ChaCha20 block size in bytes.
BlockSize = 64
stateSize = 16
chachaRounds = 20
// The constant "expand 32-byte k" as little endian uint32s.
sigma0 = uint32(0x61707865)
sigma1 = uint32(0x3320646e)
sigma2 = uint32(0x79622d32)
sigma3 = uint32(0x6b206574)
)
var (
// ErrInvalidKey is the error returned when the key is invalid.
ErrInvalidKey = errors.New("key length must be KeySize bytes")
// ErrInvalidNonce is the error returned when the nonce is invalid.
ErrInvalidNonce = errors.New("nonce length must be NonceSize/INonceSize/XNonceSize bytes")
// ErrInvalidCounter is the error returned when the counter is invalid.
ErrInvalidCounter = errors.New("block counter is invalid (out of range)")
useUnsafe = false
usingVectors = false
blocksFn = blocksRef
)
// A Cipher is an instance of ChaCha20/XChaCha20 using a particular key and
// nonce.
type Cipher struct {
state [stateSize]uint32
buf [BlockSize]byte
off int
ietf bool
}
// Reset zeros the key data so that it will no longer appear in the process's
// memory.
func (c *Cipher) Reset() {
for i := range c.state {
c.state[i] = 0
}
for i := range c.buf {
c.buf[i] = 0
}
}
// XORKeyStream sets dst to the result of XORing src with the key stream. Dst
// and src may be the same slice but otherwise should not overlap.
func (c *Cipher) XORKeyStream(dst, src []byte) {
if len(dst) < len(src) {
src = src[:len(dst)]
}
for remaining := len(src); remaining > 0; {
// Process multiple blocks at once.
if c.off == BlockSize {
nrBlocks := remaining / BlockSize
directBytes := nrBlocks * BlockSize
if nrBlocks > 0 {
blocksFn(&c.state, src, dst, nrBlocks, c.ietf)
remaining -= directBytes
if remaining == 0 {
return
}
dst = dst[directBytes:]
src = src[directBytes:]
}
// If there's a partial block, generate 1 block of keystream into
// the internal buffer.
blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
c.off = 0
}
// Process partial blocks from the buffered keystream.
toXor := BlockSize - c.off
if remaining < toXor {
toXor = remaining
}
if toXor > 0 {
for i, v := range src[:toXor] {
dst[i] = v ^ c.buf[c.off+i]
}
dst = dst[toXor:]
src = src[toXor:]
remaining -= toXor
c.off += toXor
}
}
}
// KeyStream sets dst to the raw keystream.
func (c *Cipher) KeyStream(dst []byte) {
for remaining := len(dst); remaining > 0; {
// Process multiple blocks at once.
if c.off == BlockSize {
nrBlocks := remaining / BlockSize
directBytes := nrBlocks * BlockSize
if nrBlocks > 0 {
blocksFn(&c.state, nil, dst, nrBlocks, c.ietf)
remaining -= directBytes
if remaining == 0 {
return
}
dst = dst[directBytes:]
}
// If there's a partial block, generate 1 block of keystream into
// the internal buffer.
blocksFn(&c.state, nil, c.buf[:], 1, c.ietf)
c.off = 0
}
// Process partial blocks from the buffered keystream.
toCopy := BlockSize - c.off
if remaining < toCopy {
toCopy = remaining
}
if toCopy > 0 {
copy(dst[:toCopy], c.buf[c.off:c.off+toCopy])
dst = dst[toCopy:]
remaining -= toCopy
c.off += toCopy
}
}
}
// ReKey reinitializes the ChaCha20/XChaCha20 instance with the provided key
// and nonce.
func (c *Cipher) ReKey(key, nonce []byte) error {
if len(key) != KeySize {
return ErrInvalidKey
}
switch len(nonce) {
case NonceSize:
case INonceSize:
case XNonceSize:
var subkey [KeySize]byte
var subnonce [HNonceSize]byte
copy(subnonce[:], nonce[0:16])
HChaCha(key, &subnonce, &subkey)
key = subkey[:]
nonce = nonce[16:24]
defer func() {
for i := range subkey {
subkey[i] = 0
}
}()
default:
return ErrInvalidNonce
}
c.Reset()
c.state[0] = sigma0
c.state[1] = sigma1
c.state[2] = sigma2
c.state[3] = sigma3
c.state[4] = binary.LittleEndian.Uint32(key[0:4])
c.state[5] = binary.LittleEndian.Uint32(key[4:8])
c.state[6] = binary.LittleEndian.Uint32(key[8:12])
c.state[7] = binary.LittleEndian.Uint32(key[12:16])
c.state[8] = binary.LittleEndian.Uint32(key[16:20])
c.state[9] = binary.LittleEndian.Uint32(key[20:24])
c.state[10] = binary.LittleEndian.Uint32(key[24:28])
c.state[11] = binary.LittleEndian.Uint32(key[28:32])
c.state[12] = 0
if len(nonce) == INonceSize {
c.state[13] = binary.LittleEndian.Uint32(nonce[0:4])
c.state[14] = binary.LittleEndian.Uint32(nonce[4:8])
c.state[15] = binary.LittleEndian.Uint32(nonce[8:12])
c.ietf = true
} else {
c.state[13] = 0
c.state[14] = binary.LittleEndian.Uint32(nonce[0:4])
c.state[15] = binary.LittleEndian.Uint32(nonce[4:8])
c.ietf = false
}
c.off = BlockSize
return nil
}
// Seek sets the block counter to a given offset.
func (c *Cipher) Seek(blockCounter uint64) error {
if c.ietf {
if blockCounter > math.MaxUint32 {
return ErrInvalidCounter
}
c.state[12] = uint32(blockCounter)
} else {
c.state[12] = uint32(blockCounter)
c.state[13] = uint32(blockCounter >> 32)
}
c.off = BlockSize
return nil
}
// NewCipher returns a new ChaCha20/XChaCha20 instance.
func NewCipher(key, nonce []byte) (*Cipher, error) {
c := new(Cipher)
if err := c.ReKey(key, nonce); err != nil {
return nil, err
}
return c, nil
}
// HChaCha is the HChaCha20 hash function used to make XChaCha.
func HChaCha(key []byte, nonce *[HNonceSize]byte, out *[32]byte) {
var x [stateSize]uint32 // Last 4 slots unused, sigma hardcoded.
x[0] = binary.LittleEndian.Uint32(key[0:4])
x[1] = binary.LittleEndian.Uint32(key[4:8])
x[2] = binary.LittleEndian.Uint32(key[8:12])
x[3] = binary.LittleEndian.Uint32(key[12:16])
x[4] = binary.LittleEndian.Uint32(key[16:20])
x[5] = binary.LittleEndian.Uint32(key[20:24])
x[6] = binary.LittleEndian.Uint32(key[24:28])
x[7] = binary.LittleEndian.Uint32(key[28:32])
x[8] = binary.LittleEndian.Uint32(nonce[0:4])
x[9] = binary.LittleEndian.Uint32(nonce[4:8])
x[10] = binary.LittleEndian.Uint32(nonce[8:12])
x[11] = binary.LittleEndian.Uint32(nonce[12:16])
hChaChaRef(&x, out)
}
func init() {
switch runtime.GOARCH {
case "386", "amd64":
// Abuse unsafe to skip calling binary.LittleEndian.PutUint32
// in the critical path. This is a big boost on systems that are
// little endian and not overly picky about alignment.
useUnsafe = true
}
}
var _ cipher.Stream = (*Cipher)(nil)

View File

@ -0,0 +1,95 @@
// chacha20_amd64.go - AMD64 optimized chacha20.
//
// To the extent possible under law, Yawning Angel has waived all copyright
// and related or neighboring rights to chacha20, using the Creative
// Commons "CC0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
// +build amd64,!gccgo,!appengine
package chacha20
import (
"math"
)
var usingAVX2 = false
func blocksAmd64SSE2(x *uint32, inp, outp *byte, nrBlocks uint)
func blocksAmd64AVX2(x *uint32, inp, outp *byte, nrBlocks uint)
func cpuidAmd64(cpuidParams *uint32)
func xgetbv0Amd64(xcrVec *uint32)
func blocksAmd64(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
// Probably unneeded, but stating this explicitly simplifies the assembly.
if nrBlocks == 0 {
return
}
if isIetf {
var totalBlocks uint64
totalBlocks = uint64(x[8]) + uint64(nrBlocks)
if totalBlocks > math.MaxUint32 {
panic("chacha20: Exceeded keystream per nonce limit")
}
}
if in == nil {
for i := range out {
out[i] = 0
}
in = out
}
// Pointless to call the AVX2 code for just a single block, since half of
// the output gets discarded...
if usingAVX2 && nrBlocks > 1 {
blocksAmd64AVX2(&x[0], &in[0], &out[0], uint(nrBlocks))
} else {
blocksAmd64SSE2(&x[0], &in[0], &out[0], uint(nrBlocks))
}
}
func supportsAVX2() bool {
// https://software.intel.com/en-us/articles/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family
const (
osXsaveBit = 1 << 27
avx2Bit = 1 << 5
)
// Check to see if CPUID actually supports the leaf that indicates AVX2.
// CPUID.(EAX=0H, ECX=0H) >= 7
regs := [4]uint32{0x00}
cpuidAmd64(&regs[0])
if regs[0] < 7 {
return false
}
// Check to see if the OS knows how to save/restore XMM/YMM state.
// CPUID.(EAX=01H, ECX=0H):ECX.OSXSAVE[bit 27]==1
regs = [4]uint32{0x01}
cpuidAmd64(&regs[0])
if regs[2]&osXsaveBit == 0 {
return false
}
xcrRegs := [2]uint32{}
xgetbv0Amd64(&xcrRegs[0])
if xcrRegs[0]&6 != 6 {
return false
}
// Check for AVX2 support.
// CPUID.(EAX=07H, ECX=0H):EBX.AVX2[bit 5]==1
regs = [4]uint32{0x07}
cpuidAmd64(&regs[0])
return regs[1]&avx2Bit != 0
}
func init() {
blocksFn = blocksAmd64
usingVectors = true
usingAVX2 = supportsAVX2()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,392 @@
// chacha20_ref.go - Reference ChaCha20.
//
// To the extent possible under law, Yawning Angel has waived all copyright
// and related or neighboring rights to chacha20, using the Creative
// Commons "CC0" public domain dedication. See LICENSE or
// <http://creativecommons.org/publicdomain/zero/1.0/> for full details.
package chacha20
import (
"encoding/binary"
"math"
"unsafe"
)
func blocksRef(x *[stateSize]uint32, in []byte, out []byte, nrBlocks int, isIetf bool) {
if isIetf {
var totalBlocks uint64
totalBlocks = uint64(x[8]) + uint64(nrBlocks)
if totalBlocks > math.MaxUint32 {
panic("chacha20: Exceeded keystream per nonce limit")
}
}
// This routine ignores x[0]...x[4] in favor the const values since it's
// ever so slightly faster.
for n := 0; n < nrBlocks; n++ {
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15]
for i := chachaRounds; i > 0; i -= 2 {
// quarterround(x, 0, 4, 8, 12)
x0 += x4
x12 ^= x0
x12 = (x12 << 16) | (x12 >> 16)
x8 += x12
x4 ^= x8
x4 = (x4 << 12) | (x4 >> 20)
x0 += x4
x12 ^= x0
x12 = (x12 << 8) | (x12 >> 24)
x8 += x12
x4 ^= x8
x4 = (x4 << 7) | (x4 >> 25)
// quarterround(x, 1, 5, 9, 13)
x1 += x5
x13 ^= x1
x13 = (x13 << 16) | (x13 >> 16)
x9 += x13
x5 ^= x9
x5 = (x5 << 12) | (x5 >> 20)
x1 += x5
x13 ^= x1
x13 = (x13 << 8) | (x13 >> 24)
x9 += x13
x5 ^= x9
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 2, 6, 10, 14)
x2 += x6
x14 ^= x2
x14 = (x14 << 16) | (x14 >> 16)
x10 += x14
x6 ^= x10
x6 = (x6 << 12) | (x6 >> 20)
x2 += x6
x14 ^= x2
x14 = (x14 << 8) | (x14 >> 24)
x10 += x14
x6 ^= x10
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 3, 7, 11, 15)
x3 += x7
x15 ^= x3
x15 = (x15 << 16) | (x15 >> 16)
x11 += x15
x7 ^= x11
x7 = (x7 << 12) | (x7 >> 20)
x3 += x7
x15 ^= x3
x15 = (x15 << 8) | (x15 >> 24)
x11 += x15
x7 ^= x11
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 0, 5, 10, 15)
x0 += x5
x15 ^= x0
x15 = (x15 << 16) | (x15 >> 16)
x10 += x15
x5 ^= x10
x5 = (x5 << 12) | (x5 >> 20)
x0 += x5
x15 ^= x0
x15 = (x15 << 8) | (x15 >> 24)
x10 += x15
x5 ^= x10
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 1, 6, 11, 12)
x1 += x6
x12 ^= x1
x12 = (x12 << 16) | (x12 >> 16)
x11 += x12
x6 ^= x11
x6 = (x6 << 12) | (x6 >> 20)
x1 += x6
x12 ^= x1
x12 = (x12 << 8) | (x12 >> 24)
x11 += x12
x6 ^= x11
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 2, 7, 8, 13)
x2 += x7
x13 ^= x2
x13 = (x13 << 16) | (x13 >> 16)
x8 += x13
x7 ^= x8
x7 = (x7 << 12) | (x7 >> 20)
x2 += x7
x13 ^= x2
x13 = (x13 << 8) | (x13 >> 24)
x8 += x13
x7 ^= x8
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 3, 4, 9, 14)
x3 += x4
x14 ^= x3
x14 = (x14 << 16) | (x14 >> 16)
x9 += x14
x4 ^= x9
x4 = (x4 << 12) | (x4 >> 20)
x3 += x4
x14 ^= x3
x14 = (x14 << 8) | (x14 >> 24)
x9 += x14
x4 ^= x9
x4 = (x4 << 7) | (x4 >> 25)
}
// On amd64 at least, this is a rather big boost.
if useUnsafe {
if in != nil {
inArr := (*[16]uint32)(unsafe.Pointer(&in[n*BlockSize]))
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
outArr[0] = inArr[0] ^ (x0 + sigma0)
outArr[1] = inArr[1] ^ (x1 + sigma1)
outArr[2] = inArr[2] ^ (x2 + sigma2)
outArr[3] = inArr[3] ^ (x3 + sigma3)
outArr[4] = inArr[4] ^ (x4 + x[4])
outArr[5] = inArr[5] ^ (x5 + x[5])
outArr[6] = inArr[6] ^ (x6 + x[6])
outArr[7] = inArr[7] ^ (x7 + x[7])
outArr[8] = inArr[8] ^ (x8 + x[8])
outArr[9] = inArr[9] ^ (x9 + x[9])
outArr[10] = inArr[10] ^ (x10 + x[10])
outArr[11] = inArr[11] ^ (x11 + x[11])
outArr[12] = inArr[12] ^ (x12 + x[12])
outArr[13] = inArr[13] ^ (x13 + x[13])
outArr[14] = inArr[14] ^ (x14 + x[14])
outArr[15] = inArr[15] ^ (x15 + x[15])
} else {
outArr := (*[16]uint32)(unsafe.Pointer(&out[n*BlockSize]))
outArr[0] = x0 + sigma0
outArr[1] = x1 + sigma1
outArr[2] = x2 + sigma2
outArr[3] = x3 + sigma3
outArr[4] = x4 + x[4]
outArr[5] = x5 + x[5]
outArr[6] = x6 + x[6]
outArr[7] = x7 + x[7]
outArr[8] = x8 + x[8]
outArr[9] = x9 + x[9]
outArr[10] = x10 + x[10]
outArr[11] = x11 + x[11]
outArr[12] = x12 + x[12]
outArr[13] = x13 + x[13]
outArr[14] = x14 + x[14]
outArr[15] = x15 + x[15]
}
} else {
// Slow path, either the architecture cares about alignment, or is not little endian.
x0 += sigma0
x1 += sigma1
x2 += sigma2
x3 += sigma3
x4 += x[4]
x5 += x[5]
x6 += x[6]
x7 += x[7]
x8 += x[8]
x9 += x[9]
x10 += x[10]
x11 += x[11]
x12 += x[12]
x13 += x[13]
x14 += x[14]
x15 += x[15]
if in != nil {
binary.LittleEndian.PutUint32(out[0:4], binary.LittleEndian.Uint32(in[0:4])^x0)
binary.LittleEndian.PutUint32(out[4:8], binary.LittleEndian.Uint32(in[4:8])^x1)
binary.LittleEndian.PutUint32(out[8:12], binary.LittleEndian.Uint32(in[8:12])^x2)
binary.LittleEndian.PutUint32(out[12:16], binary.LittleEndian.Uint32(in[12:16])^x3)
binary.LittleEndian.PutUint32(out[16:20], binary.LittleEndian.Uint32(in[16:20])^x4)
binary.LittleEndian.PutUint32(out[20:24], binary.LittleEndian.Uint32(in[20:24])^x5)
binary.LittleEndian.PutUint32(out[24:28], binary.LittleEndian.Uint32(in[24:28])^x6)
binary.LittleEndian.PutUint32(out[28:32], binary.LittleEndian.Uint32(in[28:32])^x7)
binary.LittleEndian.PutUint32(out[32:36], binary.LittleEndian.Uint32(in[32:36])^x8)
binary.LittleEndian.PutUint32(out[36:40], binary.LittleEndian.Uint32(in[36:40])^x9)
binary.LittleEndian.PutUint32(out[40:44], binary.LittleEndian.Uint32(in[40:44])^x10)
binary.LittleEndian.PutUint32(out[44:48], binary.LittleEndian.Uint32(in[44:48])^x11)
binary.LittleEndian.PutUint32(out[48:52], binary.LittleEndian.Uint32(in[48:52])^x12)
binary.LittleEndian.PutUint32(out[52:56], binary.LittleEndian.Uint32(in[52:56])^x13)
binary.LittleEndian.PutUint32(out[56:60], binary.LittleEndian.Uint32(in[56:60])^x14)
binary.LittleEndian.PutUint32(out[60:64], binary.LittleEndian.Uint32(in[60:64])^x15)
in = in[BlockSize:]
} else {
binary.LittleEndian.PutUint32(out[0:4], x0)
binary.LittleEndian.PutUint32(out[4:8], x1)
binary.LittleEndian.PutUint32(out[8:12], x2)
binary.LittleEndian.PutUint32(out[12:16], x3)
binary.LittleEndian.PutUint32(out[16:20], x4)
binary.LittleEndian.PutUint32(out[20:24], x5)
binary.LittleEndian.PutUint32(out[24:28], x6)
binary.LittleEndian.PutUint32(out[28:32], x7)
binary.LittleEndian.PutUint32(out[32:36], x8)
binary.LittleEndian.PutUint32(out[36:40], x9)
binary.LittleEndian.PutUint32(out[40:44], x10)
binary.LittleEndian.PutUint32(out[44:48], x11)
binary.LittleEndian.PutUint32(out[48:52], x12)
binary.LittleEndian.PutUint32(out[52:56], x13)
binary.LittleEndian.PutUint32(out[56:60], x14)
binary.LittleEndian.PutUint32(out[60:64], x15)
}
out = out[BlockSize:]
}
// Stoping at 2^70 bytes per nonce is the user's responsibility.
ctr := uint64(x[13])<<32 | uint64(x[12])
ctr++
x[12] = uint32(ctr)
x[13] = uint32(ctr >> 32)
}
}
func hChaChaRef(x *[stateSize]uint32, out *[32]byte) {
x0, x1, x2, x3 := sigma0, sigma1, sigma2, sigma3
x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15 := x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11]
for i := chachaRounds; i > 0; i -= 2 {
// quarterround(x, 0, 4, 8, 12)
x0 += x4
x12 ^= x0
x12 = (x12 << 16) | (x12 >> 16)
x8 += x12
x4 ^= x8
x4 = (x4 << 12) | (x4 >> 20)
x0 += x4
x12 ^= x0
x12 = (x12 << 8) | (x12 >> 24)
x8 += x12
x4 ^= x8
x4 = (x4 << 7) | (x4 >> 25)
// quarterround(x, 1, 5, 9, 13)
x1 += x5
x13 ^= x1
x13 = (x13 << 16) | (x13 >> 16)
x9 += x13
x5 ^= x9
x5 = (x5 << 12) | (x5 >> 20)
x1 += x5
x13 ^= x1
x13 = (x13 << 8) | (x13 >> 24)
x9 += x13
x5 ^= x9
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 2, 6, 10, 14)
x2 += x6
x14 ^= x2
x14 = (x14 << 16) | (x14 >> 16)
x10 += x14
x6 ^= x10
x6 = (x6 << 12) | (x6 >> 20)
x2 += x6
x14 ^= x2
x14 = (x14 << 8) | (x14 >> 24)
x10 += x14
x6 ^= x10
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 3, 7, 11, 15)
x3 += x7
x15 ^= x3
x15 = (x15 << 16) | (x15 >> 16)
x11 += x15
x7 ^= x11
x7 = (x7 << 12) | (x7 >> 20)
x3 += x7
x15 ^= x3
x15 = (x15 << 8) | (x15 >> 24)
x11 += x15
x7 ^= x11
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 0, 5, 10, 15)
x0 += x5
x15 ^= x0
x15 = (x15 << 16) | (x15 >> 16)
x10 += x15
x5 ^= x10
x5 = (x5 << 12) | (x5 >> 20)
x0 += x5
x15 ^= x0
x15 = (x15 << 8) | (x15 >> 24)
x10 += x15
x5 ^= x10
x5 = (x5 << 7) | (x5 >> 25)
// quarterround(x, 1, 6, 11, 12)
x1 += x6
x12 ^= x1
x12 = (x12 << 16) | (x12 >> 16)
x11 += x12
x6 ^= x11
x6 = (x6 << 12) | (x6 >> 20)
x1 += x6
x12 ^= x1
x12 = (x12 << 8) | (x12 >> 24)
x11 += x12
x6 ^= x11
x6 = (x6 << 7) | (x6 >> 25)
// quarterround(x, 2, 7, 8, 13)
x2 += x7
x13 ^= x2
x13 = (x13 << 16) | (x13 >> 16)
x8 += x13
x7 ^= x8
x7 = (x7 << 12) | (x7 >> 20)
x2 += x7
x13 ^= x2
x13 = (x13 << 8) | (x13 >> 24)
x8 += x13
x7 ^= x8
x7 = (x7 << 7) | (x7 >> 25)
// quarterround(x, 3, 4, 9, 14)
x3 += x4
x14 ^= x3
x14 = (x14 << 16) | (x14 >> 16)
x9 += x14
x4 ^= x9
x4 = (x4 << 12) | (x4 >> 20)
x3 += x4
x14 ^= x3
x14 = (x14 << 8) | (x14 >> 24)
x9 += x14
x4 ^= x9
x4 = (x4 << 7) | (x4 >> 25)
}
// HChaCha returns x0...x3 | x12...x15, which corresponds to the
// indexes of the ChaCha constant and the indexes of the IV.
if useUnsafe {
outArr := (*[16]uint32)(unsafe.Pointer(&out[0]))
outArr[0] = x0
outArr[1] = x1
outArr[2] = x2
outArr[3] = x3
outArr[4] = x12
outArr[5] = x13
outArr[6] = x14
outArr[7] = x15
} else {
binary.LittleEndian.PutUint32(out[0:4], x0)
binary.LittleEndian.PutUint32(out[4:8], x1)
binary.LittleEndian.PutUint32(out[8:12], x2)
binary.LittleEndian.PutUint32(out[12:16], x3)
binary.LittleEndian.PutUint32(out[16:20], x12)
binary.LittleEndian.PutUint32(out[20:24], x13)
binary.LittleEndian.PutUint32(out[24:28], x14)
binary.LittleEndian.PutUint32(out[28:32], x15)
}
return
}

View File

@ -63,15 +63,15 @@ func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr net.Addr) {
} }
type packet struct { type packet struct {
srcAddr *net.UDPAddr // src address srcAddr string // src address
dstAddr *net.UDPAddr // dest address dstAddr string // dest address
data []byte data []byte
} }
type cnode struct { type cnode struct {
chain *ProxyChain chain *ProxyChain
conn net.Conn conn net.Conn
srcAddr, dstAddr *net.UDPAddr srcAddr, dstAddr string
rChan, wChan chan *packet rChan, wChan chan *packet
err error err error
ttl time.Duration ttl time.Duration
@ -146,13 +146,9 @@ func (node *cnode) run() {
timer.Reset(node.ttl) timer.Reset(node.ttl)
glog.V(LDEBUG).Infof("[udp] %s <<< %s : length %d", node.srcAddr, addr, n) glog.V(LDEBUG).Infof("[udp] %s <<< %s : length %d", node.srcAddr, addr, n)
if node.dstAddr.String() != addr.String() {
glog.V(LWARNING).Infof("[udp] %s <- %s : dst-addr mismatch (%s)", node.srcAddr, node.dstAddr, addr)
break
}
select { select {
// swap srcAddr with dstAddr // swap srcAddr with dstAddr
case node.rChan <- &packet{srcAddr: node.dstAddr, dstAddr: node.srcAddr, data: b[:n]}: case node.rChan <- &packet{srcAddr: addr.String(), dstAddr: node.srcAddr, data: b[:n]}:
case <-time.After(time.Second * 3): case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard") glog.V(LWARNING).Infof("[udp] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard")
} }
@ -169,13 +165,9 @@ func (node *cnode) run() {
timer.Reset(node.ttl) timer.Reset(node.ttl)
glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s : length %d", node.srcAddr, dgram.Header.Addr.String(), len(dgram.Data)) glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s : length %d", node.srcAddr, dgram.Header.Addr.String(), len(dgram.Data))
if dgram.Header.Addr.String() != node.dstAddr.String() {
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : dst-addr mismatch (%s)", node.srcAddr, node.dstAddr, dgram.Header.Addr)
break
}
select { select {
// swap srcAddr with dstAddr // swap srcAddr with dstAddr
case node.rChan <- &packet{srcAddr: node.dstAddr, dstAddr: node.srcAddr, data: dgram.Data}: case node.rChan <- &packet{srcAddr: dgram.Header.Addr.String(), dstAddr: node.srcAddr, data: dgram.Data}:
case <-time.After(time.Second * 3): case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard") glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard")
} }
@ -187,9 +179,15 @@ func (node *cnode) run() {
for pkt := range node.wChan { for pkt := range node.wChan {
timer.Reset(node.ttl) timer.Reset(node.ttl)
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err)
continue
}
switch c := node.conn.(type) { switch c := node.conn.(type) {
case *net.UDPConn: case *net.UDPConn:
if _, err := c.WriteToUDP(pkt.data, pkt.dstAddr); err != nil { if _, err := c.WriteToUDP(pkt.data, dstAddr); err != nil {
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err)
node.err = err node.err = err
errChan <- err errChan <- err
@ -198,7 +196,7 @@ func (node *cnode) run() {
glog.V(LDEBUG).Infof("[udp] %s >>> %s : length %d", pkt.srcAddr, pkt.dstAddr, len(pkt.data)) glog.V(LDEBUG).Infof("[udp] %s >>> %s : length %d", pkt.srcAddr, pkt.dstAddr, len(pkt.data))
default: default:
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(pkt.data)), 0, ToSocksAddr(pkt.dstAddr)), pkt.data) dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(pkt.data)), 0, ToSocksAddr(dstAddr)), pkt.data)
if err := dgram.Write(c); err != nil { if err := dgram.Write(c); err != nil {
glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err)
node.err = err node.err = err
@ -255,7 +253,7 @@ func (s *UdpForwardServer) ListenAndServe() error {
} }
select { select {
case ch <- &packet{srcAddr: addr, dstAddr: raddr, data: b[:n]}: case ch <- &packet{srcAddr: addr.String(), dstAddr: raddr.String(), data: b[:n]}:
case <-time.After(time.Second * 3): case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", addr, raddr, "send queue is full, discard") glog.V(LWARNING).Infof("[udp] %s -> %s : %s", addr, raddr, "send queue is full, discard")
} }
@ -264,7 +262,12 @@ func (s *UdpForwardServer) ListenAndServe() error {
// start recv queue // start recv queue
go func(ch <-chan *packet) { go func(ch <-chan *packet) {
for pkt := range ch { for pkt := range ch {
if _, err := conn.WriteToUDP(pkt.data, pkt.dstAddr); err != nil { dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
continue
}
if _, err := conn.WriteToUDP(pkt.data, dstAddr); err != nil {
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
return return
} }
@ -285,7 +288,7 @@ func (s *UdpForwardServer) ListenAndServe() error {
} }
} }
node, ok := m[pkt.srcAddr.String()] node, ok := m[pkt.srcAddr]
if !ok { if !ok {
node = &cnode{ node = &cnode{
chain: s.Base.Chain, chain: s.Base.Chain,
@ -295,7 +298,7 @@ func (s *UdpForwardServer) ListenAndServe() error {
wChan: make(chan *packet, 32), wChan: make(chan *packet, 32),
ttl: time.Duration(s.TTL) * time.Second, ttl: time.Duration(s.TTL) * time.Second,
} }
m[pkt.srcAddr.String()] = node m[pkt.srcAddr] = node
go node.run() go node.run()
glog.V(LINFO).Infof("[udp] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m)) glog.V(LINFO).Infof("[udp] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m))
} }

View File

@ -11,7 +11,7 @@ import (
) )
const ( const (
Version = "2.3" Version = "2.4-dev"
) )
// Log level for glog // Log level for glog

View File

@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
} }
switch node.Transport { switch node.Transport {
case "ws", "wss", "tls", "http2", "ssu", "quic", "kcp", "redirect": case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu":
case "https": case "https":
node.Protocol = "http" node.Protocol = "http"
node.Transport = "tls" node.Transport = "tls"

View File

@ -32,7 +32,7 @@ func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *Prox
var cipher *ss.Cipher var cipher *ss.Cipher
var ota bool var ota bool
if node.Protocol == "ss" { if node.Protocol == "ss" || node.Transport == "ssu" {
var err error var err error
var method, password string var method, password string
@ -98,8 +98,6 @@ func (s *ProxyServer) Serve() error {
return NewRTcpForwardServer(s).Serve() return NewRTcpForwardServer(s).Serve()
case "rudp": // Remote UDP port forwarding case "rudp": // Remote UDP port forwarding
return NewRUdpForwardServer(s).Serve() return NewRUdpForwardServer(s).Serve()
case "ssu": // TODO: shadowsocks udp relay
return NewShadowUdpServer(s).ListenAndServe()
case "quic": case "quic":
return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig) return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig)
case "kcp": case "kcp":
@ -118,6 +116,12 @@ func (s *ProxyServer) Serve() error {
return NewKCPServer(s, config).ListenAndServe() return NewKCPServer(s, config).ListenAndServe()
case "redirect": case "redirect":
return NewRedsocksTCPServer(s).ListenAndServe() return NewRedsocksTCPServer(s).ListenAndServe()
case "ssu": // shadowsocks udp relay
ttl, _ := strconv.Atoi(s.Node.Get("ttl"))
if ttl <= 0 {
ttl = DefaultTTL
}
return NewShadowUdpServer(s, ttl).ListenAndServe()
default: default:
ln, err = net.Listen("tcp", node.Addr) ln, err = net.Listen("tcp", node.Addr)
} }

View File

@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"github.com/ginuerzh/gosocks5"
"github.com/golang/glog" "github.com/golang/glog"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io" "io"
@ -65,47 +66,6 @@ func (s *ShadowServer) Serve() {
glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr) glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr)
} }
type ShadowUdpServer struct {
Base *ProxyServer
Handler func(conn *net.UDPConn, addr *net.UDPAddr, data []byte)
}
func NewShadowUdpServer(base *ProxyServer) *ShadowUdpServer {
return &ShadowUdpServer{Base: base}
}
func (s *ShadowUdpServer) ListenAndServe() error {
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr)
if err != nil {
return err
}
lconn, err := net.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer lconn.Close()
if s.Handler == nil {
s.Handler = s.HandleConn
}
for {
b := make([]byte, LargeBufferSize)
n, addr, err := lconn.ReadFromUDP(b)
if err != nil {
glog.V(LWARNING).Infoln(err)
continue
}
go s.Handler(lconn, addr, b[:n])
}
}
// TODO: shadowsocks udp relay handler
func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
}
// This function is copied from shadowsocks library with some modification. // This function is copied from shadowsocks library with some modification.
func (s *ShadowServer) getRequest() (host string, ota bool, err error) { func (s *ShadowServer) getRequest() (host string, ota bool, err error) {
// buf size should at least have the same size with the largest possible // buf size should at least have the same size with the largest possible
@ -276,3 +236,109 @@ func (c *shadowConn) SetReadDeadline(t time.Time) error {
func (c *shadowConn) SetWriteDeadline(t time.Time) error { func (c *shadowConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t) return c.conn.SetWriteDeadline(t)
} }
type ShadowUdpServer struct {
Base *ProxyServer
TTL int
}
func NewShadowUdpServer(base *ProxyServer, ttl int) *ShadowUdpServer {
return &ShadowUdpServer{Base: base, TTL: ttl}
}
func (s *ShadowUdpServer) ListenAndServe() error {
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr)
if err != nil {
return err
}
lconn, err := net.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer lconn.Close()
conn := ss.NewSecurePacketConn(lconn, s.Base.cipher.Copy(), true) // force OTA on
rChan, wChan := make(chan *packet, 128), make(chan *packet, 128)
// start send queue
go func(ch chan<- *packet) {
for {
b := make([]byte, MediumBufferSize)
n, addr, err := conn.ReadFrom(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err)
continue
}
if b[3]&ss.OneTimeAuthMask > 0 {
glog.V(LWARNING).Infof("[ssu] %s -> %s : client does not support OTA", addr, laddr)
continue
}
b[3] &= ss.AddrMask
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err)
continue
}
select {
case ch <- &packet{srcAddr: addr.String(), dstAddr: dgram.Header.Addr.String(), data: b[:n+3]}:
case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, dgram.Header.Addr.String(), "send queue is full, discard")
}
}
}(wChan)
// start recv queue
go func(ch <-chan *packet) {
for pkt := range ch {
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
continue
}
if _, err := conn.WriteTo(pkt.data, dstAddr); err != nil {
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
return
}
}
}(rChan)
// mapping client to node
m := make(map[string]*cnode)
// start dispatcher
for pkt := range wChan {
// clear obsolete nodes
for k, node := range m {
if node != nil && node.err != nil {
close(node.wChan)
delete(m, k)
glog.V(LINFO).Infof("[ssu] clear node %s", k)
}
}
node, ok := m[pkt.srcAddr]
if !ok {
node = &cnode{
chain: s.Base.Chain,
srcAddr: pkt.srcAddr,
dstAddr: pkt.dstAddr,
rChan: rChan,
wChan: make(chan *packet, 32),
ttl: time.Duration(s.TTL) * time.Second,
}
m[pkt.srcAddr] = node
go node.run()
glog.V(LINFO).Infof("[ssu] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m))
}
select {
case node.wChan <- pkt:
case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, "node send queue is full, discard")
}
}
return nil
}

View File

@ -12,7 +12,7 @@ import (
"io" "io"
"strings" "strings"
"github.com/codahale/chacha20" "github.com/Yawning/chacha20"
"golang.org/x/crypto/blowfish" "golang.org/x/crypto/blowfish"
"golang.org/x/crypto/cast5" "golang.org/x/crypto/cast5"
"golang.org/x/crypto/salsa20/salsa" "golang.org/x/crypto/salsa20/salsa"
@ -65,11 +65,19 @@ func newStream(block cipher.Block, err error, key, iv []byte,
} }
} }
func newAESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
return newStream(block, err, key, iv, doe) return newStream(block, err, key, iv, doe)
} }
func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewCTR(block, iv), nil
}
func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) { func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := des.NewCipher(key) block, err := des.NewCipher(key)
return newStream(block, err, key, iv, doe) return newStream(block, err, key, iv, doe)
@ -95,7 +103,11 @@ func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
} }
func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) { func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
return chacha20.New(key, iv) return chacha20.NewCipher(key, iv)
}
func newChaCha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
return chacha20.NewCipher(key, iv)
} }
type salsaStreamCipher struct { type salsaStreamCipher struct {
@ -145,14 +157,18 @@ type cipherInfo struct {
} }
var cipherMethod = map[string]*cipherInfo{ var cipherMethod = map[string]*cipherInfo{
"aes-128-cfb": {16, 16, newAESStream}, "aes-128-cfb": {16, 16, newAESCFBStream},
"aes-192-cfb": {24, 16, newAESStream}, "aes-192-cfb": {24, 16, newAESCFBStream},
"aes-256-cfb": {32, 16, newAESStream}, "aes-256-cfb": {32, 16, newAESCFBStream},
"aes-128-ctr": {16, 16, newAESCTRStream},
"aes-192-ctr": {24, 16, newAESCTRStream},
"aes-256-ctr": {32, 16, newAESCTRStream},
"des-cfb": {8, 8, newDESStream}, "des-cfb": {8, 8, newDESStream},
"bf-cfb": {16, 8, newBlowFishStream}, "bf-cfb": {16, 8, newBlowFishStream},
"cast5-cfb": {16, 8, newCast5Stream}, "cast5-cfb": {16, 8, newCast5Stream},
"rc4-md5": {16, 16, newRC4MD5Stream}, "rc4-md5": {16, 16, newRC4MD5Stream},
"chacha20": {32, 8, newChaCha20Stream}, "chacha20": {32, 8, newChaCha20Stream},
"chacha20-ietf": {32, 12, newChaCha20IETFStream},
"salsa20": {32, 8, newSalsa20Stream}, "salsa20": {32, 8, newSalsa20Stream},
} }

View File

@ -0,0 +1,135 @@
package shadowsocks
import (
"bytes"
"fmt"
"net"
"time"
)
const (
maxPacketSize = 4096 // increase it if error occurs
)
var (
errPacketTooSmall = fmt.Errorf("[udp]read error: cannot decrypt, received packet is smaller than ivLen")
errPacketTooLarge = fmt.Errorf("[udp]read error: received packet is latger than maxPacketSize(%d)", maxPacketSize)
errBufferTooSmall = fmt.Errorf("[udp]read error: given buffer is too small to hold data")
errPacketOtaFailed = fmt.Errorf("[udp]read error: received packet has invalid ota")
)
type SecurePacketConn struct {
net.PacketConn
*Cipher
ota bool
}
func NewSecurePacketConn(c net.PacketConn, cipher *Cipher, ota bool) *SecurePacketConn {
return &SecurePacketConn{
PacketConn: c,
Cipher: cipher,
ota: ota,
}
}
func (c *SecurePacketConn) Close() error {
return c.PacketConn.Close()
}
func (c *SecurePacketConn) ReadFrom(b []byte) (n int, src net.Addr, err error) {
ota := false
cipher := c.Copy()
buf := make([]byte, 4096)
n, src, err = c.PacketConn.ReadFrom(buf)
if err != nil {
return
}
if n < c.info.ivLen {
return 0, nil, errPacketTooSmall
}
if len(b) < n-c.info.ivLen {
err = errBufferTooSmall // just a warning
}
iv := make([]byte, c.info.ivLen)
copy(iv, buf[:c.info.ivLen])
if err = cipher.initDecrypt(iv); err != nil {
return
}
cipher.decrypt(b[0:], buf[c.info.ivLen:n])
n -= c.info.ivLen
if b[idType]&OneTimeAuthMask > 0 {
ota = true
}
if c.ota && !ota {
return 0, src, errPacketOtaFailed
}
if ota {
key := cipher.key
actualHmacSha1Buf := HmacSha1(append(iv, key...), b[:n-lenHmacSha1])
if !bytes.Equal(b[n-lenHmacSha1:n], actualHmacSha1Buf) {
Debug.Printf("verify one time auth failed, iv=%v key=%v data=%v", iv, key, b)
return 0, src, errPacketOtaFailed
}
n -= lenHmacSha1
}
return
}
func (c *SecurePacketConn) WriteTo(b []byte, dst net.Addr) (n int, err error) {
cipher := c.Copy()
iv, err := cipher.initEncrypt()
if err != nil {
return
}
packetLen := len(b) + len(iv)
if c.ota {
b[idType] |= OneTimeAuthMask
packetLen += lenHmacSha1
key := cipher.key
actualHmacSha1Buf := HmacSha1(append(iv, key...), b)
b = append(b, actualHmacSha1Buf...)
}
cipherData := make([]byte, packetLen)
copy(cipherData, iv)
cipher.encrypt(cipherData[len(iv):], b)
n, err = c.PacketConn.WriteTo(cipherData, dst)
if c.ota {
n -= lenHmacSha1
}
return
}
func (c *SecurePacketConn) LocalAddr() net.Addr {
return c.PacketConn.LocalAddr()
}
func (c *SecurePacketConn) SetDeadline(t time.Time) error {
return c.PacketConn.SetDeadline(t)
}
func (c *SecurePacketConn) SetReadDeadline(t time.Time) error {
return c.PacketConn.SetReadDeadline(t)
}
func (c *SecurePacketConn) SetWriteDeadline(t time.Time) error {
return c.PacketConn.SetWriteDeadline(t)
}
func (c *SecurePacketConn) IsOta() bool {
return c.ota
}
func (c *SecurePacketConn) ForceOTA() net.PacketConn {
return NewSecurePacketConn(c.PacketConn, c.Cipher.Copy(), true)
}

View File

@ -0,0 +1,265 @@
package shadowsocks
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
const (
idType = 0 // address type index
idIP0 = 1 // ip addres start index
idDmLen = 1 // domain address length index
idDm0 = 2 // domain address start index
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
lenIPv4 = 1 + net.IPv4len + 2 // 1addrType + ipv4 + 2port
lenIPv6 = 1 + net.IPv6len + 2 // 1addrType + ipv6 + 2port
lenDmBase = 1 + 1 + 2 // 1addrType + 1addrLen + 2port, plus addrLen
lenHmacSha1 = 10
)
var (
reqList = newReqList()
natlist = newNatTable()
udpTimeout = 30 * time.Second
reqListRefreshTime = 5 * time.Minute
)
type natTable struct {
sync.Mutex
conns map[string]net.PacketConn
}
func newNatTable() *natTable {
return &natTable{conns: map[string]net.PacketConn{}}
}
func (table *natTable) Delete(index string) net.PacketConn {
table.Lock()
defer table.Unlock()
c, ok := table.conns[index]
if ok {
delete(table.conns, index)
return c
}
return nil
}
func (table *natTable) Get(index string) (c net.PacketConn, ok bool, err error) {
table.Lock()
defer table.Unlock()
c, ok = table.conns[index]
if !ok {
c, err = net.ListenPacket("udp", "")
if err != nil {
return nil, false, err
}
table.conns[index] = c
}
return
}
type requestHeaderList struct {
sync.Mutex
List map[string]([]byte)
}
func newReqList() *requestHeaderList {
ret := &requestHeaderList{List: map[string]([]byte){}}
go func() {
for {
time.Sleep(reqListRefreshTime)
ret.Refresh()
}
}()
return ret
}
func (r *requestHeaderList) Refresh() {
r.Lock()
defer r.Unlock()
for k, _ := range r.List {
delete(r.List, k)
}
}
func (r *requestHeaderList) Get(dstaddr string) (req []byte, ok bool) {
r.Lock()
defer r.Unlock()
req, ok = r.List[dstaddr]
return
}
func (r *requestHeaderList) Put(dstaddr string, req []byte) {
r.Lock()
defer r.Unlock()
r.List[dstaddr] = req
return
}
func parseHeaderFromAddr(addr net.Addr) ([]byte, int) {
// if the request address type is domain, it cannot be reverselookuped
ip, port, err := net.SplitHostPort(addr.String())
if err != nil {
return nil, 0
}
buf := make([]byte, 20)
IP := net.ParseIP(ip)
b1 := IP.To4()
iplen := 0
if b1 == nil { //ipv6
b1 = IP.To16()
buf[0] = typeIPv6
iplen = net.IPv6len
} else { //ipv4
buf[0] = typeIPv4
iplen = net.IPv4len
}
copy(buf[1:], b1)
port_i, _ := strconv.Atoi(port)
binary.BigEndian.PutUint16(buf[1+iplen:], uint16(port_i))
return buf[:1+iplen+2], 1 + iplen + 2
}
func Pipeloop(write net.PacketConn, writeAddr net.Addr, readClose net.PacketConn) {
buf := leakyBuf.Get()
defer leakyBuf.Put(buf)
defer readClose.Close()
for {
readClose.SetDeadline(time.Now().Add(udpTimeout))
n, raddr, err := readClose.ReadFrom(buf)
if err != nil {
if ne, ok := err.(*net.OpError); ok {
if ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE {
// log too many open file error
// EMFILE is process reaches open file limits, ENFILE is system limit
Debug.Println("[udp]read error:", err)
}
}
Debug.Printf("[udp]closed pipe %s<-%s\n", writeAddr, readClose.LocalAddr())
return
}
// need improvement here
if req, ok := reqList.Get(raddr.String()); ok {
write.WriteTo(append(req, buf[:n]...), writeAddr)
} else {
header, hlen := parseHeaderFromAddr(raddr)
write.WriteTo(append(header[:hlen], buf[:n]...), writeAddr)
}
}
}
func handleUDPConnection(handle *SecurePacketConn, n int, src net.Addr, receive []byte) {
var dstIP net.IP
var reqLen int
var ota bool
addrType := receive[idType]
defer leakyBuf.Put(receive)
if addrType&OneTimeAuthMask > 0 {
ota = true
}
receive[idType] &= ^OneTimeAuthMask
compatiblemode := !handle.IsOta() && ota
switch addrType & AddrMask {
case typeIPv4:
reqLen = lenIPv4
if len(receive) < reqLen {
Debug.Println("[udp]invalid received message.")
}
dstIP = net.IP(receive[idIP0 : idIP0+net.IPv4len])
case typeIPv6:
reqLen = lenIPv6
if len(receive) < reqLen {
Debug.Println("[udp]invalid received message.")
}
dstIP = net.IP(receive[idIP0 : idIP0+net.IPv6len])
case typeDm:
reqLen = int(receive[idDmLen]) + lenDmBase
if len(receive) < reqLen {
Debug.Println("[udp]invalid received message.")
}
name := string(receive[idDm0 : idDm0+int(receive[idDmLen])])
// avoid panic: syscall: string with NUL passed to StringToUTF16 on windows.
if strings.ContainsRune(name, 0x00) {
fmt.Println("[udp]invalid domain name.")
return
}
dIP, err := net.ResolveIPAddr("ip", name) // carefully with const type
if err != nil {
Debug.Printf("[udp]failed to resolve domain name: %s\n", string(receive[idDm0:idDm0+receive[idDmLen]]))
return
}
dstIP = dIP.IP
default:
Debug.Printf("[udp]addrType %d not supported", addrType)
return
}
dst := &net.UDPAddr{
IP: dstIP,
Port: int(binary.BigEndian.Uint16(receive[reqLen-2 : reqLen])),
}
if _, ok := reqList.Get(dst.String()); !ok {
req := make([]byte, reqLen)
copy(req, receive)
reqList.Put(dst.String(), req)
}
remote, exist, err := natlist.Get(src.String())
if err != nil {
return
}
if !exist {
Debug.Printf("[udp]new client %s->%s via %s ota=%v\n", src, dst, remote.LocalAddr(), ota)
go func() {
if compatiblemode {
Pipeloop(handle.ForceOTA(), src, remote)
} else {
Pipeloop(handle, src, remote)
}
natlist.Delete(src.String())
}()
} else {
Debug.Printf("[udp]using cached client %s->%s via %s ota=%v\n", src, dst, remote.LocalAddr(), ota)
}
if remote == nil {
fmt.Println("WTF")
}
remote.SetDeadline(time.Now().Add(udpTimeout))
_, err = remote.WriteTo(receive[reqLen:n], dst)
if err != nil {
if ne, ok := err.(*net.OpError); ok && (ne.Err == syscall.EMFILE || ne.Err == syscall.ENFILE) {
// log too many open file error
// EMFILE is process reaches open file limits, ENFILE is system limit
Debug.Println("[udp]write error:", err)
} else {
Debug.Println("[udp]error connecting to:", dst, err)
}
if conn := natlist.Delete(src.String()); conn != nil {
conn.Close()
}
}
// Pipeloop
return
}
func ReadAndHandleUDPReq(c *SecurePacketConn) error {
buf := leakyBuf.Get()
n, src, err := c.ReadFrom(buf[0:])
if err != nil {
return err
}
go handleUDPConnection(c, n, src, buf)
return nil
}

View File

@ -1,16 +1,16 @@
package shadowsocks package shadowsocks
import ( import (
"errors"
"fmt"
"os"
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha1"
"encoding/binary" "encoding/binary"
"errors"
"fmt"
"os"
) )
func PrintVersion() { func PrintVersion() {
const version = "1.1.5" const version = "1.2.0"
fmt.Println("shadowsocks-go version", version) fmt.Println("shadowsocks-go version", version)
} }

View File

@ -2,6 +2,12 @@
"comment": "", "comment": "",
"ignore": "test", "ignore": "test",
"package": [ "package": [
{
"checksumSHA1": "IFJyJgPCjumDG37lEb0lyRBBGZE=",
"path": "github.com/Yawning/chacha20",
"revision": "c91e78db502ff629614837aacb7aa4efa61c651a",
"revisionTime": "2016-04-30T09:49:23Z"
},
{ {
"checksumSHA1": "QPs3L3mjPoi+a9GJCjW8HhyJczM=", "checksumSHA1": "QPs3L3mjPoi+a9GJCjW8HhyJczM=",
"path": "github.com/codahale/chacha20", "path": "github.com/codahale/chacha20",
@ -15,10 +21,10 @@
"revisionTime": "2017-01-19T05:34:58Z" "revisionTime": "2017-01-19T05:34:58Z"
}, },
{ {
"checksumSHA1": "b0uHAM/lCGCJ9GeKfClvrMMWXQM=", "checksumSHA1": "idpL1fpHpfntk74IVfWtkP1PMZs=",
"path": "github.com/ginuerzh/gost", "path": "github.com/ginuerzh/gost",
"revision": "358f57add6087d77b1d978e92e2f7c8073c2f544", "revision": "321b03712af504981d35a47c50c2cfe4dd788a9d",
"revisionTime": "2017-01-21T03:14:59Z" "revisionTime": "2017-01-21T03:16:33Z"
}, },
{ {
"checksumSHA1": "URsJa4y/sUUw/STmbeYx9EKqaYE=", "checksumSHA1": "URsJa4y/sUUw/STmbeYx9EKqaYE=",
@ -153,10 +159,10 @@
"revisionTime": "2016-10-02T05:25:12Z" "revisionTime": "2016-10-02T05:25:12Z"
}, },
{ {
"checksumSHA1": "o0WHRL8mNIhfsoWlzhdJ8du6+C8=", "checksumSHA1": "MRsfMrdZwnnCTfIzT3czcj0lb0s=",
"path": "github.com/shadowsocks/shadowsocks-go/shadowsocks", "path": "github.com/shadowsocks/shadowsocks-go/shadowsocks",
"revision": "5c9897ecdf623f385ccb8c2c78e32c5256961b41", "revision": "97a5c71f80ba5f5b3e549f14a619fe557ff4f3c9",
"revisionTime": "2016-06-15T15:25:08Z" "revisionTime": "2017-01-21T20:35:16Z"
}, },
{ {
"checksumSHA1": "JsJdKXhz87gWenMwBeejTOeNE7k=", "checksumSHA1": "JsJdKXhz87gWenMwBeejTOeNE7k=",

View File

@ -63,15 +63,15 @@ func (s *TcpForwardServer) handleTcpForward(conn net.Conn, raddr net.Addr) {
} }
type packet struct { type packet struct {
srcAddr *net.UDPAddr // src address srcAddr string // src address
dstAddr *net.UDPAddr // dest address dstAddr string // dest address
data []byte data []byte
} }
type cnode struct { type cnode struct {
chain *ProxyChain chain *ProxyChain
conn net.Conn conn net.Conn
srcAddr, dstAddr *net.UDPAddr srcAddr, dstAddr string
rChan, wChan chan *packet rChan, wChan chan *packet
err error err error
ttl time.Duration ttl time.Duration
@ -146,13 +146,9 @@ func (node *cnode) run() {
timer.Reset(node.ttl) timer.Reset(node.ttl)
glog.V(LDEBUG).Infof("[udp] %s <<< %s : length %d", node.srcAddr, addr, n) glog.V(LDEBUG).Infof("[udp] %s <<< %s : length %d", node.srcAddr, addr, n)
if node.dstAddr.String() != addr.String() {
glog.V(LWARNING).Infof("[udp] %s <- %s : dst-addr mismatch (%s)", node.srcAddr, node.dstAddr, addr)
break
}
select { select {
// swap srcAddr with dstAddr // swap srcAddr with dstAddr
case node.rChan <- &packet{srcAddr: node.dstAddr, dstAddr: node.srcAddr, data: b[:n]}: case node.rChan <- &packet{srcAddr: addr.String(), dstAddr: node.srcAddr, data: b[:n]}:
case <-time.After(time.Second * 3): case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard") glog.V(LWARNING).Infof("[udp] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard")
} }
@ -169,13 +165,9 @@ func (node *cnode) run() {
timer.Reset(node.ttl) timer.Reset(node.ttl)
glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s : length %d", node.srcAddr, dgram.Header.Addr.String(), len(dgram.Data)) glog.V(LDEBUG).Infof("[udp-tun] %s <<< %s : length %d", node.srcAddr, dgram.Header.Addr.String(), len(dgram.Data))
if dgram.Header.Addr.String() != node.dstAddr.String() {
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : dst-addr mismatch (%s)", node.srcAddr, node.dstAddr, dgram.Header.Addr)
break
}
select { select {
// swap srcAddr with dstAddr // swap srcAddr with dstAddr
case node.rChan <- &packet{srcAddr: node.dstAddr, dstAddr: node.srcAddr, data: dgram.Data}: case node.rChan <- &packet{srcAddr: dgram.Header.Addr.String(), dstAddr: node.srcAddr, data: dgram.Data}:
case <-time.After(time.Second * 3): case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard") glog.V(LWARNING).Infof("[udp-tun] %s <- %s : %s", node.srcAddr, node.dstAddr, "recv queue is full, discard")
} }
@ -187,9 +179,15 @@ func (node *cnode) run() {
for pkt := range node.wChan { for pkt := range node.wChan {
timer.Reset(node.ttl) timer.Reset(node.ttl)
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err)
continue
}
switch c := node.conn.(type) { switch c := node.conn.(type) {
case *net.UDPConn: case *net.UDPConn:
if _, err := c.WriteToUDP(pkt.data, pkt.dstAddr); err != nil { if _, err := c.WriteToUDP(pkt.data, dstAddr); err != nil {
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) glog.V(LWARNING).Infof("[udp] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err)
node.err = err node.err = err
errChan <- err errChan <- err
@ -198,7 +196,7 @@ func (node *cnode) run() {
glog.V(LDEBUG).Infof("[udp] %s >>> %s : length %d", pkt.srcAddr, pkt.dstAddr, len(pkt.data)) glog.V(LDEBUG).Infof("[udp] %s >>> %s : length %d", pkt.srcAddr, pkt.dstAddr, len(pkt.data))
default: default:
dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(pkt.data)), 0, ToSocksAddr(pkt.dstAddr)), pkt.data) dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(uint16(len(pkt.data)), 0, ToSocksAddr(dstAddr)), pkt.data)
if err := dgram.Write(c); err != nil { if err := dgram.Write(c); err != nil {
glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err) glog.V(LWARNING).Infof("[udp-tun] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, err)
node.err = err node.err = err
@ -255,7 +253,7 @@ func (s *UdpForwardServer) ListenAndServe() error {
} }
select { select {
case ch <- &packet{srcAddr: addr, dstAddr: raddr, data: b[:n]}: case ch <- &packet{srcAddr: addr.String(), dstAddr: raddr.String(), data: b[:n]}:
case <-time.After(time.Second * 3): case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[udp] %s -> %s : %s", addr, raddr, "send queue is full, discard") glog.V(LWARNING).Infof("[udp] %s -> %s : %s", addr, raddr, "send queue is full, discard")
} }
@ -264,7 +262,12 @@ func (s *UdpForwardServer) ListenAndServe() error {
// start recv queue // start recv queue
go func(ch <-chan *packet) { go func(ch <-chan *packet) {
for pkt := range ch { for pkt := range ch {
if _, err := conn.WriteToUDP(pkt.data, pkt.dstAddr); err != nil { dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
continue
}
if _, err := conn.WriteToUDP(pkt.data, dstAddr); err != nil {
glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err) glog.V(LWARNING).Infof("[udp] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
return return
} }
@ -285,7 +288,7 @@ func (s *UdpForwardServer) ListenAndServe() error {
} }
} }
node, ok := m[pkt.srcAddr.String()] node, ok := m[pkt.srcAddr]
if !ok { if !ok {
node = &cnode{ node = &cnode{
chain: s.Base.Chain, chain: s.Base.Chain,
@ -295,7 +298,7 @@ func (s *UdpForwardServer) ListenAndServe() error {
wChan: make(chan *packet, 32), wChan: make(chan *packet, 32),
ttl: time.Duration(s.TTL) * time.Second, ttl: time.Duration(s.TTL) * time.Second,
} }
m[pkt.srcAddr.String()] = node m[pkt.srcAddr] = node
go node.run() go node.run()
glog.V(LINFO).Infof("[udp] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m)) glog.V(LINFO).Infof("[udp] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m))
} }

View File

@ -11,7 +11,7 @@ import (
) )
const ( const (
Version = "2.3" Version = "2.4-dev"
) )
// Log level for glog // Log level for glog

View File

@ -71,7 +71,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
} }
switch node.Transport { switch node.Transport {
case "ws", "wss", "tls", "http2", "ssu", "quic", "kcp", "redirect": case "ws", "wss", "tls", "http2", "quic", "kcp", "redirect", "ssu":
case "https": case "https":
node.Protocol = "http" node.Protocol = "http"
node.Transport = "tls" node.Transport = "tls"

View File

@ -32,7 +32,7 @@ func NewProxyServer(node ProxyNode, chain *ProxyChain, config *tls.Config) *Prox
var cipher *ss.Cipher var cipher *ss.Cipher
var ota bool var ota bool
if node.Protocol == "ss" { if node.Protocol == "ss" || node.Transport == "ssu" {
var err error var err error
var method, password string var method, password string
@ -98,8 +98,6 @@ func (s *ProxyServer) Serve() error {
return NewRTcpForwardServer(s).Serve() return NewRTcpForwardServer(s).Serve()
case "rudp": // Remote UDP port forwarding case "rudp": // Remote UDP port forwarding
return NewRUdpForwardServer(s).Serve() return NewRUdpForwardServer(s).Serve()
case "ssu": // TODO: shadowsocks udp relay
return NewShadowUdpServer(s).ListenAndServe()
case "quic": case "quic":
return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig) return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig)
case "kcp": case "kcp":
@ -118,6 +116,12 @@ func (s *ProxyServer) Serve() error {
return NewKCPServer(s, config).ListenAndServe() return NewKCPServer(s, config).ListenAndServe()
case "redirect": case "redirect":
return NewRedsocksTCPServer(s).ListenAndServe() return NewRedsocksTCPServer(s).ListenAndServe()
case "ssu": // shadowsocks udp relay
ttl, _ := strconv.Atoi(s.Node.Get("ttl"))
if ttl <= 0 {
ttl = DefaultTTL
}
return NewShadowUdpServer(s, ttl).ListenAndServe()
default: default:
ln, err = net.Listen("tcp", node.Addr) ln, err = net.Listen("tcp", node.Addr)
} }

148
ss.go
View File

@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"github.com/ginuerzh/gosocks5"
"github.com/golang/glog" "github.com/golang/glog"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
"io" "io"
@ -65,47 +66,6 @@ func (s *ShadowServer) Serve() {
glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr) glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr)
} }
type ShadowUdpServer struct {
Base *ProxyServer
Handler func(conn *net.UDPConn, addr *net.UDPAddr, data []byte)
}
func NewShadowUdpServer(base *ProxyServer) *ShadowUdpServer {
return &ShadowUdpServer{Base: base}
}
func (s *ShadowUdpServer) ListenAndServe() error {
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr)
if err != nil {
return err
}
lconn, err := net.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer lconn.Close()
if s.Handler == nil {
s.Handler = s.HandleConn
}
for {
b := make([]byte, LargeBufferSize)
n, addr, err := lconn.ReadFromUDP(b)
if err != nil {
glog.V(LWARNING).Infoln(err)
continue
}
go s.Handler(lconn, addr, b[:n])
}
}
// TODO: shadowsocks udp relay handler
func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
}
// This function is copied from shadowsocks library with some modification. // This function is copied from shadowsocks library with some modification.
func (s *ShadowServer) getRequest() (host string, ota bool, err error) { func (s *ShadowServer) getRequest() (host string, ota bool, err error) {
// buf size should at least have the same size with the largest possible // buf size should at least have the same size with the largest possible
@ -276,3 +236,109 @@ func (c *shadowConn) SetReadDeadline(t time.Time) error {
func (c *shadowConn) SetWriteDeadline(t time.Time) error { func (c *shadowConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t) return c.conn.SetWriteDeadline(t)
} }
type ShadowUdpServer struct {
Base *ProxyServer
TTL int
}
func NewShadowUdpServer(base *ProxyServer, ttl int) *ShadowUdpServer {
return &ShadowUdpServer{Base: base, TTL: ttl}
}
func (s *ShadowUdpServer) ListenAndServe() error {
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr)
if err != nil {
return err
}
lconn, err := net.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer lconn.Close()
conn := ss.NewSecurePacketConn(lconn, s.Base.cipher.Copy(), true) // force OTA on
rChan, wChan := make(chan *packet, 128), make(chan *packet, 128)
// start send queue
go func(ch chan<- *packet) {
for {
b := make([]byte, MediumBufferSize)
n, addr, err := conn.ReadFrom(b[3:]) // add rsv and frag fields to make it the standard SOCKS5 UDP datagram
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err)
continue
}
if b[3]&ss.OneTimeAuthMask > 0 {
glog.V(LWARNING).Infof("[ssu] %s -> %s : client does not support OTA", addr, laddr)
continue
}
b[3] &= ss.AddrMask
dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n+3]))
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, laddr, err)
continue
}
select {
case ch <- &packet{srcAddr: addr.String(), dstAddr: dgram.Header.Addr.String(), data: b[:n+3]}:
case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", addr, dgram.Header.Addr.String(), "send queue is full, discard")
}
}
}(wChan)
// start recv queue
go func(ch <-chan *packet) {
for pkt := range ch {
dstAddr, err := net.ResolveUDPAddr("udp", pkt.dstAddr)
if err != nil {
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
continue
}
if _, err := conn.WriteTo(pkt.data, dstAddr); err != nil {
glog.V(LWARNING).Infof("[ssu] %s <- %s : %s", pkt.dstAddr, pkt.srcAddr, err)
return
}
}
}(rChan)
// mapping client to node
m := make(map[string]*cnode)
// start dispatcher
for pkt := range wChan {
// clear obsolete nodes
for k, node := range m {
if node != nil && node.err != nil {
close(node.wChan)
delete(m, k)
glog.V(LINFO).Infof("[ssu] clear node %s", k)
}
}
node, ok := m[pkt.srcAddr]
if !ok {
node = &cnode{
chain: s.Base.Chain,
srcAddr: pkt.srcAddr,
dstAddr: pkt.dstAddr,
rChan: rChan,
wChan: make(chan *packet, 32),
ttl: time.Duration(s.TTL) * time.Second,
}
m[pkt.srcAddr] = node
go node.run()
glog.V(LINFO).Infof("[ssu] %s -> %s : new client (%d)", pkt.srcAddr, pkt.dstAddr, len(m))
}
select {
case node.wChan <- pkt:
case <-time.After(time.Second * 3):
glog.V(LWARNING).Infof("[ssu] %s -> %s : %s", pkt.srcAddr, pkt.dstAddr, "node send queue is full, discard")
}
}
return nil
}